Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/more-next-steps.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions packages/cli-core/src/commands/auth/logout.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await deleteToken();
await clearAuth();
log.success("Logged out successfully");
printNextSteps(NEXT_STEPS.LOGOUT);
}
21 changes: 21 additions & 0 deletions packages/cli-core/src/commands/completion/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,6 +18,25 @@ const GENERATORS: Record<SupportedShell, CompletionGenerator> = {
powershell: generatePowershell,
};

const INSTALL_HINTS: Record<SupportedShell, readonly string[]> = {
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);
}
Expand All @@ -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]);
}
8 changes: 8 additions & 0 deletions packages/cli-core/src/commands/config/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -119,6 +120,13 @@ async function configPush(options: ConfigPushOptions, op: Operation): Promise<vo
? "[dry-run] Validation passed — no changes applied"
: "Config pushed successfully",
);
if (options.dryRun) {
printNextSteps(
op.method === "PATCH" ? NEXT_STEPS.CONFIG_DRY_RUN_PATCH : NEXT_STEPS.CONFIG_DRY_RUN_PUT,
);
} else {
printNextSteps(NEXT_STEPS.CONFIG_PUSH);
}
}

export async function readInput(options: { file?: string; json?: string }): Promise<string> {
Expand Down
10 changes: 6 additions & 4 deletions packages/cli-core/src/commands/skill/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down Expand Up @@ -246,8 +247,9 @@ export async function skillInstall(options: SkillInstallOptions): Promise<void>
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);
}
5 changes: 2 additions & 3 deletions packages/cli-core/src/commands/switch-env/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
7 changes: 5 additions & 2 deletions packages/cli-core/src/commands/switch-env/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const available = getAvailableEnvs();
Expand Down Expand Up @@ -70,9 +71,11 @@ export async function switchEnv(environmentArg: string | undefined): Promise<voi

log.data(`Switched from ${previousEnv} to ${target}.`);

// Check if there's a stored token for the target environment
const token = await getToken();
if (!token) {
log.data(`No credentials found for ${target}. Run \`clerk auth login\` to authenticate.`);
log.data(`No credentials found for ${target}.`);
printNextSteps(NEXT_STEPS.SWITCH_ENV_NO_TOKEN);
return;
}
printNextSteps(NEXT_STEPS.SWITCH_ENV);
}
2 changes: 2 additions & 0 deletions packages/cli-core/src/commands/unlink/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getGitRepoRoot } from "../../lib/git.ts";
import { dim, cyan } from "../../lib/color.ts";
import { CliError, ERROR_CODE, throwUsageError, throwUserAbort } from "../../lib/errors.ts";
import { log } from "../../lib/log.ts";
import { NEXT_STEPS, printNextSteps } from "../../lib/next-steps.ts";

interface UnlinkOptions {
yes?: boolean;
Expand Down Expand Up @@ -40,4 +41,5 @@ export async function unlink(options: UnlinkOptions = {}): Promise<void> {

await removeProfile(existing.path);
log.data(`\nUnlinked ${cyan(label)} from ${dim(displayPath)}`);
printNextSteps(NEXT_STEPS.UNLINK);
}
15 changes: 13 additions & 2 deletions packages/cli-core/src/commands/whoami/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,28 @@ 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();
if (!token) {
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);
}
28 changes: 28 additions & 0 deletions packages/cli-core/src/lib/next-steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
Loading