From 5247a1c7a3a9aa1be031ca4d8e578cc1b3e42d9d Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 14:45:41 +0300 Subject: [PATCH 1/6] feat(completion-hint): the hint for completion script after login --- packages/cli/src/commands/auth/login.ts | 2 ++ .../cli/src/commands/completion/hint.test.ts | 24 ++++++++++++++ packages/cli/src/commands/completion/hint.ts | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 packages/cli/src/commands/completion/hint.test.ts create mode 100644 packages/cli/src/commands/completion/hint.ts diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 913df3f7..2c7ce4db 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -18,6 +18,7 @@ import { readPassword, spinner, } from "../../core/ui.ts"; +import { hintShellCompletion } from "../completion/hint.ts"; import { offerGlobalSkillInstall } from "../skills/offer.ts"; const DASHBOARD_URL = @@ -307,5 +308,6 @@ export const authLoginCommand = defineCommand<{ } await offerGlobalSkillInstall(output, installSkill); + hintShellCompletion(output); }, }); diff --git a/packages/cli/src/commands/completion/hint.test.ts b/packages/cli/src/commands/completion/hint.test.ts new file mode 100644 index 00000000..aaefeab4 --- /dev/null +++ b/packages/cli/src/commands/completion/hint.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from "bun:test"; +import { completionHint } from "./hint.ts"; + +describe("completionHint", () => { + test("names the rc file and line for zsh, bash, and fish", () => { + expect(completionHint("/bin/zsh")).toBe( + "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.zshrc.", + ); + expect(completionHint("/usr/local/bin/bash")).toBe( + "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.bashrc.", + ); + expect(completionHint("/usr/local/bin/fish")).toBe( + "Tip: enable fish completions with: `mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish`.", + ); + }); + + test("falls back to the bash form for other or missing shells", () => { + for (const shell of ["/usr/bin/shelby", undefined, ""]) { + expect(completionHint(shell)).toBe( + "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.bashrc.", + ); + } + }); +}); diff --git a/packages/cli/src/commands/completion/hint.ts b/packages/cli/src/commands/completion/hint.ts new file mode 100644 index 00000000..5f5a5c74 --- /dev/null +++ b/packages/cli/src/commands/completion/hint.ts @@ -0,0 +1,32 @@ +import { basename } from "node:path"; +import { logger } from "../../core/logger.ts"; + +/** The rc-file line that wires completions into zsh/bash. */ +const PROCESS_SUB_LINE = "source <(bunny completion)"; + +const SHELL_HINTS: Record = { + zsh: `Tip: enable shell completions by adding \`${PROCESS_SUB_LINE}\` to ~/.zshrc.`, + bash: `Tip: enable shell completions by adding \`${PROCESS_SUB_LINE}\` to ~/.bashrc.`, + // Fish lazy-loads completion files from this directory. Alternative would be: appending it to ~/.config/fish/config.fish. + fish: "Tip: enable fish completions with: `mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish`.", +}; + +/** The one-line completion tip for the given `$SHELL` value. */ +export function completionHint(shell: string | undefined): string { + // Unknown shells get the bash form, the most portable. + return SHELL_HINTS[basename(shell || "")] ?? SHELL_HINTS.bash!; +} + +/** Passive one-line completion hint shown after `bunny login`; suppressed under `--output json`. */ +export function hintShellCompletion( + output?: string, + shell: string | undefined = process.env.SHELL, +): void { + if (output === "json") return; + + // yargs doesn't support fish shell completions yet until next release. + // Once https://github.com/yargs/yargs/pull/2569 is merged, remove this check. + if (shell?.includes("fish")) return; + + logger.dim(completionHint(shell)); +} From 0d603c704cff4a18d606be5e8d2bd78fd6495e38 Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 14:47:44 +0300 Subject: [PATCH 2/6] docs(cli): source-based completion setup, add changeset --- .changeset/completion-login-hint.md | 5 +++++ packages/cli/README.md | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/completion-login-hint.md diff --git a/.changeset/completion-login-hint.md b/.changeset/completion-login-hint.md new file mode 100644 index 00000000..1af31a50 --- /dev/null +++ b/.changeset/completion-login-hint.md @@ -0,0 +1,5 @@ +--- +"@bunny.net/cli": minor +--- + +feat(login): `bunny login` now ends with a shell-specific tip for enabling tab completion (`source <(bunny completion)` for zsh/bash, a lazy-loaded completions file for fish), and the README's `bunny completion` guidance sources the script instead of appending it diff --git a/packages/cli/README.md b/packages/cli/README.md index a8ed7c43..c2e7572d 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1264,12 +1264,14 @@ The method is case-insensitive (`get` and `GET` both work). Paths are relative t ### `bunny completion` -Generate a shell completion script. Add the output to your shell profile to enable tab completion. +Generate a shell completion script. Source it from your shell profile rather than appending the script itself, so it always matches the installed CLI: ```bash -bunny completion >> ~/.zshrc +echo 'source <(bunny completion)' >> ~/.zshrc # zsh; use ~/.bashrc for bash ``` +After `bunny login` succeeds, it prints the exact line for your shell. + ## Global Options | Flag | Alias | Description | Default | From f02a5c33137f7cd18493458c3a3dc6af7d0a7215 Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 15:02:36 +0300 Subject: [PATCH 3/6] feat(completion): stay quiet for unknown shells instead of bash fallback --- packages/cli/src/commands/completion/hint.test.ts | 8 +++----- packages/cli/src/commands/completion/hint.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/completion/hint.test.ts b/packages/cli/src/commands/completion/hint.test.ts index aaefeab4..2567ac00 100644 --- a/packages/cli/src/commands/completion/hint.test.ts +++ b/packages/cli/src/commands/completion/hint.test.ts @@ -14,11 +14,9 @@ describe("completionHint", () => { ); }); - test("falls back to the bash form for other or missing shells", () => { - for (const shell of ["/usr/bin/shelby", undefined, ""]) { - expect(completionHint(shell)).toBe( - "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.bashrc.", - ); + test("returns undefined for other or missing shells", () => { + for (const shell of ["/usr/bin/nu", undefined, ""]) { + expect(completionHint(shell)).toBeUndefined(); } }); }); diff --git a/packages/cli/src/commands/completion/hint.ts b/packages/cli/src/commands/completion/hint.ts index 5f5a5c74..557d116f 100644 --- a/packages/cli/src/commands/completion/hint.ts +++ b/packages/cli/src/commands/completion/hint.ts @@ -11,13 +11,12 @@ const SHELL_HINTS: Record = { fish: "Tip: enable fish completions with: `mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish`.", }; -/** The one-line completion tip for the given `$SHELL` value. */ -export function completionHint(shell: string | undefined): string { - // Unknown shells get the bash form, the most portable. - return SHELL_HINTS[basename(shell || "")] ?? SHELL_HINTS.bash!; +/** The one-line completion tip for the given `$SHELL` value; `undefined` when the shell is unknown. */ +export function completionHint(shell: string | undefined): string | undefined { + return shell ? SHELL_HINTS[basename(shell)] : undefined; } -/** Passive one-line completion hint shown after `bunny login`; suppressed under `--output json`. */ +/** Passive one-line completion hint shown after `bunny login`; suppressed under `--output json` and for unknown shells. */ export function hintShellCompletion( output?: string, shell: string | undefined = process.env.SHELL, @@ -28,5 +27,6 @@ export function hintShellCompletion( // Once https://github.com/yargs/yargs/pull/2569 is merged, remove this check. if (shell?.includes("fish")) return; - logger.dim(completionHint(shell)); + const hint = completionHint(shell); + if (hint) logger.dim(hint); } From 822a03ee52ad10507df80169e3fb4d26d0769d90 Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 15:07:48 +0300 Subject: [PATCH 4/6] refactor(completion): require a shell in completionHint, guard missing shell in the printer --- packages/cli/src/commands/completion/hint.test.ts | 2 +- packages/cli/src/commands/completion/hint.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/completion/hint.test.ts b/packages/cli/src/commands/completion/hint.test.ts index 2567ac00..d0b854c1 100644 --- a/packages/cli/src/commands/completion/hint.test.ts +++ b/packages/cli/src/commands/completion/hint.test.ts @@ -15,7 +15,7 @@ describe("completionHint", () => { }); test("returns undefined for other or missing shells", () => { - for (const shell of ["/usr/bin/nu", undefined, ""]) { + for (const shell of ["/usr/bin/nu", ""]) { expect(completionHint(shell)).toBeUndefined(); } }); diff --git a/packages/cli/src/commands/completion/hint.ts b/packages/cli/src/commands/completion/hint.ts index 557d116f..a9ef82a3 100644 --- a/packages/cli/src/commands/completion/hint.ts +++ b/packages/cli/src/commands/completion/hint.ts @@ -12,8 +12,8 @@ const SHELL_HINTS: Record = { }; /** The one-line completion tip for the given `$SHELL` value; `undefined` when the shell is unknown. */ -export function completionHint(shell: string | undefined): string | undefined { - return shell ? SHELL_HINTS[basename(shell)] : undefined; +export function completionHint(shell: string): string | undefined { + return SHELL_HINTS[basename(shell)] } /** Passive one-line completion hint shown after `bunny login`; suppressed under `--output json` and for unknown shells. */ @@ -23,9 +23,11 @@ export function hintShellCompletion( ): void { if (output === "json") return; + if (!shell) return; + // yargs doesn't support fish shell completions yet until next release. // Once https://github.com/yargs/yargs/pull/2569 is merged, remove this check. - if (shell?.includes("fish")) return; + if (shell.includes("fish")) return; const hint = completionHint(shell); if (hint) logger.dim(hint); From 5b35f691fc684de9d1d8052cf5dae77615ac9dde Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 15:12:19 +0300 Subject: [PATCH 5/6] feat(completion): hint at appending the completion script instead of sourcing it --- .changeset/completion-login-hint.md | 2 +- packages/cli/README.md | 10 ++++++++-- packages/cli/src/commands/completion/hint.test.ts | 4 ++-- packages/cli/src/commands/completion/hint.ts | 7 ++----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.changeset/completion-login-hint.md b/.changeset/completion-login-hint.md index 1af31a50..01e641a8 100644 --- a/.changeset/completion-login-hint.md +++ b/.changeset/completion-login-hint.md @@ -2,4 +2,4 @@ "@bunny.net/cli": minor --- -feat(login): `bunny login` now ends with a shell-specific tip for enabling tab completion (`source <(bunny completion)` for zsh/bash, a lazy-loaded completions file for fish), and the README's `bunny completion` guidance sources the script instead of appending it +feat(login): `bunny login` now ends with a shell-specific tip for enabling tab completion (`bunny completion >> ~/.zshrc` style for zsh/bash, a lazy-loaded completions file for fish) diff --git a/packages/cli/README.md b/packages/cli/README.md index c2e7572d..7c00050a 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1264,10 +1264,16 @@ The method is case-insensitive (`get` and `GET` both work). Paths are relative t ### `bunny completion` -Generate a shell completion script. Source it from your shell profile rather than appending the script itself, so it always matches the installed CLI: +Generate a shell completion script. Add the output to your shell profile to enable tab completion: ```bash -echo 'source <(bunny completion)' >> ~/.zshrc # zsh; use ~/.bashrc for bash +bunny completion >> ~/.zshrc # zsh; use ~/.bashrc for bash +``` + +Fish loads completion files from its own directory: + +```bash +mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish ``` After `bunny login` succeeds, it prints the exact line for your shell. diff --git a/packages/cli/src/commands/completion/hint.test.ts b/packages/cli/src/commands/completion/hint.test.ts index d0b854c1..98fbe37c 100644 --- a/packages/cli/src/commands/completion/hint.test.ts +++ b/packages/cli/src/commands/completion/hint.test.ts @@ -4,10 +4,10 @@ import { completionHint } from "./hint.ts"; describe("completionHint", () => { test("names the rc file and line for zsh, bash, and fish", () => { expect(completionHint("/bin/zsh")).toBe( - "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.zshrc.", + "Tip: enable shell completions with: `bunny completion >> ~/.zshrc`.", ); expect(completionHint("/usr/local/bin/bash")).toBe( - "Tip: enable shell completions by adding `source <(bunny completion)` to ~/.bashrc.", + "Tip: enable shell completions with: `bunny completion >> ~/.bashrc`.", ); expect(completionHint("/usr/local/bin/fish")).toBe( "Tip: enable fish completions with: `mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish`.", diff --git a/packages/cli/src/commands/completion/hint.ts b/packages/cli/src/commands/completion/hint.ts index a9ef82a3..da6a6b3c 100644 --- a/packages/cli/src/commands/completion/hint.ts +++ b/packages/cli/src/commands/completion/hint.ts @@ -1,12 +1,9 @@ import { basename } from "node:path"; import { logger } from "../../core/logger.ts"; -/** The rc-file line that wires completions into zsh/bash. */ -const PROCESS_SUB_LINE = "source <(bunny completion)"; - const SHELL_HINTS: Record = { - zsh: `Tip: enable shell completions by adding \`${PROCESS_SUB_LINE}\` to ~/.zshrc.`, - bash: `Tip: enable shell completions by adding \`${PROCESS_SUB_LINE}\` to ~/.bashrc.`, + zsh: "Tip: enable shell completions with: `bunny completion >> ~/.zshrc`.", + bash: "Tip: enable shell completions with: `bunny completion >> ~/.bashrc`.", // Fish lazy-loads completion files from this directory. Alternative would be: appending it to ~/.config/fish/config.fish. fish: "Tip: enable fish completions with: `mkdir -p ~/.config/fish/completions && bunny completion > ~/.config/fish/completions/bunny.fish`.", }; From eb45148d3abe5cf8897394c780e88b04682dd8b6 Mon Sep 17 00:00:00 2001 From: Mohammed Abdulkareem Date: Tue, 1 Sep 2026 16:57:51 +0300 Subject: [PATCH 6/6] fix: formatting/linting --- packages/cli/src/commands/completion/hint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/completion/hint.ts b/packages/cli/src/commands/completion/hint.ts index da6a6b3c..55e931f9 100644 --- a/packages/cli/src/commands/completion/hint.ts +++ b/packages/cli/src/commands/completion/hint.ts @@ -10,7 +10,7 @@ const SHELL_HINTS: Record = { /** The one-line completion tip for the given `$SHELL` value; `undefined` when the shell is unknown. */ export function completionHint(shell: string): string | undefined { - return SHELL_HINTS[basename(shell)] + return SHELL_HINTS[basename(shell)]; } /** Passive one-line completion hint shown after `bunny login`; suppressed under `--output json` and for unknown shells. */