/** * gcloud subprocess runner for `hikoutei setup`. * * All gcloud invocations go through the `GcloudRunner` interface so unit * tests can inject a fake runner that records commands and returns scripted * results without touching the real CLI or the network. The production * implementation shells out with `node:child_process` `execFile`; a missing * binary is reported distinctly (`not_found`) from a failed invocation. */ /** Outcome of one gcloud invocation. */ export type GcloudRunResult = { readonly status: "ok"; readonly stdout: string; readonly stderr: string; } | { readonly status: "not_found"; } | { readonly status: "failed"; /** Process exit code, or null when the process could not be spawned. */ readonly code: number | null; readonly stdout: string; readonly stderr: string; }; /** Expected device/inode identity of the staging directory at `cwd`. */ export interface GcloudCwdIdentity { readonly dev: number; readonly ino: number; } /** Extra options for one gcloud invocation. */ export interface GcloudRunOptions { /** * Working directory of the subprocess. Only the key create sets it, to * run with a RELATIVE `key.json` destination from the staging * directory. When `cwdIdentity` and `cwdFd` are present, the runner * launches an isolated Node wrapper (`process.execPath`) that `chdir`s * into the pathname and verifies `statSync('.')` against the pinned * descriptor and expected identity before invoking `gcloud` with no * `cwd` option. Pinning the fd prevents inode reuse from validating a * replacement. The parent process CWD is never changed. Other callers omit * these fields and run * `gcloud` directly (see `keyProvision.ts`). */ readonly cwd?: string; /** * Expected staging identity captured by `prepareStageDir`. Only * meaningful with `cwd`; the key-create call always sets it with `cwdFd`. */ readonly cwdIdentity?: GcloudCwdIdentity; /** Open staging-directory descriptor kept alive through the child launch. */ readonly cwdFd?: number; } /** Runs gcloud with the given arguments and returns the process outcome. */ export interface GcloudRunner { run(args: readonly string[], options?: GcloudRunOptions): Promise; } /** * Wraps a runner so a throwing invocation becomes a sanitized failed result. * * Every gcloud invocation in the setup flow goes through this one cycle-free * adapter: a rejected promise (spawn/transport failure) is reduced to * `{ status: "failed", code: null, stdout: "", stderr: "" }` so each phase * maps it to its stable error code (`user_token_failed`, project/API/SA/key * codes, ...) instead of a CLI `unexpected`. The thrown text may carry * tokens or key material and is never forwarded. Deliberate stderr * classification (such as the already-exists marker) still works because * non-thrown results pass through unchanged. */ export declare function createSafeRunner(runner: GcloudRunner): GcloudRunner; /** * Exact `gcloud auth login` arguments used for the interactive handoff. * * These mirror {@link DRIVE_ACCESS_COMMAND} in `humanAuth.ts`; the constant * is duplicated here so `gcloudRunner` (which `humanAuth` imports) stays free * of a runtime import cycle. `--enable-gdrive-access` grants the Drive scope * needed to create and own the spreadsheet; `--force` refreshes the cached * credentials of an already-logged-in account that lacks the scope. */ export declare const LOGIN_ARGS: readonly ["auth", "login", "--enable-gdrive-access", "--force"]; /** * Sanitized outcome of the interactive `gcloud auth login` handoff. * * Only the process exit result is exposed: stdout, stderr, and any access * token stay in the user's own gcloud credential store and are never * captured, stored, checkpointed, or forwarded by Hikoutei. */ export type GcloudLoginResult = { readonly status: "ok"; } | { readonly status: "not_found"; } | { readonly status: "spawn_error"; } | { readonly status: "failed"; readonly code: number | null; }; /** * Minimal child-process surface the login runner observes. * * The real `spawn` returns a `ChildProcess`; tests inject a fake that emits * `error`/`exit`. Only the two lifecycle events the runner maps to a sanitized * result are required. */ export interface LoginChildProcess { on(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown; on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown; } /** Spawns the login subprocess; injectable so tests assert the exact command and stdio. */ export type LoginSpawner = (command: string, args: readonly string[], options: { readonly stdio: "inherit"; }) => LoginChildProcess; /** Runs the interactive `gcloud auth login` handoff attached to the terminal. */ export interface GcloudLoginRunner { runInteractiveLogin(): Promise; } /** * Production interactive login runner. * * Spawns `gcloud auth login --enable-gdrive-access --force` with the terminal * streams inherited (`stdio: "inherit"`) so the user completes the browser * OAuth flow in their own gcloud session and Hikoutei never touches the * resulting token. Only the exit outcome is reduced to a sanitized result: * `ENOENT` (gcloud not installed) is `not_found`, any other spawn failure is * `spawn_error`, a non-zero exit is `failed` with the exit code, and a clean * exit is `ok`. The runner resolves exactly once even if both `error` and * `exit` arrive. * * @param spawner Process spawner; defaults to Node's `spawn`. Injectable so * tests assert the exact command and the inherited stdio without spawning. */ export declare function createInteractiveLoginRunner(spawner?: LoginSpawner): GcloudLoginRunner; /** * Production runner: executes `gcloud ` with `execFile`. * * `not_found` is returned when the binary is absent from PATH so the preflight * can produce a clear "install gcloud" error instead of a generic failure. */ export declare function createGcloudRunner(): GcloudRunner; //# sourceMappingURL=gcloudRunner.d.ts.map