/** * Service-credential secret resolution (ISS-0127, D7). * * Service credentials (a Proxmox API token, a DigitalOcean API token, an SSH * password) are a special kind of secret: at `service add` time the service * does not exist yet, so there is no encrypted store to write the value to * out-of-band and no safe sink for a bus reply. Per design decision D7 these * secrets therefore travel by **flag or env var**, never over the event bus. * * The resolution order is: explicit `--` → `$ENV` → (only when stdin is * a TTY) a local masked `password` prompt → otherwise a fail-fast error naming * the flag and env var. This keeps a zero-TTY `service add` possible while the * credential never lands on the bus, and the local password prompt is the one * direct prompt the recurrence gate (D6) tolerates — precisely because a * flag/env path always exists alongside it. */ import { promptPassword } from './prompts'; export interface ServiceCredentialSpec { /** Human-readable field name used in the prompt + error, e.g. "API token secret". */ field: string; /** The flag value (already resolved from `flags[]`), if the operator passed `--`. */ flagValue: string | boolean | undefined; /** The CLI flag the operator would pass, e.g. `api-token-secret`. */ flag: string; /** The environment variable that supplies the value headlessly, e.g. `PROXMOX_API_TOKEN_SECRET`. */ envVar: string; } /** * Resolve a service-credential secret from flag → env → (TTY) password prompt → * error. Returns the secret value as a string. Throws an actionable error when * no value is available on a non-TTY run. */ export async function resolveServiceCredential(spec: ServiceCredentialSpec): Promise { if (typeof spec.flagValue === 'string' && spec.flagValue.trim() !== '') { return spec.flagValue.trim(); } const fromEnv = process.env[spec.envVar]; if (fromEnv && fromEnv.trim() !== '') { return fromEnv.trim(); } if (process.stdin.isTTY) { return promptPassword({ message: `${spec.field}:`, validate: (val) => (!val || val.trim() === '' ? `${spec.field} is required` : undefined), }); } throw new Error(`${spec.field} required: pass --${spec.flag} or set $${spec.envVar} (no TTY)`); }