Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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`

Expand Down
2 changes: 1 addition & 1 deletion docs/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
6 changes: 3 additions & 3 deletions docs/specs/mimo-code-setup-prd/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`

Expand Down
4 changes: 2 additions & 2 deletions src/cli/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
);

Expand All @@ -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.",
);
}

Expand Down
9 changes: 7 additions & 2 deletions src/install/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ export interface FileSystem {
): Promise<void>;
}

export interface PasswordPromptOptions {
mask?: boolean | string;
}

export interface PromptAdapter {
password(message: string): Promise<string>;
password(message: string, options?: PasswordPromptOptions): Promise<string>;
select<TValue extends string>(
message: string,
choices: readonly { name: string; value: TValue }[],
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions src/install/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
}
Expand All @@ -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.",
});
}

Expand Down
3 changes: 2 additions & 1 deletion test/install/secrets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
7 changes: 6 additions & 1 deletion test/install/test-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
HttpJsonRequest,
HttpJsonResponse,
InstallerDeps,
PasswordPromptOptions,
} from "../../src/install/deps.js";
import { createNodeFileSystem } from "../../src/install/deps.js";

Expand All @@ -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;
Expand All @@ -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 = {
Expand Down Expand Up @@ -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) {
Expand Down
Loading