Interactive flows
Tagsmith's contract is 100% non-interactive capability equals 100% interactive capability. Every interactive decision maps to an explicit flag, and every explicit-flag invocation does the same thing the interactive flow would do. Interactive mode only fills omissions; it never reinterprets invalid input.
Prompt eligibility
Tagsmith opens prompts only when all of the following hold:
- argv parsed cleanly (unknown command/flag/shorthand/attached-value/missing-value/invalid-enum/conflict all fail before any prompt)
--help/-hnot present--version/-vnot present--jsonnot active--github-outputnot active- raw output not active (
init --dry-run) process.stdin.isTTY === trueprocess.stdout.isTTY === trueCIenvironment variable is unset, empty,0, orfalse
Notes:
stderrbeing a TTY is not sufficient. Prompts need both stdin and stdout TTY.CI=truedisables prompts even on pseudo-TTYs.TERM=dumbdoes not disable prompts by itself. Clack decides how much decoration it can render.- No
--non-interactiveflag. TTY/CI detection is sufficient. - No
--yes/-y. Tagsmith does not ship a confirmation skip. If you want non-interactive behavior, supply the flags explicitly.
Bare tagsmith
In an eligible TTY, bare tagsmith opens an action menu after confirming Git repo context:
init Create a Tagsmith config file.
tag Resolve, create, and optionally push a release tag.
validate Validate a release tag and emit CI-safe facts.
targets List configured release targets.Selecting one runs that command's interactive flow. The menu does not loop — after the chosen command completes, Tagsmith exits.
Bare tagsmith in non-TTY contexts prints global help and exits 0. Outside a Git repo, even bare tagsmith fails with Git repository not found from <cwd>.
Globals carry into the chosen command
--config <path> and --verbose may be supplied before bare tagsmith and apply to whichever command you pick:
tagsmith --config ./release/tagsmith.jsonc --verbose--config and --verbose are never prompted — they are explicit-only.
Per-command interactive flows
init
- If the destination does not exist: confirm creation, then write.
- If it exists and
--forcewas not given: choose between "overwrite" and the safe-negative option. Default: safe-negative. - If it exists and
--forcewas given: confirmation is still required before mutation. init --dry-runis raw mode and never prompts, even in a TTY.
targets
Non-mutating. Prompts nothing. Renders config warnings via Clack-friendly UI and prints target facts.
validate
Read-only. No mutation confirmation.
- Prompts for
--tagif missing (manual entry; no discovery). - If
--targetand--channelare missing, offers optional assertions:- "infer target and channel from tag" (default)
- "assert target"
- "assert target and channel"
- In multi-target configs, asserting a channel requires asserting a target first (otherwise the channel list would be ambiguously merged).
--json and --github-output bypass all prompts and preserve existing stream contracts.
tag
Input collection order: target → channel → version intent. Rationale: channel strategy determines which bump choices are valid.
- Load and validate config first.
- Target: auto-select if single-target. Otherwise prompt with config-order menu when
--targetmissing. - Channel: auto-select if the target has only one total channel. Otherwise prompt with config-order menu when
--channelmissing. - Version intent: skip if
--bumpor--versionwas given. Otherwise prompt:- "bump" → menu filtered by strategy (
stableshowsmajor|minor|patch;prereleaseshows all four). - "explicit version" → enter a SemVer literal with strategy-shaped hints (stable example:
1.2.3; prerelease example:1.2.3-rc.1).
- "bump" → menu filtered by strategy (
- Preflight — runs full preflight; on failure, stop before review with the canonical error.
- Review screen — shows target, channel, strategy, version intent, resolved version, rendered tag, rendered annotated message, full commit SHA, and the equivalent non-interactive command (canonical flag order, shell-escaped).
- Confirmation:
- Without
--push: "local create" / "create and push" / "no action". Default: local create. - With
--push: confirm or cancel. Default: cancel (safe-negative).
- Without
- Execute the chosen action. On push or post-push verification failure, the local tag remains.
Even when every flag is supplied, the review/confirmation still runs in interactive mode. That's intentional — interactive runs always pause before mutation. CI / machine modes / non-TTY runs execute as soon as preflight passes.
tag --dry-run in interactive mode shows the dry-run facts and exits with no confirmation prompt.
No auto-pivot
Interactive tag with a missing config fails with the canonical error and may suggest tagsmith init. It must not auto-launch init for you. Each command stays within its own scope.
Equivalent command rendering
Whenever a review screen shows an equivalent command, it follows strict rules:
- canonical binary name
tagsmith - canonical flag order from the shared CLI contract, not user-supplied order
- includes
--config <path>if the user supplied one - omits
--verbose(diagnostic, not workflow intent) - includes the real command flags:
--target,--channel,--bump,--version,--push,--dry-run,--forcewhere applicable - shell-escapes arguments when needed (especially config paths with spaces)
- never includes prompt-only confirmation concepts
Example:
tagsmith --config './release config/tagsmith.jsonc' tag --target api --channel rc --bump prerelease --pushThis is the value of the interactive review: it's a copy-paste-ready non-interactive invocation you can drop into CI or a script.
Cancellation
Cancellation sources:
- Ctrl+C during any prompt.
- Selecting the safe-negative option in a review (e.g. "no action" or "cancel").
Behavior:
- exit code
1 - no mutation
- no machine output
- no stack trace
- concise message:
tagsmith failed: tagsmith cancelled.
Confirmation defaults (matrix)
| Situation | Default |
|---|---|
init overwrites an existing config | safe-negative |
tag with --push review | safe-negative (cancel) |
tag without --push review | local create (primary) |
tag review with no --push preselected, three-way action | local create |
Output safety
- Interactive mode never emits JSON or GitHub output.
--jsonand--github-outputalways disable prompts.init --dry-runraw output always disables prompts.- Interactive warnings use the same strings as non-interactive human mode, rendered through Clack.
--verbosemay coexist with prompts in TTY human mode. Verbose lines are emitted before or after active prompts, not interleaved.