Files
vulp/src/cli.rs
T
dualfroz 0468c032c6 Phases 0-1 + Sprint A: module split, CLI layer, org-refresh fix, hygiene
- LICENSE (MIT) + README truth-pass; docs/cli.md CLI contract
- CLI subcommands (src/cli_cmds/) per "TUI for users, CLI for users+AI"
- Hub/detail refactor: keymap dispatch, modal split, action enum, BgMsg bus
- Fix: org repos were never refreshed (owner==username match); refresh now
  parallel — 49 repos in ~11s, surfaced 70 previously invisible PRs
- Fix: 3 date bugs in detail/git.rs (format-local, Local::now, 1y month math)
- Stash indicator + [m] actions, session persistence, gh CLI token fallback
- Legacy UI modules removed; plan/ docs (roadmap, audit, debate, handoff)
2026-07-06 14:42:52 +02:00

237 lines
6.8 KiB
Rust

//! Command-line surface: `clap` argument definitions. `vulp` with no
//! subcommand launches the TUI hub; every hub operation is also reachable
//! through a subcommand here so scripts and AI agents can drive vulp without
//! a terminal UI. Machine consumers: pass `--json` and see `docs/cli.md`.
//!
//! Exit codes: 0 ok · 1 execution error · 2 bad usage (clap) ·
//! 3 auth missing/invalid · 4 not found (repo/todo/project).
use clap::{Args, Parser, Subcommand};
#[derive(Parser)]
#[command(name = "vulp", about = "Multi-repo git workflow manager", version)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
/// Show timing debug panel at bottom of TUI
#[arg(long, hide = true)]
pub debug: bool,
/// Machine-readable JSON output on stdout (list/status/stats/... commands)
#[arg(long, global = true)]
pub json: bool,
}
#[derive(Subcommand)]
pub enum Commands {
/// Configure vulp settings
Config,
/// Manage provider accounts
Auth(AuthArgs),
/// List workspace repos (name, branch, dirty/ahead/behind, language)
List {
/// Only repos with uncommitted changes
#[arg(long)] dirty: bool,
/// Only repos behind their remote
#[arg(long)] behind: bool,
/// Only repos ahead of their remote
#[arg(long)] ahead: bool,
/// Filter by language (substring, case-insensitive)
#[arg(long)] lang: Option<String>,
/// Filter by vulp project name
#[arg(long)] project: Option<String>,
},
/// Detailed status of one repo
Status { repo: String },
/// Rescan the workspace and print a summary
Scan,
/// Pull/push/commit+push one or more repos
Sync {
/// Repo names (omit with --all or --project)
repos: Vec<String>,
/// Sync every repo in the workspace
#[arg(long)] all: bool,
/// Sync every repo in a vulp project
#[arg(long)] project: Option<String>,
/// pull | push | commit-push
#[arg(long, default_value = "pull")] action: String,
/// Commit message (required for commit-push)
#[arg(short, long)] message: Option<String>,
},
/// Tag a release and publish it on the provider
Release {
repo: String,
/// Tag, e.g. v1.2.0
#[arg(long)] tag: String,
/// Release notes body
#[arg(long, default_value = "")] notes: String,
},
/// Manage todos
Todo {
#[command(subcommand)]
action: TodoCmd,
},
/// List / create pull requests
Pr {
#[command(subcommand)]
action: PrCmd,
},
/// List / create issues
Issue {
#[command(subcommand)]
action: IssueCmd,
},
/// Manage vulp projects (repo groupings)
Project {
#[command(subcommand)]
action: ProjectCmd,
},
/// Per-repo utilities (url/path/open/label/bookmark/pin/collab)
Repo {
#[command(subcommand)]
action: RepoCmd,
},
/// Create a new repo: local init + create on provider + push
Init {
name: String,
/// Create as private on the provider
#[arg(long)] private: bool,
/// Repo description
#[arg(long, default_value = "")] description: String,
},
/// Workspace-wide commit statistics
Stats {
/// 1d | 7d | 30d | 90d | 1y | all
#[arg(long, default_value = "30d")] period: String,
},
/// Recent vulp-performed actions (activity log)
Activity {
#[arg(long, default_value_t = 20)] limit: usize,
/// commit | push | pull | release | new repo | todo | pr | issue
#[arg(long)] kind: Option<String>,
},
/// List configured provider accounts
Accounts,
/// Fetch metadata/PRs/issues + merged/closed counters from provider APIs
Refresh,
}
#[derive(Subcommand)]
pub enum TodoCmd {
/// List todos
List {
/// open | in-progress | done | cancelled
#[arg(long)] status: Option<String>,
},
/// Add a todo
Add {
title: String,
#[arg(long, default_value = "")] desc: String,
#[arg(long)] project: Option<String>,
},
/// Mark a todo done
Done { id: u64 },
/// Mark a todo in-progress
Start { id: u64 },
/// Mark a todo cancelled
Cancel { id: u64 },
/// Delete a todo
Rm { id: u64 },
}
#[derive(Subcommand)]
pub enum PrCmd {
/// List cached open PRs (run `vulp refresh` first for fresh data)
List {
#[arg(long)] repo: Option<String>,
},
/// Open a pull request on the provider
Create {
#[arg(long)] repo: String,
#[arg(long)] title: String,
#[arg(long)] head: String,
#[arg(long, default_value = "main")] base: String,
},
}
#[derive(Subcommand)]
pub enum IssueCmd {
/// List cached open issues (run `vulp refresh` first for fresh data)
List {
#[arg(long)] repo: Option<String>,
},
/// Open an issue on the provider
Create {
#[arg(long)] repo: String,
#[arg(long)] title: String,
#[arg(long, default_value = "")] body: String,
},
}
#[derive(Subcommand)]
pub enum ProjectCmd {
/// List projects with their repos
List,
/// Create a project
Create {
name: String,
/// Hex color, e.g. "#e74c3c"
#[arg(long, default_value = "#3da7ff")] color: String,
},
/// Assign repos to a project
Assign { name: String, repos: Vec<String> },
/// Delete a project (repos stay on disk)
Rm { name: String },
}
#[derive(Subcommand)]
pub enum RepoCmd {
/// Print the repo's remote URL
Url { repo: String },
/// Print the repo's absolute path
Path { repo: String },
/// Open the repo page in the browser
Open { repo: String },
/// Set/clear the repo's display label (empty = reset)
Label { repo: String, #[arg(default_value = "")] name: String },
/// Toggle bookmark
Bookmark { repo: String },
/// Toggle pin
Pin { repo: String },
/// Invite a collaborator
Collab {
repo: String,
user: String,
/// pull | push | admin
#[arg(long, default_value = "push")] role: String,
},
}
#[derive(Args)]
pub struct AuthArgs {
#[command(subcommand)]
pub action: Option<AuthAction>,
}
/// `vulp auth` subcommands. Account state is a flat list of credentials
/// (one per provider account); `Switch` changes which one is the default
/// rather than removing the others, so multiple accounts can stay configured
/// at once.
#[derive(Subcommand)]
pub enum AuthAction {
Add {
#[arg(long)] provider: Option<String>,
#[arg(long)] token: Option<String>,
#[arg(long)] alias: Option<String>,
#[arg(long)] url: Option<String>,
},
/// Auto-import credentials from the gh CLI (no token needed)
Import,
Remove { account: String },
Switch { account: String },
Test { account: Option<String> },
}