diff --git a/.changeset/completion-login-hint.md b/.changeset/completion-login-hint.md new file mode 100644 index 00000000..01e641a8 --- /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 (`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 a8ed7c43..7c00050a 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1264,12 +1264,20 @@ 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. Add the output to your shell profile to enable tab completion: ```bash -bunny completion >> ~/.zshrc +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. + ## Global Options | Flag | Alias | Description | Default | 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..98fbe37c --- /dev/null +++ b/packages/cli/src/commands/completion/hint.test.ts @@ -0,0 +1,22 @@ +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 with: `bunny completion >> ~/.zshrc`.", + ); + expect(completionHint("/usr/local/bin/bash")).toBe( + "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`.", + ); + }); + + test("returns undefined for other or missing shells", () => { + 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 new file mode 100644 index 00000000..55e931f9 --- /dev/null +++ b/packages/cli/src/commands/completion/hint.ts @@ -0,0 +1,31 @@ +import { basename } from "node:path"; +import { logger } from "../../core/logger.ts"; + +const SHELL_HINTS: Record = { + 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`.", +}; + +/** 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)]; +} + +/** 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, +): 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; + + const hint = completionHint(shell); + if (hint) logger.dim(hint); +}