argsbarg 3.6.1 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.2] - 2026-06-23
11
+
12
+ ### Added
13
+
14
+ - **`cli-program.md`** — “Read flags once, resolve once” pattern for multi-surface leaves (`read*Flags` + `resolve*Input`).
15
+
10
16
  ## [3.6.1] - 2026-06-23
11
17
 
12
18
  ### Fixed
@@ -393,7 +399,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
393
399
  - Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
394
400
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
395
401
 
396
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.6.1...HEAD
402
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.6.2...HEAD
403
+ [3.6.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.6.2
397
404
  [3.6.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.6.1
398
405
  [3.6.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.6.0
399
406
  [3.5.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.5.0
@@ -195,6 +195,59 @@ const { limit, "skip-readiness": skipReadiness, timeout } = ctx.readLeafInputs()
195
195
 
196
196
  Cross-field rules (e.g. `--match-remote` requires `--branch`) stay in consumer `resolve*` layers — argsbarg does not validate those.
197
197
 
198
+ ## Read flags once, resolve once
199
+
200
+ For apps with **Ink + headless + MCP** (multiple surfaces per leaf), avoid scattering `ctx.hasFlag` / `ctx.stringOpt` through the handler. Use two layers:
201
+
202
+ | Layer | Responsibility |
203
+ | --- | --- |
204
+ | **`read*Flags(ctx)`** | Read coerced values from `ctx` (`readLeafInputs()`, `durationOpt`, `commaListOpt`, shared mutator flags) into one typed struct |
205
+ | **`resolve*Input(flags)`** | Cross-field validation and defaults; returns `{ ok, input }` or `{ ok: false, error }` |
206
+
207
+ The handler calls **`read*Flags` once**, passes the struct to **`resolve*Input`**, then branches to Ink, headless, or MCP with the same resolved input.
208
+
209
+ **Shared reads** — when many leaves share options (`yes`, `dry-run`, `json`), one app-level helper (e.g. `readMutatingFlags(ctx)`) plus per-command extensions:
210
+
211
+ ```typescript
212
+ // cli/flags.ts
213
+ export function readMutatingFlags(ctx: CliContext) {
214
+ const dryRun = ctx.hasFlag("dry-run");
215
+ return {
216
+ dryRun,
217
+ yes: ctx.hasFlag("yes"),
218
+ explicitJson: wantsExplicitJson(ctx, ctx.hasFlag("json")),
219
+ };
220
+ }
221
+
222
+ // commands/reset/resolve.ts
223
+ export function readResetFlags(ctx: CliContext) {
224
+ return {
225
+ ...readMutatingFlags(ctx),
226
+ env: ctx.args[0],
227
+ force: ctx.hasFlag("force"),
228
+ services: ctx.commaListOpt("services"),
229
+ };
230
+ }
231
+
232
+ export function resolveResetInput(flags: ReturnType<typeof readResetFlags>) {
233
+ if (!flags.env) return { ok: false, error: "…" };
234
+ return { ok: true, input: { env: flags.env, force: flags.force, services: flags.services } };
235
+ }
236
+
237
+ // command handler
238
+ handler: async (ctx) => {
239
+ const flags = readResetFlags(ctx);
240
+ await dispatchMutatingCommand({
241
+ dryRun: flags.dryRun,
242
+ headless: shouldRunHeadlessWithYes(ctx, { yes: flags.yes, hasRequiredArgs: !!flags.env, dryRun: flags.dryRun }),
243
+ resolve: () => resolveResetInput(flags),
244
+ /* … */
245
+ });
246
+ };
247
+ ```
248
+
249
+ **JSON-only CLIs** — a single `readCommandOptions(ctx)` wrapping `readLeafInputs()` per shared option set is usually enough; full `resolve*` layering is optional.
250
+
198
251
  ## Headless-capable handlers
199
252
 
200
253
  Simple leaves (read args, print stdout) are already headless — no extra work. **Any handler that might mount Ink, prompt, or open a browser should also implement a scriptable fast path** for:
@@ -19,5 +19,6 @@ When adding or changing argsbarg schema, leaf handlers, or MCP exposure:
19
19
  - Interactive leaves: one headless path for MCP, non-TTY CLI, and `--yes` / `--dry-run` / `--json` (`shouldRunHeadless*`, `requireYesInNonTty`); not raw `isTTY`.
20
20
  - String options: `format` / `default` / `pattern` per `cli-program.md`; `readLeafInputs()` for multi-flag leaves.
21
21
  - Varargs (`argMax: 0`): CLI space-separated; MCP JSON array only — no comma-splitting positionals.
22
+ - Multi-surface leaves (Ink + headless + MCP): one **`read*Flags(ctx)`** per command (or shared family helper + extensions); one **`resolve*Input(flags)`** for cross-field rules — handler reads ctx once, all paths share the struct.
22
23
 
23
24
  **App-specific conventions:** add below or in a separate `.cursor/rules/` file (shared flags path, Ink vs JSON-only, etc.).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "3.6.1",
3
+ "version": "3.6.2",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"