argsbarg 5.1.14 → 5.1.15

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,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.1.15] - 2026-07-06
11
+
12
+
10
13
  ## [5.1.14] - 2026-07-06
11
14
 
12
15
 
@@ -16,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
19
 
17
20
  - **`configure --remove-all`** — skill uninstall actions no longer reinstall skills after printing "Removing …".
18
21
  - **Interactive `configure`** — choosing `n` on an installed skill now removes the skill directory (same bug as `--remove-all`, different code path).
22
+ - **Configure closing summary** — counts artifacts (not per-file paths) and uses Removed/Installed/Synced instead of always "Updated N file(s)."
19
23
 
20
24
  ### Changed
21
25
 
@@ -677,7 +681,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
677
681
  - 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`).
678
682
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
679
683
 
680
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.14...HEAD
684
+ [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.15...HEAD
685
+ [5.1.15]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.15
681
686
  [5.1.14]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.14
682
687
  [5.1.13]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.13
683
688
  [5.1.11]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "5.1.14",
3
+ "version": "5.1.15",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -7,7 +7,12 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { appConfigFileExists } from "../config/file.ts";
10
- import { appConfigHasEntries, parseConfigureOpts, validateConfigureOpts } from "../configure/index.ts";
10
+ import {
11
+ appConfigHasEntries,
12
+ formatConfigureMutationSummary,
13
+ parseConfigureOpts,
14
+ validateConfigureOpts,
15
+ } from "../configure/index.ts";
11
16
  import { Cli } from "../index.ts";
12
17
  import { detectInstalledArtifacts } from "../install/detect-installed.ts";
13
18
  import { parseInstallOpts } from "../install/opts.ts";
@@ -64,6 +69,45 @@ describe("configure opts", () => {
64
69
  });
65
70
  });
66
71
 
72
+ describe("configure mutation summary", () => {
73
+ test("interactive uninstall reports removed artifacts not file count", () => {
74
+ const msg = formatConfigureMutationSummary(
75
+ { paths: ["~/.cursor/skills/testapp/"], installed: 0, removed: 1, configured: 0 },
76
+ {},
77
+ );
78
+ expect(msg).toBe("Removed 1 artifact.");
79
+ });
80
+
81
+ test("interactive install reports installed artifacts not file count", () => {
82
+ const msg = formatConfigureMutationSummary(
83
+ {
84
+ paths: ["~/.cursor/skills/testapp/SKILL.md", "~/.cursor/skills/testapp/reference.md"],
85
+ installed: 1,
86
+ removed: 0,
87
+ configured: 0,
88
+ },
89
+ {},
90
+ );
91
+ expect(msg).toBe("Installed 1 artifact.");
92
+ });
93
+
94
+ test("sync mode uses synced verb and artifact count", () => {
95
+ const msg = formatConfigureMutationSummary(
96
+ { paths: ["a", "b", "c"], installed: 3, removed: 0, configured: 0 },
97
+ { sync: true },
98
+ );
99
+ expect(msg).toBe("Synced 3 artifacts.");
100
+ });
101
+
102
+ test("remove-all uses removed verb", () => {
103
+ const msg = formatConfigureMutationSummary(
104
+ { paths: ["a"], installed: 0, removed: 2, configured: 0 },
105
+ { removeAll: true },
106
+ );
107
+ expect(msg).toBe("Removed 2 artifacts.");
108
+ });
109
+ });
110
+
67
111
  /** Tests for install paths. */
68
112
  describe("install paths", () => {
69
113
  test("resolveInstallPaths includes skill and mcp paths", () => {
@@ -105,46 +105,113 @@ function runSkillAction(root: CliProgram, kind: InstallActionKind, opts: Install
105
105
  });
106
106
  }
107
107
 
108
+ /** Tallies configure mutations for the closing summary and `--json` output. */
109
+ export interface ConfigureMutationSummary {
110
+ paths: string[];
111
+ installed: number;
112
+ removed: number;
113
+ configured: number;
114
+ }
115
+
116
+ function emptyMutationSummary(): ConfigureMutationSummary {
117
+ return { paths: [], installed: 0, removed: 0, configured: 0 };
118
+ }
119
+
120
+ function mergeMutationSummary(into: ConfigureMutationSummary, from: ConfigureMutationSummary): void {
121
+ into.paths.push(...from.paths);
122
+ into.installed += from.installed;
123
+ into.removed += from.removed;
124
+ into.configured += from.configured;
125
+ }
126
+
127
+ function recordArtifactMutation(
128
+ summary: ConfigureMutationSummary,
129
+ paths: string[],
130
+ kind: "installed" | "removed" | "configured",
131
+ ): void {
132
+ if (paths.length === 0) return;
133
+ summary.paths.push(...paths);
134
+ summary[kind]++;
135
+ }
136
+
137
+ /** Human-readable closing line for configure mutations. */
138
+ export function formatConfigureMutationSummary(summary: ConfigureMutationSummary, opts: ConfigureOpts): string | null {
139
+ if (summary.paths.length === 0) return null;
140
+
141
+ if (opts.removeAll || opts.removeConfig) {
142
+ const n = summary.removed;
143
+ if (n === 0) return null;
144
+ return n === 1 ? "Removed 1 artifact." : `Removed ${n} artifacts.`;
145
+ }
146
+
147
+ if (opts.sync) {
148
+ const n = summary.installed;
149
+ if (n === 0) return null;
150
+ return n === 1 ? "Synced 1 artifact." : `Synced ${n} artifacts.`;
151
+ }
152
+
153
+ const parts: string[] = [];
154
+ if (summary.removed > 0) {
155
+ parts.push(summary.removed === 1 ? "Removed 1 artifact" : `Removed ${summary.removed} artifacts`);
156
+ }
157
+ if (summary.installed > 0) {
158
+ parts.push(summary.installed === 1 ? "Installed 1 artifact" : `Installed ${summary.installed} artifacts`);
159
+ }
160
+ if (summary.configured > 0) {
161
+ parts.push("Updated app config");
162
+ }
163
+ if (parts.length === 0) return null;
164
+ return `${parts.join("; ")}.`;
165
+ }
166
+
167
+ /** Runs one install/uninstall action and returns changed paths. */
168
+ function runPlanAction(
169
+ root: CliProgram,
170
+ action: InstallAction | UninstallAction,
171
+ opts: InstallOpts,
172
+ paths: ReturnType<typeof resolveInstallPaths>,
173
+ ): string[] {
174
+ if ("kind" in action && action.kind) {
175
+ const skillTarget = skillTargetFromActionKind(action.kind);
176
+ if (skillTarget) {
177
+ const runResult = action.run();
178
+ if (runResult.length > 0) return runResult;
179
+ if (opts.uninstall) {
180
+ const skillDir = skillDirFromUninstallSummary(action.summary, paths);
181
+ return skillDir ? uninstallSkillDir(skillDir, !!opts.dry) : [];
182
+ }
183
+ return runSkillAction(root, action.kind as InstallActionKind, opts);
184
+ }
185
+ }
186
+ if (!("kind" in action) || !action.kind) {
187
+ const skillDir = skillDirFromUninstallSummary(action.summary, paths);
188
+ if (skillDir) return uninstallSkillDir(skillDir, !!opts.dry);
189
+ }
190
+ return action.run();
191
+ }
192
+
108
193
  /** Runs install or uninstall actions and collects changed paths. */
109
194
  function executePlan(
110
195
  root: CliProgram,
111
196
  actions: Array<InstallAction | UninstallAction>,
112
197
  opts: InstallOpts,
113
198
  showProgress: boolean,
114
- ): string[] {
115
- const changed: string[] = [];
199
+ ): ConfigureMutationSummary {
200
+ const summary = emptyMutationSummary();
116
201
  const paths = resolveInstallPaths(root);
117
202
  for (const action of actions) {
118
203
  if (showProgress) {
119
204
  installInfo(action.message, opts);
120
205
  }
121
- if ("kind" in action && action.kind) {
122
- const skillTarget = skillTargetFromActionKind(action.kind);
123
- if (skillTarget) {
124
- const runResult = action.run();
125
- if (runResult.length > 0) {
126
- changed.push(...runResult);
127
- } else if (opts.uninstall) {
128
- const skillDir = skillDirFromUninstallSummary(action.summary, paths);
129
- if (skillDir) {
130
- changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
131
- }
132
- } else {
133
- changed.push(...runSkillAction(root, action.kind as InstallActionKind, opts));
134
- }
135
- continue;
136
- }
137
- }
138
- if (!("kind" in action) || !action.kind) {
139
- const skillDir = skillDirFromUninstallSummary(action.summary, paths);
140
- if (skillDir) {
141
- changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
142
- continue;
143
- }
206
+ const changed = runPlanAction(root, action, opts, paths);
207
+ if (changed.length === 0) continue;
208
+ if (action.kind === "configure") {
209
+ recordArtifactMutation(summary, changed, opts.uninstall ? "removed" : "configured");
210
+ } else {
211
+ recordArtifactMutation(summary, changed, opts.uninstall ? "removed" : "installed");
144
212
  }
145
- changed.push(...action.run());
146
213
  }
147
- return changed;
214
+ return summary;
148
215
  }
149
216
 
150
217
  /** Builds plan context limited to a single artifact key. */
@@ -184,7 +251,7 @@ function actionsForTarget(
184
251
  }
185
252
 
186
253
  /** Walks enabled targets with per-target prompts (TTY required). */
187
- async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
254
+ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<ConfigureMutationSummary> {
188
255
  if (!process.stdin.isTTY) {
189
256
  throw new Error("Interactive configure requires a TTY. Use flags such as --sync --yes.");
190
257
  }
@@ -194,7 +261,7 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
194
261
  const detected = buildDetectedSnapshot(root, paths);
195
262
  const effective = resolveEffectiveInstallTargets(root.configure, root);
196
263
  const mutationOpts: InstallOpts = { dry: opts.dry, json: opts.json };
197
- const changed: string[] = [];
264
+ const summary = emptyMutationSummary();
198
265
 
199
266
  for (const target of INSTALL_TARGETS) {
200
267
  if (target.key === "app") continue;
@@ -208,7 +275,7 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
208
275
  const result = runConfigure(root, { context: "standalone", showHeading: false });
209
276
  if (result.changed) {
210
277
  installOut(`Wrote config: ${displayAppConfigPath(root)}`, mutationOpts);
211
- changed.push(displayAppConfigPath(root));
278
+ recordArtifactMutation(summary, [displayAppConfigPath(root)], "configured");
212
279
  }
213
280
  continue;
214
281
  }
@@ -231,29 +298,32 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
231
298
  runTargetPreflight(root, paths, mutationOpts, installActions);
232
299
  }
233
300
 
234
- changed.push(...executePlan(root, actions, { ...mutationOpts, uninstall: choice === "uninstall" }, true));
301
+ mergeMutationSummary(
302
+ summary,
303
+ executePlan(root, actions, { ...mutationOpts, uninstall: choice === "uninstall" }, true),
304
+ );
235
305
  }
236
306
 
237
- return changed;
307
+ return summary;
238
308
  }
239
309
 
240
310
  /** Runs sync, remove, or status modes without per-target prompts. */
241
- async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
311
+ async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<ConfigureMutationSummary> {
242
312
  const installOpts = configureToInstallOpts(opts);
243
313
  const paths = resolveInstallPaths(root);
244
314
 
245
315
  if (installOpts.status) {
246
316
  printInstallStatus(root, installOpts);
247
- return [];
317
+ return emptyMutationSummary();
248
318
  }
249
319
 
250
- const changed: string[] = [];
320
+ const summary = emptyMutationSummary();
251
321
 
252
322
  if (installOpts.reinstall && !installOpts.uninstall) {
253
323
  const bootstrapped = ensureAppConfigFile(root, !!installOpts.dry);
254
324
  if (bootstrapped) {
255
325
  const display = displayAppConfigPath(root);
256
- changed.push(display);
326
+ recordArtifactMutation(summary, [display], "configured");
257
327
  installOut(`Initialized config: ${display}`, installOpts);
258
328
  }
259
329
  }
@@ -274,7 +344,8 @@ async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Pro
274
344
  runTargetPreflight(root, paths, installOpts, installActions);
275
345
  }
276
346
 
277
- return [...changed, ...executePlan(root, actions, installOpts, true)];
347
+ mergeMutationSummary(summary, executePlan(root, actions, installOpts, true));
348
+ return summary;
278
349
  }
279
350
 
280
351
  /** Main configure command orchestrator. */
@@ -288,22 +359,22 @@ export async function cliConfigure(root: CliProgram, rawOpts: Record<string, str
288
359
 
289
360
  const isInteractive = !opts.sync && !opts.removeAll && !opts.removeConfig && !opts.status;
290
361
 
291
- let changed: string[] = [];
362
+ let summary = emptyMutationSummary();
292
363
  try {
293
- changed = isInteractive ? await runInteractiveConfigure(root, opts) : await runAutomatedConfigure(root, opts);
364
+ summary = isInteractive ? await runInteractiveConfigure(root, opts) : await runAutomatedConfigure(root, opts);
294
365
  } catch (mutationErr) {
295
366
  installErr(mutationErr instanceof Error ? mutationErr.message : String(mutationErr));
296
367
  process.exit(1);
297
368
  }
298
369
 
299
370
  if (opts.json && !opts.status) {
300
- process.stdout.write(`${JSON.stringify(changed, null, 2)}\n`);
371
+ process.stdout.write(`${JSON.stringify(summary.paths, null, 2)}\n`);
301
372
  process.exit(0);
302
373
  }
303
374
 
304
- if (!opts.status && changed.length > 0) {
305
- const verb = opts.removeAll || opts.removeConfig ? "Removed" : opts.sync ? "Synced" : "Updated";
306
- installOut(`${verb} ${changed.length} file(s).`, configureToInstallOpts(opts));
375
+ const closing = formatConfigureMutationSummary(summary, opts);
376
+ if (!opts.status && closing) {
377
+ installOut(closing, configureToInstallOpts(opts));
307
378
  }
308
379
 
309
380
  process.exit(0);