From 7393231f61563ef2430b274e88eb6d86fd2a825b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 22 Feb 2026 11:03:14 -0800 Subject: [PATCH 1/4] ref: Extract shared skill selection logic in add command Warden finding code-simplifier-ed2bac3e Severity: medium Co-Authored-By: Warden --- src/cli/commands/add.ts | 71 ++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/cli/commands/add.ts b/src/cli/commands/add.ts index d279d444..dcc4145a 100644 --- a/src/cli/commands/add.ts +++ b/src/cli/commands/add.ts @@ -187,6 +187,29 @@ async function promptRemoteSkillSelection( } } +/** + * Resolve which skill to add from explicit option, interactive prompt, or error. + * Returns the skill name, or null if the user cancelled, or a numeric exit code on error. + */ +async function resolveSkillName( + options: CLIOptions, + reporter: Reporter, + prompt: () => Promise, + usageTip: string, +): Promise { + if (options.skill) { + return options.skill; + } + if (reporter.mode.isTTY) { + reporter.blank(); + const selected = await prompt(); + return selected ?? 0; + } + reporter.error('Skill name required when not running interactively.'); + reporter.tip(usageTip); + return 1; +} + const DEFAULT_TRIGGERS = [ { type: 'pull_request' as const, @@ -271,21 +294,14 @@ async function runAddRemote( } // Get skill to add (from --skill or interactive prompt) - let skillName: string | null; - - if (options.skill) { - skillName = options.skill; - } else if (reporter.mode.isTTY) { - reporter.blank(); - skillName = await promptRemoteSkillSelection(remoteSkills, configuredSkills, reporter); - if (!skillName) { - return 0; // User quit or no skills available - } - } else { - reporter.error('Skill name required when not running interactively.'); - reporter.tip(`Use: warden add --remote ${remote} --skill `); - return 1; - } + const resolved = await resolveSkillName( + options, + reporter, + () => promptRemoteSkillSelection(remoteSkills, configuredSkills, reporter), + `Use: warden add --remote ${remote} --skill `, + ); + if (typeof resolved === 'number') return resolved; + const skillName = resolved; // Validate skill exists in remote, retry with fresh fetch if using stale cache let availableSkills = remoteSkills; @@ -413,23 +429,14 @@ export async function runAdd(options: CLIOptions, reporter: Reporter): Promise or warden add --list'); - return 1; - } + const resolved = await resolveSkillName( + options, + reporter, + () => promptSkillSelection(skills, configuredSkills, reporter), + 'Use: warden add or warden add --list', + ); + if (typeof resolved === 'number') return resolved; + const skillName = resolved; // 9. Validate skill exists if (!skills.has(skillName)) { From 0d605357c62c88426e6412a8732148b0453f9cf3 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Sun, 22 Feb 2026 11:03:22 -0800 Subject: [PATCH 2/4] fix: Validate setup-app port and timeout arguments Warden finding code-simplifier-395781e1 Severity: medium Co-Authored-By: Warden --- src/cli/args.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/cli/args.ts b/src/cli/args.ts index badc3ee1..f836d254 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -396,6 +396,16 @@ export function parseCliArgs(argv: string[] = process.argv.slice(2)): ParsedArgs // Handle setup-app command if (positionals.includes('setup-app')) { + const port = values.port ? parseInt(values.port as string, 10) : 3000; + if (Number.isNaN(port)) { + console.error(`Invalid --port value: ${values.port}`); + process.exit(1); + } + const timeout = values.timeout ? parseInt(values.timeout as string, 10) : 300; + if (Number.isNaN(timeout)) { + console.error(`Invalid --timeout value: ${values.timeout}`); + process.exit(1); + } return { command: 'setup-app', options: CLIOptionsSchema.parse({ @@ -404,8 +414,8 @@ export function parseCliArgs(argv: string[] = process.argv.slice(2)): ParsedArgs }), setupAppOptions: { org: values.org as string | undefined, - port: values.port ? parseInt(values.port as string, 10) : 3000, - timeout: values.timeout ? parseInt(values.timeout as string, 10) : 300, + port, + timeout, name: values.name as string | undefined, open: !values['no-open'], }, From 8276165c4f5722ec38f245b6fde104a35a493a0a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 23 Feb 2026 09:49:20 -0800 Subject: [PATCH 3/4] fix: Preserve behavior in resolveSkillName extraction Move reporter.blank() out of the shared helper so only runAddRemote calls it, matching the original behavior where runAdd did not print an extra blank line before the skill selection prompt. Co-Authored-By: Claude Opus 4.6 --- src/cli/commands/add.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/commands/add.ts b/src/cli/commands/add.ts index dcc4145a..4e2f0394 100644 --- a/src/cli/commands/add.ts +++ b/src/cli/commands/add.ts @@ -201,7 +201,6 @@ async function resolveSkillName( return options.skill; } if (reporter.mode.isTTY) { - reporter.blank(); const selected = await prompt(); return selected ?? 0; } @@ -294,6 +293,7 @@ async function runAddRemote( } // Get skill to add (from --skill or interactive prompt) + if (reporter.mode.isTTY) reporter.blank(); const resolved = await resolveSkillName( options, reporter, From 619d9b3e4ba0521e79e606084a5ff983bf28b47c Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 23 Feb 2026 09:54:05 -0800 Subject: [PATCH 4/4] fix: Only print blank line before interactive prompt, not on explicit --skill Co-Authored-By: Claude Opus 4.6 --- src/cli/commands/add.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/commands/add.ts b/src/cli/commands/add.ts index 4e2f0394..3e7912a6 100644 --- a/src/cli/commands/add.ts +++ b/src/cli/commands/add.ts @@ -293,7 +293,7 @@ async function runAddRemote( } // Get skill to add (from --skill or interactive prompt) - if (reporter.mode.isTTY) reporter.blank(); + if (!options.skill && reporter.mode.isTTY) reporter.blank(); const resolved = await resolveSkillName( options, reporter,