diff --git a/apps/cli/src/env.ts b/apps/cli/src/env.ts index 822c8e7d..7ba216dd 100644 --- a/apps/cli/src/env.ts +++ b/apps/cli/src/env.ts @@ -115,6 +115,17 @@ interface CredentialValidation { error?: string; } +/** + * Whether the shell environment already carries a usable credential — the host's + * pi login, or an API key for the selected provider. Reads process.env only. + */ +export function hasExportedCredentials(): boolean { + if (shouldUsePiAuth()) return true; + const spec = resolveModelSpec(); + if (typeof spec === 'string') return false; + return hasCredential(spec.providerId); +} + /** Whether a curated provider has its own named credential set (API key plus any extra var). */ function hasNamedCredential(providerId: CuratedProviderId): boolean { const apiKeys = PROVIDER_API_KEY_ENV[providerId]; diff --git a/apps/cli/src/home.ts b/apps/cli/src/home.ts index e6d61b6c..c319df45 100644 --- a/apps/cli/src/home.ts +++ b/apps/cli/src/home.ts @@ -16,6 +16,11 @@ export function getConfigFile(): string { return path.join(SHANNON_HOME, 'config.toml'); } +/** Whether the npx-mode credential file (`~/.shannon/config.toml`) exists on disk. */ +export function configFileExists(): boolean { + return fs.existsSync(getConfigFile()); +} + export function getWorkspacesDir(): string { return getMode() === 'local' ? path.resolve('workspaces') : path.join(SHANNON_HOME, 'workspaces'); } diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 383cdb3d..e449d505 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -18,8 +18,10 @@ import { setup } from './commands/setup.js'; import { start } from './commands/start.js'; import { status } from './commands/status.js'; import { stop } from './commands/stop.js'; +import { hasExportedCredentials } from './env.js'; import { crash, enableJsonErrors, fail, failUsage, failWith, jsonErrorsEnabled } from './errors.js'; import { availableCommands, helpTopics, isHelpableCommand, printCommandHelp, START_OPTIONS } from './help.js'; +import { configFileExists } from './home.js'; import { commandPrefix, getMode, isLocal, type Mode } from './mode.js'; import { displaySplash } from './splash.js'; import { closestMatch } from './suggest.js'; @@ -135,6 +137,21 @@ Docs & source: https://github.com/KeygraphHQ/shannon `); } +/** + * First-run guidance for a bare `npx @keygraph/shannon` invocation when neither a + * credentials file nor an exported shell credential exists. Walks the user to `setup`. + */ +function showSetupPrompt(): void { + const prefix = commandPrefix(); + console.log(` +Welcome to Shannon — AI Pentester by Keygraph + +No credentials configured yet. To get started, run: + + ${prefix} setup +`); +} + interface ParsedStartArgs { url: string; repo: string; @@ -240,7 +257,12 @@ async function main(): Promise { } else { const bare = command === undefined; if (bare && stdoutIsTerminal()) displaySplash(isLocal() ? undefined : getVersion()); - showHelp(bare); + const needsSetup = bare && !isLocal() && !configFileExists() && !hasExportedCredentials(); + if (needsSetup) { + showSetupPrompt(); + } else { + showHelp(bare); + } } return; }