Repository navigation
Conversation
Implements Phase 1 of INTERACTIVE-COMMANDS-PLAN.md: on an interactive session, missing mandatory options and connection arguments are now prompted for before the action body runs; every non-interactive surface (CI, scripts, MCP, e2e) behaves byte-identically to before. - PromptGateState: pure prompt gate (FRODO_NO_PROMPT / FRODO_TEST / TTYs / setNeverPrompt kill switch), unit-testable, 4 unit tests. - PromptGate: promptInput/promptPassword hand-built on @inquirer/core, matching the established hand-built prompt pattern (Escape/empty-Enter resolve '' so callers keep their existing missing-input error paths). readline-buffer based (paste-safe); the completed line is mirrored from the preceding keystroke because rl.line is already consumed when Enter's keypress fires (verified against @inquirer/core 12.0.3). - ResolveInteractiveInputs: preAction resolution pass. Host picker from saved connection profiles (single profile skipped silently, dropdown with "Enter a host manually..." otherwise, free text when none); username/password prompts only when no saved profile can supply a usable credential; deferred mandatory-option prompts (dropdown when the option carries choices()). - FrodoStubCommand reimplements missingMandatoryOptionValue verbatim from commander (typings omit it) as the override point; FrodoCommand defers violations to the resolution pass instead of throwing, which makes all 57 existing makeOptionMandatory() sites prompt-capable with zero syntax change. Non-interactive sessions re-throw commander's exact message/code. - Injection writes both processedArgs (action parameter source) and command.args (handleDefaultArgsAndOpts source) - the two surfaces commander/this repo read, verified against both. - --no-prompt on every FrodoCommand (Runtime group); MCP stdio server start calls setNeverPrompt() as belt-and-braces. - inquirer packages moved devDependencies -> dependencies (production prompt surface now). All flows verified live in a PTY (host dropdown, single-profile skip, username prefill, password masking, escape abort, --no-prompt, FRODO_NO_PROMPT, enum dropdown, non-TTY error equivalence, bare frodo conn unchanged). Co-Authored-By: Claude Code <noreply@anthropic.com>
…eclined
Two issues found after opening the PR (CI Test(26) caught the first):
1. tsdown auto-externalizes packages listed in package.json `dependencies`
unless named in deps.alwaysBundle. Moving the inquirer packages from
devDependencies to dependencies therefore left require("@inquirer/core")
external in the SEA bundle, where no node_modules exists to resolve it
(ERR_UNKNOWN_BUILTIN_MODULE at runtime; my earlier local SEA build
predated the dependency move and masked it). Both tsdown configs now
alwaysBundle the three inquirer packages. Verified: rebuilt SEA bundle
has zero external inquirer requires and the full PTY prompt chain works
against the fresh binary.
2. An @inquirer/core prompt rejects with ExitPromptError on stdin EOF
(Ctrl-D) and Ctrl-C. For gap-fill prompts both mean "no value
supplied" and now degrade to the declined result (''), so the caller
falls through to its pre-existing missing-input error instead of
dumping a raw stack trace.
Also removes the failed baseline-worktree experiment's leftovers (the
symlinked-node_modules worktree could not run the e2e harness at all -
623/632 suites failing is a broken environment, not a signal).
Co-Authored-By: Claude Code <noreply@anthropic.com>
…sign Follow-up to the dependency move in b122fba: in this repo, every bundled runtime package (commander, tinyrainbow, @inquirer/confirm, and now promptInput/promptPassword) deliberately lives in devDependencies -- that is what makes tsdown inline it and keeps the published package zero-dep (empty dependencies field, empty npm audit --omit=dev surface, no install weight). Declaring the inquirer packages as `dependencies` followed conventional npm metadata but inverted this repo's convention: tsdown auto-externalizes declared deps (needing deps.alwaysBundle to force inline, per aa31f40), consumers would install three packages whose code is also inlined, and npm audit would start scanning the inquirer tree again. Reverted: packages back to devDependencies, alwaysBundle dropped from both tsdown configs, placement convention documented in both configs so the next dep addition doesn't repeat the round trip. Verified: fresh dist + SEA bundles have zero external @InQuirer requires (18 inlined regions), SEA binary runs, full PTY prompt chain re-verified against the fresh binary, npm audit --omit=dev = 0. Co-Authored-By: Claude Code <noreply@anthropic.com>
Maintainers and contributors need to know that in this repo "imported at runtime" does NOT mean "declare as dependencies": the published package is zero-dep by design, bundled runtime packages live in devDependencies (tsdown inlines those), and anything declared under dependencies is externalized - which breaks the SEA binary (no node_modules to resolve against) and reopens the npm audit surface we keep empty. Hit for real with the @inquirer/* packages in this PR. - BUILD-ENV.md: new §2.2.1 (rule, tsup->tsdown option mapping, SEA failure mode, stale-binary gotcha, verification one-liner) + §11 history row. - CONTRIBUTE.md: callout in the build section pointing contributors at §2.2.1 before they add a package. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Phase 1 of
INTERACTIVE-COMMANDS-PLAN.md(analysis doc on the rockcarver workspace root): on an interactive session, missing mandatory options and connection arguments are now prompted for before the action body runs. Every non-interactive surface (CI, scripts, MCP, e2e) behaves byte-identically to before — same messages, same exit codes, enforced by a structural gate, not convention.frodo idm schema object describeon a TTY, before → after:What's in the box
PromptGateState.ts, unit-tested): prompts fire only when ALL ofFRODO_NO_PROMPTunset,FRODO_TEST !== '1', stdin+stdout are TTYs, andsetNeverPrompt()hasn't been called. Outside the gate the resolution pass degrades to exactly the old error behavior.PromptGate.ts):promptInput/promptPasswordhand-built on@inquirer/core, matching the established hand-built pattern (EscapableSelectPrompt,JourneyDebugPrompt): Escape/empty-Enter resolve''so callers keep their existing missing-input error paths (packaged prompts throwExitPromptErrorinstead). readline-buffer based (paste-safe); the completed line is mirrored from the preceding keystroke becauserl.lineis already consumed when Enter's keypress fires (verified against @inquirer/core 12.0.3 in an isolated PTY harness).ResolveInteractiveInputs.ts, wired via an asyncpreActionhook — commander's hook chain awaits returned promises):escapableSelectdropdown; single profile skipped silently (therunInteractivePreferredCredentialPickerconvention); "Enter a host manually…" item; free-text when no profiles exist.--browser/--devicesuppress them (their flow IS the interactive part) but not the host prompt.FrodoStubCommand.missingMandatoryOptionValueis reimplemented verbatim from commander (its typings omit the method) as the override point;FrodoCommanddefers violations to the pass instead of throwing — which makes all 57 existingmakeOptionMandatory()call sites prompt-capable with zero syntax change. Options withchoices()get a dropdown.--no-prompton everyFrodoCommand(Runtime options group) plus theFRODO_NO_PROMPTenv var;frodo mcp server start --transport stdiocallssetNeverPrompt()as belt-and-braces (stdio IS the protocol channel).devDependencies→dependencies(production prompt surface now).Two subtle bugs found and fixed during live PTY verification
rl.lineis already consumed when Enter's keypress fires — the first cut read it there and silently discarded every answer as "(no input)". Fixed by mirroring the buffer into prompt state on every keystroke.command.processedArgs(what commander's action wrapper slices positional parameters from) andcommand.args(whathandleDefaultArgsAndOptsiterates to copy into frodo-lib state) — verified against commander's_processArguments(which never writes back) and this repo's shim.Verification
All flows verified live in a real PTY (via the repo's generic
test/utils/shell_pty_driver.py):--no-prompt/FRODO_NO_PROMPTon a TTYchoices()(real resolver, standalone harness)frodo connunchanged (no double-prompt with its own picker)Notes / follow-ups
@inquirer/selectrode along todependencieswith the others but is still imported nowhere (pre-existing) — keep-vs-drop is a separate decision.🤖 Generated with Claude Code