agent-loadout 0.1.0 → 1.0.0

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/dist/index.js CHANGED
@@ -1,392 +1,79 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ PRESETS,
4
+ TOOLS,
5
+ getToolsByIds,
6
+ getToolsByPreset,
7
+ validateToolIds
8
+ } from "./chunk-JYIPAISH.js";
2
9
 
3
10
  // src/index.ts
4
11
  import { Command } from "commander";
5
- import chalk6 from "chalk";
12
+ import chalk10 from "chalk";
6
13
 
7
- // src/catalog.ts
8
- var PRESETS = [
9
- {
10
- id: "core",
11
- name: "Core",
12
- description: "Fundamentals every terminal should have",
13
- defaultOn: true
14
- },
15
- {
16
- id: "agent",
17
- name: "Agent",
18
- description: "Tools that specifically improve LLM workflows",
19
- defaultOn: true
20
- },
21
- {
22
- id: "media",
23
- name: "Media",
24
- description: "Image, audio, and video pipeline tools",
25
- defaultOn: false
26
- },
27
- {
28
- id: "dx",
29
- name: "DX",
30
- description: "Developer experience and quality of life",
31
- defaultOn: false
32
- },
33
- {
34
- id: "security",
35
- name: "Security",
36
- description: "Scanning and CI hygiene",
37
- defaultOn: false
14
+ // src/platform.ts
15
+ import { execa } from "execa";
16
+ async function hasCommand(cmd) {
17
+ try {
18
+ await execa(cmd, ["--version"], { timeout: 5e3 });
19
+ return true;
20
+ } catch {
21
+ return false;
38
22
  }
39
- ];
40
- var TOOLS = [
41
- // ── Core ──────────────────────────────────────────────
42
- {
43
- id: "rg",
44
- name: "ripgrep",
45
- package: "ripgrep",
46
- installMethod: "brew",
47
- verify: "rg --version",
48
- description: "Fast code search",
49
- preset: "core"
50
- },
51
- {
52
- id: "fd",
53
- name: "fd",
54
- package: "fd",
55
- installMethod: "brew",
56
- verify: "fd --version",
57
- description: "Fast file finder",
58
- preset: "core"
59
- },
60
- {
61
- id: "jq",
62
- name: "jq",
63
- package: "jq",
64
- installMethod: "brew",
65
- verify: "jq --version",
66
- description: "JSON processor",
67
- preset: "core"
68
- },
69
- {
70
- id: "yq",
71
- name: "yq",
72
- package: "yq",
73
- installMethod: "brew",
74
- verify: "yq --version",
75
- description: "YAML processor",
76
- preset: "core"
77
- },
78
- {
79
- id: "bat",
80
- name: "bat",
81
- package: "bat",
82
- installMethod: "brew",
83
- verify: "bat --version",
84
- description: "Cat with syntax highlighting",
85
- preset: "core"
86
- },
87
- {
88
- id: "tree",
89
- name: "tree",
90
- package: "tree",
91
- installMethod: "brew",
92
- verify: "tree --version",
93
- description: "Directory structure viewer",
94
- preset: "core"
95
- },
96
- {
97
- id: "gh",
98
- name: "GitHub CLI",
99
- package: "gh",
100
- installMethod: "brew",
101
- verify: "gh --version",
102
- description: "GitHub CLI for PRs, issues, releases",
103
- preset: "core"
104
- },
105
- {
106
- id: "fzf",
107
- name: "fzf",
108
- package: "fzf",
109
- installMethod: "brew",
110
- verify: "fzf --version",
111
- description: "Fuzzy finder",
112
- preset: "core"
113
- },
114
- {
115
- id: "xh",
116
- name: "xh",
117
- package: "xh",
118
- installMethod: "brew",
119
- verify: "xh --version",
120
- description: "Friendly HTTP client",
121
- preset: "core"
122
- },
123
- // ── Agent ─────────────────────────────────────────────
124
- {
125
- id: "shellcheck",
126
- name: "shellcheck",
127
- package: "shellcheck",
128
- installMethod: "brew",
129
- verify: "shellcheck --version",
130
- description: "Static analysis for shell scripts",
131
- preset: "agent"
132
- },
133
- {
134
- id: "ast-grep",
135
- name: "ast-grep",
136
- package: "ast-grep",
137
- installMethod: "brew",
138
- verify: "sg --version",
139
- description: "Structural code search/replace",
140
- preset: "agent"
141
- },
142
- {
143
- id: "just",
144
- name: "just",
145
- package: "just",
146
- installMethod: "brew",
147
- verify: "just --version",
148
- description: "Command runner (agent-readable task menu)",
149
- preset: "agent"
150
- },
151
- {
152
- id: "grex",
153
- name: "grex",
154
- package: "grex",
155
- installMethod: "brew",
156
- verify: "grex --version",
157
- description: "Generate regex from examples",
158
- preset: "agent"
159
- },
160
- {
161
- id: "knip",
162
- name: "knip",
163
- package: "knip",
164
- installMethod: "npm",
165
- verify: "knip --version",
166
- description: "Find unused code/deps in TS/JS",
167
- preset: "agent"
168
- },
169
- {
170
- id: "sd",
171
- name: "sd",
172
- package: "sd",
173
- installMethod: "brew",
174
- verify: "sd --version",
175
- description: "Simpler sed replacement",
176
- preset: "agent"
177
- },
178
- {
179
- id: "hyperfine",
180
- name: "hyperfine",
181
- package: "hyperfine",
182
- installMethod: "brew",
183
- verify: "hyperfine --version",
184
- description: "CLI benchmarking",
185
- preset: "agent"
186
- },
187
- {
188
- id: "tokei",
189
- name: "tokei",
190
- package: "tokei",
191
- installMethod: "brew",
192
- verify: "tokei --version",
193
- description: "Code statistics",
194
- preset: "agent"
195
- },
196
- {
197
- id: "tldr",
198
- name: "tldr",
199
- package: "tldr",
200
- installMethod: "brew",
201
- verify: "tldr --version",
202
- description: "Quick man page summaries",
203
- preset: "agent"
204
- },
205
- {
206
- id: "biome",
207
- name: "biome",
208
- package: "biome",
209
- installMethod: "brew",
210
- verify: "biome --version",
211
- description: "Lint + format JS/TS",
212
- preset: "agent"
213
- },
214
- {
215
- id: "difftastic",
216
- name: "difftastic",
217
- package: "difftastic",
218
- installMethod: "brew",
219
- verify: "difft --version",
220
- description: "Structural/AST diff",
221
- preset: "agent"
222
- },
223
- // ── Media ─────────────────────────────────────────────
224
- {
225
- id: "ffmpeg",
226
- name: "ffmpeg",
227
- package: "ffmpeg",
228
- installMethod: "brew",
229
- verify: "ffmpeg -version",
230
- description: "Audio/video Swiss army knife",
231
- preset: "media"
232
- },
233
- {
234
- id: "exiftool",
235
- name: "exiftool",
236
- package: "exiftool",
237
- installMethod: "brew",
238
- verify: "exiftool -ver",
239
- description: "Image/media metadata",
240
- preset: "media"
241
- },
242
- {
243
- id: "imagemagick",
244
- name: "ImageMagick",
245
- package: "imagemagick",
246
- installMethod: "brew",
247
- verify: "magick -version",
248
- description: "Image transforms",
249
- preset: "media"
250
- },
251
- {
252
- id: "svgo",
253
- name: "svgo",
254
- package: "svgo",
255
- installMethod: "npm",
256
- verify: "svgo --version",
257
- description: "SVG optimiser",
258
- preset: "media"
259
- },
260
- // ── DX ────────────────────────────────────────────────
261
- {
262
- id: "eza",
263
- name: "eza",
264
- package: "eza",
265
- installMethod: "brew",
266
- verify: "eza --version",
267
- description: "Modern ls replacement",
268
- preset: "dx"
269
- },
270
- {
271
- id: "zoxide",
272
- name: "zoxide",
273
- package: "zoxide",
274
- installMethod: "brew",
275
- verify: "zoxide --version",
276
- description: "Smarter cd",
277
- preset: "dx"
278
- },
279
- {
280
- id: "delta",
281
- name: "delta",
282
- package: "git-delta",
283
- installMethod: "brew",
284
- verify: "delta --version",
285
- description: "Better git diffs",
286
- preset: "dx"
287
- },
288
- {
289
- id: "glow",
290
- name: "glow",
291
- package: "glow",
292
- installMethod: "brew",
293
- verify: "glow --version",
294
- description: "Terminal markdown renderer",
295
- preset: "dx"
296
- },
297
- {
298
- id: "mise",
299
- name: "mise",
300
- package: "mise",
301
- installMethod: "brew",
302
- verify: "mise --version",
303
- description: "Runtime version manager",
304
- preset: "dx"
305
- },
306
- {
307
- id: "watchexec",
308
- name: "watchexec",
309
- package: "watchexec",
310
- installMethod: "brew",
311
- verify: "watchexec --version",
312
- description: "Run commands on file change",
313
- preset: "dx"
314
- },
315
- {
316
- id: "mkcert",
317
- name: "mkcert",
318
- package: "mkcert",
319
- installMethod: "brew",
320
- verify: "mkcert --version",
321
- description: "Local HTTPS certs",
322
- preset: "dx"
323
- },
324
- {
325
- id: "lazygit",
326
- name: "lazygit",
327
- package: "lazygit",
328
- installMethod: "brew",
329
- verify: "lazygit --version",
330
- description: "TUI git client",
331
- preset: "dx"
332
- },
333
- {
334
- id: "dust",
335
- name: "dust",
336
- package: "dust",
337
- installMethod: "brew",
338
- verify: "dust --version",
339
- description: "Disk usage tree",
340
- preset: "dx"
341
- },
342
- {
343
- id: "btm",
344
- name: "bottom",
345
- package: "bottom",
346
- installMethod: "brew",
347
- verify: "btm --version",
348
- description: "System monitor TUI",
349
- preset: "dx"
350
- },
351
- // ── Security ──────────────────────────────────────────
352
- {
353
- id: "trivy",
354
- name: "trivy",
355
- package: "trivy",
356
- installMethod: "brew",
357
- verify: "trivy --version",
358
- description: "Vulnerability scanner",
359
- preset: "security"
360
- },
361
- {
362
- id: "act",
363
- name: "act",
364
- package: "act",
365
- installMethod: "brew",
366
- verify: "act --version",
367
- description: "Run GitHub Actions locally",
368
- preset: "security"
369
- },
370
- {
371
- id: "gitleaks",
372
- name: "gitleaks",
373
- package: "gitleaks",
374
- installMethod: "brew",
375
- verify: "gitleaks version",
376
- description: "Secrets scanner",
377
- preset: "security"
23
+ }
24
+ var CANDIDATES = {
25
+ darwin: ["brew", "npm"],
26
+ linux: ["apt", "npm", "cargo"],
27
+ win32: ["scoop", "npm", "cargo"]
28
+ };
29
+ async function detectPlatform() {
30
+ const platform = process.platform;
31
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
32
+ const candidates = CANDIDATES[platform] ?? [];
33
+ const checks = await Promise.all(
34
+ candidates.map(async (mgr) => {
35
+ const cmd = mgr === "apt" ? "apt-get" : mgr;
36
+ const found = await hasCommand(cmd);
37
+ return found ? mgr : null;
38
+ })
39
+ );
40
+ const available = checks.filter((m) => m !== null);
41
+ if (!available.includes("npm")) available.push("npm");
42
+ return { platform, arch, available };
43
+ }
44
+
45
+ // src/resolve.ts
46
+ function resolveInstallPlan(tools, info) {
47
+ const resolved = [];
48
+ const skipped = [];
49
+ for (const tool of tools) {
50
+ const platformInstalls = tool.install[info.platform];
51
+ if (platformInstalls === null) {
52
+ skipped.push({ tool, reason: "not available on this platform" });
53
+ continue;
54
+ }
55
+ const match = platformInstalls.find((pi) => info.available.includes(pi.method));
56
+ if (!match) {
57
+ const required = platformInstalls.map((pi) => pi.method).join(" or ");
58
+ skipped.push({ tool, reason: `requires ${required}` });
59
+ continue;
60
+ }
61
+ resolved.push({ tool, method: match.method, package: match.package });
378
62
  }
379
- ];
380
- function getToolsByPreset(presetId) {
381
- return TOOLS.filter((t) => t.preset === presetId);
63
+ return { resolved, skipped };
382
64
  }
383
- function getToolsByIds(ids) {
384
- return TOOLS.filter((t) => ids.includes(t.id));
65
+ function getVerifyCommand(tool, platform) {
66
+ const v = tool.verify;
67
+ if (typeof v === "string") return v;
68
+ return v[platform] ?? v.darwin ?? Object.values(v)[0] ?? "";
385
69
  }
386
70
 
387
- // src/brew.ts
71
+ // src/installers/index.ts
72
+ import chalk6 from "chalk";
73
+
74
+ // src/installers/brew.ts
388
75
  import { writeFile } from "fs/promises";
389
- import { execa } from "execa";
76
+ import { execa as execa2 } from "execa";
390
77
  import chalk from "chalk";
391
78
 
392
79
  // src/paths.ts
@@ -394,34 +81,33 @@ import { homedir } from "os";
394
81
  import { join } from "path";
395
82
  import { mkdir } from "fs/promises";
396
83
  var BASE_DIR = join(homedir(), ".agent-loadout");
397
- var SKILLS_DIR = join(homedir(), ".claude", "skills");
84
+ var GENERIC_SKILLS = join(BASE_DIR, "skills");
85
+ var SKILL_TARGETS = {
86
+ claude: join(homedir(), ".claude", "skills")
87
+ };
398
88
  var paths = {
399
89
  base: BASE_DIR,
400
90
  receipt: join(BASE_DIR, "receipt.json"),
401
- brewfile: join(BASE_DIR, "Brewfile"),
402
- skills: SKILLS_DIR
91
+ /** null on non-darwin platforms */
92
+ brewfile: process.platform === "darwin" ? join(BASE_DIR, "Brewfile") : null,
93
+ localBin: join(homedir(), ".local", "bin"),
94
+ skillTargets: SKILL_TARGETS,
95
+ genericSkills: GENERIC_SKILLS
403
96
  };
404
97
  async function ensureDir() {
405
98
  await mkdir(paths.base, { recursive: true });
406
99
  }
407
- async function ensureSkillsDir() {
408
- await mkdir(paths.skills, { recursive: true });
409
- }
410
-
411
- // src/brew.ts
412
- async function checkBrewInstalled() {
413
- try {
414
- await execa("brew", ["--version"]);
415
- return true;
416
- } catch {
417
- return false;
100
+ async function ensureSkillDirs() {
101
+ for (const dir of Object.values(paths.skillTargets)) {
102
+ await mkdir(dir, { recursive: true });
418
103
  }
104
+ await mkdir(paths.genericSkills, { recursive: true });
419
105
  }
420
- function generateBrewfile(tools) {
421
- const brewTools = tools.filter((t) => t.installMethod === "brew");
422
- if (brewTools.length === 0) return "";
423
- const lines = brewTools.map((t) => `brew "${t.package}"`);
424
- return lines.join("\n") + "\n";
106
+
107
+ // src/installers/brew.ts
108
+ function generateBrewfile(packages) {
109
+ if (packages.length === 0) return "";
110
+ return packages.map((r) => `brew "${r.package}"`).join("\n") + "\n";
425
111
  }
426
112
  async function writeBrewfile(content) {
427
113
  await ensureDir();
@@ -429,84 +115,192 @@ async function writeBrewfile(content) {
429
115
  }
430
116
  async function runBrewBundle() {
431
117
  try {
432
- await execa("brew", ["bundle", "--file", paths.brewfile], {
118
+ await execa2("brew", ["bundle", "--file", paths.brewfile], {
433
119
  stdio: "inherit"
434
120
  });
435
121
  } catch {
436
122
  console.log(
437
123
  chalk.yellow(
438
- "\n Some brew packages may have failed to install. Run 'brew bundle --file ~/.agent-loadout/Brewfile' to retry."
124
+ "\n Some brew packages may have failed. Run 'brew bundle --file ~/.agent-loadout/Brewfile' to retry."
439
125
  )
440
126
  );
441
127
  }
442
128
  }
129
+ async function runBrewInstall(packages) {
130
+ if (packages.length === 0) return;
131
+ const brewfile = generateBrewfile(packages);
132
+ await writeBrewfile(brewfile);
133
+ await runBrewBundle();
134
+ }
443
135
 
444
- // src/npm.ts
445
- import { execa as execa2 } from "execa";
136
+ // src/installers/npm.ts
137
+ import { execa as execa3 } from "execa";
446
138
  import chalk2 from "chalk";
447
- async function checkNpmInstalled() {
139
+ async function runNpmInstall(packages) {
140
+ if (packages.length === 0) return;
141
+ const pkgNames = packages.map((r) => r.package);
448
142
  try {
449
- await execa2("npm", ["--version"]);
450
- return true;
143
+ await execa3("npm", ["install", "-g", ...pkgNames], { stdio: "inherit" });
451
144
  } catch {
452
- return false;
145
+ console.log(
146
+ chalk2.yellow(
147
+ `
148
+ npm install failed. Try manually: npm install -g ${pkgNames.join(" ")}`
149
+ )
150
+ );
453
151
  }
454
152
  }
455
- function getNpmTools(tools) {
456
- return tools.filter((t) => t.installMethod === "npm");
457
- }
458
- function getNpmInstallCommand(tools) {
459
- const npmTools = getNpmTools(tools);
460
- if (npmTools.length === 0) return [];
461
- return npmTools.map((t) => t.package);
153
+
154
+ // src/installers/apt.ts
155
+ import { execa as execa4 } from "execa";
156
+ import chalk3 from "chalk";
157
+ async function runAptInstall(packages) {
158
+ if (packages.length === 0) return;
159
+ const pkgNames = packages.map((r) => r.package);
160
+ try {
161
+ console.log(chalk3.dim(" Running apt-get update..."));
162
+ await execa4("sudo", ["apt-get", "update", "-qq"], { stdio: "inherit" });
163
+ await execa4("sudo", ["apt-get", "install", "-y", "-qq", ...pkgNames], {
164
+ stdio: "inherit"
165
+ });
166
+ } catch {
167
+ console.log(
168
+ chalk3.yellow(
169
+ `
170
+ apt install failed. Try manually: sudo apt-get install -y ${pkgNames.join(" ")}`
171
+ )
172
+ );
173
+ }
462
174
  }
463
- async function runNpmInstall(packages) {
464
- if (packages.length === 0) return true;
175
+
176
+ // src/installers/scoop.ts
177
+ import { execa as execa5 } from "execa";
178
+ import chalk4 from "chalk";
179
+ var EXTRAS_PACKAGES = /* @__PURE__ */ new Set([
180
+ "lazygit",
181
+ "delta",
182
+ "bottom",
183
+ "glow",
184
+ "gum",
185
+ "mkcert"
186
+ ]);
187
+ async function runScoopInstall(packages) {
188
+ if (packages.length === 0) return;
189
+ const needsExtras = packages.some((r) => EXTRAS_PACKAGES.has(r.package));
190
+ if (needsExtras) {
191
+ try {
192
+ await execa5("scoop", ["bucket", "add", "extras"], { stdio: "inherit" });
193
+ } catch {
194
+ }
195
+ }
196
+ const pkgNames = packages.map((r) => r.package);
465
197
  try {
466
- await execa2("npm", ["install", "-g", ...packages], { stdio: "inherit" });
467
- return true;
198
+ await execa5("scoop", ["install", ...pkgNames], { stdio: "inherit" });
468
199
  } catch {
469
200
  console.log(
470
- chalk2.yellow(
201
+ chalk4.yellow(
471
202
  `
472
- npm install failed. Try manually: npm install -g ${packages.join(" ")}`
203
+ scoop install failed. Try manually: scoop install ${pkgNames.join(" ")}`
473
204
  )
474
205
  );
475
- return false;
206
+ }
207
+ }
208
+
209
+ // src/installers/cargo.ts
210
+ import { execa as execa6 } from "execa";
211
+ import chalk5 from "chalk";
212
+ async function runCargoInstall(packages) {
213
+ if (packages.length === 0) return;
214
+ console.log(chalk5.dim(" Note: cargo installs compile from source \u2014 this may take a while."));
215
+ for (const { package: pkg } of packages) {
216
+ try {
217
+ await execa6("cargo", ["install", pkg], { stdio: "inherit" });
218
+ } catch {
219
+ console.log(
220
+ chalk5.yellow(`
221
+ cargo install ${pkg} failed. Try manually: cargo install ${pkg}`)
222
+ );
223
+ }
224
+ }
225
+ }
226
+
227
+ // src/installers/index.ts
228
+ var INSTALLERS = {
229
+ brew: runBrewInstall,
230
+ npm: runNpmInstall,
231
+ apt: runAptInstall,
232
+ scoop: runScoopInstall,
233
+ cargo: runCargoInstall
234
+ };
235
+ function getInstallerFn(method) {
236
+ return INSTALLERS[method];
237
+ }
238
+ async function runInstallPlan(plan) {
239
+ const groups = /* @__PURE__ */ new Map();
240
+ for (const item of plan.resolved) {
241
+ const group = groups.get(item.method) ?? [];
242
+ group.push(item);
243
+ groups.set(item.method, group);
244
+ }
245
+ for (const [method, packages] of groups) {
246
+ console.log(chalk6.bold(`
247
+ Installing via ${method}...`));
248
+ await getInstallerFn(method)(packages);
476
249
  }
477
250
  }
478
251
 
479
252
  // src/verify.ts
480
- import { execa as execa3 } from "execa";
481
- import chalk3 from "chalk";
482
- async function checkTool(tool) {
253
+ import { execa as execa7 } from "execa";
254
+ import chalk7 from "chalk";
255
+ function getExtraPaths(platform) {
256
+ if (platform === "darwin") {
257
+ return "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin";
258
+ }
259
+ if (platform === "linux") {
260
+ const home = process.env.HOME ?? "";
261
+ return `${home}/.local/bin:${home}/.cargo/bin`;
262
+ }
263
+ if (platform === "win32") {
264
+ const userProfile = process.env.USERPROFILE ?? "";
265
+ return `${userProfile}\\scoop\\shims:${userProfile}\\.cargo\\bin`;
266
+ }
267
+ return "";
268
+ }
269
+ async function checkTool(tool, platform) {
483
270
  try {
484
- const [cmd, ...args] = tool.verify.split(" ");
485
- const result = await execa3(cmd, args, { timeout: 5e3 });
271
+ const cmd = getVerifyCommand(tool, platform);
272
+ const [bin, ...args] = cmd.split(" ");
273
+ const extraPaths = getExtraPaths(platform);
274
+ const pathSep = platform === "win32" ? ";" : ":";
275
+ const env = {
276
+ ...process.env,
277
+ PATH: `${extraPaths}${pathSep}${process.env.PATH}`
278
+ };
279
+ const result = await execa7(bin, args, { timeout: 5e3, env });
486
280
  const version = result.stdout.split("\n")[0].trim();
487
281
  return { id: tool.id, name: tool.name, installed: true, version };
488
282
  } catch {
489
283
  return { id: tool.id, name: tool.name, installed: false, version: "" };
490
284
  }
491
285
  }
492
- async function verifyTools(tools) {
493
- return Promise.all(tools.map(checkTool));
286
+ async function verifyTools(tools, platform = process.platform) {
287
+ return Promise.all(tools.map((t) => checkTool(t, platform)));
494
288
  }
495
289
  function printVerifyResults(results) {
496
290
  const maxName = Math.max(...results.map((r) => r.name.length));
497
291
  for (const r of results) {
498
292
  const name = r.name.padEnd(maxName);
499
293
  if (r.installed) {
500
- console.log(` ${chalk3.green("\u2713")} ${name} ${chalk3.dim(r.version)}`);
294
+ console.log(` ${chalk7.green("\u2713")} ${name} ${chalk7.dim(r.version)}`);
501
295
  } else {
502
- console.log(` ${chalk3.red("\u2717")} ${name} ${chalk3.red("not found")}`);
296
+ console.log(` ${chalk7.red("\u2717")} ${name} ${chalk7.red("not found")}`);
503
297
  }
504
298
  }
505
299
  const installed = results.filter((r) => r.installed).length;
506
300
  const total = results.length;
507
301
  console.log();
508
302
  console.log(
509
- installed === total ? chalk3.green(` All ${total} tools installed.`) : chalk3.yellow(` ${installed}/${total} tools installed.`)
303
+ installed === total ? chalk7.green(` All ${total} tools installed.`) : chalk7.yellow(` ${installed}/${total} tools installed.`)
510
304
  );
511
305
  }
512
306
  function verifyResultsToJson(results) {
@@ -519,7 +313,7 @@ function verifyResultsToJson(results) {
519
313
 
520
314
  // src/receipt.ts
521
315
  import { readFile, writeFile as writeFile2 } from "fs/promises";
522
- import chalk4 from "chalk";
316
+ import chalk8 from "chalk";
523
317
  async function readReceipt() {
524
318
  try {
525
319
  const raw = await readFile(paths.receipt, "utf-8");
@@ -527,7 +321,7 @@ async function readReceipt() {
527
321
  } catch (err) {
528
322
  if (err instanceof SyntaxError) {
529
323
  console.log(
530
- chalk4.yellow(
324
+ chalk8.yellow(
531
325
  " Warning: ~/.agent-loadout/receipt.json is corrupted. Ignoring."
532
326
  )
533
327
  );
@@ -542,7 +336,7 @@ async function writeReceipt(receipt) {
542
336
 
543
337
  // src/ui.ts
544
338
  import { checkbox, confirm } from "@inquirer/prompts";
545
- import chalk5 from "chalk";
339
+ import chalk9 from "chalk";
546
340
  async function selectPresets() {
547
341
  return checkbox({
548
342
  message: "Which presets do you want to install?",
@@ -562,9 +356,9 @@ async function selectTools(presetIds) {
562
356
  const selectedIds = await checkbox({
563
357
  message: "Toggle individual tools (all selected by default)",
564
358
  choices: available.map((t) => {
565
- const badge = installedIds.has(t.id) ? chalk5.green(" (installed)") : "";
359
+ const badge = installedIds.has(t.id) ? chalk9.green(" (installed)") : "";
566
360
  return {
567
- name: `${t.name}${badge} \u2014 ${chalk5.dim(t.description)}`,
361
+ name: `${t.name}${badge} \u2014 ${chalk9.dim(t.description)}`,
568
362
  value: t.id,
569
363
  checked: true
570
364
  };
@@ -572,22 +366,51 @@ async function selectTools(presetIds) {
572
366
  });
573
367
  return TOOLS.filter((t) => selectedIds.includes(t.id));
574
368
  }
575
- function printPreview(tools) {
576
- const brewfile = generateBrewfile(tools);
577
- const npmPackages = getNpmInstallCommand(tools);
369
+ function printPreview(tools, plan, platformInfo) {
370
+ const platformLabel = chalk9.cyan(platformInfo.platform);
371
+ const archLabel = chalk9.dim(platformInfo.arch);
372
+ console.log(`
373
+ Platform: ${platformLabel} ${archLabel}`);
374
+ if (plan.skipped.length > 0) {
375
+ console.log(chalk9.yellow(`
376
+ Skipped (${plan.skipped.length} tools unavailable on this platform):`));
377
+ for (const { tool, reason } of plan.skipped) {
378
+ console.log(chalk9.dim(` \u2022 ${tool.name} \u2014 ${reason}`));
379
+ }
380
+ }
381
+ const groups = /* @__PURE__ */ new Map();
382
+ for (const { method, package: pkg } of plan.resolved) {
383
+ const list = groups.get(method) ?? [];
384
+ list.push(pkg);
385
+ groups.set(method, list);
386
+ }
387
+ if (groups.size === 0) {
388
+ console.log(chalk9.dim("\nNothing to install."));
389
+ return;
390
+ }
578
391
  console.log();
579
- if (brewfile) {
580
- console.log(chalk5.bold("Brewfile:"));
581
- console.log(chalk5.dim(brewfile));
582
- console.log(
583
- chalk5.dim(" \u2192 brew bundle --file ~/.agent-loadout/Brewfile")
584
- );
392
+ for (const [method, packages] of groups) {
393
+ const label = chalk9.bold(`${method}:`);
394
+ console.log(label);
395
+ const preview = getInstallPreview(method, packages);
396
+ console.log(chalk9.dim(` ${preview}`));
585
397
  console.log();
586
398
  }
587
- if (npmPackages.length > 0) {
588
- console.log(chalk5.bold("npm globals:"));
589
- console.log(chalk5.dim(` \u2192 npm install -g ${npmPackages.join(" ")}`));
590
- console.log();
399
+ }
400
+ function getInstallPreview(method, packages) {
401
+ switch (method) {
402
+ case "brew":
403
+ return `brew bundle --file ~/.agent-loadout/Brewfile`;
404
+ case "npm":
405
+ return `npm install -g ${packages.join(" ")}`;
406
+ case "apt":
407
+ return `sudo apt-get install -y ${packages.join(" ")}`;
408
+ case "scoop":
409
+ return `scoop install ${packages.join(" ")}`;
410
+ case "cargo":
411
+ return `cargo install ${packages.join(" ")}`;
412
+ default:
413
+ return packages.join(" ");
591
414
  }
592
415
  }
593
416
  async function confirmInstall() {
@@ -595,7 +418,7 @@ async function confirmInstall() {
595
418
  }
596
419
 
597
420
  // src/skills.ts
598
- import { writeFile as writeFile3 } from "fs/promises";
421
+ import { access, writeFile as writeFile3 } from "fs/promises";
599
422
  import { join as join2 } from "path";
600
423
 
601
424
  // src/skills/rg.ts
@@ -614,13 +437,19 @@ Search file contents across a codebase. Faster than grep, respects .gitignore by
614
437
  - Files only (no content): \`rg -l "pattern"\`
615
438
  - Count matches: \`rg -c "pattern"\`
616
439
  - Case insensitive: \`rg -i "pattern"\`
440
+ - JSON output (agent-parseable): \`rg "pattern" --json\`
441
+ - Exit non-zero if no matches: \`rg --exit-status "pattern"\`
617
442
 
618
443
  ## Output format
619
444
  \`file:line:column:matched_text\` \u2014 one match per line, stable and parseable.
620
445
 
446
+ ## Why it matters for agents
447
+ \`--json\` output is directly parseable; rg is the backbone of fast codebase search. \`--exit-status\` makes it composable in CI pipelines and shell conditionals.
448
+
621
449
  ## Gotchas
622
450
  - Skips hidden files and .gitignore'd paths by default. Use \`--hidden\` or \`--no-ignore\` to include them.
623
451
  - For literal dots, brackets etc. in patterns, use \`-F\` (fixed string) to avoid regex escaping issues.
452
+ - \`--json\` emits structured events (match, begin, end, summary) \u2014 parse with \`jq\` for agent workflows.
624
453
  `.trim();
625
454
 
626
455
  // src/skills/fd.ts
@@ -641,9 +470,13 @@ Find files by name pattern. Faster than \`find\`, respects .gitignore, sane defa
641
470
  ## Output format
642
471
  One path per line, relative to search root.
643
472
 
473
+ ## Why it matters for agents
474
+ \`-x\` flag enables batch file operations without shell loops \u2014 agents can use \`fd -e ts -x sd 'old' 'new' {}\` to safely refactor across many files.
475
+
644
476
  ## Gotchas
645
477
  - Regex by default. Use \`-g\` for glob patterns.
646
478
  - Ignores .gitignore'd files by default. Use \`--no-ignore\` to include.
479
+ - On Debian/Ubuntu the binary is \`fdfind\`, not \`fd\`. Use \`alias fd=fdfind\` or install via cargo.
647
480
  `.trim();
648
481
 
649
482
  // src/skills/jq.ts
@@ -701,6 +534,14 @@ View file contents with syntax highlighting, line numbers, and git diff indicato
701
534
  - Show line range: \`bat -r 10:20 file.ts\`
702
535
  - Force language: \`bat -l json file.txt\`
703
536
  - Use as pager for other commands: \`command | bat -l json\`
537
+ - Disable pager for scripts: \`bat --paging=never file.ts\`
538
+
539
+ ## Why it matters for agents
540
+ \`-l\` flag lets agents force syntax for stdin \u2014 useful for highlighting API responses or generated content piped through bat.
541
+
542
+ ## Gotchas
543
+ - On Debian/Ubuntu the binary is \`batcat\`, not \`bat\`. Use \`alias bat=batcat\` or install via cargo.
544
+ - Use \`--paging=never\` in scripts to suppress the interactive pager.
704
545
  `.trim();
705
546
 
706
547
  // src/skills/tree.ts
@@ -716,6 +557,14 @@ Visualise directory structure as a tree. Useful for understanding project layout
716
557
  - Show only directories: \`tree -d\`
717
558
  - Ignore patterns: \`tree -I 'node_modules|dist'\`
718
559
  - With file sizes: \`tree -sh\`
560
+ - JSON output: \`tree -J path/\`
561
+
562
+ ## Why it matters for agents
563
+ \`-J\` gives a JSON file tree \u2014 lets agents understand project structure in one call without recursive \`ls\` loops.
564
+
565
+ ## Gotchas
566
+ - Use \`--charset ascii\` for plain output in non-unicode terminals or CI logs.
567
+ - \`-J\` JSON output includes type, name, and children \u2014 structured project map in one call.
719
568
  `.trim();
720
569
 
721
570
  // src/skills/gh.ts
@@ -735,13 +584,20 @@ Create PRs, manage issues, check CI status, manage releases \u2014 all without l
735
584
  - View repo: \`gh repo view\`
736
585
  - API calls: \`gh api repos/owner/repo/pulls/123/comments\`
737
586
  - Clone: \`gh repo clone owner/repo\`
587
+ - List workflow runs: \`gh run list --json name,status,conclusion\`
588
+ - Watch a run live: \`gh run watch\`
589
+ - Cross-repo PR list: \`gh pr list -R owner/repo --json number,title,state\`
738
590
 
739
591
  ## Output format
740
592
  Supports \`--json\` on most commands for structured output. E.g. \`gh pr list --json number,title,state\`.
741
593
 
594
+ ## Why it matters for agents
595
+ \`--json\` on every command + \`gh api\` unlocks full GitHub automation without parsing HTML. Agents can open PRs, check CI, and comment on issues programmatically.
596
+
742
597
  ## Gotchas
743
598
  - Requires authentication: \`gh auth login\`
744
599
  - \`gh api\` is very powerful for anything not covered by built-in commands.
600
+ - In CI, set \`GH_TOKEN\` env var instead of running \`gh auth login\`.
745
601
  `.trim();
746
602
 
747
603
  // src/skills/fzf.ts
@@ -775,10 +631,14 @@ Lint shell scripts (bash, sh, dash) for common mistakes: quoting issues, unset v
775
631
  - GCC-style output: \`shellcheck --format=gcc script.sh\`
776
632
  - Exclude specific rules: \`shellcheck --exclude=SC2034 script.sh\`
777
633
  - Check from stdin: \`echo '#!/bin/bash' | shellcheck -\`
634
+ - Errors only (no warnings): \`shellcheck --severity error script.sh\`
778
635
 
779
636
  ## Output format
780
637
  Default output is human-readable with line numbers and fix suggestions. Use \`--format=json\` for structured output.
781
638
 
639
+ ## Why it matters for agents
640
+ Agents frequently generate shell scripts \u2014 shellcheck catches quoting bugs, unset variables, and portability issues before they cause failures. Use \`--format=json\` to parse results programmatically.
641
+
782
642
  ## Gotchas
783
643
  - Scripts need a shebang (\`#!/bin/bash\`) or use \`--shell=\` flag.
784
644
  - SC codes (e.g. SC2086) link to detailed wiki explanations.
@@ -845,7 +705,10 @@ Generate a regular expression from a set of example strings. Useful when you kno
845
705
  - Verbose regex: \`grex --verbose "foo-123" "bar-456"\`
846
706
 
847
707
  ## Gotchas
848
- - Output is a regex string, not a replacement. Useful as input for rg, sd, or code.
708
+ - Output is a raw regex string \u2014 pipe directly into \`rg\`, \`sd\`, or save to a variable.
709
+
710
+ ## Why it matters for agents
711
+ Lets agents generate correct regex from test cases rather than hallucinating patterns \u2014 eliminates a whole class of regex bugs.
849
712
  `.trim();
850
713
 
851
714
  // src/skills/knip.ts
@@ -860,12 +723,16 @@ Detect unused files, exports, dependencies, and types in TypeScript/JavaScript p
860
723
  - Unused files only: \`knip --include files\`
861
724
  - Unused exports only: \`knip --include exports\`
862
725
  - Unused deps only: \`knip --include dependencies\`
863
- - JSON output: \`knip --reporter json\`
726
+ - Machine-readable with scopes: \`knip --reporter json --include files,exports,dependencies\`
727
+
728
+ ## Why it matters for agents
729
+ Identifies dead code before large refactors \u2014 agents can safely delete unused files and exports flagged by knip without breaking the build.
864
730
 
865
731
  ## Gotchas
866
732
  - Must be run at project root (where package.json lives).
867
733
  - May need a knip.json config for monorepos or non-standard project structures.
868
734
  - Some frameworks have plugins (Next.js, Remix, etc.) \u2014 check docs if results seem wrong.
735
+ - Exits 1 if issues found \u2014 CI-friendly; pipe to \`jq\` to filter specific categories.
869
736
  `.trim();
870
737
 
871
738
  // src/skills/sd.ts
@@ -884,6 +751,9 @@ Find and replace in files. Like sed but with intuitive syntax \u2014 no escaping
884
751
  ## Gotchas
885
752
  - Uses regex by default. Use \`-F\` for fixed/literal strings.
886
753
  - Modifies files in place when given a filename. Use \`-p\` to preview first.
754
+
755
+ ## Why it matters for agents
756
+ Safer than sed for multi-line replacements \u2014 predictable escaping without shell quoting nightmares. Combine with \`fd -x\` for safe bulk refactors across many files.
887
757
  `.trim();
888
758
 
889
759
  // src/skills/hyperfine.ts
@@ -899,6 +769,11 @@ Benchmark commands to compare performance. Runs commands multiple times and repo
899
769
  - With warmup: \`hyperfine --warmup 3 'command'\`
900
770
  - Export results: \`hyperfine --export-json results.json 'command'\`
901
771
  - Min runs: \`hyperfine --min-runs 20 'command'\`
772
+ - Non-interactive + export: \`hyperfine --style basic --export-json results.json 'cmd1' 'cmd2'\`
773
+ - With prepare step: \`hyperfine --prepare 'make clean' 'make build'\`
774
+
775
+ ## Why it matters for agents
776
+ \`--export-json\` lets agents compare builds and commands quantitatively \u2014 structured results include mean, stddev, min, max per command.
902
777
 
903
778
  ## Gotchas
904
779
  - Wrap commands in quotes.
@@ -917,10 +792,16 @@ Get a quick overview of a codebase: languages, lines of code, comments, blanks.
917
792
  - Specific path: \`tokei src/\`
918
793
  - JSON output: \`tokei --output json\`
919
794
  - Sort by lines: \`tokei --sort lines\`
795
+ - Exclude dirs: \`tokei --output json --exclude node_modules,dist src/\`
796
+ - Filter languages: \`tokei --languages TypeScript,Rust\`
797
+ - Per-file breakdown: \`tokei --files src/\`
920
798
 
921
799
  ## Output format
922
800
  Table with language, files, lines, code, comments, blanks.
923
801
  Use \`--output json\` for structured output.
802
+
803
+ ## Why it matters for agents
804
+ Gives agents a language/complexity map before large refactors \u2014 quickly understand the scale and dominant languages of an unfamiliar codebase.
924
805
  `.trim();
925
806
 
926
807
  // src/skills/ffmpeg.ts
@@ -972,6 +853,9 @@ Read, write, and strip metadata (EXIF, IPTC, XMP) from images and media files.
972
853
  ## Gotchas
973
854
  - Field names are case-insensitive.
974
855
  - Use \`-json\` for structured output.
856
+
857
+ ## Why it matters for agents
858
+ \`-json\` output enables structured metadata extraction from any media file \u2014 agents can batch-read EXIF data, filter by GPS coordinates, or rename files by capture date programmatically.
975
859
  `.trim();
976
860
 
977
861
  // src/skills/imagemagick.ts
@@ -994,6 +878,9 @@ Resize, crop, convert, and manipulate images from the command line.
994
878
 
995
879
  ## Gotchas
996
880
  - The binary is \`magick\` (ImageMagick 7). Older versions used \`convert\`.
881
+
882
+ ## Why it matters for agents
883
+ Batch image processing without opening apps \u2014 useful for automated asset pipelines. Agents can resize, convert formats, and generate thumbnails in a single \`mogrify\` invocation.
997
884
  `.trim();
998
885
 
999
886
  // src/skills/svgo.ts
@@ -1008,10 +895,15 @@ Optimise SVG files by removing unnecessary metadata, comments, and reducing prec
1008
895
  - Optimise in place: \`svgo input.svg\`
1009
896
  - Folder: \`svgo -f ./icons/ -o ./icons-optimised/\`
1010
897
  - Show savings: \`svgo input.svg --pretty\`
898
+ - Multipass (better compression): \`svgo --multipass input.svg -o output.svg\`
899
+ - Check savings without writing: \`svgo --dry-run input.svg\`
1011
900
 
1012
901
  ## Gotchas
1013
902
  - Default plugins are usually fine. Override with \`--config svgo.config.js\` if needed.
1014
903
  - In-place by default when no \`-o\` specified. Pipe or use \`-o\` for safety.
904
+
905
+ ## Why it matters for agents
906
+ Automated SVG optimisation in build pipelines \u2014 measurable, reproducible size savings. Agents can run \`svgo -f ./icons/\` after any icon library update.
1015
907
  `.trim();
1016
908
 
1017
909
  // src/skills/eza.ts
@@ -1070,6 +962,9 @@ Add to ~/.gitconfig:
1070
962
 
1071
963
  ## Gotchas
1072
964
  - The brew package is called \`git-delta\`, but the binary is \`delta\`.
965
+
966
+ ## Why it matters for agents
967
+ Makes \`git diff\` and \`git log -p\` output readable \u2014 useful when agents are reviewing code changes or summarising commits for users.
1073
968
  `.trim();
1074
969
 
1075
970
  // src/skills/glow.ts
@@ -1083,6 +978,11 @@ Render markdown files beautifully in the terminal. Great for reading READMEs, do
1083
978
  - Render file: \`glow README.md\`
1084
979
  - Render with pager: \`glow -p README.md\`
1085
980
  - Render from stdin: \`cat CHANGELOG.md | glow\`
981
+ - Disable pager: \`glow --no-pager README.md\`
982
+ - Fixed width: \`glow --width 100 README.md\`
983
+
984
+ ## Why it matters for agents
985
+ Renders markdown cleanly in terminal output \u2014 useful for displaying skill files, changelogs, or generated docs to users without raw markdown symbols.
1086
986
  `.trim();
1087
987
 
1088
988
  // src/skills/mise.ts
@@ -1105,6 +1005,9 @@ Uses \`.mise.toml\` or \`.tool-versions\` in project root. This ensures determin
1105
1005
 
1106
1006
  ## Gotchas
1107
1007
  - Run \`mise activate zsh\` (or bash/fish) in your shell profile for automatic version switching.
1008
+
1009
+ ## Why it matters for agents
1010
+ Manages runtime versions declaratively via \`.mise.toml\` \u2014 agents can use \`mise exec -- node script.js\` to pin the exact Node/Python/Ruby version without modifying global state.
1108
1011
  `.trim();
1109
1012
 
1110
1013
  // src/skills/watchexec.ts
@@ -1123,6 +1026,9 @@ Watch files for changes and re-run a command. Language-agnostic alternative to n
1123
1026
  ## Gotchas
1124
1027
  - Use \`-e\` to filter by extension, \`-w\` to filter by directory.
1125
1028
  - Use \`--restart\` for long-running processes (servers), otherwise it waits for completion.
1029
+
1030
+ ## Why it matters for agents
1031
+ Enables live-reload dev loops \u2014 agents can set up reactive pipelines (\`watchexec -e ts "pnpm typecheck"\`) and report on each change without polling.
1126
1032
  `.trim();
1127
1033
 
1128
1034
  // src/skills/mkcert.ts
@@ -1136,10 +1042,15 @@ Generate locally-trusted HTTPS certificates for development. No more "insecure"
1136
1042
  - First-time setup: \`mkcert -install\` (installs local CA)
1137
1043
  - Generate cert: \`mkcert localhost 127.0.0.1 ::1\`
1138
1044
  - Generate for custom domain: \`mkcert "myapp.local" "*.myapp.local"\`
1045
+ - Custom output paths: \`mkcert -cert-file cert.pem -key-file key.pem localhost\`
1046
+ - Wildcard + Docker/VM: \`mkcert -install && mkcert "*.local" localhost 127.0.0.1\`
1139
1047
 
1140
1048
  ## Gotchas
1141
1049
  - \`mkcert -install\` only needs to run once per machine.
1142
1050
  - Output is two files: cert.pem and key.pem. Point your dev server at them.
1051
+
1052
+ ## Why it matters for agents
1053
+ Enables local HTTPS in one command \u2014 no CA setup complexity. Agents scaffolding full-stack dev environments can run \`mkcert -install && mkcert localhost\` to get HTTPS working immediately.
1143
1054
  `.trim();
1144
1055
 
1145
1056
  // src/skills/trivy.ts
@@ -1155,10 +1066,17 @@ Scan filesystems, container images, and code repos for known vulnerabilities.
1155
1066
  - Scan a container image: \`trivy image myapp:latest\`
1156
1067
  - Only critical/high: \`trivy fs --severity CRITICAL,HIGH .\`
1157
1068
  - Scan for secrets: \`trivy fs --scanners secret .\`
1069
+ - CI gate (fail on findings): \`trivy fs --severity CRITICAL,HIGH --exit-code 1 --no-progress .\`
1070
+ - Offline (skip DB update): \`trivy fs --skip-update --format json .\`
1071
+
1072
+ ## Why it matters for agents
1073
+ Gives agents a security gate before deployments. \`--format json --exit-code 1\` creates a composable CI step \u2014 agents can parse findings and summarise critical vulnerabilities.
1158
1074
 
1159
1075
  ## Gotchas
1160
1076
  - First run downloads a vulnerability database (can be slow).
1161
1077
  - Use \`--format json\` for structured output.
1078
+ - Use \`--exit-code 1\` to fail CI pipelines on findings; omit for reporting-only mode.
1079
+ - \`--no-progress\` keeps CI logs clean.
1162
1080
  `.trim();
1163
1081
 
1164
1082
  // src/skills/act.ts
@@ -1174,11 +1092,16 @@ Test GitHub Actions workflows without pushing. Runs workflows in Docker containe
1174
1092
  - Run specific event: \`act push\`
1175
1093
  - Run specific job: \`act -j build\`
1176
1094
  - Dry run: \`act -n\`
1095
+ - Pass secrets: \`act push --secret MY_TOKEN="value"\`
1096
+ - List available jobs: \`act --list\`
1177
1097
 
1178
1098
  ## Gotchas
1179
1099
  - Requires Docker to be running.
1180
1100
  - Not all GitHub Actions features are supported locally (secrets, some contexts).
1181
1101
  - Use \`-n\` (dry run) first to see what would happen.
1102
+
1103
+ ## Why it matters for agents
1104
+ Test CI workflows locally before pushing \u2014 saves agent roundtrips to GitHub. Agents can run \`act -n\` to validate workflow YAML without Docker overhead.
1182
1105
  `.trim();
1183
1106
 
1184
1107
  // src/skills/xh.ts
@@ -1196,11 +1119,17 @@ Send HTTP requests from the terminal. Cleaner syntax than curl, JSON-first, colo
1196
1119
  - Follow redirects: \`xh --follow get example.com\`
1197
1120
  - Save response to file: \`xh get example.com/file.zip > file.zip\`
1198
1121
  - Show request/response headers: \`xh --print=hHbB get httpbin.org/get\`
1122
+ - Fail on 4xx/5xx: \`xh --check-status get api.example.com\`
1123
+ - Response body only: \`xh -b get api.example.com\`
1124
+ - Headers only: \`xh -h get api.example.com\`
1125
+ - Download file: \`xh --download get example.com/file.zip\`
1126
+
1127
+ ## Why it matters for agents
1128
+ Cleaner than curl for API testing \u2014 \`key=value\` JSON syntax removes quoting complexity. \`--check-status\` makes error handling trivial: non-zero exit on any 4xx/5xx.
1199
1129
 
1200
1130
  ## Gotchas
1201
1131
  - \`key=value\` sends as JSON string; \`key:=value\` sends raw JSON (numbers, bools, arrays).
1202
1132
  - Defaults to HTTPS if scheme is omitted.
1203
- - Use \`--check-status\` to exit non-zero on 4xx/5xx responses.
1204
1133
  `.trim();
1205
1134
 
1206
1135
  // src/skills/tldr.ts
@@ -1242,6 +1171,9 @@ Fast, zero-config linter and formatter for JavaScript/TypeScript projects. Repla
1242
1171
  - Requires a \`biome.json\` config or \`--config-path\` flag; \`biome init\` generates a sensible default.
1243
1172
  - Not 100% compatible with all ESLint rules \u2014 check the migration guide when switching existing projects.
1244
1173
  - \`biome check\` is read-only by default; pass \`--write\` to apply fixes.
1174
+
1175
+ ## Why it matters for agents
1176
+ Zero-config lint+format in one command \u2014 drop-in for eslint+prettier in CI. \`biome ci .\` exits 1 on issues and is non-destructive.
1245
1177
  `.trim();
1246
1178
 
1247
1179
  // src/skills/difftastic.ts
@@ -1262,6 +1194,9 @@ Compare files by syntax tree, not line-by-line. Understands code structure so re
1262
1194
  - Supports most languages automatically via file extension detection.
1263
1195
  - Output is always side-by-side; pipe width matters \u2014 use a wide terminal.
1264
1196
  - Falls back to line-diff for unsupported file types.
1197
+
1198
+ ## Why it matters for agents
1199
+ Understands code structure \u2014 avoids false-positive diffs from formatting changes. Agents using \`GIT_EXTERNAL_DIFF=difft git diff\` get semantic change summaries, not noise.
1265
1200
  `.trim();
1266
1201
 
1267
1202
  // src/skills/lazygit.ts
@@ -1289,6 +1224,9 @@ Interactive terminal UI for git \u2014 stage hunks, commit, branch, rebase, and
1289
1224
  - Requires git to be installed (it's a UI wrapper, not a replacement).
1290
1225
  - Config lives at \`~/.config/lazygit/config.yml\`.
1291
1226
  - Mouse support is on by default \u2014 click panels to navigate.
1227
+
1228
+ ## Why it matters for agents
1229
+ Note: interactive TUI only \u2014 not suitable for agent automation. For scripted git operations use \`git\` CLI directly; for GitHub automation use \`gh\`.
1292
1230
  `.trim();
1293
1231
 
1294
1232
  // src/skills/dust.ts
@@ -1310,6 +1248,9 @@ Visualise what's eating disk space in a directory tree. Faster and more readable
1310
1248
  - Output is proportional bars + sizes; percentages are relative to the scanned root, not total disk.
1311
1249
  - Use \`-d 1\` for a quick top-level summary before drilling down.
1312
1250
  - Symlinks are not followed by default \u2014 add \`-L\` to follow them.
1251
+
1252
+ ## Why it matters for agents
1253
+ Identifies large directories before disk operations \u2014 agents can quickly find what to clean up with \`dust -d 1\` before running builds or copying large directories.
1313
1254
  `.trim();
1314
1255
 
1315
1256
  // src/skills/btm.ts
@@ -1336,6 +1277,9 @@ Real-time TUI system monitor \u2014 CPU, memory, network, disk, and process list
1336
1277
  ## Gotchas
1337
1278
  - Config lives at \`~/.config/bottom/bottom.toml\` \u2014 customise colours and layout there.
1338
1279
  - \`--basic\` mode is useful in constrained terminals or for quick checks.
1280
+
1281
+ ## Why it matters for agents
1282
+ Note: interactive TUI \u2014 not suitable for scripting. For programmatic process info use \`procs --json\`; for one-shot CPU/memory snapshots use \`ps\` or \`top -l 1\`.
1339
1283
  `.trim();
1340
1284
 
1341
1285
  // src/skills/gitleaks.ts
@@ -1359,10 +1303,336 @@ gitleaks protect --staged # pre-commit hook
1359
1303
  gitleaks detect # CI full scan
1360
1304
  \`\`\`
1361
1305
 
1306
+ ## Why it matters for agents
1307
+ Agents editing configuration files or adding credentials must scan before committing. Exit code 1 on findings makes it trivially composable as a pre-commit gate.
1308
+
1362
1309
  ## Gotchas
1363
1310
  - Findings include file, line, rule, and matched secret fragment \u2014 review before dismissing.
1364
1311
  - Use a \`.gitleaksignore\` file to whitelist known false positives.
1365
1312
  - Does not redact secrets from history \u2014 use \`git filter-repo\` to remove them.
1313
+ - Returns exit code 1 if leaks found, 0 if clean \u2014 use directly in CI pipelines.
1314
+ - Custom rules: \`gitleaks detect --config custom-rules.toml --report-format json\`
1315
+ `.trim();
1316
+
1317
+ // src/skills/pandoc.ts
1318
+ var pandoc_default = `
1319
+ # pandoc \u2014 Universal document converter
1320
+
1321
+ ## When to use
1322
+ Convert between document formats: markdown, HTML, PDF, docx, LaTeX, RST, EPUB, and dozens more. The single tool for any "convert X to Y" task.
1323
+
1324
+ ## Trusted commands
1325
+ - Markdown to HTML: \`pandoc input.md -o output.html\`
1326
+ - Markdown to PDF: \`pandoc input.md -o output.pdf\` (requires LaTeX or --pdf-engine=weasyprint)
1327
+ - Markdown to docx: \`pandoc input.md -o output.docx\`
1328
+ - HTML to markdown: \`pandoc input.html -o output.md\`
1329
+ - With standalone template: \`pandoc input.md -s -o output.html\`
1330
+ - Custom CSS: \`pandoc input.md -s --css=style.css -o output.html\`
1331
+ - List supported formats: \`pandoc --list-input-formats\` / \`pandoc --list-output-formats\`
1332
+
1333
+ ## Why it matters for agents
1334
+ Agents frequently need to transform content between formats for delivery. Pandoc handles the conversion so the agent focuses on content.
1335
+
1336
+ ## Gotchas
1337
+ - PDF output requires a PDF engine (wkhtmltopdf, weasyprint, or LaTeX). Use \`--pdf-engine=weasyprint\` if LaTeX isn't installed.
1338
+ - Output format is inferred from the file extension.
1339
+ - Use \`-s\` (standalone) for complete documents with headers, not fragments.
1340
+ `.trim();
1341
+
1342
+ // src/skills/duckdb.ts
1343
+ var duckdb_default = `
1344
+ # duckdb \u2014 SQL analytics on files
1345
+
1346
+ ## When to use
1347
+ Query CSV, JSON, Parquet, or Excel files with SQL. No database setup, no import step \u2014 just point and query.
1348
+
1349
+ ## Trusted commands
1350
+ - Query a CSV: \`duckdb -c "SELECT * FROM 'data.csv' LIMIT 10"\`
1351
+ - Aggregate: \`duckdb -c "SELECT col, COUNT(*) FROM 'data.csv' GROUP BY col"\`
1352
+ - Query JSON: \`duckdb -c "SELECT * FROM read_json_auto('data.json')"\`
1353
+ - Query Parquet: \`duckdb -c "SELECT * FROM 'data.parquet'"\`
1354
+ - Join files: \`duckdb -c "SELECT a.*, b.name FROM 'users.csv' a JOIN 'orders.csv' b ON a.id = b.user_id"\`
1355
+ - Export result: \`duckdb -c "COPY (SELECT * FROM 'data.csv') TO 'out.parquet' (FORMAT PARQUET)"\`
1356
+ - Interactive REPL: \`duckdb\`
1357
+
1358
+ ## Why it matters for agents
1359
+ When an agent encounters a data file, duckdb lets it explore and answer questions via SQL instead of writing custom Python/JS parsing code. Massive time saver for any data task.
1360
+
1361
+ ## Gotchas
1362
+ - Auto-detects CSV headers and types. Use \`read_csv('file.csv', header=false)\` to disable.
1363
+ - For one-off queries, use \`-c\`. For multiple, use the REPL or a .sql file.
1364
+ - Outputs to stdout in table format by default. Use \`-csv\` or \`-json\` for machine-readable output.
1365
+ `.trim();
1366
+
1367
+ // src/skills/htmlq.ts
1368
+ var htmlq_default = `
1369
+ # htmlq \u2014 jq for HTML
1370
+
1371
+ ## When to use
1372
+ Extract content from HTML files or piped HTML using CSS selectors. Like jq but for HTML/web content.
1373
+
1374
+ ## Trusted commands
1375
+ - Extract by selector: \`cat page.html | htmlq '.article-title'\`
1376
+ - Get attribute: \`cat page.html | htmlq 'a' --attribute href\`
1377
+ - Text only (strip tags): \`cat page.html | htmlq '.content' --text\`
1378
+ - Pipe from curl: \`curl -s https://example.com | htmlq 'h1'\`
1379
+ - Multiple selectors: \`cat page.html | htmlq 'h1, h2, h3'\`
1380
+ - Pretty print: \`cat page.html | htmlq --pretty 'body'\`
1381
+
1382
+ ## Why it matters for agents
1383
+ When agents fetch web content or work with HTML files, htmlq extracts exactly what's needed without writing a parser.
1384
+
1385
+ ## Gotchas
1386
+ - Reads from stdin \u2014 always pipe input to it.
1387
+ - CSS selectors only (not XPath).
1388
+ - Use \`--text\` to get text content without HTML tags.
1389
+ `.trim();
1390
+
1391
+ // src/skills/typos.ts
1392
+ var typos_default = `
1393
+ # typos \u2014 Source code spell checker
1394
+
1395
+ ## When to use
1396
+ Find and fix typos in source code, comments, docs, filenames, and variable names. Runs in milliseconds across entire codebases.
1397
+
1398
+ ## Trusted commands
1399
+ - Check current directory: \`typos\`
1400
+ - Check specific path: \`typos src/\`
1401
+ - Auto-fix typos: \`typos --write-changes\`
1402
+ - Diff mode (show what would change): \`typos --diff\`
1403
+ - Check specific file types: \`typos --type rust src/\`
1404
+ - Ignore a word: add to \`_typos.toml\`: \`[default.extend-words]\` \u2192 \`teh = "teh"\`
1405
+
1406
+ ## Why it matters for agents
1407
+ Agents generate a lot of code. Running typos as a final pass catches misspellings in variable names, comments, and docs that slip past linters.
1408
+
1409
+ ## Gotchas
1410
+ - Configure exceptions in \`_typos.toml\` or \`.typos.toml\` at project root.
1411
+ - Understands common programming conventions (camelCase, snake_case, etc.).
1412
+ - Very fast \u2014 designed to run on every commit.
1413
+ `.trim();
1414
+
1415
+ // src/skills/gum.ts
1416
+ var gum_default = `
1417
+ # gum \u2014 Interactive UI components for shell scripts
1418
+
1419
+ ## When to use
1420
+ Add interactive prompts, spinners, confirmations, file pickers, and styled text to shell scripts without complex TUI code.
1421
+
1422
+ ## Trusted commands
1423
+ - Confirm (y/n): \`gum confirm "Deploy to production?"\`
1424
+ - Choose from list: \`gum choose "option1" "option2" "option3"\`
1425
+ - Text input: \`gum input --placeholder "Enter name"\`
1426
+ - Multi-line input: \`gum write --placeholder "Description"\`
1427
+ - Spinner while running: \`gum spin --title "Building..." -- make build\`
1428
+ - Filter list (fuzzy): \`echo "a\\nb\\nc" | gum filter\`
1429
+ - Styled text: \`gum style --foreground 212 --bold "Done!"\`
1430
+ - File picker: \`gum file .\`
1431
+
1432
+ ## Why it matters for agents
1433
+ When agents create shell scripts or automation, gum adds polished interactivity with single commands. No dependency on dialog, whiptail, or complex TUI libraries.
1434
+
1435
+ ## Gotchas
1436
+ - Each command returns the selected/entered value to stdout \u2014 capture with \`$(gum ...)\`.
1437
+ - Part of the Charm ecosystem (same as glow, lazygit).
1438
+ - \`gum confirm\` returns exit code 0 (yes) or 1 (no).
1439
+ `.trim();
1440
+
1441
+ // src/skills/direnv.ts
1442
+ var direnv_default = `
1443
+ # direnv \u2014 Auto-load env vars per directory
1444
+
1445
+ ## When to use
1446
+ Automatically load and unload environment variables when entering/leaving a directory. Each project gets its own env without polluting your shell.
1447
+
1448
+ ## Trusted commands
1449
+ - Allow current .envrc: \`direnv allow\`
1450
+ - Block current .envrc: \`direnv deny\`
1451
+ - Edit .envrc: \`direnv edit .\`
1452
+ - Check status: \`direnv status\`
1453
+ - Reload: \`direnv reload\`
1454
+
1455
+ ## Common .envrc patterns
1456
+ - Set vars: \`export API_KEY=xxx\`
1457
+ - Add to PATH: \`PATH_add ./bin\`
1458
+ - Use a specific Node version: \`use node 20\` (with mise/nvm layout)
1459
+ - Load .env file: \`dotenv\`
1460
+
1461
+ ## Gotchas
1462
+ - Requires shell hook \u2014 add \`eval "$(direnv hook zsh)"\` to .zshrc (or bash equivalent).
1463
+ - New .envrc files must be explicitly allowed with \`direnv allow\` (security feature).
1464
+ - Changes to .envrc require re-allowing.
1465
+ `.trim();
1466
+
1467
+ // src/skills/procs.ts
1468
+ var procs_default = `
1469
+ # procs \u2014 Modern ps replacement
1470
+
1471
+ ## When to use
1472
+ List and search running processes with structured, colourful output. Replacement for \`ps aux | grep\`.
1473
+
1474
+ ## Trusted commands
1475
+ - List all processes: \`procs\`
1476
+ - Search by name: \`procs node\`
1477
+ - Tree view: \`procs --tree\`
1478
+ - Watch mode: \`procs --watch\`
1479
+ - Sort by CPU: \`procs --sortd cpu\`
1480
+ - Sort by memory: \`procs --sortd mem\`
1481
+
1482
+ ## Why it matters for agents
1483
+ Agents diagnosing "port in use" or runaway process issues get structured output without pipe chains.
1484
+
1485
+ ## Gotchas
1486
+ - Output is human-readable by default. For scripting, pipe through \`--color=never\` or use standard \`ps\` + jq.
1487
+ - Search matches against command name and full command line.
1488
+ `.trim();
1489
+
1490
+ // src/skills/uv.ts
1491
+ var uv_default = `
1492
+ # uv \u2014 Fast Python package and env manager
1493
+
1494
+ ## When to use
1495
+ Manage Python virtual environments and install packages. 10-100x faster than pip. Drop-in replacement for pip, pip-tools, and virtualenv.
1496
+
1497
+ ## Trusted commands
1498
+ - Create venv: \`uv venv\`
1499
+ - Install package: \`uv pip install requests\`
1500
+ - Install from requirements: \`uv pip install -r requirements.txt\`
1501
+ - Compile requirements (lock): \`uv pip compile requirements.in -o requirements.txt\`
1502
+ - Run a script with deps: \`uv run --with requests script.py\`
1503
+ - Init a new project: \`uv init my-project\`
1504
+ - Add dependency: \`uv add requests\`
1505
+ - Sync project: \`uv sync\`
1506
+
1507
+ ## Why it matters for agents
1508
+ When agents set up Python projects or install dependencies, uv makes it near-instant. No waiting for pip's dependency resolver.
1509
+
1510
+ ## Gotchas
1511
+ - Creates \`.venv/\` by default (same as standard venv).
1512
+ - \`uv pip\` commands are pip-compatible \u2014 same flags and syntax.
1513
+ - \`uv run\` can run scripts with inline dependencies without a project.
1514
+ `.trim();
1515
+
1516
+ // src/skills/hexyl.ts
1517
+ var hexyl_default = `
1518
+ # hexyl \u2014 Hex viewer
1519
+
1520
+ ## When to use
1521
+ Inspect binary files, check file headers/magic bytes, debug encoding issues, or examine corrupted data.
1522
+
1523
+ ## Trusted commands
1524
+ - View file: \`hexyl file.bin\`
1525
+ - First N bytes: \`hexyl -n 256 file.bin\`
1526
+ - Skip to offset: \`hexyl -s 0x100 file.bin\`
1527
+ - Pipe from stdin: \`echo "hello" | hexyl\`
1528
+ - Specific range: \`hexyl -r 0x10..0x20 file.bin\`
1529
+
1530
+ ## Why it matters for agents
1531
+ Agents encountering unknown binary files can quickly identify format (via magic bytes), spot encoding issues, or verify file integrity.
1532
+
1533
+ ## Gotchas
1534
+ - Output is always to stdout \u2014 not interactive/scrollable. Pipe to \`less -R\` for large files.
1535
+ - Colour codes bytes by type: NULL, ASCII printable, ASCII whitespace, non-ASCII.
1536
+ `.trim();
1537
+
1538
+ // src/skills/taplo.ts
1539
+ var taplo_default = `
1540
+ # taplo \u2014 TOML toolkit
1541
+
1542
+ ## When to use
1543
+ Lint, format, and query TOML files. Completes the config format toolkit: jq (JSON) + yq (YAML) + taplo (TOML).
1544
+
1545
+ ## Trusted commands
1546
+ - Format a file: \`taplo fmt Cargo.toml\`
1547
+ - Format all TOML: \`taplo fmt\`
1548
+ - Check formatting: \`taplo fmt --check\`
1549
+ - Lint: \`taplo lint Cargo.toml\`
1550
+ - Get a value: \`taplo get -f pyproject.toml "project.name"\`
1551
+
1552
+ ## Why it matters for agents
1553
+ Agents editing Cargo.toml, pyproject.toml, or any .toml config can validate and format cleanly. Prevents syntax errors in config files.
1554
+
1555
+ ## Gotchas
1556
+ - Config via \`taplo.toml\` or \`.taplo.toml\` at project root.
1557
+ - \`taplo get\` uses dot-notation paths for querying.
1558
+ - Supports schema validation for known TOML formats (Cargo, pyproject, etc.).
1559
+ - \`taplo fmt --check\` exits 1 on formatting diffs \u2014 CI-friendly.
1560
+ - Use \`--colored=off\` for clean CI output.
1561
+ `.trim();
1562
+
1563
+ // src/skills/semgrep.ts
1564
+ var semgrep_default = `
1565
+ # semgrep \u2014 Multi-language static analysis
1566
+
1567
+ ## When to use
1568
+ Scan code for security vulnerabilities, bugs, and anti-patterns across 30+ languages. Write custom rules or use community rulesets.
1569
+
1570
+ ## Trusted commands
1571
+ - Scan with auto rules: \`semgrep scan --config auto\`
1572
+ - Scan specific directory: \`semgrep scan --config auto src/\`
1573
+ - Security-focused scan: \`semgrep scan --config p/security-audit\`
1574
+ - OWASP top 10: \`semgrep scan --config p/owasp-top-ten\`
1575
+ - Specific language: \`semgrep scan --config p/typescript\`
1576
+ - Scan single file: \`semgrep scan --config auto path/to/file.ts\`
1577
+ - JSON output: \`semgrep scan --config auto --json\`
1578
+
1579
+ ## Why it matters for agents
1580
+ Agents can run security and quality scans before committing code. Much broader language coverage than shellcheck or biome alone.
1581
+
1582
+ ## Gotchas
1583
+ - First run downloads rules (needs internet). Subsequent runs use cache.
1584
+ - \`--config auto\` selects rules based on detected languages.
1585
+ - Community rules at semgrep.dev/explore \u2014 use \`--config p/RULESET\` to reference them.
1586
+ - Can write custom rules in YAML for project-specific patterns.
1587
+ `.trim();
1588
+
1589
+ // src/skills/age.ts
1590
+ var age_default = `
1591
+ # age \u2014 Simple file encryption
1592
+
1593
+ ## When to use
1594
+ Encrypt and decrypt files with a passphrase or public key. Modern replacement for GPG \u2014 much simpler API.
1595
+
1596
+ ## Trusted commands
1597
+ - Encrypt with passphrase: \`age -p -o secret.age secret.txt\`
1598
+ - Decrypt: \`age -d -o secret.txt secret.age\`
1599
+ - Generate key pair: \`age-keygen -o key.txt\`
1600
+ - Encrypt to recipient: \`age -r age1... -o secret.age secret.txt\`
1601
+ - Encrypt to multiple recipients: \`age -r age1... -r age1... -o secret.age secret.txt\`
1602
+ - Pipe: \`echo "secret" | age -p > encrypted.age\`
1603
+
1604
+ ## Why it matters for agents
1605
+ When agents handle .env files, API keys, or sensitive configs, age provides simple encrypt/decrypt without GPG complexity.
1606
+
1607
+ ## Gotchas
1608
+ - Passphrase mode (\`-p\`) prompts interactively. For scripting, pipe passphrase or use recipient mode.
1609
+ - Output file must be specified with \`-o\` (doesn't overwrite input).
1610
+ - age-keygen outputs private key to file, prints public key to stderr.
1611
+ `.trim();
1612
+
1613
+ // src/skills/doggo.ts
1614
+ var doggo_default = `
1615
+ # doggo \u2014 Modern DNS client
1616
+
1617
+ ## When to use
1618
+ Look up DNS records for domains. Modern alternative to dig/nslookup with coloured output and JSON support.
1619
+
1620
+ ## Trusted commands
1621
+ - Basic lookup: \`doggo example.com\`
1622
+ - Specific record type: \`doggo example.com MX\`
1623
+ - All common types: \`doggo example.com A AAAA MX TXT CNAME\`
1624
+ - Use specific nameserver: \`doggo example.com @8.8.8.8\`
1625
+ - DNS over HTTPS: \`doggo example.com @https://dns.google/dns-query\`
1626
+ - JSON output: \`doggo example.com --json\`
1627
+ - Short output: \`doggo example.com --short\`
1628
+
1629
+ ## Why it matters for agents
1630
+ Agents verifying domain setups, debugging DNS issues, or checking propagation get structured output they can parse.
1631
+
1632
+ ## Gotchas
1633
+ - Defaults to system resolver. Specify \`@server\` to use a specific nameserver.
1634
+ - Supports DoH (DNS over HTTPS) and DoT (DNS over TLS).
1635
+ - JSON output with \`--json\` is ideal for piping to jq.
1366
1636
  `.trim();
1367
1637
 
1368
1638
  // src/skills.ts
@@ -1404,18 +1674,67 @@ var SKILL_CONTENT = {
1404
1674
  lazygit: lazygit_default,
1405
1675
  dust: dust_default,
1406
1676
  btm: btm_default,
1407
- gitleaks: gitleaks_default
1677
+ gitleaks: gitleaks_default,
1678
+ pandoc: pandoc_default,
1679
+ duckdb: duckdb_default,
1680
+ htmlq: htmlq_default,
1681
+ typos: typos_default,
1682
+ gum: gum_default,
1683
+ direnv: direnv_default,
1684
+ procs: procs_default,
1685
+ uv: uv_default,
1686
+ hexyl: hexyl_default,
1687
+ taplo: taplo_default,
1688
+ semgrep: semgrep_default,
1689
+ age: age_default,
1690
+ doggo: doggo_default
1408
1691
  };
1409
- function skillPath(toolId) {
1410
- return join2(paths.skills, `${PREFIX}-${toolId}.md`);
1692
+ function skillFilename(toolId) {
1693
+ return `${PREFIX}-${toolId}.md`;
1694
+ }
1695
+ function buildFrontmatter(tool) {
1696
+ const lines = [
1697
+ "---",
1698
+ `tool: ${tool.id}`,
1699
+ `name: ${tool.name}`,
1700
+ `description: ${tool.description}`,
1701
+ `category: ${tool.preset}`
1702
+ ];
1703
+ if (tool.tags?.length) {
1704
+ lines.push(`tags: [${tool.tags.join(", ")}]`);
1705
+ }
1706
+ if (tool.seeAlso?.length) {
1707
+ lines.push(`see-also: [${tool.seeAlso.join(", ")}]`);
1708
+ }
1709
+ lines.push("source: agent-loadout", "---", "");
1710
+ return lines.join("\n");
1711
+ }
1712
+ async function findToolsMissingSkills(toolIds, dir = paths.skillTargets.claude) {
1713
+ const results = await Promise.all(
1714
+ toolIds.map(async (id) => {
1715
+ const filePath = join2(dir, skillFilename(id));
1716
+ try {
1717
+ await access(filePath);
1718
+ return null;
1719
+ } catch {
1720
+ return id;
1721
+ }
1722
+ })
1723
+ );
1724
+ return results.filter((id) => id !== null);
1411
1725
  }
1412
1726
  async function writeSkills(tools) {
1413
- await ensureSkillsDir();
1727
+ await ensureSkillDirs();
1728
+ const allDirs = [...Object.values(paths.skillTargets), paths.genericSkills];
1414
1729
  let written = 0;
1415
1730
  for (const tool of tools) {
1416
1731
  const content = SKILL_CONTENT[tool.id];
1417
1732
  if (!content) continue;
1418
- await writeFile3(skillPath(tool.id), content + "\n");
1733
+ const filename = skillFilename(tool.id);
1734
+ const frontmatter = buildFrontmatter(tool);
1735
+ for (const dir of allDirs) {
1736
+ await writeFile3(join2(dir, filename), frontmatter + content + "\n");
1737
+ }
1419
1738
  written++;
1420
1739
  }
1421
1740
  return written;
@@ -1423,24 +1742,26 @@ async function writeSkills(tools) {
1423
1742
 
1424
1743
  // src/index.ts
1425
1744
  var program = new Command();
1426
- program.name("agent-loadout").description("One command to load out your terminal for agentic coding").version("0.1.0");
1745
+ program.name("agent-loadout").description("One command to load out your terminal for agentic coding").version("1.0.0");
1427
1746
  process.on("SIGINT", () => {
1428
- console.log(chalk6.dim("\n Cancelled."));
1747
+ console.log(chalk10.dim("\n Cancelled."));
1429
1748
  process.exit(0);
1430
1749
  });
1431
- program.command("install", { isDefault: true }).description("Install tools (interactive by default)").option("--preset <presets...>", "Install specific presets without prompts").option("--all", "Install everything").option("--apply", "Actually run the install (default is preview only)").action(async (opts) => {
1432
- const hasBrew = await checkBrewInstalled();
1433
- if (!hasBrew) {
1434
- console.log(chalk6.red("Homebrew is required but not installed."));
1435
- console.log(
1436
- chalk6.dim(
1437
- 'Install it: https://brew.sh \u2192 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
1438
- )
1439
- );
1440
- process.exit(1);
1441
- }
1750
+ program.command("install", { isDefault: true }).description("Install tools (interactive by default)").option("--preset <presets...>", "Install specific presets without prompts").option("--tool <tools...>", "Install specific tools by ID").option("--skip <tools...>", "Exclude specific tools by ID").option("--all", "Install everything").option("--apply", "Actually run the install (default is preview only)").action(async (opts) => {
1751
+ const platformInfo = await detectPlatform();
1442
1752
  let tools;
1443
- if (opts.all) {
1753
+ if (opts.tool) {
1754
+ const { valid, invalid } = validateToolIds(opts.tool);
1755
+ if (invalid.length > 0) {
1756
+ console.log(
1757
+ chalk10.red(
1758
+ `Unknown tool${invalid.length > 1 ? "s" : ""}: ${invalid.map((t) => `'${t}'`).join(", ")}. Run ${chalk10.dim("agent-loadout list --json")} to see all tool IDs.`
1759
+ )
1760
+ );
1761
+ process.exit(1);
1762
+ }
1763
+ tools = getToolsByIds(valid);
1764
+ } else if (opts.all) {
1444
1765
  tools = TOOLS;
1445
1766
  } else if (opts.preset) {
1446
1767
  const rawIds = opts.preset;
@@ -1448,7 +1769,7 @@ program.command("install", { isDefault: true }).description("Install tools (inte
1448
1769
  const invalid = rawIds.filter((p) => !validIds.includes(p));
1449
1770
  if (invalid.length > 0) {
1450
1771
  console.log(
1451
- chalk6.red(
1772
+ chalk10.red(
1452
1773
  `Unknown preset${invalid.length > 1 ? "s" : ""}: ${invalid.map((p) => `'${p}'`).join(", ")}. Available: ${validIds.join(", ")}`
1453
1774
  )
1454
1775
  );
@@ -1459,79 +1780,76 @@ program.command("install", { isDefault: true }).description("Install tools (inte
1459
1780
  } else {
1460
1781
  const presetIds = await selectPresets();
1461
1782
  if (presetIds.length === 0) {
1462
- console.log(chalk6.dim("No presets selected. Nothing to install."));
1783
+ console.log(chalk10.dim("No presets selected. Nothing to install."));
1463
1784
  return;
1464
1785
  }
1465
1786
  tools = await selectTools(presetIds);
1466
1787
  if (tools.length === 0) {
1467
- console.log(chalk6.dim("No tools selected. Nothing to install."));
1788
+ console.log(chalk10.dim("No tools selected. Nothing to install."));
1468
1789
  return;
1469
1790
  }
1470
1791
  }
1471
- printPreview(tools);
1472
- if (!opts.apply && (opts.all || opts.preset)) {
1473
- console.log(
1474
- chalk6.yellow("Dry run \u2014 add --apply to install. Example:")
1475
- );
1792
+ if (opts.skip) {
1793
+ const skipSet = new Set(opts.skip);
1794
+ tools = tools.filter((t) => !skipSet.has(t.id));
1795
+ if (tools.length === 0) {
1796
+ console.log(chalk10.dim("All tools were skipped. Nothing to install."));
1797
+ return;
1798
+ }
1799
+ }
1800
+ const plan = resolveInstallPlan(tools, platformInfo);
1801
+ printPreview(tools, plan, platformInfo);
1802
+ if (!opts.apply && (opts.all || opts.preset || opts.tool)) {
1803
+ const parts = [];
1804
+ if (opts.tool) parts.push(`--tool ${opts.tool.join(" ")}`);
1805
+ else if (opts.all) parts.push("--all");
1806
+ else parts.push(`--preset ${opts.preset.join(" ")}`);
1807
+ if (opts.skip) parts.push(`--skip ${opts.skip.join(" ")}`);
1808
+ console.log(chalk10.yellow("Dry run \u2014 add --apply to install. Example:"));
1476
1809
  console.log(
1477
- chalk6.dim(
1478
- ` npx agent-loadout install ${opts.all ? "--all" : `--preset ${opts.preset.join(" ")}`} --apply`
1479
- )
1810
+ chalk10.dim(` npx agent-loadout install ${parts.join(" ")} --apply`)
1480
1811
  );
1481
1812
  return;
1482
1813
  }
1483
- if (!opts.all && !opts.preset) {
1814
+ if (plan.resolved.length === 0) {
1815
+ console.log(chalk10.dim("No installable tools for this platform."));
1816
+ return;
1817
+ }
1818
+ if (!opts.all && !opts.preset && !opts.tool) {
1484
1819
  const proceed = await confirmInstall();
1485
1820
  if (!proceed) {
1486
- console.log(chalk6.dim("Cancelled."));
1821
+ console.log(chalk10.dim("Cancelled."));
1487
1822
  return;
1488
1823
  }
1489
1824
  }
1490
- const npmPackages = getNpmInstallCommand(tools);
1491
- if (npmPackages.length > 0) {
1492
- const hasNpm = await checkNpmInstalled();
1493
- if (!hasNpm) {
1494
- console.log(
1495
- chalk6.yellow(
1496
- `npm is required for: ${npmPackages.join(", ")}. Skipping npm packages (install Node.js to include them).`
1497
- )
1498
- );
1499
- }
1500
- }
1501
- console.log();
1502
- const brewfile = generateBrewfile(tools);
1503
- if (brewfile) {
1504
- console.log(chalk6.bold("Installing brew packages..."));
1505
- await writeBrewfile(brewfile);
1506
- await runBrewBundle();
1507
- }
1508
- if (npmPackages.length > 0 && await checkNpmInstalled()) {
1509
- console.log(chalk6.bold("Installing npm globals..."));
1510
- await runNpmInstall(npmPackages);
1511
- }
1825
+ await runInstallPlan(plan);
1512
1826
  console.log();
1513
- console.log(chalk6.bold("Verifying..."));
1514
- const results = await verifyTools(tools);
1827
+ console.log(chalk10.bold("Verifying..."));
1828
+ const resolvedTools = plan.resolved.map((r) => r.tool);
1829
+ const results = await verifyTools(resolvedTools, platformInfo.platform);
1515
1830
  printVerifyResults(results);
1516
- const skillCount = await writeSkills(tools);
1831
+ const skillCount = await writeSkills(resolvedTools);
1517
1832
  if (skillCount > 0) {
1833
+ const targets = Object.keys(paths.skillTargets).join(", ");
1518
1834
  console.log(
1519
- chalk6.dim(`
1520
- ${skillCount} skill files written to ~/.claude/skills/`)
1835
+ chalk10.dim(`
1836
+ ${skillCount} skill files written to ${targets} + ~/.agent-loadout/skills/`)
1521
1837
  );
1522
1838
  }
1523
1839
  await writeReceipt({
1840
+ platform: platformInfo.platform,
1524
1841
  selections: tools.map((t) => t.id),
1525
1842
  installed: verifyResultsToJson(results),
1526
1843
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1527
1844
  });
1528
- console.log(chalk6.dim(" Receipt saved to ~/.agent-loadout/receipt.json"));
1845
+ console.log(chalk10.dim(" Receipt saved to ~/.agent-loadout/receipt.json"));
1529
1846
  });
1530
1847
  program.command("verify").alias("doctor").description("Check which tools are installed").option("--json", "Output as JSON").action(async (opts) => {
1848
+ const platformInfo = await detectPlatform();
1531
1849
  const receipt = await readReceipt();
1532
1850
  const toolIds = receipt?.selections ?? TOOLS.map((t) => t.id);
1533
1851
  const tools = getToolsByIds(toolIds);
1534
- const results = await verifyTools(tools);
1852
+ const results = await verifyTools(tools, platformInfo.platform);
1535
1853
  const installed = results.filter((r) => r.installed).length;
1536
1854
  const allInstalled = installed === results.length;
1537
1855
  if (opts.json) {
@@ -1539,6 +1857,7 @@ program.command("verify").alias("doctor").description("Check which tools are ins
1539
1857
  JSON.stringify(
1540
1858
  {
1541
1859
  ok: allInstalled,
1860
+ platform: platformInfo.platform,
1542
1861
  installed,
1543
1862
  total: results.length,
1544
1863
  tools: results
@@ -1552,26 +1871,67 @@ program.command("verify").alias("doctor").description("Check which tools are ins
1552
1871
  }
1553
1872
  process.exit(allInstalled ? 0 : 1);
1554
1873
  });
1555
- program.command("list").description("Print the tool catalog").option("--json", "Output as JSON").action((opts) => {
1874
+ program.command("list").description("Print the tool catalog").option("--json", "Output as JSON").option("--brewfile", "Output macOS Brewfile (darwin only)").action(async (opts) => {
1875
+ if (opts.brewfile) {
1876
+ const { generateBrewfileFromCatalog } = await import("./catalog-PTLCQEDW.js");
1877
+ console.log(generateBrewfileFromCatalog());
1878
+ return;
1879
+ }
1556
1880
  if (opts.json) {
1557
1881
  console.log(JSON.stringify({ presets: PRESETS, tools: TOOLS }, null, 2));
1558
1882
  return;
1559
1883
  }
1884
+ const platformInfo = await detectPlatform();
1560
1885
  for (const preset of PRESETS) {
1561
- const marker = preset.defaultOn ? chalk6.green("\u25CF") : chalk6.dim("\u25CB");
1886
+ const marker = preset.defaultOn ? chalk10.green("\u25CF") : chalk10.dim("\u25CB");
1562
1887
  console.log(
1563
1888
  `
1564
- ${marker} ${chalk6.bold(preset.name)} \u2014 ${preset.description}`
1889
+ ${marker} ${chalk10.bold(preset.name)} \u2014 ${preset.description}`
1565
1890
  );
1566
1891
  const presetTools = TOOLS.filter((t) => t.preset === preset.id);
1567
1892
  const maxName = Math.max(...presetTools.map((t) => t.name.length));
1568
1893
  for (const t of presetTools) {
1569
- const method = t.installMethod === "npm" ? chalk6.cyan("npm") : chalk6.yellow("brew");
1894
+ const platformInstalls = t.install[platformInfo.platform];
1895
+ const method = platformInstalls ? chalk10.yellow(platformInstalls[0].method) : chalk10.dim("n/a");
1570
1896
  console.log(
1571
- ` ${t.name.padEnd(maxName)} ${method} ${chalk6.dim(t.description)}`
1897
+ ` ${t.name.padEnd(maxName)} ${method} ${chalk10.dim(t.description)}`
1572
1898
  );
1573
1899
  }
1574
1900
  }
1575
1901
  console.log();
1576
1902
  });
1903
+ program.command("skills").description("Write skill files for installed tools (fills gaps by default)").option("--force", "Rewrite skill files for all installed tools").action(async (opts) => {
1904
+ const platformInfo = await detectPlatform();
1905
+ const results = await verifyTools(TOOLS, platformInfo.platform);
1906
+ const installedIds = new Set(results.filter((r) => r.installed).map((r) => r.id));
1907
+ const installedTools = TOOLS.filter((t) => installedIds.has(t.id));
1908
+ if (installedTools.length === 0) {
1909
+ console.log(chalk10.dim(" No installed tools found."));
1910
+ return;
1911
+ }
1912
+ if (opts.force) {
1913
+ console.log(chalk10.dim(` Writing skills for ${installedTools.length} installed tools...`));
1914
+ const written2 = await writeSkills(installedTools);
1915
+ const targets2 = Object.keys(paths.skillTargets).join(", ");
1916
+ console.log(
1917
+ chalk10.green(` ${written2} skill files written to ${targets2} + ~/.agent-loadout/skills/`)
1918
+ );
1919
+ return;
1920
+ }
1921
+ console.log(chalk10.dim(` Scanning ${installedTools.length} installed tools...`));
1922
+ const missingIds = await findToolsMissingSkills(installedTools.map((t) => t.id));
1923
+ if (missingIds.length === 0) {
1924
+ console.log(chalk10.green(` \u2713 All ${installedTools.length} installed tools already have skill files.`));
1925
+ return;
1926
+ }
1927
+ const alreadyHave = installedTools.length - missingIds.length;
1928
+ console.log(chalk10.dim(` ${alreadyHave} already have skill files`));
1929
+ console.log(chalk10.dim(` Writing ${missingIds.length} missing: ${missingIds.join(", ")}`));
1930
+ const toolsToWrite = installedTools.filter((t) => missingIds.includes(t.id));
1931
+ const written = await writeSkills(toolsToWrite);
1932
+ const targets = Object.keys(paths.skillTargets).join(", ");
1933
+ console.log(
1934
+ chalk10.green(` ${written} skill files written to ${targets} + ~/.agent-loadout/skills/`)
1935
+ );
1936
+ });
1577
1937
  program.parse();