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..af5d7876 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 > $__fish_config_dir/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..ae991a96 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,13 @@ 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..0e20b452 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,19 @@ 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); + + 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); } diff --git a/packages/cli-core/src/lib/next-steps.ts b/packages/cli-core/src/lib/next-steps.ts index e6402016..692c6c3e 100644 --- a/packages/cli-core/src/lib/next-steps.ts +++ b/packages/cli-core/src/lib/next-steps.ts @@ -41,6 +41,34 @@ 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 link` to connect this directory to a different application", + "Run `clerk apps list` to browse your applications", + ], + SKILL_INSTALL: [ + "Start a new Claude Code or Codex session — the skill is now active for this project", + "Or run `clerk init` to scaffold Clerk yourself", + ], + CONFIG_PUSH: [ + "Run `clerk config pull` to confirm the live configuration", + "Run `clerk doctor` to verify your setup", + ], + CONFIG_DRY_RUN_PATCH: ["Run `clerk config patch` without `--dry-run` to apply these changes"], + CONFIG_DRY_RUN_PUT: ["Run `clerk config put` without `--dry-run` to apply these changes"], + 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; /**