diff --git a/.agents/skills/coding-prompt-normalizer/references/repo-context-routing.md b/.agents/skills/coding-prompt-normalizer/references/repo-context-routing.md index 0c0aa85..5c770ad 100644 --- a/.agents/skills/coding-prompt-normalizer/references/repo-context-routing.md +++ b/.agents/skills/coding-prompt-normalizer/references/repo-context-routing.md @@ -33,7 +33,7 @@ Include a repository constraint only when it changes the task: - project activation target is `.mimocode/mimocode.json` - the managed provider key is `provider.gonkagate` - project scope should write only activation settings -- safe secret inputs are hidden prompt, `GONKAGATE_API_KEY`, or +- safe secret inputs are masked prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` - plain `--api-key` is intentionally unsupported - secrets should stay under `~/.gonkagate/mimo-code/...`, not inside the diff --git a/.claude/skills/coding-prompt-normalizer/references/repo-context-routing.md b/.claude/skills/coding-prompt-normalizer/references/repo-context-routing.md index 0c0aa85..5c770ad 100644 --- a/.claude/skills/coding-prompt-normalizer/references/repo-context-routing.md +++ b/.claude/skills/coding-prompt-normalizer/references/repo-context-routing.md @@ -33,7 +33,7 @@ Include a repository constraint only when it changes the task: - project activation target is `.mimocode/mimocode.json` - the managed provider key is `provider.gonkagate` - project scope should write only activation settings -- safe secret inputs are hidden prompt, `GONKAGATE_API_KEY`, or +- safe secret inputs are masked prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` - plain `--api-key` is intentionally unsupported - secrets should stay under `~/.gonkagate/mimo-code/...`, not inside the diff --git a/AGENTS.md b/AGENTS.md index 2ab0ebc..6ec2283 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,7 +47,7 @@ The intended happy path is: 1. user runs `npx @gonkagate/mimo-code-setup` 2. installer validates local `mimo` -3. installer collects a GonkaGate `gp-...` key through a hidden prompt, +3. installer collects a GonkaGate `gp-...` key through a masked prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` 4. installer calls `GET /v1/models` with that key and offers all returned GonkaGate models diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c67e8a..b01f81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ default - live GonkaGate `/v1/models` catalog fetch after safe API-key intake, with every returned model written into `provider.gonkagate.models` +- masked interactive GonkaGate API-key prompt ## [0.1.0] - 2026-06-11 diff --git a/README.md b/README.md index 1a72cb2..9a6bd55 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ npx @gonkagate/mimo-code-setup The happy path is: 1. The CLI checks that `mimo` is installed and supported. -2. It asks for your GonkaGate API key in a hidden prompt. +2. It asks for your GonkaGate API key in a masked prompt. 3. It calls `GET /v1/models` and offers every model returned by GonkaGate. 4. It asks whether GonkaGate should be activated for `user` or `project` scope. @@ -106,7 +106,7 @@ npx @gonkagate/mimo-code-setup Under the hood, the shipped runtime: - validates local `mimo` -- accepts the secret only through a hidden prompt, `GONKAGATE_API_KEY`, or +- accepts the secret only through a masked prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` - fetches the live GonkaGate model catalog from `https://api.gonkagate.com/v1/models` @@ -144,7 +144,7 @@ default and must not contain the secret or the secret file path. Safe secret inputs: -- hidden interactive prompt +- masked interactive prompt - `GONKAGATE_API_KEY` - `--api-key-stdin` diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 395ca67..0285153 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -13,7 +13,7 @@ a hardcoded allowlist. 2. Resolve safe config and state paths without mutating shell profiles or `.env` files. 3. Collect a GonkaGate API key through safe inputs only: - `GONKAGATE_API_KEY`, hidden interactive prompt, or `--api-key-stdin`. + `GONKAGATE_API_KEY`, masked interactive prompt, or `--api-key-stdin`. 4. Fetch `https://api.gonkagate.com/v1/models` with Bearer auth and build the setup picker from every returned model id. 5. Store the secret under `~/.gonkagate/mimo-code/api-key`. diff --git a/docs/security.md b/docs/security.md index 21f6493..50b7c51 100644 --- a/docs/security.md +++ b/docs/security.md @@ -8,7 +8,7 @@ catalog fetch, managed storage, and diagnostics. Allowed future inputs: -- hidden interactive prompt +- masked interactive prompt - `GONKAGATE_API_KEY` - `--api-key-stdin` diff --git a/docs/specs/mimo-code-setup-prd/spec.md b/docs/specs/mimo-code-setup-prd/spec.md index 6f3fd69..f76e681 100644 --- a/docs/specs/mimo-code-setup-prd/spec.md +++ b/docs/specs/mimo-code-setup-prd/spec.md @@ -81,7 +81,7 @@ The tool: 1. validates local `mimo` 2. verifies that the installed MiMoCode version is supported or clearly reports that it is newer than the last audited baseline -3. accepts a GonkaGate API key through a hidden prompt, `GONKAGATE_API_KEY`, or +3. accepts a GonkaGate API key through a masked prompt, `GONKAGATE_API_KEY`, or `--api-key-stdin` 4. calls `GET /v1/models` with that key and offers every returned GonkaGate model id @@ -124,7 +124,7 @@ Contributor user: - one public npm package: `@gonkagate/mimo-code-setup` - one public repository: `GonkaGate/mimo-code-setup` - configuration of already installed local MiMoCode -- hidden or automation-safe secret input +- masked or automation-safe secret input - installer-owned managed secret file - live GonkaGate model picker backed by `GET /v1/models` - `user` and `project` setup scope @@ -237,7 +237,7 @@ security docs, and the PRD whenever it changes. Allowed: -- hidden interactive prompt +- masked interactive prompt - `GONKAGATE_API_KEY` - `--api-key-stdin` diff --git a/src/cli/parse.ts b/src/cli/parse.ts index bf6a24f..f7a1939 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -38,7 +38,7 @@ function createProgram(): Command { `Base URL: ${GONKAGATE_BASE_URL}`, `Provider package: ${CURRENT_PROVIDER_PACKAGE}`, `Secret binding: ${MANAGED_SECRET_FILE_REF}`, - "Safe secret inputs: hidden prompt, GONKAGATE_API_KEY, --api-key-stdin", + "Safe secret inputs: masked prompt, GONKAGATE_API_KEY, --api-key-stdin", ].join("\n"), ); @@ -48,7 +48,7 @@ function createProgram(): Command { export function parseCliOptions(argv: readonly string[]): CliOptions { if (argv.some((arg) => arg === "--api-key" || arg.startsWith("--api-key="))) { throw new Error( - "Plain --api-key is not supported. Use a hidden prompt, GONKAGATE_API_KEY, or --api-key-stdin.", + "Plain --api-key is not supported. Use a masked prompt, GONKAGATE_API_KEY, or --api-key-stdin.", ); } diff --git a/src/install/deps.ts b/src/install/deps.ts index bd9d9b0..64e1391 100644 --- a/src/install/deps.ts +++ b/src/install/deps.ts @@ -76,8 +76,12 @@ export interface FileSystem { ): Promise; } +export interface PasswordPromptOptions { + mask?: boolean | string; +} + export interface PromptAdapter { - password(message: string): Promise; + password(message: string, options?: PasswordPromptOptions): Promise; select( message: string, choices: readonly { name: string; value: TValue }[], @@ -115,7 +119,8 @@ export function createNodeDeps(): InstallerDeps { http: createNodeHttpClient(), platform: process.platform, prompts: { - password: (message) => password({ message }), + password: (message, options) => + password({ mask: options?.mask, message }), select: (message, choices) => select({ choices: [...choices], message }), }, readStdin: () => readStreamText(process.stdin), diff --git a/src/install/secrets.ts b/src/install/secrets.ts index 35fa67b..c8508c9 100644 --- a/src/install/secrets.ts +++ b/src/install/secrets.ts @@ -25,7 +25,7 @@ export async function collectGonkaGateApiKey( if (deps.streams.stdin.isTTY === true && deps.streams.stdout.isTTY === true) { return validateSecret( - await deps.prompts.password("GonkaGate API key"), + await deps.prompts.password("GonkaGate API key", { mask: true }), "prompt", ); } @@ -34,7 +34,7 @@ export async function collectGonkaGateApiKey( category: "secret_intake", code: "non_interactive_secret_required", message: - "A GonkaGate API key is required. Use a hidden prompt, GONKAGATE_API_KEY, or --api-key-stdin.", + "A GonkaGate API key is required. Use a masked prompt, GONKAGATE_API_KEY, or --api-key-stdin.", }); } diff --git a/test/install/secrets.test.ts b/test/install/secrets.test.ts index 9aee85e..0a32f1d 100644 --- a/test/install/secrets.test.ts +++ b/test/install/secrets.test.ts @@ -22,11 +22,12 @@ test("secret intake accepts env and stdin without depending on durable env runti stdinDeps.cleanup(); }); -test("secret intake uses hidden prompt only for interactive TTYs", async () => { +test("secret intake uses masked prompt only for interactive TTYs", async () => { const deps = createTestDeps(); deps.queuePrompt("gp-prompt-secret"); const result = await collectGonkaGateApiKey({}, deps); assert.deepEqual(result, { key: "gp-prompt-secret", source: "prompt" }); + assert.deepEqual(deps.passwordPromptLog, [{ mask: true }]); deps.cleanup(); const nonInteractive = createTestDeps(); diff --git a/test/install/test-deps.ts b/test/install/test-deps.ts index f0aca4d..64f110e 100644 --- a/test/install/test-deps.ts +++ b/test/install/test-deps.ts @@ -7,6 +7,7 @@ import type { HttpJsonRequest, HttpJsonResponse, InstallerDeps, + PasswordPromptOptions, } from "../../src/install/deps.js"; import { createNodeFileSystem } from "../../src/install/deps.js"; @@ -25,6 +26,7 @@ export interface TestDeps extends InstallerDeps { cleanup(): void; commandLog: RecordedCommand[]; httpLog: RecordedHttpRequest[]; + passwordPromptLog: PasswordPromptOptions[]; queueCommand(result: CommandExecutionResult): void; queueHttpResponse(result: HttpJsonResponse): void; queuePrompt(value: string): void; @@ -43,6 +45,7 @@ export function createTestDeps(): TestDeps { const commandLog: RecordedCommand[] = []; const httpResults: HttpJsonResponse[] = []; const httpLog: RecordedHttpRequest[] = []; + const passwordPromptLog: PasswordPromptOptions[] = []; const promptValues: string[] = []; const deps: TestDeps = { @@ -80,9 +83,11 @@ export function createTestDeps(): TestDeps { }, }, httpLog, + passwordPromptLog, platform: process.platform, prompts: { - async password() { + async password(_message, options) { + passwordPromptLog.push(options ?? {}); return promptValues.shift() ?? ""; }, async select(_message, choices) {