agent-cli 0.70.2__py3-none-any.whl → 0.72.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. agent_cli/_extras.json +4 -3
  2. agent_cli/_requirements/memory.txt +14 -1
  3. agent_cli/_requirements/rag.txt +14 -1
  4. agent_cli/_requirements/vad.txt +1 -85
  5. agent_cli/_requirements/wyoming.txt +71 -0
  6. agent_cli/agents/assistant.py +24 -28
  7. agent_cli/agents/autocorrect.py +30 -4
  8. agent_cli/agents/chat.py +45 -15
  9. agent_cli/agents/memory/__init__.py +19 -1
  10. agent_cli/agents/memory/add.py +3 -3
  11. agent_cli/agents/memory/proxy.py +20 -11
  12. agent_cli/agents/rag_proxy.py +42 -10
  13. agent_cli/agents/speak.py +23 -3
  14. agent_cli/agents/transcribe.py +21 -3
  15. agent_cli/agents/transcribe_daemon.py +34 -22
  16. agent_cli/agents/voice_edit.py +18 -10
  17. agent_cli/cli.py +25 -2
  18. agent_cli/config_cmd.py +30 -11
  19. agent_cli/core/deps.py +6 -3
  20. agent_cli/core/transcription_logger.py +1 -1
  21. agent_cli/core/vad.py +6 -24
  22. agent_cli/dev/cli.py +295 -65
  23. agent_cli/docs_gen.py +18 -8
  24. agent_cli/install/extras.py +44 -13
  25. agent_cli/install/hotkeys.py +22 -11
  26. agent_cli/install/services.py +54 -14
  27. agent_cli/opts.py +43 -22
  28. agent_cli/server/cli.py +128 -62
  29. agent_cli/server/proxy/api.py +77 -19
  30. agent_cli/services/__init__.py +46 -5
  31. {agent_cli-0.70.2.dist-info → agent_cli-0.72.1.dist-info}/METADATA +627 -246
  32. {agent_cli-0.70.2.dist-info → agent_cli-0.72.1.dist-info}/RECORD +35 -34
  33. {agent_cli-0.70.2.dist-info → agent_cli-0.72.1.dist-info}/WHEEL +0 -0
  34. {agent_cli-0.70.2.dist-info → agent_cli-0.72.1.dist-info}/entry_points.txt +0 -0
  35. {agent_cli-0.70.2.dist-info → agent_cli-0.72.1.dist-info}/licenses/LICENSE +0 -0
agent_cli/dev/cli.py CHANGED
@@ -114,7 +114,27 @@ if TYPE_CHECKING:
114
114
 
115
115
  app = typer.Typer(
116
116
  name="dev",
117
- help="Parallel development environment manager using git worktrees.",
117
+ help="""Parallel development environment manager using git worktrees.
118
+
119
+ Creates isolated working directories for each feature branch, allowing you to
120
+ work on multiple features simultaneously without stashing changes. Each worktree
121
+ has its own branch and working directory.
122
+
123
+ **Common workflows:**
124
+
125
+ - `dev new feature-x -a` — Create worktree + start AI agent in new terminal tab
126
+ - `dev new feature-x -e -a` — Create worktree + open editor + start agent
127
+ - `dev new -a -p "Fix the auth bug"` — Create worktree + start agent with prompt
128
+ - `dev status` — See all worktrees with uncommitted changes
129
+ - `dev clean --merged` — Remove worktrees whose PRs are merged
130
+
131
+ **Automatic features:**
132
+
133
+ - Project setup (npm install, poetry install, uv sync, etc.)
134
+ - Environment file copying (.env, .env.local)
135
+ - Direnv setup (.envrc generation)
136
+ - Git submodules and LFS initialization
137
+ """,
118
138
  add_completion=True,
119
139
  rich_markup_mode="markdown",
120
140
  no_args_is_help=True,
@@ -130,7 +150,11 @@ def dev_callback(
130
150
  typer.Option("--config", "-c", help="Path to config file"),
131
151
  ] = None,
132
152
  ) -> None:
133
- """Parallel development environment manager using git worktrees."""
153
+ """Parallel development environment manager using git worktrees.
154
+
155
+ Creates isolated working directories for each feature branch. Each worktree
156
+ has its own branch, allowing parallel development without stashing changes.
157
+ """
134
158
  set_config_defaults(ctx, config_file)
135
159
  if ctx.invoked_subcommand is not None:
136
160
  set_process_title(f"dev-{ctx.invoked_subcommand}")
@@ -478,27 +502,47 @@ def _launch_agent(
478
502
  def new( # noqa: C901, PLR0912, PLR0915
479
503
  branch: Annotated[
480
504
  str | None,
481
- typer.Argument(help="Branch name (auto-generated if not provided)"),
505
+ typer.Argument(
506
+ help="Branch name for the worktree. If omitted, generates a random name like 'clever-fox'",
507
+ ),
482
508
  ] = None,
483
509
  from_ref: Annotated[
484
510
  str | None,
485
- typer.Option("--from", "-f", help="Create branch from this ref (default: main/master)"),
511
+ typer.Option(
512
+ "--from",
513
+ "-f",
514
+ help="Git ref (branch/tag/commit) to branch from. Defaults to origin/main or origin/master",
515
+ ),
486
516
  ] = None,
487
517
  editor: Annotated[
488
518
  bool,
489
- typer.Option("--editor", "-e", help="Open in editor after creation"),
519
+ typer.Option(
520
+ "--editor",
521
+ "-e",
522
+ help="Open the worktree in an editor. Uses --with-editor, config default, or auto-detects",
523
+ ),
490
524
  ] = False,
491
525
  agent: Annotated[
492
526
  bool,
493
- typer.Option("--agent", "-a", help="Start AI coding agent after creation"),
527
+ typer.Option(
528
+ "--agent",
529
+ "-a",
530
+ help="Start an AI coding agent in a new terminal tab. Uses --with-agent, config default, or auto-detects. Implied by --prompt",
531
+ ),
494
532
  ] = False,
495
533
  agent_name: Annotated[
496
534
  str | None,
497
- typer.Option("--with-agent", help="Specific agent to use (claude, codex, gemini, aider)"),
535
+ typer.Option(
536
+ "--with-agent",
537
+ help="Which AI agent to start: claude, codex, gemini, aider, copilot, cn (Continue), opencode, cursor-agent",
538
+ ),
498
539
  ] = None,
499
540
  editor_name: Annotated[
500
541
  str | None,
501
- typer.Option("--with-editor", help="Specific editor to use (cursor, vscode, zed)"),
542
+ typer.Option(
543
+ "--with-editor",
544
+ help="Which editor to open: cursor, vscode, zed, nvim, vim, emacs, sublime, idea, pycharm, etc.",
545
+ ),
502
546
  ] = None,
503
547
  default_agent: Annotated[
504
548
  str | None,
@@ -510,28 +554,37 @@ def new( # noqa: C901, PLR0912, PLR0915
510
554
  ] = None,
511
555
  setup: Annotated[
512
556
  bool,
513
- typer.Option("--setup/--no-setup", help="Run automatic project setup"),
557
+ typer.Option(
558
+ "--setup/--no-setup",
559
+ help="Run project setup after creation: npm/pnpm/yarn install, poetry/uv sync, cargo build, etc. Auto-detects project type",
560
+ ),
514
561
  ] = True,
515
562
  copy_env: Annotated[
516
563
  bool,
517
- typer.Option("--copy-env/--no-copy-env", help="Copy .env files from main repo"),
564
+ typer.Option(
565
+ "--copy-env/--no-copy-env",
566
+ help="Copy .env, .env.local, .env.example from main repo to worktree",
567
+ ),
518
568
  ] = True,
519
569
  fetch: Annotated[
520
570
  bool,
521
- typer.Option("--fetch/--no-fetch", help="Git fetch before creating"),
571
+ typer.Option(
572
+ "--fetch/--no-fetch",
573
+ help="Run 'git fetch' before creating the worktree to ensure refs are up-to-date",
574
+ ),
522
575
  ] = True,
523
576
  direnv: Annotated[
524
577
  bool | None,
525
578
  typer.Option(
526
579
  "--direnv/--no-direnv",
527
- help="Set up direnv (generate .envrc, run direnv allow). Default: enabled if direnv is installed.",
580
+ help="Generate .envrc based on project type and run 'direnv allow'. Auto-enabled if direnv is installed",
528
581
  ),
529
582
  ] = None,
530
583
  agent_args: Annotated[
531
584
  list[str] | None,
532
585
  typer.Option(
533
586
  "--agent-args",
534
- help="Extra arguments to pass to the agent (e.g., --agent-args='--dangerously-skip-permissions')",
587
+ help="Extra CLI args for the agent. Can be repeated. Example: --agent-args='--dangerously-skip-permissions'",
535
588
  ),
536
589
  ] = None,
537
590
  prompt: Annotated[
@@ -539,7 +592,7 @@ def new( # noqa: C901, PLR0912, PLR0915
539
592
  typer.Option(
540
593
  "--prompt",
541
594
  "-p",
542
- help="Initial prompt to pass to the AI agent (e.g., --prompt='Fix the login bug')",
595
+ help="Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug'",
543
596
  ),
544
597
  ] = None,
545
598
  prompt_file: Annotated[
@@ -547,17 +600,44 @@ def new( # noqa: C901, PLR0912, PLR0915
547
600
  typer.Option(
548
601
  "--prompt-file",
549
602
  "-P",
550
- help="Read initial prompt from a file (avoids shell quoting issues with long prompts)",
603
+ help="Read the agent prompt from a file. Useful for long prompts to avoid shell quoting. Implies --agent",
551
604
  exists=True,
552
605
  readable=True,
553
606
  ),
554
607
  ] = None,
555
608
  verbose: Annotated[
556
609
  bool,
557
- typer.Option("--verbose", "-v", help="Show detailed output and stream command output"),
610
+ typer.Option(
611
+ "--verbose",
612
+ "-v",
613
+ help="Stream output from setup commands instead of hiding it",
614
+ ),
558
615
  ] = False,
559
616
  ) -> None:
560
- """Create a new parallel development environment (git worktree)."""
617
+ """Create a new parallel development environment (git worktree).
618
+
619
+ Creates an isolated git worktree with its own branch and working directory.
620
+ Optionally runs project setup, opens an editor, and/or starts an AI agent.
621
+
622
+ **What happens:**
623
+
624
+ 1. Creates git worktree at `../REPO-worktrees/BRANCH/`
625
+ 2. Copies .env files from main repo (--copy-env)
626
+ 3. Runs project setup: npm install, uv sync, etc. (--setup)
627
+ 4. Sets up direnv if installed (--direnv)
628
+ 5. Opens editor if requested (-e/--editor)
629
+ 6. Starts AI agent in new terminal tab if requested (-a/--agent or --prompt)
630
+
631
+ **Examples:**
632
+
633
+ - `dev new feature-x` — Create worktree, branching from origin/main (default)
634
+ - `dev new feature-x -a` — Create + start Claude/detected agent
635
+ - `dev new feature-x -e -a` — Create + open editor + start agent
636
+ - `dev new -a --prompt "Fix auth bug"` — Auto-named branch + agent with task
637
+ - `dev new hotfix --from v1.2.3` — Branch from a tag instead of main
638
+ - `dev new feature-x --from origin/develop` — Branch from develop instead
639
+ - `dev new feature-x --with-agent aider --with-editor cursor` — Specific tools
640
+ """
561
641
  # Handle prompt-file option (takes precedence over --prompt)
562
642
  if prompt_file is not None:
563
643
  prompt = prompt_file.read_text().strip()
@@ -676,10 +756,17 @@ def new( # noqa: C901, PLR0912, PLR0915
676
756
  def list_envs(
677
757
  json_output: Annotated[
678
758
  bool,
679
- typer.Option("--json", help="Output as JSON for automation"),
759
+ typer.Option(
760
+ "--json",
761
+ help="Output as JSON. Fields: name, path, branch, is_main, is_detached, is_locked, is_prunable",
762
+ ),
680
763
  ] = False,
681
764
  ) -> None:
682
- """List all dev environments (worktrees) for the current repository."""
765
+ """List all dev environments (worktrees) for the current repository.
766
+
767
+ Shows each worktree's name, branch, path, and status (main, detached, locked, prunable).
768
+ Use `dev status` for more details including uncommitted changes and commit history.
769
+ """
683
770
  _ensure_git_repo()
684
771
 
685
772
  worktrees = worktree.list_worktrees()
@@ -779,17 +866,28 @@ def _is_stale(status: worktree.WorktreeStatus, stale_days: int) -> bool:
779
866
  def status_cmd( # noqa: PLR0915
780
867
  stale_days: Annotated[
781
868
  int,
782
- typer.Option("--stale-days", "-s", help="Highlight worktrees inactive for N+ days"),
869
+ typer.Option(
870
+ "--stale-days",
871
+ "-s",
872
+ help="Mark worktrees as stale if inactive for N+ days (default: 7)",
873
+ ),
783
874
  ] = 7,
784
875
  json_output: Annotated[
785
876
  bool,
786
- typer.Option("--json", help="Output as JSON for automation"),
877
+ typer.Option(
878
+ "--json",
879
+ help="Output as JSON with fields: name, branch, modified, staged, untracked, ahead, behind, last_commit_timestamp, is_stale",
880
+ ),
787
881
  ] = False,
788
882
  ) -> None:
789
- """Show status of all dev environments (worktrees) with git status.
883
+ """Show detailed status of all dev environments with git information.
884
+
885
+ Displays for each worktree:
886
+ - **Changes**: Modified (M), Staged (S), Untracked (?) file counts
887
+ - **↑/↓**: Commits ahead (+N) or behind (-N) upstream
888
+ - **Last Commit**: Time since last commit, with ⚠️ for stale worktrees
790
889
 
791
- Displays file changes (Modified, Staged, Untracked), commits ahead/behind
792
- upstream, and last commit time for each worktree.
890
+ Use this to find worktrees with uncommitted work or that need cleanup.
793
891
  """
794
892
  _ensure_git_repo()
795
893
 
@@ -877,18 +975,39 @@ def status_cmd( # noqa: PLR0915
877
975
 
878
976
  @app.command("rm")
879
977
  def remove(
880
- name: Annotated[str, typer.Argument(help="Branch or directory name of the worktree to remove")],
978
+ name: Annotated[
979
+ str,
980
+ typer.Argument(help="Worktree to remove. Can be branch name or directory name"),
981
+ ],
881
982
  force: Annotated[
882
983
  bool,
883
- typer.Option("--force", "-f", help="Force removal even with uncommitted changes"),
984
+ typer.Option(
985
+ "--force",
986
+ "-f",
987
+ help="Force removal even if worktree has uncommitted changes",
988
+ ),
884
989
  ] = False,
885
990
  delete_branch: Annotated[
886
991
  bool,
887
- typer.Option("--delete-branch", "-d", help="Also delete the branch"),
992
+ typer.Option(
993
+ "--delete-branch",
994
+ "-d",
995
+ help="Also delete the git branch (not just the worktree)",
996
+ ),
997
+ ] = False,
998
+ yes: Annotated[
999
+ bool,
1000
+ typer.Option("--yes", "-y", help="Skip confirmation prompt"),
888
1001
  ] = False,
889
- yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
890
1002
  ) -> None:
891
- """Remove a dev environment (worktree)."""
1003
+ """Remove a dev environment (worktree) and optionally its branch.
1004
+
1005
+ By default, prompts for confirmation and refuses to remove worktrees with
1006
+ uncommitted changes. Use --force to override, --delete-branch to also
1007
+ delete the git branch.
1008
+
1009
+ Cannot remove the main worktree.
1010
+ """
892
1011
  repo_root = _ensure_git_repo()
893
1012
 
894
1013
  wt = worktree.find_worktree_by_name(name, repo_root)
@@ -920,11 +1039,16 @@ def remove(
920
1039
 
921
1040
  @app.command("path")
922
1041
  def path_cmd(
923
- name: Annotated[str, typer.Argument(help="Branch name or directory name of the worktree")],
1042
+ name: Annotated[
1043
+ str,
1044
+ typer.Argument(help="Worktree to get path for. Can be branch name or directory name"),
1045
+ ],
924
1046
  ) -> None:
925
- """Print the path to a dev environment (for shell integration).
1047
+ """Print the absolute path to a dev environment.
1048
+
1049
+ Useful for shell integration and scripting.
926
1050
 
927
- Usage: cd "$(agent-cli dev path my-feature)"
1051
+ **Example:** `cd "$(agent-cli dev path my-feature)"`
928
1052
  """
929
1053
  repo_root = _ensure_git_repo()
930
1054
 
@@ -937,13 +1061,24 @@ def path_cmd(
937
1061
 
938
1062
  @app.command("editor")
939
1063
  def open_editor(
940
- name: Annotated[str, typer.Argument(help="Branch name or directory name of the worktree")],
1064
+ name: Annotated[
1065
+ str,
1066
+ typer.Argument(help="Worktree to open. Can be branch name or directory name"),
1067
+ ],
941
1068
  editor_name: Annotated[
942
1069
  str | None,
943
- typer.Option("--editor", "-e", help="Specific editor to use"),
1070
+ typer.Option(
1071
+ "--editor",
1072
+ "-e",
1073
+ help="Override auto-detection. Options: cursor, vscode, zed, nvim, vim, emacs, sublime, idea, pycharm, etc.",
1074
+ ),
944
1075
  ] = None,
945
1076
  ) -> None:
946
- """Open a dev environment in an editor."""
1077
+ """Open a dev environment in an editor.
1078
+
1079
+ Without --editor, auto-detects based on current environment or uses
1080
+ the first available editor. Run `dev editors` to see what's available.
1081
+ """
947
1082
  repo_root = _ensure_git_repo()
948
1083
 
949
1084
  wt = worktree.find_worktree_by_name(name, repo_root)
@@ -974,16 +1109,25 @@ def open_editor(
974
1109
 
975
1110
  @app.command("agent")
976
1111
  def start_agent(
977
- name: Annotated[str, typer.Argument(help="Branch name or directory name of the worktree")],
1112
+ name: Annotated[
1113
+ str,
1114
+ typer.Argument(
1115
+ help="Worktree to start the agent in. Can be branch name or directory name",
1116
+ ),
1117
+ ],
978
1118
  agent_name: Annotated[
979
1119
  str | None,
980
- typer.Option("--agent", "-a", help="Specific agent (claude, codex, gemini, aider)"),
1120
+ typer.Option(
1121
+ "--agent",
1122
+ "-a",
1123
+ help="Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted",
1124
+ ),
981
1125
  ] = None,
982
1126
  agent_args: Annotated[
983
1127
  list[str] | None,
984
1128
  typer.Option(
985
1129
  "--agent-args",
986
- help="Extra arguments to pass to the agent (e.g., --agent-args='--dangerously-skip-permissions')",
1130
+ help="Extra CLI args for the agent. Example: --agent-args='--dangerously-skip-permissions'",
987
1131
  ),
988
1132
  ] = None,
989
1133
  prompt: Annotated[
@@ -991,7 +1135,7 @@ def start_agent(
991
1135
  typer.Option(
992
1136
  "--prompt",
993
1137
  "-p",
994
- help="Initial prompt to pass to the AI agent (e.g., --prompt='Fix the login bug')",
1138
+ help="Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth'",
995
1139
  ),
996
1140
  ] = None,
997
1141
  prompt_file: Annotated[
@@ -999,13 +1143,23 @@ def start_agent(
999
1143
  typer.Option(
1000
1144
  "--prompt-file",
1001
1145
  "-P",
1002
- help="Read initial prompt from a file (avoids shell quoting issues with long prompts)",
1146
+ help="Read the agent prompt from a file instead of command line",
1003
1147
  exists=True,
1004
1148
  readable=True,
1005
1149
  ),
1006
1150
  ] = None,
1007
1151
  ) -> None:
1008
- """Start an AI coding agent in a dev environment."""
1152
+ """Start an AI coding agent in an existing dev environment.
1153
+
1154
+ Launches the agent directly in your current terminal (not a new tab).
1155
+ Use this when the worktree already exists and you want to start/resume work.
1156
+
1157
+ **Examples:**
1158
+
1159
+ - `dev agent my-feature` — Start auto-detected agent in worktree
1160
+ - `dev agent my-feature -a claude` — Start Claude specifically
1161
+ - `dev agent my-feature -p "Continue the auth refactor"` — Start with a task
1162
+ """
1009
1163
  # Handle prompt-file option (takes precedence over --prompt)
1010
1164
  if prompt_file is not None:
1011
1165
  prompt = prompt_file.read_text().strip()
@@ -1057,10 +1211,20 @@ def start_agent(
1057
1211
  def list_agents(
1058
1212
  json_output: Annotated[
1059
1213
  bool,
1060
- typer.Option("--json", help="Output as JSON for automation"),
1214
+ typer.Option(
1215
+ "--json",
1216
+ help="Output as JSON with name, command, is_available, is_current, install_url",
1217
+ ),
1061
1218
  ] = False,
1062
1219
  ) -> None:
1063
- """List available AI coding agents."""
1220
+ """List available AI coding agents and their installation status.
1221
+
1222
+ Shows all supported agents: claude, codex, gemini, aider, copilot,
1223
+ cn (Continue), opencode, cursor-agent.
1224
+
1225
+ ✓ = installed, ✗ = not installed (shows install URL).
1226
+ Current agent (detected from parent process) is marked.
1227
+ """
1064
1228
  current = coding_agents.detect_current_agent()
1065
1229
 
1066
1230
  if json_output:
@@ -1099,10 +1263,20 @@ def list_agents(
1099
1263
  def list_editors_cmd(
1100
1264
  json_output: Annotated[
1101
1265
  bool,
1102
- typer.Option("--json", help="Output as JSON for automation"),
1266
+ typer.Option(
1267
+ "--json",
1268
+ help="Output as JSON with name, command, is_available, is_current, install_url",
1269
+ ),
1103
1270
  ] = False,
1104
1271
  ) -> None:
1105
- """List available editors."""
1272
+ """List available editors and their installation status.
1273
+
1274
+ Shows all supported editors: cursor, vscode, zed, nvim, vim, emacs,
1275
+ sublime, idea, pycharm, webstorm, goland, rustrover.
1276
+
1277
+ ✓ = installed, ✗ = not installed.
1278
+ Current editor (if detectable) is marked.
1279
+ """
1106
1280
  current = editors.detect_current_editor()
1107
1281
 
1108
1282
  if json_output:
@@ -1141,10 +1315,17 @@ def list_editors_cmd(
1141
1315
  def list_terminals_cmd(
1142
1316
  json_output: Annotated[
1143
1317
  bool,
1144
- typer.Option("--json", help="Output as JSON for automation"),
1318
+ typer.Option("--json", help="Output as JSON with name, is_available, is_current"),
1145
1319
  ] = False,
1146
1320
  ) -> None:
1147
- """List available terminals."""
1321
+ """List available terminal multiplexers and their status.
1322
+
1323
+ Shows supported terminals: tmux, zellij, kitty, iTerm2, Terminal.app,
1324
+ Warp, GNOME Terminal.
1325
+
1326
+ These are used to open new tabs when launching AI agents with `dev new -a`.
1327
+ The current terminal (if detectable) is marked.
1328
+ """
1148
1329
  current = terminals.detect_current_terminal()
1149
1330
 
1150
1331
  if json_output:
@@ -1207,12 +1388,25 @@ def _doctor_check_git() -> None:
1207
1388
 
1208
1389
  @app.command("run")
1209
1390
  def run_cmd(
1210
- name: Annotated[str, typer.Argument(help="Branch name or directory name of the worktree")],
1211
- command: Annotated[list[str], typer.Argument(help="Command to run in the worktree")],
1391
+ name: Annotated[
1392
+ str,
1393
+ typer.Argument(help="Worktree to run command in. Can be branch name or directory name"),
1394
+ ],
1395
+ command: Annotated[
1396
+ list[str],
1397
+ typer.Argument(help="Command and arguments to run"),
1398
+ ],
1212
1399
  ) -> None:
1213
- """Run a command in a dev environment.
1400
+ """Run a command in a dev environment's directory.
1214
1401
 
1215
- Example: agent-cli dev run my-feature npm test
1402
+ Executes the command with the worktree as the current directory.
1403
+ Exit code is passed through from the command.
1404
+
1405
+ **Examples:**
1406
+
1407
+ - `dev run my-feature npm test` — Run tests
1408
+ - `dev run my-feature git status` — Check git status
1409
+ - `dev run my-feature bash -c "npm install && npm test"` — Multiple commands
1216
1410
  """
1217
1411
  repo_root = _ensure_git_repo()
1218
1412
 
@@ -1380,26 +1574,47 @@ def _clean_no_commits_worktrees(
1380
1574
  def clean(
1381
1575
  merged: Annotated[
1382
1576
  bool,
1383
- typer.Option("--merged", help="Remove worktrees with merged PRs (requires gh CLI)"),
1577
+ typer.Option(
1578
+ "--merged",
1579
+ help="Also remove worktrees whose GitHub PRs are merged (requires gh CLI and auth)",
1580
+ ),
1384
1581
  ] = False,
1385
1582
  no_commits: Annotated[
1386
1583
  bool,
1387
1584
  typer.Option(
1388
1585
  "--no-commits",
1389
- help="Remove worktrees with no commits ahead of default branch",
1586
+ help="Also remove worktrees with 0 commits ahead of default branch (abandoned branches)",
1390
1587
  ),
1391
1588
  ] = False,
1392
1589
  dry_run: Annotated[
1393
1590
  bool,
1394
- typer.Option("--dry-run", "-n", help="Show what would be done without doing it"),
1591
+ typer.Option(
1592
+ "--dry-run",
1593
+ "-n",
1594
+ help="Preview what would be removed without actually removing",
1595
+ ),
1596
+ ] = False,
1597
+ yes: Annotated[
1598
+ bool,
1599
+ typer.Option("--yes", "-y", help="Skip confirmation prompts"),
1395
1600
  ] = False,
1396
- yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
1397
1601
  ) -> None:
1398
1602
  """Clean up stale worktrees and empty directories.
1399
1603
 
1400
- Runs `git worktree prune` and removes empty worktree directories.
1401
- With --merged, also removes worktrees whose PRs have been merged.
1402
- With --no-commits, removes worktrees with no commits ahead of the default branch.
1604
+ Always runs `git worktree prune` to remove stale administrative files,
1605
+ and removes empty directories in the worktrees folder.
1606
+
1607
+ **Modes:**
1608
+
1609
+ - Default: Just prune stale refs and empty directories
1610
+ - `--merged`: Also remove worktrees whose PRs are merged on GitHub
1611
+ - `--no-commits`: Also remove worktrees with no commits (abandoned branches)
1612
+
1613
+ **Examples:**
1614
+
1615
+ - `dev clean` — Basic cleanup
1616
+ - `dev clean --merged` — Remove worktrees with merged PRs
1617
+ - `dev clean --merged --dry-run` — Preview what would be removed
1403
1618
  """
1404
1619
  repo_root = _ensure_git_repo()
1405
1620
 
@@ -1445,10 +1660,20 @@ def clean(
1445
1660
  def doctor(
1446
1661
  json_output: Annotated[
1447
1662
  bool,
1448
- typer.Option("--json", help="Output as JSON for automation"),
1663
+ typer.Option("--json", help="Output as JSON with git, editors, agents, terminals status"),
1449
1664
  ] = False,
1450
1665
  ) -> None:
1451
- """Check system requirements and available integrations."""
1666
+ """Check system requirements and show available integrations.
1667
+
1668
+ Shows availability status for:
1669
+ - **Git**: Is git installed? Are we in a git repo?
1670
+ - **Editors**: Which editors are installed and which is current?
1671
+ - **Agents**: Which AI coding agents are installed?
1672
+ - **Terminals**: Which terminal multiplexers are detected?
1673
+
1674
+ Items marked with ✓ are available, ✗ are not installed.
1675
+ Current editor/agent/terminal is marked with (current).
1676
+ """
1452
1677
  current_editor = editors.detect_current_editor()
1453
1678
  current_agent = coding_agents.detect_current_agent()
1454
1679
  current_terminal = terminals.detect_current_terminal()
@@ -1537,17 +1762,22 @@ def _get_current_repo_root() -> Path | None:
1537
1762
  def install_skill(
1538
1763
  force: Annotated[
1539
1764
  bool,
1540
- typer.Option("--force", "-f", help="Overwrite existing skill files"),
1765
+ typer.Option("--force", "-f", help="Overwrite existing skill files if already installed"),
1541
1766
  ] = False,
1542
1767
  ) -> None:
1543
1768
  """Install Claude Code skill for parallel agent orchestration.
1544
1769
 
1545
- Installs a skill that teaches Claude Code how to use 'agent-cli dev' to
1770
+ Installs a skill that teaches Claude Code how to use `agent-cli dev` to
1546
1771
  spawn parallel AI coding agents in isolated git worktrees.
1547
1772
 
1548
- The skill is installed to .claude/skills/agent-cli-dev/ in the current
1549
- repository. Once installed, Claude Code can automatically use it when
1550
- you ask to work on multiple features or parallelize development tasks.
1773
+ **What it does:**
1774
+
1775
+ - Copies skill files to `.claude/skills/agent-cli-dev/` in current repo
1776
+ - Enables Claude Code to automatically spawn parallel agents
1777
+ - Works when you ask to "work on multiple features" or "parallelize tasks"
1778
+
1779
+ **Alternative:** Install globally via Claude Code plugin marketplace:
1780
+ `claude plugin marketplace add basnijholt/agent-cli`
1551
1781
  """
1552
1782
  # Use current repo root (works in worktrees too)
1553
1783
  repo_root = _get_current_repo_root()
agent_cli/docs_gen.py CHANGED
@@ -80,16 +80,26 @@ def _extract_options_from_click(cmd: click.Command) -> list[dict[str, Any]]:
80
80
  options = []
81
81
  for param in cmd.params:
82
82
  if isinstance(param, click.Option):
83
- # Get the primary option name (longest one, usually --foo)
84
- opt_names = [n for n in param.opts if n.startswith("--")]
85
- if not opt_names:
86
- opt_names = param.opts
87
- primary_name = max(opt_names, key=len) if opt_names else param.name
88
-
89
- # Handle boolean flags
83
+ # Get long and short option names
84
+ long_opts = [n for n in param.opts if n.startswith("--")]
85
+ short_opts = [n for n in param.opts if n.startswith("-") and not n.startswith("--")]
86
+
87
+ # Build display name: prefer long form, include short if available
88
+ if long_opts:
89
+ primary_name = max(long_opts, key=len)
90
+ # Include short flag if available (e.g., "--from", "-f" -> "--from, -f")
91
+ if short_opts:
92
+ primary_name = f"{primary_name}, {short_opts[0]}"
93
+ elif short_opts:
94
+ primary_name = short_opts[0]
95
+ else:
96
+ primary_name = f"--{param.name}"
97
+
98
+ # Handle boolean flags with --foo/--no-foo pattern
90
99
  if param.is_flag and param.secondary_opts:
91
100
  # e.g., --llm/--no-llm
92
- primary_name = f"{opt_names[0]}/{param.secondary_opts[0]}"
101
+ base_opt = long_opts[0] if long_opts else param.opts[0]
102
+ primary_name = f"{base_opt}/{param.secondary_opts[0]}"
93
103
 
94
104
  # Get panel from rich_help_panel or use default
95
105
  panel = getattr(param, "rich_help_panel", None) or "Options"