Skip to content

feat: shell completion for jmp, jmp-admin, and j#422

Open
raballew wants to merge 13 commits intojumpstarter-dev:mainfrom
raballew:039-shell-completion
Open

feat: shell completion for jmp, jmp-admin, and j#422
raballew wants to merge 13 commits intojumpstarter-dev:mainfrom
raballew:039-shell-completion

Conversation

@raballew
Copy link
Copy Markdown
Member

@raballew raballew commented Apr 7, 2026

Summary

  • Auto-source shell completions for jmp, jmp-admin, and j when entering jmp shell (bash, zsh, fish)
  • Bake j subcommand names into shell init scripts at startup for instant TAB completion (no gRPC on every keypress)
  • Add j completion {bash,zsh,fish} subcommand with fast-path dispatch that avoids the full async stack and catches SystemExit cleanly
  • Add shared make_completion_command factory in jumpstarter-cli-common reused across all three CLIs

Closes #35

Test plan

  • 12 unit tests for _generate_shell_init covering bash/zsh/fish with and without j_commands, profiles, and unknown shells
  • 6 unit tests for j completion covering bash/zsh/fish script generation, error cases, and SystemExit handling
  • Manual: jmp shell then TAB-complete jmp, jmp-admin, and j subcommands in bash/zsh/fish
  • Manual: j completion bash | source /dev/stdin outside jmp shell

🤖 Generated with Claude Code

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 7, 2026

Deploy Preview for jumpstarter-docs failed. Why did it fail? →

Name Link
🔨 Latest commit b6b7bae
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69da0f5583302e0008adf04a

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a reusable Click completion-command factory, wires shell completions into jmp and jmp-admin, adds async-aware completion handling for the j entrypoint, and refactors shell-init generation and launch to support allowlisted j subcommands and their safe embedding into shell init scripts.

Changes

