use clap::{Parser, Subcommand};
use clap_complete::Shell;
#[derive(Parser, Debug)]
#[command(
name = "tbdflow",
author = "Claes Adamsson @cladam",
version,
about = "A CLI tool for Trunk-Based Development (TBD) workflows",
long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(long)]
pub verbose: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(after_help = "NON-INTERACTIVE USAGE:\n \
tbdflow init --yes # Use all defaults\n \
tbdflow init --yes --main-branch trunk # Custom trunk name\n \
tbdflow init --yes --remote git@github.com:org/repo.git\n\n\
FLAGS:\n \
--yes / -y Accept defaults, skip all interactive prompts\n \
--main-branch Set the main branch name (default: main)\n \
--remote Link and push to a remote repository URL")]
Init {
#[arg(
short = 'y',
long = "yes",
alias = "defaults",
alias = "non-interactive"
)]
non_interactive: bool,
#[arg(long)]
main_branch: Option<String>,
#[arg(long)]
remote: Option<String>,
},
#[command(alias = "show")]
Info {
#[arg(short, long, default_value_t = false)]
edit: bool,
},
Update,
#[command(
after_help = "Use the imperative, present tense: \"change\" not \"changed\". Think of This commit will...\n\
COMMON COMMIT TYPES:\n \
feat: A new feature for the user.\n \
fix: A bug fix for the user.\n \
chore: Routine tasks, maintenance, dependency updates.\n \
docs: Documentation changes.\n \
style: Code style changes (formatting, etc).\n \
refactor: Code changes that neither fix a bug nor add a feature.\n \
test: Adding or improving tests.\n\n\
EXAMPLES:\n \
tbdflow commit --type feat --scope api -m \"add user endpoint\"\n \
tbdflow commit -t fix -m \"fix login bug\" --breaking\n \
tbdflow commit -t chore -m \"update dependencies\" --tag \"v0.4.0\"\n \
tbdflow commit -t refactor -m \"rename internal API\" --breaking --breaking-description \"The `getUser` function has been renamed to `fetchUser`.\"\n \
tbdflow commit -t fix -s ui -m \"fix button alignment\" --issue \"#123\""
)]
Commit {
#[arg(short, long)]
r#type: Option<String>,
#[arg(short, long)]
scope: Option<String>,
#[arg(short, long)]
message: Option<String>,
#[arg(short, long)]
breaking: bool,
#[arg(long)]
breaking_description: Option<String>,
#[arg(long)]
tag: Option<String>,
#[arg(long, default_value_t = false)]
no_verify: bool,
#[arg(long)]
issue: Option<String>,
#[arg(long)]
body: Option<String>,
#[arg(long, default_value_t = false, hide = true)]
include_projects: bool,
},
#[command(after_help = "EXAMPLES:\n \
tbdflow branch --type feat --name \"user-profile-page\" --issue \"ABC-123\"\n \
tbdflow branch -t fix -n \"login-bug\" --issue \"CBA-456\n \
tbdflow branch -t chore -n \"update-dependencies\" -f \"39b68b5\"")]
Branch {
#[arg(short, long)]
r#type: Option<String>,
#[arg(short, long)]
name: Option<String>,
#[arg(long)]
issue: Option<String>,
#[arg(short, long)]
from_commit: Option<String>,
},
#[command(after_help = "EXAMPLES:\n \
tbdflow complete --type \"feature\" --name \"user-profile-page\"\n \
tbdflow complete -t \"release\" -n \"1.2.0\"")]
Complete {
#[arg(short, long)]
r#type: Option<String>,
#[arg(short, long)]
name: Option<String>,
},
Sync,
#[command(
name = "radar",
after_help = "OVERLAP DETECTION FOR SOCIAL CODING:\n \
In TBD, everyone integrates frequently. Radar makes the invisible visible\n \
by showing who else is working on the same files — before you push.\n\n\
DETECTION LEVELS (configurable in .tbdflow.yml):\n \
file: Same files touched (fast, default)\n \
line: Overlapping line ranges (precise)\n\n\
EXAMPLES:\n \
tbdflow radar # Scan for overlaps\n \
tbdflow --verbose radar # Scan with detailed git output\n \
tbdflow --dry-run radar # Preview what would be checked"
)]
Radar,
Status,
#[command(name = "current-branch")]
CurrentBranch,
#[command(name = "check-branches")]
CheckBranches,
#[command(name = "generate-man-page", hide = true)] #[command(after_help = "EXAMPLES:\n \
tbdflow generate-man-page > tbdflow.1\n \
man ./tbdflow.1")]
GenerateManPage,
#[command(name = "generate-completion", hide = true)] Completion {
#[arg(value_enum)]
shell: Shell,
},
#[command(
name = "changelog",
after_help = "EXAMPLES:\n \
tbdflow changelog --from v1.0.0 --to v2.0.0\n \
tbdflow changelog --unreleased\n \
tbdflow changelog --from v1.0.0"
)]
Changelog {
#[arg(long)]
from: Option<String>,
#[arg(long)]
to: Option<String>,
#[arg(long, default_value_t = false)]
unreleased: bool,
},
#[command(name = "config", hide = true)]
Config {
#[arg(long)]
get_dod: bool,
},
#[command(name = "head-sha", hide = true)]
HeadSha,
#[command(
name = "undo",
after_help = "THE PANIC BUTTON:\n \
In TBD, if the trunk breaks, fix it or revert it immediately.\n \
This command cleanly reverts a commit by SHA, syncs with remote first,\n \
and pushes the revert — restoring the trunk to a green state.\n\n\
EXAMPLES:\n \
tbdflow undo abc1234 # Revert a specific commit\n \
tbdflow undo abc1234 --no-push # Revert locally without pushing\n \
tbdflow --dry-run undo abc1234 # Preview what would happen"
)]
Undo {
sha: String,
#[arg(long, default_value_t = false)]
no_push: bool,
},
#[command(
name = "note",
aliases = ["n", "+"],
after_help = "INTENT LOG — CAPTURE THE \"WHY\" BEFORE IT'S LOST:\n \
Use this command to leave breadcrumbs while you work.\n \
Notes are appended to .tbdflow-intent.json (never committed)\n \
and automatically included in your next commit message.\n\n\
SHORTCUTS:\n \
tbdflow n Short alias\n \
tbdflow + Append alias\n\n\
EXAMPLES:\n \
tbdflow + \"trying factory pattern\"\n \
tbdflow n \"factory too complex, switching to trait\"\n \
tbdflow note --show"
)]
Note {
message: Option<String>,
#[arg(long, default_value_t = false)]
show: bool,
},
#[command(
name = "task",
subcommand,
after_help = "TASK CONTEXT — NAME YOUR WORK:\n \
Start a task to give your intent notes a heading.\n \
The task name appears in the Intent Log section of your commit.\n\n\
EXAMPLES:\n \
tbdflow task start \"Refactor auth logic\"\n \
tbdflow task show\n \
tbdflow task clear"
)]
Task(TaskAction),
#[command(
name = "recover",
after_help = "tbdflow WIP Guard\n \
Snapshots are captured automatically when you use 'tbdflow n', 'tbdflow sync' or 'tbdflow radar'.\n \
Use this command to list and restore them.\n\n\
EXAMPLES:\n \
tbdflow recover --list # Show available snapshots\n \
tbdflow recover 1 # Restore snapshot #1\n \
tbdflow recover a7b8c9d0 # Restore by hash"
)]
Recover {
selector: Option<String>,
#[arg(long, default_value_t = false)]
list: bool,
},
#[command(
name = "review",
after_help = "EXAMPLES:\n \
tbdflow review abc1234 # Create review for a specific commit\n \
tbdflow review --trigger # Create review for HEAD commit\n \
tbdflow review --digest # Show commits since yesterday\n \
tbdflow review --digest --since \"3 days ago\"\n \
tbdflow review --approve abc1234 # Mark commit as reviewed\n \
tbdflow review --concern abc1234 -m \"Thread safety issue\"\n \
tbdflow review --dismiss abc1234 -m \"Won't fix, out of scope\"\n\n\
WORKFLOW:\n \
1. Commit directly to main with 'tbdflow commit'\n \
2. Review is triggered automatically (if enabled) or manually\n \
3. Team reviews asynchronously without blocking\n \
4. Use --concern to flag issues (keeps issue open)\n \
5. Use --approve to mark commits as reviewed\n \
6. Use --dismiss to close without fixing"
)]
Review {
#[arg(conflicts_with_all = ["digest", "approve", "concern", "dismiss"])]
sha: Option<String>,
#[arg(long, conflicts_with_all = ["digest", "approve", "concern", "dismiss"])]
trigger: bool,
#[arg(long, conflicts_with_all = ["trigger", "approve", "concern", "dismiss"])]
digest: bool,
#[arg(long, conflicts_with_all = ["trigger", "digest", "concern", "dismiss"])]
approve: Option<String>,
#[arg(long, conflicts_with_all = ["trigger", "digest", "approve", "dismiss"])]
concern: Option<String>,
#[arg(long, conflicts_with_all = ["trigger", "digest", "approve", "concern"])]
dismiss: Option<String>,
#[arg(short, long)]
message: Option<String>,
#[arg(long, default_value = "1 day ago")]
since: String,
#[arg(long, value_delimiter = ',')]
reviewers: Option<Vec<String>>,
},
}
#[derive(Subcommand, Debug)]
pub enum TaskAction {
Start {
description: String,
},
Show,
Clear,
}