feat(cli): launch completion — shell completions (bash/zsh/fish) with dynamic completion#232
Conversation
…th dynamic completion Add `launch completion <bash|zsh|fish>` (prints the script), `launch completion install` (idempotently wires it into the shell rc via a fenced managed block, or prints the manual step), and a hidden `__complete` callback that resolves candidates by walking the live commander tree — so new commands appear with no reinstall, and dynamic values (app handles, -p/--profile, plan/drift surfaces, snapshot names) resolve through the existing core loaders. CLI stays pure commander wiring; all script generation, rc-file splicing, and candidate resolution live in core/completion.ts. gen-docs now flattens via commander's visibleCommands() so the internal __complete never leaks into the public reference or llms.txt. Closes #222 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (14)
✅ Files skipped from review due to trivial changes (11)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds ChangesShell Completion Feature
README Test-Count Badge Update
Sequence Diagram(s)sequenceDiagram
participant User as User Shell
participant LaunchCLI as launch CLI
participant CompletionCore as src/core/completion.ts
participant ConfigApps as launch.config.ts / apps/
rect rgba(100, 150, 200, 0.5)
Note over User,ConfigApps: Install completion to shell rc file
User->>LaunchCLI: launch completion install [--shell zsh]
LaunchCLI->>CompletionCore: installCompletion({ shell })
CompletionCore->>CompletionCore: detectShell() or parseShell()
CompletionCore->>CompletionCore: spliceManagedBlock(rcContents, block)
CompletionCore-->>LaunchCLI: InstallResult { status: "installed" | "manual" }
LaunchCLI-->>User: logs outcome or prints manual step
end
rect rgba(200, 150, 100, 0.5)
Note over User,ConfigApps: Print completion script
User->>LaunchCLI: launch completion zsh
LaunchCLI->>CompletionCore: completionScript("zsh")
CompletionCore-->>LaunchCLI: zsh script string
LaunchCLI-->>User: process.stdout.write(script)
end
rect rgba(150, 200, 100, 0.5)
Note over User,ConfigApps: Resolve tab-completion candidates
User->>LaunchCLI: __complete [typed words...]
LaunchCLI->>CompletionCore: resolveCompletions(words, program)
CompletionCore->>ConfigApps: load profile / discover apps / query registry
ConfigApps-->>CompletionCore: candidate values[]
CompletionCore-->>LaunchCLI: string[] candidates
LaunchCLI-->>User: newline-separated completions
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| export type InstallResult = | ||
| | { | ||
| /** Launch wrote the managed block into the rc file. */ | ||
| readonly status: "installed"; | ||
| /** The shell that was wired up. */ | ||
| readonly shell: Shell; | ||
| /** Absolute path to the rc file that was written. */ | ||
| readonly rcFile: string; | ||
| /** Whether an existing managed block was replaced (`true`) vs. a fresh block appended (`false`). */ | ||
| readonly updated: boolean; | ||
| } | ||
| | { | ||
| /** Launch could not safely edit the rc file; the user should add {@link line} by hand. */ | ||
| readonly status: "manual"; | ||
| /** The shell the steps are for. */ | ||
| readonly shell: Shell; | ||
| /** Absolute path to the rc file the line belongs in. */ | ||
| readonly rcFile: string; | ||
| /** The exact line to add to the rc file. */ | ||
| readonly line: string; | ||
| }; |
There was a problem hiding this comment.
Suggestion: Move this exported result shape to src/core/types.ts and import it here, so domain-level public types stay centralized instead of being defined in a feature module. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
This is a public domain-level shape exported from a feature module, which matches the rule requiring shared domain shapes and provider interfaces to live in src/core/types.ts rather than being defined inline elsewhere. The violation is real and directly visible in the existing code.
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/completion.ts
**Line:** 285:305
**Comment:**
*Custom Rule: Move this exported result shape to `src/core/types.ts` and import it here, so domain-level public types stay centralized instead of being defined in a feature module.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| export interface InstallOptions { | ||
| /** The shell to wire up. Defaults to {@link detectShell}; throws when neither is resolvable. */ | ||
| shell?: Shell; | ||
| /** Home directory to resolve the rc file under — overridable so tests don't touch the real `~`. */ | ||
| home?: string; | ||
| } |
There was a problem hiding this comment.
Suggestion: Relocate this exported options interface to src/core/types.ts and consume it via import in this module to follow the single-source rule for shared type definitions. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
This exported interface is defined directly in src/core/completion.ts instead of being centralized in src/core/types.ts, which violates the rule against redefining shared domain shapes outside the core types file. The suggestion accurately identifies that real violation.
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/completion.ts
**Line:** 308:313
**Comment:**
*Custom Rule: Relocate this exported options interface to `src/core/types.ts` and consume it via import in this module to follow the single-source rule for shared type definitions.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| words=("\${COMP_WORDS[@]:1}") | ||
| candidates="$(launch completion ${COMPLETE_SUBCOMMAND} -- "\${words[@]}" 2>/dev/null)" |
There was a problem hiding this comment.
Suggestion: Bash completion passes all tokens after launch, not the tokens up to the cursor position. Since the resolver assumes the last token is the active one, completions become wrong when the user triggers tab completion in the middle of a command line. Slice COMP_WORDS using COMP_CWORD (or otherwise pass cursor-aware tokens) so the callback receives only the active prefix context. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Bash mid-line completion suggests candidates for wrong token.
- ⚠️ Editing long commands interactively becomes confusing and error-prone.Steps of Reproduction ✅
1. Bash users enable completion via `launch completion bash`, which invokes
`bashCompletionScript()` defined in `src/core/completion.ts:78-88`; the emitted script
defines `_launch_complete` and wires it with `complete -o default -F _launch_complete
launch` at line 86.
2. The `_launch_complete` function in that script sets `words=("${COMP_WORDS[@]:1}")` and
then calls the Node callback as `candidates="$(launch completion ${COMPLETE_SUBCOMMAND} --
"${words[@]}" 2>/dev/null)"` (lines 82-83 in `src/core/completion.ts`), forwarding ALL
tokens after `launch`, regardless of the current cursor position.
3. In an interactive bash session, consider a line `launch snapshot diff prod other` where
the user moves the cursor back to the `"prod"` word and presses `<Tab>`. Bash sets
`COMP_WORDS=("launch" "snapshot" "diff" "prod" "other")` and `COMP_CWORD=3`, but
`_launch_complete` still forwards `words=("snapshot" "diff" "prod" "other")` to `launch
completion __complete`, so the Node handler in `src/cli/commands/completion.ts:55-61`
receives `["snapshot","diff","prod","other"]`.
4. `resolveCompletions` at `src/core/completion.ts:225-241` uses `current =
words[words.length - 1]` (line 227), which in this mid-line case becomes `"other"` instead
of the word under the cursor (`"prod"`). This causes the resolver to compute candidates
based on the trailing context rather than the active token, so the suggestions the user
sees while editing the middle of a command reflect `"other"` and not `"prod"`, leading to
incorrect or missing completions for mid-line edits.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/completion.ts
**Line:** 82:83
**Comment:**
*Api Mismatch: Bash completion passes all tokens after `launch`, not the tokens up to the cursor position. Since the resolver assumes the last token is the active one, completions become wrong when the user triggers tab completion in the middle of a command line. Slice `COMP_WORDS` using `COMP_CWORD` (or otherwise pass cursor-aware tokens) so the callback receives only the active prefix context.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| return `# launch (zsh) completion — eval with: source <(launch completion zsh) | ||
| _launch_complete() { | ||
| local -a candidates | ||
| candidates=("\${(@f)$(launch completion ${COMPLETE_SUBCOMMAND} -- "\${words[2,-1]}" 2>/dev/null)}") |
There was a problem hiding this comment.
Suggestion: The zsh callback forwards ${words[2,-1]} inside double quotes, which collapses multiple typed tokens into a single argument before invoking __complete. That breaks command-tree descent for nested commands because the resolver receives one space-joined token instead of separate words. Expand the array as separate arguments (zsh array-safe expansion) so each token is passed individually. [api mismatch]
Severity Level: Critical 🚨
- ❌ Zsh completion fails for nested commands and subcommands.
- ⚠️ Dynamic values mis-resolved when multiple tokens typed.Steps of Reproduction ✅
1. The main CLI registers the completion group via `registerCompletionCommand(program)` in
`src/cli/program.ts:12-17`, which is invoked for the default program instance.
2. `registerCompletionCommand` wires the hidden callback `completion __complete
[words...]` at `src/cli/commands/completion.ts:55-61`, whose action calls
`resolveCompletions(words, program)` and writes the candidates.
3. On zsh, a user enables completion by running `launch completion zsh` (implemented by
`zshCompletionScript()` in `src/core/completion.ts:94-103`) and sourcing the emitted
script, which defines `_launch_complete` and the line `candidates=("${(@f)$(launch
completion ${COMPLETE_SUBCOMMAND} -- "${words[2,-1]}" 2>/dev/null)}")` at
`src/core/completion.ts:98`.
4. In zsh, when the user types `launch snapshot d` and presses `<Tab>`, the completion
system sets `words=("launch" "snapshot" "d")`; `${words[2,-1]}` expands to `snapshot d`,
and the quoting `"${words[2,-1]}"` passes that as a SINGLE argument to `launch completion
__complete --`, so the `words` array received by the Node handler in
`src/cli/commands/completion.ts:58-59` is `["snapshot d"]`. `resolveCompletions(["snapshot
d"], program)` (see `src/core/completion.ts:225-241`) calls `descend()` at
`src/core/completion.ts:199-208`, which cannot match a subcommand literally named
`"snapshot d"`, so it treats this as the root command and returns only top-level
commands/flags instead of suggesting the `diff` subcommand of `snapshot`. This
demonstrates that multiple CLI tokens after `launch` are merged into one argument,
breaking nested-command completion for zsh.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/completion.ts
**Line:** 98:98
**Comment:**
*Api Mismatch: The zsh callback forwards `${words[2,-1]}` inside double quotes, which collapses multiple typed tokens into a single argument before invoking `__complete`. That breaks command-tree descent for nested commands because the resolver receives one space-joined token instead of separate words. Expand the array as separate arguments (zsh array-safe expansion) so each token is passed individually.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| function hasPositional(words: string[], pathLength: number): boolean { | ||
| const afterCommand = words.slice(pathLength, -1); // drop the command path and the in-progress word | ||
| return afterCommand.some((word) => !word.startsWith("-")); |
There was a problem hiding this comment.
Suggestion: Positional detection currently treats any non-flag token after the command path as a positional argument, but that also includes values belonging to options (for example after --app). This causes dynamic positional completion to be incorrectly disabled once a flag value is present. Update positional detection to skip option-value pairs (or parse known value-taking flags) instead of only checking startsWith("-"). [incorrect condition logic]
Severity Level: Major ⚠️
- ❌ Plan and snapshot positional completions disabled when flags used.
- ⚠️ Users must remember surfaces and snapshots without shell hints.Steps of Reproduction ✅
1. The hidden completion callback `completion __complete [words...]` is wired in
`src/cli/commands/completion.ts:55-61` and calls `resolveCompletions(words, program)` at
line 59; this command is registered on the main CLI via
`registerCompletionCommand(program)` in `src/cli/program.ts:12-17,151`.
2. `resolveCompletions` in `src/core/completion.ts:225-241` uses `ARGUMENT_SOURCES` at
`src/core/completion.ts:182-187` to provide dynamic positional candidates for commands
like `"plan"` (backed by `planSurfaceIds` at lines 156-160) and `"snapshot
diff"`/`"snapshot export"` (backed by `snapshotNames` at lines 162-163).
3. When the user types a command like `launch plan --app atlas <Tab>`, the callback
receives `words=["plan", "--app", "atlas", ""]` (command path `["plan"]` with flags and
values afterward). `resolveCompletions` computes `path=["plan"]` via `descend()` at
`src/core/completion.ts:199-208` and then looks up `argumentSource =
ARGUMENT_SOURCES.get("plan")` at lines 237-238.
4. Before using the dynamic positional source, `resolveCompletions` calls
`hasPositional(words, path.length)` at `src/core/completion.ts:238-239`. `hasPositional`
(lines 249-251) slices `afterCommand = words.slice(1, -1)` → `["--app", "atlas"]` and then
returns `true` because `"atlas"` does not start with `"-"`. This incorrectly treats the
flag value `"atlas"` as a positional argument, causing `argumentSource &&
!hasPositional(...)` to short-circuit and skip positional completion; as a result, dynamic
`plan` surface suggestions (e.g. `"catalog"`, `"listing"`, verified by
`resolveCompletions` tests in `src/core/completion.test.ts:79-83`) no longer appear
whenever any value-taking flag (like `--app`) is present.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/completion.ts
**Line:** 249:251
**Comment:**
*Incorrect Condition Logic: Positional detection currently treats any non-flag token after the command path as a positional argument, but that also includes values belonging to options (for example after `--app`). This causes dynamic positional completion to be incorrectly disabled once a flag value is present. Update positional detection to skip option-value pairs (or parse known value-taking flags) instead of only checking `startsWith("-")`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
🧹 Nitpick comments (1)
src/core/completion.ts (1)
359-359: 💤 Low valueConsider using
dirnamefor clarity.Using
dirname(rcFile)is more idiomatic thanjoin(rcFile, "..")for resolving the parent directory. Both are functionally equivalent.+import { dirname, join } from "node:path"; ... -const rcDir = join(rcFile, ".."); +const rcDir = dirname(rcFile);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/completion.ts` at line 359, Replace the idiomatic but less clear `join(rcFile, "..")` pattern with the built-in `dirname(rcFile)` function for resolving the parent directory of rcFile. Update the rcDir variable assignment to use dirname instead of join with the ".." parent directory reference, and ensure dirname is imported from the path module if it is not already.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/core/completion.ts`:
- Line 359: Replace the idiomatic but less clear `join(rcFile, "..")` pattern
with the built-in `dirname(rcFile)` function for resolving the parent directory
of rcFile. Update the rcDir variable assignment to use dirname instead of join
with the ".." parent directory reference, and ensure dirname is imported from
the path module if it is not already.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 21fdc6fa-6269-4148-a9aa-66e5d0a8a739
📒 Files selected for processing (18)
README.de.mdREADME.es.mdREADME.fr.mdREADME.ja.mdREADME.ko.mdREADME.mdREADME.pt-BR.mdREADME.ru.mdREADME.zh-CN.mddocs/commands.mdllms.txtscripts/gen-docs.tssrc/cli/commands/completion.test.tssrc/cli/commands/completion.tssrc/cli/program.tssrc/core/completion.test.tssrc/core/completion.tssrc/core/types.ts
There was a problem hiding this comment.
3 issues found across 18 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Resolve generated-doc collisions with #229 (app-extension signing) and #231 (deferred audit probes) by regenerating docs/commands.md, llms.txt, and the README badge rows from the merged source. No source conflicts (types.ts auto-merged). Full gate green: 1668 tests, docs in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…itional detection
Review fixes (cubic P1/P1/P2, codeant) to the completion logic:
- bash: slice COMP_WORDS to COMP_CWORD so mid-line completion resolves the word
under the cursor, not the trailing word.
- zsh: pass words[2,CURRENT] with the (@) flag so each token is a separate arg;
the old quoted "${words[2,-1]}" joined them into one string and broke
command-tree descent for nested commands (e.g. `snapshot diff`).
- resolveCompletions: a value following a value-taking flag (e.g. `--app web`)
is no longer miscounted as a positional, so dynamic positional completion is
not wrongly skipped after flags. Adds valueTakingFlags() (walks ancestors).
Tests lock in the cursor slice, the (@) token splitting, and the
positional-after-flag-value case. InstallResult/InstallOptions stay local to
the module (feature-local operation shapes, like the ASC *Resource types) —
only the shared Shell enum lives in types.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the review in c8042ef: Real bugs fixed (cubic P1/P1/P2, codeant):
Type location (codeant, Minor): keeping |
Rollup release covering the feature surface landed since v0.27.0: snapshot restore + App ID capabilities capture & housekeeping (#191), plan/drift offers & screenshots surface planners (#140), shell completions (#232), testflight feedback (#234), and the maximalist agent skills + collapsible README features (#237). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User description
What
Adds the
launch completioncommand group:launch completion <bash|zsh|fish>— prints the completion script to stdout.launch completion install [--shell <s>]— idempotently wires the script into the shell's rc file via a fenced# launch-completion start/endmanaged block (re-running never duplicates), or prints the manual step when it can't safely edit (e.g. fish without~/.config/fish).__completecallback that computes candidates by walking the live commander tree — new commands appear with no reinstall, and dynamic values resolve through existing core loaders: app handles (-a/--app),-p/--profile,plan/driftsurfaces, andsnapshot diff/exportnames.Why (developer journey)
59 commands + ~30 flags with no tab-completion = an invisible surface. Tab-completion is how a newcomer discovers commands (
launch <Tab>) and how the daily user avoids typos on handles/flags. It's the discoverability on-ramp for everything else. Part of #180.Design
gh/kubectl/helm): the emitted script is tiny and stable; it shells back tolaunch completion __completeon every<Tab>. Keeps the static script future-proof and lets dynamic values stay live.spliceManagedBlock), and candidate resolution live incore/completion.ts. The command file is pure commander wiring.scripts/gen-docs.tsnow flattens via commander'svisibleCommands(), so the internal__completenever leaks into the public reference orllms.txt.Shelltype added tocore/types.ts(single source of truth).Tests
core/completion.test.tscovers each shell's script output, install idempotency (running twice yields one block),spliceManagedBlocksurgery, shell detection, and the candidate resolver's precedence; CLI integration test beside it. Full gate green: typecheck, lint, 1632 tests, build,format:check,docs:check(in sync).Closes #222
🤖 Generated with Claude Code
Summary by cubic
Add
launch completionwith dynamic tab-completion for bash, zsh, and fish so commands, flags, and app-specific values are discoverable without reinstall. Fixes improve mid-line and nested subcommand completion and correct flag-value vs positional detection. Implements #222.New Features
launch completion <bash|zsh|fish>prints the script; defaults to$SHELLwhen omitted.launch completion installadds a managed# launch-completion start/endblock; safe to re-run and prints a manual step if it can’t edit (e.g. fish without~/.config/fish).__completewalks the live commander tree; completes-a/--app,-p/--profile,plan/driftsurfaces, andsnapshot diff/export.visibleCommands(); command ref shows 61 commands and badges show 1651 tests.Bug Fixes
COMP_WORDStoCOMP_CWORDfor correct mid-line completion.words[2,CURRENT]with(@)so tokens stay separate; fixes nested subcommands.--app web).Written for commit c8042ef. Summary will update on new commits.
CodeAnt-AI Description
Add shell tab completion for the Launch CLI
What Changed
launch completionto print completion scripts for bash, zsh, and fish, plus an install command to wire them into the shell configImpact
✅ Faster command discovery✅ Fewer typos in flags and app handles✅ No duplicate shell setup lines💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
New Features
launch completionto provide shell tab-completion, including automatic script output and an install option for bash, zsh, and fish (with a manual fallback when rc updates aren’t possible).Documentation
help [command]coverage and refreshed generated command listings.Tests