Cohort / File(s) Summary
Common completion factory
python/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion.py, python/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion_test.py
New make_completion_command() factory producing a completion Click subcommand for `bash
Admin CLI
python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion.py, python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion_test.py, python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/__init__.py
Added completion module using common factory and registered it on the admin command group; tests for shell outputs and error handling.
jmp CLI
python/packages/jumpstarter-cli/jumpstarter_cli/completion.py, python/packages/jumpstarter-cli/jumpstarter_cli/completion_test.py
Replaced package-local completion implementation with shared factory call (make_completion_command) for jmp.
j entrypoint & async completion
python/packages/jumpstarter-cli/jumpstarter_cli/j.py, python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
Added async-aware _j_shell_complete() with timeout and short-circuit handling when j completion ... is invoked or completion env var set; tests cover timeout and exit handling.
Shell integration & utils
python/packages/jumpstarter/jumpstarter/common/utils.py, python/packages/jumpstarter/jumpstarter/common/utils_test.py, python/packages/jumpstarter-cli/jumpstarter_cli/shell.py
Refactored shell init/launch into helpers, added _validate_j_commands and _generate_shell_init, extended launch_shell(..., j_commands=...), extract j subcommands from client and pass into shell init; comprehensive tests and lifecycle/cleanup checks.
CI workflow
.github/workflows/python-tests.yaml
Install fish on CI (Linux and macOS) to support fish-related tests.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Entry as j/jmp (entrypoint)
    participant Factory as make_completion_command
    participant ClickGen as ClickCompletionClass
    participant Shell as Shell (bash/zsh/fish)

    User->>Entry: run "j|jmp completion <shell>"
    Entry->>Factory: invoke completion command
    Factory->>ClickGen: get_completion_class(shell) & instantiate
    ClickGen-->>Factory: completion source()
    Factory-->>Entry: emit completion script (stdout)
    Entry-->>User: printed completion script
    User->>Shell: source/install emitted script
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, python

Suggested reviewers

  • mangelajo
  • evakhoni

Poem

🐇 I hopped through code with nimble feet,

I stitched the shells so completions meet.
Bash, zsh, fish now finish your line,
Tiny rabbit, one commit fine.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing shell completion features for three CLI tools (jmp, jmp-admin, and j).
Description check ✅ Passed The description is directly related to the changeset, providing a detailed summary of the completion features added, test coverage, and manual verification steps.
Linked Issues check ✅ Passed All primary objectives from issue #35 are met: shell completion is implemented for bash/zsh/fish, a completion subcommand is provided, and dynamic j command completion is included.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing shell completion features across the three CLIs and their infrastructure, with no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@raballew raballew marked this pull request as ready for review April 8, 2026 10:23
@raballew raballew requested review from evakhoni and mangelajo April 8, 2026 10:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py`:
- Around line 58-67: The test currently only ensures no exception is raised but
doesn't check the fallback payload; modify
test_j_shell_complete_returns_empty_on_timeout to capture the result of
run(_j_shell_complete) and assert it equals the expected timeout fallback (e.g.,
an empty list) when env_async (patched as slow_env) sleeps past
_COMPLETION_TIMEOUT_SECONDS; reference the patched symbol
"jumpstarter_cli.j.env_async", the async contextmanager helper "slow_env", the
test runner call "run(_j_shell_complete)", and the timeout constant
"_COMPLETION_TIMEOUT_SECONDS" to locate where to add the assertion.
- Around line 45-55: The test currently only verifies mock_client.cli was
fetched but doesn't ensure the returned CLI callable was actually invoked and
that the SystemExit path ran; update the test for _j_shell_complete to exercise
the SystemExit branch by making mock_client.cli.return_value raise SystemExit(0)
(as you've set on mock_cli_group) and then asserting that the returned callable
was called (e.g. assert mock_client.cli.return_value.assert_called_once()) and
that run(_j_shell_complete) completes without propagating the SystemExit;
reference mock_client.cli, mock_client.cli.return_value (mock_cli_group) and the
_j_shell_complete invocation when adding the assertions.

In `@python/packages/jumpstarter-cli/jumpstarter_cli/j.py`:
- Around line 32-44: The timeout isn't interrupting the thread wait because
to_thread.run_sync is called without abandon_on_cancel; update the call in the
block using anyio.fail_after/_COMPLETION_TIMEOUT_SECONDS (inside the async with
BlockingPortal() and async with env_async(portal, stack) as client) to await
to_thread.run_sync(_run_completion, abandon_on_cancel=True) so the fail_after
timeout can cancel the await immediately; keep the surrounding context (function
_run_completion, client.cli(), BlockingPortal, env_async) unchanged.

In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 106-111: The generated completion function _j_completion currently
provides the subcommand list for every cursor position; change its body to only
offer the baked subcommands when the cursor is on the first argument by checking
COMP_CWORD (e.g., if [ "${COMP_CWORD}" -eq 1 ]; then COMPREPLY=($(compgen -W
"..." -- "${COMP_WORDS[COMP_CWORD]}")); fi) and otherwise leave COMPREPLY empty
so default/file completions can run; update the code that builds _j_completion
(the block that uses j_commands, cmds, and lines.append) to emit that COMP_CWORD
guard around the compgen call.
- Around line 183-189: The temp ZDOTDIR assignment in _generate_shell_init
(tmpdir, zshrc_path, env["ZDOTDIR"]) causes zsh to read startup files from the
temp dir and break user profiles; remove the env["ZDOTDIR"] = tmpdir assignment
and instead keep ZDOTDIR unchanged, write init_content to tmpdir/.zshrc, and
ensure the shell loads this file by explicitly sourcing that file when launching
zsh (e.g., prepend a `source {zshrc_path}` invocation to the shell command or
add the temp file to the command invocation) while leaving cmd.extend(["--rcs",
"-o", "inc_append_history", "-o", "share_history"]) as-is so the user’s real
~/.zshenv and friends are still read.
- Around line 155-172: The _launch_fish function currently injects the context
and init_file.name directly into the fish init command (via f'printf
"{context}"; ' and unquoted init_file.name), which permits shell injection;
change _launch_fish to treat context as data by passing it through the
environment (like _launch_bash/_launch_zsh do) and reference it from the fish
function via the environment variable, and avoid interpolating init_file.name
into shell code by using a safely quoted/escaped path or sourcing via a separate
argument; keep using _run_process to launch the shell and add the environment
key (e.g., FISH_PROMPT_CONTEXT) to common_env before calling _run_process.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e091e91-4dda-463f-b83c-382cf818ca3c

📥 Commits

Reviewing files that changed from the base of the PR and between d713354 and 01bcfc7.

📒 Files selected for processing (11)
  • python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/__init__.py
  • python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion.py
  • python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion_test.py
  • python/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion.py
  • python/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion_test.py
  • python/packages/jumpstarter-cli/jumpstarter_cli/completion.py
  • python/packages/jumpstarter-cli/jumpstarter_cli/j.py
  • python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
  • python/packages/jumpstarter-cli/jumpstarter_cli/shell.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/common/utils.py (1)

174-176: Consider quoting the init file path for defense in depth.

While init_file.name is system-generated by tempfile.NamedTemporaryFile and unlikely to contain special characters, quoting it provides defense against edge cases (e.g., unusual TMPDIR values).

♻️ Suggested improvement
     if init_file:
-        init_cmd += f"; source {init_file.name}"
+        init_cmd += f"; source {shlex.quote(init_file.name)}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 174 -
176, The init file path used when building init_cmd is not quoted; update the
code that builds init_cmd (the block that appends f"; source {init_file.name}")
to safely quote the path (e.g., via shlex.quote or equivalent) before
interpolation so the call to _run_process([shell, "--init-command", init_cmd],
fish_env, lease) gets a properly escaped filename; adjust the code that sets
init_cmd and the branch that checks init_file to use the quoted value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 174-176: The init file path used when building init_cmd is not
quoted; update the code that builds init_cmd (the block that appends f"; source
{init_file.name}") to safely quote the path (e.g., via shlex.quote or
equivalent) before interpolation so the call to _run_process([shell,
"--init-command", init_cmd], fish_env, lease) gets a properly escaped filename;
adjust the code that sets init_cmd and the branch that checks init_file to use
the quoted value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5ea477e-d702-40b0-b0e1-a15aac6f105a

📥 Commits

Reviewing files that changed from the base of the PR and between 01bcfc7 and 1b69a06.

📒 Files selected for processing (4)
  • python/packages/jumpstarter-cli/jumpstarter_cli/j.py
  • python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
✅ Files skipped from review due to trivial changes (1)
  • python/packages/jumpstarter-cli/jumpstarter_cli/j.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

@mangelajo
Copy link
Copy Markdown
Member

Something is weird at my side:

➜  python git:(039-shell-completion) ✗ export PATH=$(pwd)/.venv/bin:$PATH
➜  python git:(039-shell-completion) ✗ jmp shell -l board-type=qc8650
[04/08/2026 16:07:58] INFO     [jumpstarter.client.lease] Acquiring lease 019d6d6b-da70-76c2-9235-d789b272009b for selector board-type=qc8650 for duration 0:30:00
                      INFO     Waiting for ready connection at /var/folders/j_/9v47_5750_s85_5j4tybsx200000gn/T/jumpstarter-dtb075vs/socket
[04/08/2026 16:07:59] INFO     [jumpstarter_cli.shell] Waiting for beforeLease hook to complete...
(eval):39: command not found: compdef
(eval):39: command not found: compdef
(eval):39: command not found: compdef
majopela@majopela-mac python % exit
[04/08/2026 16:08:03] INFO     [jumpstarter.client.lease] Releasing Lease 019d6d6b-da70-76c2-9235-d789b272009b

@mangelajo
Copy link
Copy Markdown
Member

this is MacOS / zsh

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
python/packages/jumpstarter/jumpstarter/common/utils.py (2)

159-178: Context injection fixed; minor remaining inconsistency with init file path.

The critical shell injection via context is now properly fixed using the _JMP_SHELL_CONTEXT environment variable (line 160, 168).

Line 176 still interpolates init_file.name directly into the fish command. While this is safe in practice (tempfile generates controlled paths like /tmp/tmpXXXXXX.sh), the past review suggested passing it via environment variable for consistency with the context fix.

♻️ Optional: Use env var for init file path
 def _launch_fish(shell, init_file, common_env, context, lease):
-    fish_env = common_env | {"_JMP_SHELL_CONTEXT": context}
+    fish_env = common_env | {"_JMP_SHELL_CONTEXT": context}
     fish_fn = (
         # ... prompt function unchanged ...
     )
     init_cmd = fish_fn
     if init_file:
-        init_cmd += f"; source {init_file.name}"
+        fish_env["_JMP_SHELL_INIT"] = init_file.name
+        init_cmd += '; source "$_JMP_SHELL_INIT"'
     return _run_process([shell, "--init-command", init_cmd], fish_env, lease)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 159 -
178, _in _launch_fish the init file path is still interpolated directly via
init_file.name into init_cmd; change this to pass the path through the
environment like you did for context: add a key (e.g. "_JMP_FISH_INIT") to
fish_env containing init_file.name (when init_file is present), and reference
that env var in init_cmd instead of init_file.name so _run_process receives no
direct string interpolation of the path; update any logic that builds init_cmd
and the call to _run_process accordingly.

99-145: Good implementation with minor inconsistency in shell detection.

The zsh compinit initialization (line 123) correctly addresses the macOS "command not found: compdef" error reported in PR comments. The bash COMP_CWORD check (lines 110-111) properly limits subcommand completion to the first argument position.

Minor inconsistency: bash uses .endswith("bash") (matches both bash and paths like /usr/bin/bash), while zsh and fish use exact string matches. Since shell_name is derived from os.path.basename() in launch_shell, this works correctly, but the style is inconsistent.

♻️ Optional: Consistent shell detection
-    if shell_name.endswith("bash"):
+    if shell_name == "bash":

Or alternatively, use .endswith() for all shells to handle variants like zsh-5.9.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 99 -
145, The shell-detection logic in _generate_shell_init is inconsistent: bash
uses shell_name.endswith("bash") but zsh and fish use exact equality; make
detection uniform by using endswith for zsh and fish as well (e.g., change
comparisons in the branches that currently use shell_name == "zsh" and
shell_name == "fish" to shell_name.endswith("zsh") and
shell_name.endswith("fish")), so variants like "zsh-5.9" or paths still match
consistently with the bash branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 159-178: _in _launch_fish the init file path is still interpolated
directly via init_file.name into init_cmd; change this to pass the path through
the environment like you did for context: add a key (e.g. "_JMP_FISH_INIT") to
fish_env containing init_file.name (when init_file is present), and reference
that env var in init_cmd instead of init_file.name so _run_process receives no
direct string interpolation of the path; update any logic that builds init_cmd
and the call to _run_process accordingly.
- Around line 99-145: The shell-detection logic in _generate_shell_init is
inconsistent: bash uses shell_name.endswith("bash") but zsh and fish use exact
equality; make detection uniform by using endswith for zsh and fish as well
(e.g., change comparisons in the branches that currently use shell_name == "zsh"
and shell_name == "fish" to shell_name.endswith("zsh") and
shell_name.endswith("fish")), so variants like "zsh-5.9" or paths still match
consistently with the bash branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb6e6b21-e74e-4c24-90a5-9937ef44de36

📥 Commits

Reviewing files that changed from the base of the PR and between 1b69a06 and 2e888a8.

📒 Files selected for processing (2)
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
✅ Files skipped from review due to trivial changes (1)
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

@raballew raballew enabled auto-merge (squash) April 8, 2026 19:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 119-131: The zsh branch currently appends the source of ~/.zshrc
before running compinit, causing compdef errors; modify the block handling
shell_name.endswith("zsh") so compinit (the "autoload -Uz compinit && compinit"
line) and the eval "$(jmp ...)" / eval "$(jmp-admin ...)" completions are added
to lines before the optional '[ -f ~/.zshrc ] && source ~/.zshrc' when
use_profiles is True, preserving the j_commands logic (compdef vs eval "$(j
...)" behavior) and returning the joined lines as before.
- Around line 126-128: The zsh `_arguments` invocation built in utils.py when
j_commands is present uses wrong syntax; update the string produced by the
lines.append call (the one currently building compdef with f"compdef '_arguments
\"1:(({cmds}))\"' j") to include the required message field and single
parentheses around the value list (for example use a message like "subcommand"
and value list "(cmd1 cmd2 ...)"); ensure the f-string around variable
j_commands (cmds) produces `_arguments '1:subcommand:(<cmds>)'` and that quoting
is correct so the final compdef call registers properly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62822921-b23b-460d-a58d-23b050ead306

📥 Commits

Reviewing files that changed from the base of the PR and between 2e888a8 and 4dc45a8.

📒 Files selected for processing (2)
  • python/packages/jumpstarter/jumpstarter/common/utils.py
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py`:
- Around line 292-300: The zsh test invoking subprocess.run with ["zsh",
"--rcs", "-c", "exit 0"] doesn't source init files in non-interactive mode and
must be changed to spawn zsh in interactive or login mode (e.g., use "-i" or
"-l") so the completion init logic runs; update the subprocess.run call(s) that
reference zsh to use an interactive/login flag or otherwise explicitly source
the tmpdir ~/.zshrc within the invoked command. Also update the bash test that
uses ["bash", "--rcfile", ... , "-c", ...] to instead explicitly source the
rcfile in the command (modify the subprocess.run invocation referencing
bash/--rcfile) so the bash initialization is actually executed during the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 255e6b21-8a63-477a-a595-4818689ba271

📥 Commits

Reviewing files that changed from the base of the PR and between 4dc45a8 and 5e6a0d3.

📒 Files selected for processing (2)
  • .github/workflows/python-tests.yaml
  • python/packages/jumpstarter/jumpstarter/common/utils_test.py

@raballew raballew force-pushed the 039-shell-completion branch from 54ba220 to 9774aa7 Compare April 9, 2026 12:33
@mangelajo
Copy link
Copy Markdown
Member

➜  python git:(039-shell-completion) ✗ jmp shell -l target=j784s4evm,enabled=true
[04/10/2026 12:24:59] INFO     [jumpstarter.client.lease] Acquiring lease 019d76ec-6e10-707c-98aa-a3d6922e3327 for selector target=j784s4evm,enabled=true for duration 0:30:00
                      INFO     Waiting for ready connection at /var/folders/j_/9v47_5750_s85_5j4tybsx200000gn/T/jumpstarter-3z7n8ze5/socket
[04/10/2026 12:25:01] INFO     [jumpstarter_cli.shell] Waiting for beforeLease hook to complete...
majopela@majopela-mac python % j
Usage: j [OPTIONS] COMMAND [ARGS]...

  Generic composite device

Options:
  --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]
                                  Set the log level
  --help                          Show this message and exit.

Commands:
  power    SNMP power control commands
  serial   Serial port client
  ssh      Run SSH command with arguments
  storage  Software-defined flasher interface
  tcp      Generic Network Connection
  tmt      Run TMT command with arguments
  vnc      Open a VNC session and block until the user closes it.
majopela@majopela-mac python % j
power    serial   ssh      storage  tcp      tmt      vnc

majopela@majopela-mac python % j storage

I found 2 problems:

  • [CRITICAL] The nice prompt is lost
  • [LOW] Anything beyond first level of j command is not autocompleted (but could be analyzed on a follow up PR)

For jmp it seems to work well.

@raballew
Copy link
Copy Markdown
Member Author

@ambient-code why is CI failing?

@raballew raballew force-pushed the 039-shell-completion branch from 7f73dd8 to b6b7bae Compare April 11, 2026 09:07
@mangelajo
Copy link
Copy Markdown
Member

mangelajo commented Apr 13, 2026

Now completion for bare jmp does not work, but the prompt is back

@raballew
Copy link
Copy Markdown
Member Author

raballew commented Apr 13, 2026

⬢ [toolbx]~/code/jumpstarter/python% autoload -Uz compinit && compinit
⬢ [toolbx]~/code/jumpstarter/python% export PATH="$(uv run which jmp | xargs dirname):$PATH" 
Installed 4 packages in 2ms
⬢ [toolbx]~/code/jumpstarter/python% eval "$(uv run jmp completion zsh)"
Installed 4 packages in 2ms
⬢ [toolbx]~/code/jumpstarter/python% jmp c    jmp c
completion  -- Generate shell completion script.
config      -- Manage local configurations
create      -- Create a resource

raballew and others added 11 commits April 13, 2026 16:50
Extract a shared make_completion_command factory into jumpstarter-cli-common
and use it in both jmp and jmp-admin CLIs. This adds a `completion` subcommand
to jmp-admin (and by extension `jmp admin`) supporting bash, zsh, and fish.

Generated-By: Forge/20260407_145514_2280530_d26e63ff
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The shared make_completion_command factory lacked direct unit tests in
its own package, relying only on indirect coverage from downstream
packages. This adds tests exercising the factory with a minimal Click
group for all shell types and error cases.

Generated-By: Forge/20260407_145514_2280530_d26e63ff
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-source completions for jmp, jmp-admin, and j when entering jmp shell
(bash, zsh, fish). For j, command names are baked into the shell init script
at startup to avoid slow gRPC calls on every TAB press.

Adds `j completion {bash,zsh,fish}` subcommand for standalone use, with a
fast-path dispatch that avoids the full async stack. Catches SystemExit from
Click's completion handler before it propagates through anyio task groups.

Closes jumpstarter-dev#35

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix zsh temp file leak by cleaning up .zshrc in _launch_zsh finally block (F001)
- Validate j_commands against safe pattern to prevent shell injection (F002)
- Refactor launch_shell into per-shell helpers to reduce complexity (F003)
- Add debug logging when j command extraction fails (F004)
- Add test for zsh temp file cleanup in launch_shell (F005)
- Add 5-second timeout to _j_shell_complete to prevent shell freezes (F006)
- Rename admin completion tests to describe expected behavior (F007)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add None guard for get_completion_class return value (F001)
- Simplify zsh temp file handling with mkdtemp, eliminating unnecessary
  intermediate file (F002)
- Quote fish completion argument for defense-in-depth (F003)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix shell injection in fish prompt by passing context via env var
- Add COMP_CWORD guard to limit bash completion to first argument
- Restore original ZDOTDIR in generated zshrc to preserve user config
- Add abandon_on_cancel=True to completion thread for clean timeout
- Strengthen test assertions for completion and timeout behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The zsh init script used compdef without ensuring compinit had been
called. On macOS zsh where compinit is not loaded by /etc/zshrc, this
caused "command not found: compdef" errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When use_profiles=True, the user's ~/.zshrc (or ~/.bashrc) is sourced
inside the init script, which typically sets PROMPT/PS1 and overrides
the custom jumpstarter prompt set via environment variable. Fix by
appending the prompt assignment at the end of the init script so it
runs after user profile sourcing. Pass context via _JMP_SHELL_CONTEXT
env var (consistent with fish) instead of interpolating it directly.

Also refactor _launch_bash to manage its own init file lifecycle,
matching the pattern used by _launch_zsh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@raballew raballew force-pushed the 039-shell-completion branch from b6b7bae to 2529afe Compare April 13, 2026 14:51
raballew and others added 2 commits April 13, 2026 18:42
When the user's shell profile modifies PATH (common on macOS where
/etc/zshenv runs path_helper, or with oh-my-zsh), the bare command
names jmp/jmp-admin/j may no longer be found by the time the
completion eval commands run. Resolve the CLI binary paths at init
generation time using shutil.which() so the completion scripts
reference absolute paths that survive PATH modifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test relied on jmp being installed on PATH but CI environments
do not have it, causing shutil.which to return None and the test
to fail with bare command names instead of absolute paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFE: terminal autocomplete for jumpstarter

2 participants