fix(cli): restore --help handling when it follows a subcommand or unknown flag

The #4162 refactor removed parseCliArgs' inline --help handler assuming
loader.ts's fast-path covered it, but loader.ts only intercepts --help/-h
as argv[1]. That broke:

- gsd update --help — fell through to runUpdate() (subcommand help
  check sat dead-code below the update handler)
- gsd --unknown --help in non-TTY — tripped the TTY gate and exited 1

Move the subcommand-help check ahead of every subcommand handler and
fall back to general help when no subcommand matches, so --help wins
whenever it appears anywhere in argv.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeremy 2026-04-14 05:50:47 -05:00
parent 1c2096d8f4
commit 1a8ba9a43b

View file

@ -20,7 +20,7 @@ import { migratePiCredentials } from './pi-migration.js'
import { shouldRunOnboarding, runOnboarding } from './onboarding.js'
import chalk from 'chalk'
import { checkForUpdates } from './update-check.js'
import { printSubcommandHelp } from './help-text.js'
import { printHelp, printSubcommandHelp } from './help-text.js'
import { applySecurityOverrides } from './security-overrides.js'
import { validateConfiguredModel } from './startup-model-validation.js'
import {
@ -126,6 +126,20 @@ async function reapplyValidatedModelOnFallback(
const cliFlags = parseCliArgs(process.argv)
const isPrintMode = cliFlags.print || cliFlags.mode !== undefined
// `gsd [subcommand] --help` / `-h` — print help before any subcommand runs.
// loader.ts only catches --help/-h as the *first* arg; here we handle the
// case where it appears later (e.g. `gsd update --help`, `gsd --foo --help`).
// Prefer subcommand-specific help when the first positional is a known
// subcommand, otherwise fall back to general help.
if (process.argv.includes('--help') || process.argv.includes('-h')) {
const helpSubcommand = cliFlags.messages[0]
const version = process.env.GSD_VERSION || '0.0.0'
if (!helpSubcommand || !printSubcommandHelp(helpSubcommand, version)) {
printHelp(version)
}
process.exit(0)
}
// RTK bootstrap — runs once per process, memoized via a module-level promise
// so concurrent callers await the same initialization.
let rtkBootstrapPromise: Promise<void> | undefined
@ -167,14 +181,6 @@ if (!process.stdin.isTTY && !isPrintMode && !hasSubcommand && !cliFlags.listMode
printNonTtyErrorAndExit(undefined, false)
}
// `gsd <subcommand> --help` — show subcommand-specific help
const subcommand = cliFlags.messages[0]
if (subcommand && process.argv.includes('--help')) {
if (printSubcommandHelp(subcommand, process.env.GSD_VERSION || '0.0.0')) {
process.exit(0)
}
}
const packageCommand = await runPackageCommand({
appName: 'gsd',
args: process.argv.slice(2),