argsbarg 5.1.13 → 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,11 +7,19 @@ 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
+
13
+ ## [5.1.14] - 2026-07-06
14
+
15
+
10
16
  ## [5.1.13] - 2026-07-06
11
17
 
12
18
  ### Fixed
13
19
 
14
20
  - **`configure --remove-all`** — skill uninstall actions no longer reinstall skills after printing "Removing …".
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)."
15
23
 
16
24
  ### Changed
17
25
 
@@ -673,7 +681,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
673
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`).
674
682
  - Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
675
683
 
676
- [Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.1.13...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
686
+ [5.1.14]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.14
677
687
  [5.1.13]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.13
678
688
  [5.1.11]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.11
679
689
  [5.1.10]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.1.10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "5.1.13",
3
+ "version": "5.1.15",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -7,12 +7,19 @@ 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";
14
19
  import { resolveClaudeDesktopMcpPath, resolveInstallPaths } from "../install/paths.ts";
15
20
  import { buildInstallPlan, buildUpdatePlan } from "../install/plan.ts";
21
+ import { installTargetForKey } from "../install/target-registry.ts";
22
+ import { buildDetectedSnapshot, buildTargetPlanContext } from "../install/target-scope.ts";
16
23
  import { buildUninstallPlan } from "../install/uninstall.ts";
17
24
  import type { CliProgram } from "../types.ts";
18
25
 
@@ -62,6 +69,45 @@ describe("configure opts", () => {
62
69
  });
63
70
  });
64
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
+
65
111
  /** Tests for install paths. */
66
112
  describe("install paths", () => {
67
113
  test("resolveInstallPaths includes skill and mcp paths", () => {
@@ -153,6 +199,29 @@ describe("remove plan", () => {
153
199
  expect(result.exitCode).toBe(0);
154
200
  expect(existsSync(paths.cursorSkillDir)).toBe(false);
155
201
  });
202
+
203
+ test("scoped skill uninstall action removes directory without global uninstall flag", () => {
204
+ const skillApp: CliProgram = {
205
+ ...fixture,
206
+ configure: { agentIntegration: "skill" },
207
+ };
208
+ const paths = resolveInstallPaths(skillApp);
209
+ mkdirSync(paths.cursorSkillDir, { recursive: true });
210
+ writeFileSync(join(paths.cursorSkillDir, "SKILL.md"), "# test\n", "utf8");
211
+
212
+ const detected = buildDetectedSnapshot(skillApp, paths);
213
+ const ctx = buildTargetPlanContext(skillApp, paths, {}, detected);
214
+ const target = installTargetForKey("cursorSkill");
215
+ expect(target).toBeDefined();
216
+ const actions = target!.planUninstall({
217
+ ...ctx,
218
+ mode: "uninstall-scoped",
219
+ include: (key) => key === "cursorSkill",
220
+ });
221
+ expect(actions).toHaveLength(1);
222
+ expect(actions[0]!.run().length).toBeGreaterThan(0);
223
+ expect(existsSync(paths.cursorSkillDir)).toBe(false);
224
+ });
156
225
  });
157
226
 
158
227
  /** Tests for app config wizard. */
@@ -105,43 +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
- if (opts.uninstall) {
125
- const skillDir = skillDirFromUninstallSummary(action.summary, paths);
126
- if (skillDir) {
127
- changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
128
- }
129
- } else {
130
- changed.push(...runSkillAction(root, action.kind as InstallActionKind, opts));
131
- }
132
- continue;
133
- }
134
- }
135
- if (!("kind" in action) || !action.kind) {
136
- const skillDir = skillDirFromUninstallSummary(action.summary, paths);
137
- if (skillDir) {
138
- changed.push(...uninstallSkillDir(skillDir, !!opts.dry));
139
- continue;
140
- }
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");
141
212
  }
142
- changed.push(...action.run());
143
213
  }
144
- return changed;
214
+ return summary;
145
215
  }
146
216
 
147
217
  /** Builds plan context limited to a single artifact key. */
@@ -181,7 +251,7 @@ function actionsForTarget(
181
251
  }
182
252
 
183
253
  /** Walks enabled targets with per-target prompts (TTY required). */
184
- async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
254
+ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<ConfigureMutationSummary> {
185
255
  if (!process.stdin.isTTY) {
186
256
  throw new Error("Interactive configure requires a TTY. Use flags such as --sync --yes.");
187
257
  }
@@ -191,7 +261,7 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
191
261
  const detected = buildDetectedSnapshot(root, paths);
192
262
  const effective = resolveEffectiveInstallTargets(root.configure, root);
193
263
  const mutationOpts: InstallOpts = { dry: opts.dry, json: opts.json };
194
- const changed: string[] = [];
264
+ const summary = emptyMutationSummary();
195
265
 
196
266
  for (const target of INSTALL_TARGETS) {
197
267
  if (target.key === "app") continue;
@@ -205,7 +275,7 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
205
275
  const result = runConfigure(root, { context: "standalone", showHeading: false });
206
276
  if (result.changed) {
207
277
  installOut(`Wrote config: ${displayAppConfigPath(root)}`, mutationOpts);
208
- changed.push(displayAppConfigPath(root));
278
+ recordArtifactMutation(summary, [displayAppConfigPath(root)], "configured");
209
279
  }
210
280
  continue;
211
281
  }
@@ -228,29 +298,32 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
228
298
  runTargetPreflight(root, paths, mutationOpts, installActions);
229
299
  }
230
300
 
231
- changed.push(...executePlan(root, actions, mutationOpts, true));
301
+ mergeMutationSummary(
302
+ summary,
303
+ executePlan(root, actions, { ...mutationOpts, uninstall: choice === "uninstall" }, true),
304
+ );
232
305
  }
233
306
 
234
- return changed;
307
+ return summary;
235
308
  }
236
309
 
237
310
  /** Runs sync, remove, or status modes without per-target prompts. */
238
- async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
311
+ async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<ConfigureMutationSummary> {
239
312
  const installOpts = configureToInstallOpts(opts);
240
313
  const paths = resolveInstallPaths(root);
241
314
 
242
315
  if (installOpts.status) {
243
316
  printInstallStatus(root, installOpts);
244
- return [];
317
+ return emptyMutationSummary();
245
318
  }
246
319
 
247
- const changed: string[] = [];
320
+ const summary = emptyMutationSummary();
248
321
 
249
322
  if (installOpts.reinstall && !installOpts.uninstall) {
250
323
  const bootstrapped = ensureAppConfigFile(root, !!installOpts.dry);
251
324
  if (bootstrapped) {
252
325
  const display = displayAppConfigPath(root);
253
- changed.push(display);
326
+ recordArtifactMutation(summary, [display], "configured");
254
327
  installOut(`Initialized config: ${display}`, installOpts);
255
328
  }
256
329
  }
@@ -271,7 +344,8 @@ async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Pro
271
344
  runTargetPreflight(root, paths, installOpts, installActions);
272
345
  }
273
346
 
274
- return [...changed, ...executePlan(root, actions, installOpts, true)];
347
+ mergeMutationSummary(summary, executePlan(root, actions, installOpts, true));
348
+ return summary;
275
349
  }
276
350
 
277
351
  /** Main configure command orchestrator. */
@@ -285,22 +359,22 @@ export async function cliConfigure(root: CliProgram, rawOpts: Record<string, str
285
359
 
286
360
  const isInteractive = !opts.sync && !opts.removeAll && !opts.removeConfig && !opts.status;
287
361
 
288
- let changed: string[] = [];
362
+ let summary = emptyMutationSummary();
289
363
  try {
290
- changed = isInteractive ? await runInteractiveConfigure(root, opts) : await runAutomatedConfigure(root, opts);
364
+ summary = isInteractive ? await runInteractiveConfigure(root, opts) : await runAutomatedConfigure(root, opts);
291
365
  } catch (mutationErr) {
292
366
  installErr(mutationErr instanceof Error ? mutationErr.message : String(mutationErr));
293
367
  process.exit(1);
294
368
  }
295
369
 
296
370
  if (opts.json && !opts.status) {
297
- process.stdout.write(`${JSON.stringify(changed, null, 2)}\n`);
371
+ process.stdout.write(`${JSON.stringify(summary.paths, null, 2)}\n`);
298
372
  process.exit(0);
299
373
  }
300
374
 
301
- if (!opts.status && changed.length > 0) {
302
- const verb = opts.removeAll || opts.removeConfig ? "Removed" : opts.sync ? "Synced" : "Updated";
303
- installOut(`${verb} ${changed.length} file(s).`, configureToInstallOpts(opts));
375
+ const closing = formatConfigureMutationSummary(summary, opts);
376
+ if (!opts.status && closing) {
377
+ installOut(closing, configureToInstallOpts(opts));
304
378
  }
305
379
 
306
380
  process.exit(0);
@@ -12,6 +12,7 @@ import type {
12
12
  TargetPlanContext,
13
13
  UninstallAction,
14
14
  } from "./target-types.ts";
15
+ import { uninstallSkillDir } from "./uninstall.ts";
15
16
 
16
17
  export interface SkillHostSpec {
17
18
  key: CliInstallArtifactKey;
@@ -97,7 +98,7 @@ export class SkillInstallTarget extends InstallTarget {
97
98
  kind: this.actionKind,
98
99
  summary: `${this.uninstallPrefix}: ${displayInstallPath(dir)}/`,
99
100
  message: `Removing ${this.spec.label} skill ${displayInstallPath(dir)}/`,
100
- run: () => [],
101
+ run: () => uninstallSkillDir(dir, ctx.dry),
101
102
  },
102
103
  ];
103
104
  }