1use std::path::PathBuf;
2
3use clap::{Parser, ValueEnum};
4
5use crate::ValidationError;
6use crate::commands::Command;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
9pub enum OutputFormat {
10 Text,
11 Json,
12}
13
14#[derive(Debug, Clone, Parser)]
15#[command(
16 version,
17 long_version = nils_build_info::long_version(env!("CARGO_PKG_VERSION")),
18 about = "Rust implementation of the plan-issue orchestration workflow.",
19 long_about = "Run live or local plan-issue orchestration flows with a typed command contract and deterministic runtime workspace.",
20 after_help = "EXAMPLES:\n plan-issue start-plan --issue 123 --repo owner/repo\n plan-issue-local build-task-spec --plan docs/plans/example/example-plan.md --sprint 1\n plan-issue status-plan --issue 123 --format json\n\nENVIRONMENT:\n PLAN_ISSUE_HOME Runtime workspace override.\n XDG_STATE_HOME Base state directory when PLAN_ISSUE_HOME is unset.\n HOME Fallback base state directory.\n\nEXIT CODES:\n 0 success\n 1 runtime error\n 64 command-line usage error\n 65 invalid input data\n\nUSAGE PATHS:\n - plan-issue: live GitHub-backed orchestration\n - plan-issue-local: local-first rehearsal and dry-run flow\n\nUNSUPPORTED IN PLAN-ISSUE-LOCAL:\n - Any --issue path that requires live GitHub reads/writes (for example: status-plan/ready-plan with --issue, close-plan with --issue-only, cleanup-worktrees).\n\nUSE INSTEAD:\n - plan-issue <command> ... (live GitHub path)\n - --body-file + --dry-run flows (local rehearsal path where supported)\n\nRUNTIME WORKSPACE:\n - Pass --state-dir <PATH> to override the workspace root, or export PLAN_ISSUE_HOME.\n - Default: ${XDG_STATE_HOME:-$HOME/.local/state}/plan-issue.\n\nBoth binaries share the same typed command contract.",
21 disable_help_subcommand = true
22)]
23pub struct Cli {
24 #[arg(long, global = true, value_name = "owner/repo")]
26 pub repo: Option<String>,
27
28 #[arg(long, global = true)]
30 pub dry_run: bool,
31
32 #[arg(short = 'f', long, global = true)]
34 pub force: bool,
35
36 #[arg(long, global = true, hide = true, conflicts_with = "format")]
38 pub json: bool,
39
40 #[arg(long, global = true, value_enum)]
42 pub format: Option<OutputFormat>,
43
44 #[arg(long, global = true, value_name = "PATH")]
48 pub state_dir: Option<PathBuf>,
49
50 #[command(subcommand)]
51 pub command: Command,
52}
53
54impl Cli {
55 pub fn resolve_output_format(&self) -> Result<OutputFormat, ValidationError> {
56 if self.json || matches!(self.format, Some(OutputFormat::Json)) {
57 return Ok(OutputFormat::Json);
58 }
59
60 Ok(OutputFormat::Text)
61 }
62
63 pub fn validate(&self) -> Result<(), ValidationError> {
64 self.command.validate(self.dry_run)
65 }
66}