diff --git a/src/cli/commands/vault_put.ts b/src/cli/commands/vault_put.ts index ebedb6c1..f0540892 100644 --- a/src/cli/commands/vault_put.ts +++ b/src/cli/commands/vault_put.ts @@ -83,7 +83,8 @@ export function resolveKeyValue( return { error: `Invalid argument format: ${argument}\n\n` + - `Provide the value inline or via stdin:\n` + + `Provide the value as a separate argument, inline, or via stdin:\n` + + ` swamp vault put ${argument} \n` + ` swamp vault put ${argument}=\n` + ` echo "" | swamp vault put ${argument}`, }; @@ -109,13 +110,14 @@ type AnyOptions = any; export const vaultPutCommand = new Command() .name("put") .description("Store a secret in a vault") - .arguments(" ") + .arguments(" [value:string]") .option("--repo-dir ", "Repository directory", { default: "." }) .option("-f, --force", "Skip confirmation prompt when overwriting") .action(async function ( options: AnyOptions, vaultName: string, - keyValue: string, + keyOrKeyValue: string, + valueArg: string | undefined, ) { const cliCtx = createContext(options as GlobalOptions, ["vault", "put"]); cliCtx.logger.debug`Storing secret in vault: ${vaultName}`; @@ -125,44 +127,52 @@ export const vaultPutCommand = new Command() outputMode: cliCtx.outputMode, }); - // Parse KEY=VALUE argument, or KEY with value from stdin/interactive prompt. - const parsed = parseKeyValue(keyValue); + // Resolve key and value from arguments. + // Priority: explicit value arg > KEY=VALUE format > stdin > interactive prompt. let key: string; let value: string; let stdinContent: string | null = null; - if (parsed) { - key = parsed.key; - value = parsed.value; - } else if (!isStdinTty()) { - stdinContent = await readStdin(); - const resolved = resolveKeyValue(keyValue, stdinContent); - if ("error" in resolved) { - throw new UserError(resolved.error); - } - key = resolved.key; - value = resolved.value; - } else if (cliCtx.outputMode === "log") { - key = keyValue; - if (key.length === 0) { - throw new UserError("Key cannot be empty"); - } - try { - value = await readSecretFromTty(`Enter value for ${key}: `); - } catch (err) { - if (err instanceof Error && err.message === "Cancelled.") { - renderVaultPutCancelled(cliCtx.outputMode); - return; - } - throw err; - } + if (valueArg !== undefined) { + // 3-arg form: swamp vault put + key = keyOrKeyValue; + value = valueArg; } else { - const resolved = resolveKeyValue(keyValue, null); - if ("error" in resolved) { - throw new UserError(resolved.error); + // 2-arg form: try KEY=VALUE, then stdin, then interactive prompt. + const parsed = parseKeyValue(keyOrKeyValue); + if (parsed) { + key = parsed.key; + value = parsed.value; + } else if (!isStdinTty()) { + stdinContent = await readStdin(); + const resolved = resolveKeyValue(keyOrKeyValue, stdinContent); + if ("error" in resolved) { + throw new UserError(resolved.error); + } + key = resolved.key; + value = resolved.value; + } else if (cliCtx.outputMode === "log") { + key = keyOrKeyValue; + if (key.length === 0) { + throw new UserError("Key cannot be empty"); + } + try { + value = await readSecretFromTty(`Enter value for ${key}: `); + } catch (err) { + if (err instanceof Error && err.message === "Cancelled.") { + renderVaultPutCancelled(cliCtx.outputMode); + return; + } + throw err; + } + } else { + const resolved = resolveKeyValue(keyOrKeyValue, null); + if ("error" in resolved) { + throw new UserError(resolved.error); + } + key = resolved.key; + value = resolved.value; } - key = resolved.key; - value = resolved.value; } cliCtx.logger.debug`Parsed key: ${key}`; diff --git a/src/cli/commands/vault_put_test.ts b/src/cli/commands/vault_put_test.ts index 58ddd62e..bcaf12f3 100644 --- a/src/cli/commands/vault_put_test.ts +++ b/src/cli/commands/vault_put_test.ts @@ -97,10 +97,11 @@ Deno.test("resolveKeyValue - error when no = and no stdin", () => { assertEquals("error" in result, true); }); -Deno.test("resolveKeyValue - error message includes both usage formats", () => { +Deno.test("resolveKeyValue - error message includes all usage formats", () => { const result = resolveKeyValue("MY_KEY", null); assertEquals("error" in result, true); if ("error" in result) { + assertEquals(result.error.includes("MY_KEY "), true); assertEquals(result.error.includes("MY_KEY="), true); assertEquals(result.error.includes("echo"), true); }