diff --git a/.changeset/completion-login-hint.md b/.changeset/completion-login-hint.md new file mode 100644 index 00000000..4ad607be --- /dev/null +++ b/.changeset/completion-login-hint.md @@ -0,0 +1,5 @@ +--- +"@bunny.net/cli": patch +--- + +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 df66821c..9b78fcfd 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1350,12 +1350,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 53906bb0..5021537e 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -1,5 +1,6 @@ import { randomBytes } from "node:crypto"; import { createCoreClient } from "@bunny.net/openapi-client"; +import { hintShellCompletion } from "@/commands/completion/hint.ts"; import { offerGlobalSkillInstall } from "@/commands/skills/offer.ts"; import { profileExists, resolveConfig, setProfile } from "@/config/index.ts"; import { clientOptions } from "@/core/client-options.ts"; @@ -303,5 +304,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); +}