feat(virtual-commands): JSON body templates, choices, interactive prompts, and non-interactive mode - #115
Merged
Conversation
- Widen .localstorage to .localstorage* (covers numbered variants) - Ignore .claude/settings.local.json (local dev tool config) - Add forceExit: true to jest.config.js to cleanly terminate workers after ESM module mocks keep handles open
…ices, and prompt examples
Static config (mage-remote-run.json):
- Add `create-customer` POST command demonstrating nested JSON body
template with ${paramName} placeholders
- Add `get-products-by-visibility` GET command demonstrating choices
with object form (name/value pairs) wired to a predefined filter
Dynamic registration (index.js):
- Register `set-product-status` PUT command at runtime showing how to
combine choices (Enabled/Disabled) with a body template in JavaScript
Update plugin-loader test to expect 6 registered commands.
…tive prompts
- Replace plain property list with a table for option properties
- Add JSON Request Body Templates section: nested structure, ${param}
and {:param} syntax, type preservation, static literals
- Add Option Choices section: simple string choices, object choices with
name/value, interactive select prompt behaviour, CLI validation
- Note the non-interactive mode (CI, NO_INTERACTIVE env vars) inline
where prompt behaviour is described
Add a `body` field to virtual command definitions allowing structured
JSON request bodies instead of the previous flat key→value payload.
- Add `interpolateBodyTemplate()` that recursively walks the template:
pure single-placeholder strings (e.g. "${email}") receive the original
typed value (number, boolean) rather than being string-coerced; nested
objects, arrays, and static primitives pass through unchanged
- Refactor payload building into two explicit steps: path-parameter
substitution (tracked via a Set) then body construction, making the
branch between template-driven and flat-payload modes clear
- Wrap the final payload in a `resolvedPayload` IIFE that handles empty
objects and array bodies correctly before passing to client.request()
- Add `body` to the config JSON Schema with additionalProperties: true
- Add 9 tests covering nested structure, type preservation, brace syntax,
path-param isolation, and flat-payload regression guard
Allow option definitions to specify a `choices` array restricting the
option to a set of predefined values.
- Add choices to `addCommandOption()`: when choices are present, register
the option via Commander's `Option` class with `.choices()` so the
allowed values appear in `--help` output and CLI-provided values are
validated automatically
- Add a choices-resolution loop in the action handler: when a choices
option has no value at execution time, prompt the user with an
interactive `select()` picker; already-provided values skip the prompt
- Choices may be plain strings or `{ name, value }` objects — the name
is shown in the prompt, the value is sent in the request
- Add `choices` to the config JSON Schema (items: string | {name, value})
- Add 6 tests covering: value provided (no prompt), value missing (prompt
called), object choices, default forwarded to select(), no-choices guard,
and combined choices + body template usage
…options When a required option is not provided, the CLI now prompts interactively instead of immediately erroring, making the commands self-describing. Interactive mode (TTY terminal, no CI/NO_INTERACTIVE env vars): - String options → `input()` prompt with inline non-empty validation - Sensitive fields (password, secret, token, apikey, credential, …) → masked `password()` prompt; detected via the option name/description - Options with `choices` (already had select) are handled in the same loop - Optional options without choices are never prompted Non-interactive mode (automatic when CI or NO_INTERACTIVE=1 is set, or when stdin is not a TTY — e.g. piped/scripted usage): - All missing required options are collected and reported together as one error: "Missing required options: --sku, --email" - No prompts are shown; the command exits cleanly via handleError() Implementation: - Add `isInteractiveMode()`: returns false when CI env var, NO_INTERACTIVE, or stdin is not a TTY — no prompts in CI/CD pipelines or scripts - Add `isSensitiveOption(name, description)`: regex-based heuristic to choose between input() and password() - Unify the choices-resolution loop and the new required-input loop into one pass over parameterKeys, branching on interactive/non-interactive - Remove `requiredOption` from `addCommandOption()` — required validation now lives entirely in the action handler, enabling prompting before error - Add 11 tests: interactive input, password for sensitive fields, fallback message, no-prompt for optional/boolean, value-already-provided guard, and 6 non-interactive tests (error message, CI detection, NO_INTERACTIVE)
…IVE/NONINTERACTIVE aliases - Extract isInteractiveMode() from virtual.js into lib/utils.js as a named export - Add NON_INTERACTIVE=1 and NONINTERACTIVE=1 as aliases for NO_INTERACTIVE=1 - Cover isInteractiveMode with 8 unit tests in tests/utils.test.js - Update utils mock in virtual.test.js to include isInteractiveMode
…in.isTTY Interactive test blocks (choices support, interactive input prompts) were setting process.stdin.isTTY=true to enable interactive mode, but the mock implementation also checked process.env.CI which is always set in GitHub Actions, causing all interactive tests to fail in CI. Switch to controlling the mock directly with mockReturnValue(true/false) so test behavior is environment-independent.
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
bodyobject in the config; values are interpolated from CLI options using${key}or{: key }syntax, with type-preserving substitution for pure placeholderschoicesin an option definition wires up Commander.choices()validation and auto-triggers an interactiveselectprompt when the value is missinginput()orpassword()prompts (sensitive field names detected automatically); non-required options with choices also prompt interactivelyisInteractiveMode()extracted tolib/utils.jsas a shared export; now also honoursNON_INTERACTIVE=1andNONINTERACTIVE=1in addition toNO_INTERACTIVE=1andCITest plan
npm test— all 658 tests pass across 45 suitesisInteractiveModeunit tests cover: TTY true/false/undefined,NO_INTERACTIVE,NON_INTERACTIVE,NONINTERACTIVE,CI, combined env varsNON_INTERACTIVE=1 mage-remote-run <cmd>exits with error on missing required options instead of promptingbodytemplate interpolation with nested objects and arrays