From b142425f198336488d57ebd6fe8d49d6e36276ed Mon Sep 17 00:00:00 2001 From: Rafael Thayto Date: Wed, 29 Apr 2026 22:56:54 -0300 Subject: [PATCH 1/4] feat: extend next-step suggestions to more commands Add contextual next-step output to `auth logout`, `switch-env`, `unlink`, `whoami`, `completion`, `skill install`, `config patch`, and `config put` so users see the natural follow-up action after each successful run. Suggestions are gated on human/interactive mode and centralized in the `NEXT_STEPS` registry. --- .changeset/more-next-steps.md | 5 ++++ packages/cli-core/src/commands/auth/logout.ts | 2 ++ .../cli-core/src/commands/completion/index.ts | 21 +++++++++++++++ packages/cli-core/src/commands/config/push.ts | 4 +++ .../cli-core/src/commands/skill/install.ts | 10 ++++--- .../src/commands/switch-env/index.test.ts | 5 ++-- .../cli-core/src/commands/switch-env/index.ts | 7 +++-- .../cli-core/src/commands/unlink/index.ts | 2 ++ .../cli-core/src/commands/whoami/index.ts | 10 +++++-- packages/cli-core/src/lib/next-steps.ts | 27 +++++++++++++++++++ 10 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 .changeset/more-next-steps.md diff --git a/.changeset/more-next-steps.md b/.changeset/more-next-steps.md new file mode 100644 index 00000000..1eeb1bcc --- /dev/null +++ b/.changeset/more-next-steps.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Show contextual next-step suggestions after `clerk auth logout`, `clerk switch-env`, `clerk unlink`, `clerk whoami`, `clerk completion`, `clerk skill install`, `clerk config patch`, and `clerk config put` to point users to the natural follow-up action. diff --git a/packages/cli-core/src/commands/auth/logout.ts b/packages/cli-core/src/commands/auth/logout.ts index 7266f365..f1011b54 100644 --- a/packages/cli-core/src/commands/auth/logout.ts +++ b/packages/cli-core/src/commands/auth/logout.ts @@ -1,9 +1,11 @@ import { deleteToken } from "../../lib/credential-store.ts"; import { clearAuth } from "../../lib/config.ts"; import { log } from "../../lib/log.ts"; +import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.ts"; export async function logout(): Promise { await deleteToken(); await clearAuth(); log.success("Logged out successfully"); + printNextSteps(NEXT_STEPS.LOGOUT); } diff --git a/packages/cli-core/src/commands/completion/index.ts b/packages/cli-core/src/commands/completion/index.ts index 0e0f3ee9..a3fd7113 100644 --- a/packages/cli-core/src/commands/completion/index.ts +++ b/packages/cli-core/src/commands/completion/index.ts @@ -3,6 +3,7 @@ import { generate as generateZsh } from "./shells/zsh.ts"; import { generate as generateFish } from "./shells/fish.ts"; import { generate as generatePowershell } from "./shells/powershell.ts"; import { throwUsageError } from "../../lib/errors.ts"; +import { printNextSteps } from "../../lib/next-steps.ts"; type CompletionGenerator = (binaryName: string) => string; @@ -17,6 +18,25 @@ const GENERATORS: Record = { powershell: generatePowershell, }; +const INSTALL_HINTS: Record = { + bash: [ + 'Run `eval "$(clerk completion bash)"` to enable completions in this session', + "Append the same line to ~/.bashrc to make it permanent", + ], + zsh: [ + 'Run `eval "$(clerk completion zsh)"` to enable completions in this session', + "Append the same line to ~/.zshrc to make it permanent", + ], + fish: [ + "Run `clerk completion fish | source` to enable completions in this session", + "Run `clerk completion fish > ~/.config/fish/completions/clerk.fish` to install permanently", + ], + powershell: [ + "Run `clerk completion powershell | Out-String | Invoke-Expression` to enable completions in this session", + "Append the same line to your PowerShell profile to make it permanent", + ], +}; + function isSupportedShell(shell: string): shell is SupportedShell { return (SUPPORTED_SHELLS as readonly string[]).includes(shell); } @@ -41,4 +61,5 @@ Run 'clerk completion --help' for full setup instructions.`, throwUsageError(`Unsupported shell: ${shell}. Supported: ${SUPPORTED_SHELLS.join(", ")}`); } process.stdout.write(GENERATORS[shell]("clerk")); + printNextSteps(INSTALL_HINTS[shell]); } diff --git a/packages/cli-core/src/commands/config/push.ts b/packages/cli-core/src/commands/config/push.ts index 940e878f..52da6ce5 100644 --- a/packages/cli-core/src/commands/config/push.ts +++ b/packages/cli-core/src/commands/config/push.ts @@ -6,6 +6,7 @@ import { confirm } from "../../lib/prompts.ts"; import { dim, bold, red, green } from "../../lib/color.ts"; import { withSpinner } from "../../lib/spinner.ts"; import { log } from "../../lib/log.ts"; +import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.ts"; interface ConfigPushOptions { app?: string; @@ -119,6 +120,9 @@ async function configPush(options: ConfigPushOptions, op: Operation): Promise { diff --git a/packages/cli-core/src/commands/skill/install.ts b/packages/cli-core/src/commands/skill/install.ts index 108adf09..223f879c 100644 --- a/packages/cli-core/src/commands/skill/install.ts +++ b/packages/cli-core/src/commands/skill/install.ts @@ -35,6 +35,7 @@ import { } from "../../lib/runners.js"; import { isNonEmpty } from "../../lib/helpers/arrays.js"; import { detectPackageManager, type PackageManager } from "../../lib/package-manager.js"; +import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.js"; import clerkSkillMd from "../../../../../skills/clerk-cli/SKILL.md" with { type: "text" }; import clerkAuthMd from "../../../../../skills/clerk-cli/references/auth.md" with { type: "text" }; @@ -246,8 +247,9 @@ export async function skillInstall(options: SkillInstallOptions): Promise if (!runner) return; const ok = await installClerkSkillCore(runner, cwd, interactive); - if (ok) { - log.blank(); - log.success("clerk-cli skill installed. AI agents now have Clerk context in this project."); - } + if (!ok) return; + + log.blank(); + log.success("clerk-cli skill installed. AI agents now have Clerk context in this project."); + printNextSteps(NEXT_STEPS.SKILL_INSTALL); } diff --git a/packages/cli-core/src/commands/switch-env/index.test.ts b/packages/cli-core/src/commands/switch-env/index.test.ts index 706be4de..f51bdb90 100644 --- a/packages/cli-core/src/commands/switch-env/index.test.ts +++ b/packages/cli-core/src/commands/switch-env/index.test.ts @@ -136,9 +136,8 @@ describe("switch-env", () => { logSpy = spyOn(console, "log").mockImplementation(() => {}); await runSwitchEnv("staging"); - expect(captured.out).toContain( - "No credentials found for staging. Run `clerk auth login` to authenticate.", - ); + expect(captured.out).toContain("No credentials found for staging."); + expect(captured.err).toContain("clerk auth login"); }); test("does not warn about credentials when token exists", async () => { diff --git a/packages/cli-core/src/commands/switch-env/index.ts b/packages/cli-core/src/commands/switch-env/index.ts index f10d4933..a0c893ba 100644 --- a/packages/cli-core/src/commands/switch-env/index.ts +++ b/packages/cli-core/src/commands/switch-env/index.ts @@ -19,6 +19,7 @@ import { CliError } from "../../lib/errors.ts"; import { log } from "../../lib/log.ts"; import { isHuman } from "../../mode.ts"; import { select } from "../../lib/listage.ts"; +import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.ts"; export async function switchEnv(environmentArg: string | undefined): Promise { const available = getAvailableEnvs(); @@ -70,9 +71,11 @@ export async function switchEnv(environmentArg: string | undefined): Promise { await removeProfile(existing.path); log.data(`\nUnlinked ${cyan(label)} from ${dim(displayPath)}`); + printNextSteps(NEXT_STEPS.UNLINK); } diff --git a/packages/cli-core/src/commands/whoami/index.ts b/packages/cli-core/src/commands/whoami/index.ts index c8fdae5b..33548226 100644 --- a/packages/cli-core/src/commands/whoami/index.ts +++ b/packages/cli-core/src/commands/whoami/index.ts @@ -3,6 +3,8 @@ import { fetchUserInfo } from "../../lib/token-exchange.ts"; import { withSpinner } from "../../lib/spinner.ts"; import { log } from "../../lib/log.ts"; import { AuthError } from "../../lib/errors.ts"; +import { resolveProfile } from "../../lib/config.ts"; +import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.ts"; export async function whoami() { const token = await getValidToken(); @@ -10,10 +12,14 @@ export async function whoami() { throw new AuthError({ reason: "not_logged_in" }); } + let userInfo; try { - const userInfo = await withSpinner("Fetching account info...", () => fetchUserInfo(token)); - log.data(userInfo.email); + userInfo = await withSpinner("Fetching account info...", () => fetchUserInfo(token)); } catch { throw new AuthError({ reason: "session_expired" }); } + log.data(userInfo.email); + + const profile = await resolveProfile(process.cwd()); + printNextSteps(profile ? NEXT_STEPS.WHOAMI_LINKED : NEXT_STEPS.WHOAMI); } diff --git a/packages/cli-core/src/lib/next-steps.ts b/packages/cli-core/src/lib/next-steps.ts index e6402016..079fecd3 100644 --- a/packages/cli-core/src/lib/next-steps.ts +++ b/packages/cli-core/src/lib/next-steps.ts @@ -41,6 +41,33 @@ export const NEXT_STEPS = { "Run `clerk config schema --keys billing` to see all available settings", "Run `clerk config pull --keys billing` to see current values", ], + SWITCH_ENV: [ + "Run `clerk env pull` to fetch environment variables for this environment", + "Run `clerk doctor` to verify your setup", + ], + SWITCH_ENV_NO_TOKEN: [ + "Run `clerk auth login` to authenticate for this environment", + "Run `clerk env pull` to fetch environment variables", + ], + UNLINK: [ + "Run `clerk apps list` to browse your applications", + "Run `clerk link` to connect this directory to a different application", + ], + SKILL_INSTALL: [ + "Start a new Claude Code session — the skill is now active for this project", + "Run `clerk init` to scaffold Clerk if you haven't already", + "Run `clerk config pull` to share your live Clerk instance configuration with the agent", + ], + CONFIG_PUSH: [ + "Run `clerk config pull` to confirm the live configuration", + "Run `clerk doctor` to verify your integration", + ], + LOGOUT: ["Run `clerk auth login` to sign in again"], + WHOAMI: ["Run `clerk link` to connect this directory to an application"], + WHOAMI_LINKED: [ + "Run `clerk apps list` to see your other applications", + "Run `clerk config pull` to inspect the live configuration of this instance", + ], } as const; /** From a96e614ae7f93e46a98777d233dfeef6ec0c5fb8 Mon Sep 17 00:00:00 2001 From: Rafael Thayto Date: Thu, 30 Apr 2026 09:13:25 -0300 Subject: [PATCH 2/4] fix(whoami): make resolveProfile call best-effort Wrap the resolveProfile(process.cwd()) call in a try/catch so that whoami cannot fail after successfully fetching and printing account info. The profile resolution does git/config discovery which could throw in edge cases; this should not crash the command. --- packages/cli-core/src/commands/whoami/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli-core/src/commands/whoami/index.ts b/packages/cli-core/src/commands/whoami/index.ts index 33548226..0e20b452 100644 --- a/packages/cli-core/src/commands/whoami/index.ts +++ b/packages/cli-core/src/commands/whoami/index.ts @@ -20,6 +20,11 @@ export async function whoami() { } log.data(userInfo.email); - const profile = await resolveProfile(process.cwd()); - printNextSteps(profile ? NEXT_STEPS.WHOAMI_LINKED : NEXT_STEPS.WHOAMI); + let isLinked = false; + try { + isLinked = Boolean(await resolveProfile(process.cwd())); + } catch { + // Best-effort only: don't fail whoami when local profile resolution fails. + } + printNextSteps(isLinked ? NEXT_STEPS.WHOAMI_LINKED : NEXT_STEPS.WHOAMI); } From 662427627a0c541e203fab0cfea91f192d5c4715 Mon Sep 17 00:00:00 2001 From: Rafael Thayto Date: Wed, 6 May 2026 15:45:08 -0300 Subject: [PATCH 3/4] fix(completion): use \$__fish_config_dir for portable fish install hint --- packages/cli-core/src/commands/completion/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-core/src/commands/completion/index.ts b/packages/cli-core/src/commands/completion/index.ts index a3fd7113..af5d7876 100644 --- a/packages/cli-core/src/commands/completion/index.ts +++ b/packages/cli-core/src/commands/completion/index.ts @@ -29,7 +29,7 @@ const INSTALL_HINTS: Record = { ], fish: [ "Run `clerk completion fish | source` to enable completions in this session", - "Run `clerk completion fish > ~/.config/fish/completions/clerk.fish` to install permanently", + "Run `clerk completion fish > $__fish_config_dir/completions/clerk.fish` to install permanently", ], powershell: [ "Run `clerk completion powershell | Out-String | Invoke-Expression` to enable completions in this session", From 36237e758e9366ebc8f80f45d57ecb0d6020ef5d Mon Sep 17 00:00:00 2001 From: Rafael Thayto Date: Wed, 6 May 2026 16:42:29 -0300 Subject: [PATCH 4/4] fix(next-steps): tighten wording per review - CONFIG_PUSH: replace "verify your integration" with "verify your setup" to avoid confusion with OAuth provider integrations. - UNLINK: lead with `clerk link` since relinking is the more common follow-up than browsing. - SKILL_INSTALL: drop the misleading `clerk config pull` line (pulling doesn't share context with a running agent), name Codex alongside Claude Code, and call out `clerk init` as the manual alternative. - Dry-run pushes now suggest re-running the same command without `--dry-run` to apply the previewed changes. --- packages/cli-core/src/commands/config/push.ts | 6 +++++- packages/cli-core/src/lib/next-steps.ts | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/cli-core/src/commands/config/push.ts b/packages/cli-core/src/commands/config/push.ts index 52da6ce5..ae991a96 100644 --- a/packages/cli-core/src/commands/config/push.ts +++ b/packages/cli-core/src/commands/config/push.ts @@ -120,7 +120,11 @@ async function configPush(options: ConfigPushOptions, op: Operation): Promise