kenmark-skills 1.1.1 → 1.1.5

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/README.md CHANGED
@@ -40,11 +40,12 @@ For installing, updating, and cleaning up skills on your machine (pairs with **`
40
40
 
41
41
  | Skill | Purpose |
42
42
  | --- | --- |
43
- | [`skills-install-recommended`](skills/user-skills/skills-install-recommended/SKILL.md) | Install curated third-party packs (Impeccable, ECC, ) with scope and pack selection |
43
+ | [`skills-install-recommended`](skills/user-skills/skills-install-recommended/SKILL.md) | Install curated third-party packs (Impeccable, ECC, Graphify, code review, SEO/GEO) with scope and pack selection |
44
44
  | [`skills-update`](skills/user-skills/skills-update/SKILL.md) | Refresh Kenmark skills and optionally reinstall recommended packs |
45
45
  | [`skills-maintain`](skills/user-skills/skills-maintain/SKILL.md) | Inventory installed skills, group duplicates, recommend keep vs remove (no auto-delete) |
46
+ | [`subagents-maintain`](skills/user-skills/subagents-maintain/SKILL.md) | Inventory installed sub-agents across Claude, Cursor, Codex, Gemini, OpenCode, MiniMax; recommend keep vs remove (no auto-delete) |
46
47
 
47
- **Typical path:** `skills-init` or `npx kenmark-skills setup` → **`skills-install-recommended`** for curated packs → day-to-day **`skills-router`** → periodic **`skills-update`** / **`skills-maintain`**.
48
+ **Typical path:** `skills-init` or `npx kenmark-skills setup` → **`skills-install-recommended`** for curated packs → day-to-day **`skills-router`** → periodic **`skills-update`** / **`skills-maintain`** / **`subagents-maintain`**.
48
49
 
49
50
  These skills assume a **`brain/`** knowledge base (standards, changelog, optional issue tracker). On a new repo: **`init-brain`** → **`issues-setup`** if you track work in `brain/issues/` → **`commit-push`** when shipping.
50
51
 
@@ -63,10 +64,11 @@ All `kenmark-skills` commands **prompt in the terminal by default** (TTY). For a
63
64
  | --- | --- | --- |
64
65
  | `init` | `npx kenmark-skills init` | `npx kenmark-skills init --global --ide all -y` |
65
66
  | `setup` | `npx kenmark-skills setup` | `npx kenmark-skills setup --global --ide cursor -y` |
66
- | `install-recommended` | `npx kenmark-skills install-recommended` | `npx kenmark-skills install-recommended --ids impeccable,ecc --global -y` |
67
+ | `install-recommended` | `npx kenmark-skills install-recommended` | `npx kenmark-skills install-recommended --ids impeccable --global -y` |
67
68
  | `update` | `npx kenmark-skills update` | `npx kenmark-skills update --both --global -y` |
68
69
  | `adopt` | `npx kenmark-skills adopt` | `npx kenmark-skills adopt --global --ide all -y` |
69
70
  | `inventory` | `npx kenmark-skills inventory` | `npx kenmark-skills inventory --markdown ./report.md -y` |
71
+ | `subagents-inventory` | `npx kenmark-skills subagents-inventory` | `npx kenmark-skills subagents-inventory --markdown ./agents.md --include-marketplaces -y` |
70
72
 
71
73
  ### Option A — Global npm install (recommended)
72
74
 
@@ -79,12 +81,12 @@ npx kenmark-skills init
79
81
 
80
82
  ### Kenmark skill hub (`~/.kenmark`)
81
83
 
82
- Kenmark first-party skills and **curated catalog** skills (Impeccable, ECC skill folders) are stored once under:
84
+ Kenmark first-party skills and **curated catalog** skills (Impeccable, ECC, Graphify, code review, SEO/GEO, and more) are stored once under:
83
85
 
84
86
  - **Store:** `~/.kenmark/store/skills/<name>/`
85
87
  - **Manifest:** `~/.kenmark/manifest.json`
86
88
 
87
- `setup` copies bundled skills into the store, then **symlinks** (or copies on Windows when symlinks are unavailable) into each IDE path. **`update`** refreshes the store and runs **`adopt`** by default to consolidate catalog skills into the store and relink IDEs. **Adopt** includes Kenmark bundled skills, Impeccable, and ECC skills listed in the ECC install profile (`--ecc-profile`, default `core`) not arbitrary third-party trees such as gstack mirrors.
89
+ `setup` copies bundled skills into the store, then **symlinks** (or copies on Windows when symlinks are unavailable) into each IDE path. **`update`** refreshes the store and runs **`adopt`** by default to consolidate catalog skills into the store and relink IDEs. **Adopt** includes Kenmark bundled skills and adoptable catalog packs (Impeccable, ECC, and more) when present on disk.
88
90
 
89
91
  | Flag | Purpose |
90
92
  | --- | --- |
@@ -157,18 +159,30 @@ npx kenmark-skills inventory --markdown ~/Desktop/skills-report.md --json ~/Desk
157
159
  npx kenmark-skills inventory --include-plugins
158
160
  ```
159
161
 
160
- Install curated packs from [`recommended-catalog.json`](skills/user-skills/recommended-catalog.json) via **npx** (no git clone). Default is **global** install; use **project** for the current repo only.
162
+ ### Sub-agents inventory
163
+
164
+ Scan installed sub-agents (Claude, Cursor, Codex, Gemini, OpenCode, MiniMax) and get a keep / dedupe / remove report. Same output layout as the skills inventory, but walks `agents/` directories and reads YAML frontmatter (`name`, `description`, `tools`, `model`).
165
+
166
+ ```bash
167
+ npx kenmark-skills subagents-inventory
168
+ npx kenmark-skills subagents-inventory --markdown ~/Desktop/subagents-report.md --json ~/Desktop/subagents-inventory.json
169
+ npx kenmark-skills subagents-inventory --include-marketplaces
170
+ ```
171
+
172
+ For guided chat cleanup (no auto-delete), use the **`subagents-maintain`** skill — it pairs with **`skills-maintain`** for the full library audit.
173
+
174
+ Install curated packs from [`recommended-catalog.json`](skills/user-skills/recommended-catalog.json) — Impeccable, ECC, Graphify, code review, SEO/GEO, and more. Install methods vary by pack: npx, git clone, uv/pipx, or `npx ecc-install` with a profile. Default is **global** install; use **project** for the current repo only.
161
175
 
162
176
  ```bash
163
177
  npx kenmark-skills install-recommended --list
164
178
 
165
- # Interactive: scope → packs → ECC profile
179
+ # Interactive: scope → packs
166
180
  npx kenmark-skills install-recommended
167
181
 
168
182
  # Non-interactive
169
183
  npx kenmark-skills install-recommended --all --global -y
170
184
  npx kenmark-skills install-recommended --all --project -y
171
- npx kenmark-skills install-recommended --ids impeccable,ecc --global --ecc-profile core -y
185
+ npx kenmark-skills install-recommended --ids impeccable --global -y
172
186
  npx kenmark-skills install-recommended --dry-run --all --project
173
187
  ```
174
188
 
@@ -184,7 +198,7 @@ npx kenmark-skills update
184
198
  # Non-interactive
185
199
  npx kenmark-skills update --both --global -y
186
200
  npx kenmark-skills update --kenmark-only --global --ide all -y
187
- npx kenmark-skills update --recommended-only --global --ids impeccable,ecc -y
201
+ npx kenmark-skills update --recommended-only --global --ids impeccable -y
188
202
  npx kenmark-skills update --npm-only -y
189
203
  ```
190
204
 
@@ -297,6 +311,7 @@ kenmark-skills/
297
311
  ├── issues-*/
298
312
  ├── skills-maintain/
299
313
  ├── skills-install-recommended/
314
+ ├── subagents-maintain/
300
315
  └── recommended-catalog.json
301
316
  ```
302
317
 
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v1.1.4 — Multi-pack recommended catalog
4
+
5
+ - **recommended-catalog.json (v3):** Catalog expanded from 1 to 5 packs: `impeccable` (default), `ecc` (minimal/core/full profiles, default `minimal`), `graphify` (Python via uv/pipx), `code-review-skill` (git clone), `seo-geo-claude-skills` (npx). Dropped `defaults.eccProfile`; the profile now lives on the `ecc` pack itself.
6
+ - **scripts/skills-install-recommended.js:** Reused existing `id === "ecc"` special-casing to wire up the ECC profile prompt and `{{profile}}` placeholder substitution. CLI now accepts `--ecc-profile minimal|core|full` (e.g. `npx kenmark-skills install-recommended --ids ecc --ecc-profile minimal --global -y`).
7
+ - **skills-install-recommended, skills-init, skills-update, README:** Docs and examples updated to reflect the multi-pack catalog. The "ECC, gstack not in this catalog" note is gone; the Impeccable-only pack table now lists all 5 packs with their install methods.
8
+ - **Removed:** `compound-engineering` pack entry (replaced by `ecc` as the preferred harness pack).
9
+
10
+ ## v1.1.3 — Impeccable-only recommended catalog
11
+
12
+ - **recommended-catalog.json (v3):** Removed `ecc` and `gstack` packs; only **Impeccable** remains (`defaultSelected: true`). Dropped `defaults.eccProfile`.
13
+ - **skills-install-recommended, skills-init, skills-update, README:** Docs and examples updated for Impeccable-only curated installs. ECC/gstack can still be installed separately outside the catalog.
14
+
15
+ ## v1.1.2 — gstack in recommended catalog
16
+
17
+ - **recommended-catalog.json:** Added [gstack](https://github.com/garrytan/gstack) pack (`id: gstack`) with global `git clone` + `./setup` and project team-mode via `gstack-team-init optional`; `defaultSelected: false`.
18
+ - **skills-install-recommended:** Documented gstack install commands, requirements (Git, Bun), and overlap note with ECC.
19
+
3
20
  ## v1.1.1 — ECC adopt scope fix
4
21
 
5
22
  - **`kenmark-hub.js`:** ECC adopt names come from ECC `install-profiles.json` / `install-modules.json` (profile from catalog, default `core`), not every skill under `~/.claude/skills`. Optional pack-level `adoptSkillNames` override in `recommended-catalog.json`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kenmark-skills",
3
- "version": "1.1.1",
3
+ "version": "1.1.5",
4
4
  "description": "Skills by Kenmark ITan Solutions — Cursor/Codex agent skills from our development workflows. Created by Tanooj Mehra & Adwait Date.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -19,6 +19,7 @@
19
19
  "scripts/skills-inventory.js",
20
20
  "scripts/skills-install-recommended.js",
21
21
  "scripts/skills-update.js",
22
+ "scripts/subagents-inventory.js",
22
23
  "skills/user-skills/recommended-catalog.json"
23
24
  ],
24
25
  "scripts": {
@@ -26,6 +27,7 @@
26
27
  "setup": "node scripts/cli.js setup",
27
28
  "uninstall:skills": "node scripts/cli.js uninstall",
28
29
  "inventory": "node scripts/cli.js inventory",
30
+ "subagents-inventory": "node scripts/cli.js subagents-inventory",
29
31
  "install-recommended": "node scripts/cli.js install-recommended",
30
32
  "update": "node scripts/cli.js update",
31
33
  "setup:skills": "node scripts/setup-skills.js",
package/scripts/cli.js CHANGED
@@ -16,6 +16,7 @@ function printUsage() {
16
16
  console.log(" kenmark-skills setup [--global|--project] [--ide <target>] [-y]");
17
17
  console.log(" kenmark-skills uninstall [--global|--project] [--ide <target>] [-y]");
18
18
  console.log(" kenmark-skills inventory [--json path] [--markdown path] [--include-plugins]");
19
+ console.log(" kenmark-skills subagents-inventory [--json path] [--markdown path] [--include-plugins] [--include-marketplaces]");
19
20
  console.log(" kenmark-skills install-recommended [--list] [--all] [--ids a,b] [--global|--project] [-y]");
20
21
  console.log(" kenmark-skills update [--kenmark-only|--recommended-only|--both] [--global|--project] [-y]");
21
22
  console.log(" kenmark-skills adopt [--global|--project] [--ide <target>] [--dry-run] [-y]");
@@ -67,6 +68,14 @@ if (command === "inventory") {
67
68
  process.exit(result.status === null ? 1 : result.status);
68
69
  }
69
70
 
71
+ if (command === "subagents-inventory" || command === "agents-inventory") {
72
+ const scriptPath = path.join(__dirname, "subagents-inventory.js");
73
+ const result = spawnSync(process.execPath, [scriptPath, ...args.slice(1)], {
74
+ stdio: "inherit"
75
+ });
76
+ process.exit(result.status === null ? 1 : result.status);
77
+ }
78
+
70
79
  if (command === "install-recommended") {
71
80
  const scriptPath = path.join(__dirname, "skills-install-recommended.js");
72
81
  const result = spawnSync(process.execPath, [scriptPath, ...args.slice(1)], {
@@ -140,6 +140,70 @@ async function confirmPlan(lines, dryRun = false) {
140
140
  return false;
141
141
  }
142
142
 
143
+ /**
144
+ * @param {Array<{id: string, name: string, defaultSelected?: boolean}>} packs
145
+ * @returns {Promise<string[]>} selected pack ids
146
+ */
147
+ async function promptSelectPacks(packs) {
148
+ const rl = createRl();
149
+ console.log("\nSelect packs to install:\n");
150
+ packs.forEach((p, i) => {
151
+ const mark = p.defaultSelected ? " [default]" : "";
152
+ console.log(` ${i + 1}) ${p.id}${mark} — ${p.name}`);
153
+ });
154
+ console.log("\nEnter: number(s) 1,2 · ids impeccable,ecc · all · defaults · Enter for defaults\n");
155
+ const answer = await ask(rl, "Choice> ");
156
+ rl.close();
157
+ if (!answer) {
158
+ return packs.filter((p) => p.defaultSelected).map((p) => p.id);
159
+ }
160
+ const lower = answer.toLowerCase();
161
+ if (lower === "all") return packs.map((p) => p.id);
162
+ if (lower === "defaults" || lower === "default" || lower === "d") {
163
+ return packs.filter((p) => p.defaultSelected).map((p) => p.id);
164
+ }
165
+ const nums = lower.split(/[\s,]+/).filter(Boolean);
166
+ const byNum = [];
167
+ for (const part of nums) {
168
+ const n = parseInt(part, 10);
169
+ if (!Number.isNaN(n) && n >= 1 && n <= packs.length) {
170
+ byNum.push(packs[n - 1].id);
171
+ }
172
+ }
173
+ if (byNum.length > 0) return [...new Set(byNum)];
174
+ return answer.split(",").map((s) => s.trim()).filter(Boolean);
175
+ }
176
+
177
+ /**
178
+ * @param {{install?: {profiles?: Array<{id: string, description: string}>, defaultProfile?: string}}} pack
179
+ * @param {string|null} preset
180
+ * @returns {Promise<string>}
181
+ */
182
+ async function promptEccProfile(pack, preset) {
183
+ if (preset) return preset;
184
+ const profiles = pack.install?.profiles;
185
+ if (!profiles?.length) {
186
+ return pack.install?.defaultProfile || "core";
187
+ }
188
+ const rl = createRl();
189
+ console.log("\nECC install profile:");
190
+ profiles.forEach((p, i) => {
191
+ console.log(` ${i + 1}) ${p.id} — ${p.description}`);
192
+ });
193
+ const defaultId = pack.install.defaultProfile || "core";
194
+ const answer = await ask(rl, `Profile [1-${profiles.length} or id] (default ${defaultId}): `);
195
+ rl.close();
196
+ if (!answer) return defaultId;
197
+ const num = parseInt(answer, 10);
198
+ if (!Number.isNaN(num) && num >= 1 && num <= profiles.length) {
199
+ return profiles[num - 1].id;
200
+ }
201
+ const byId = profiles.find((p) => p.id === answer);
202
+ if (byId) return byId.id;
203
+ console.error(`Unknown profile "${answer}", using ${defaultId}.`);
204
+ return defaultId;
205
+ }
206
+
143
207
  function banner(title, subtitle) {
144
208
  console.log(`\n${"═".repeat(60)}`);
145
209
  console.log(` ${title}`);
@@ -156,6 +220,8 @@ module.exports = {
156
220
  promptScope,
157
221
  promptAction,
158
222
  promptIde,
223
+ promptSelectPacks,
224
+ promptEccProfile,
159
225
  confirmPlan,
160
226
  banner
161
227
  };
@@ -18,7 +18,23 @@ const VENDORED_PREFIXES = [
18
18
  "plugins/cache/"
19
19
  ];
20
20
 
21
+ const AGENT_VENDORED_PREFIXES = [
22
+ "gstack/",
23
+ ".cursor/agents/gstack",
24
+ ".factory/agents/gstack",
25
+ ".agents/agents/gstack",
26
+ ".gbrain/agents/gstack",
27
+ ".hermes/agents/gstack",
28
+ ".kiro/agents/gstack",
29
+ ".openclaw/agents/gstack",
30
+ ".opencode/agents/gstack",
31
+ ".slate/agents/gstack",
32
+ "plugins/cache/",
33
+ "plugins/marketplaces/"
34
+ ];
35
+
21
36
  const IDE_SCAN_PRIORITY = ["agents", "cursor", "claude", "gemini", "codex", "opencode", "minimax"];
37
+ const AGENT_IDE_SCAN_PRIORITY = ["claude", "cursor", "agents", "gemini", "codex", "opencode", "minimax"];
22
38
 
23
39
  function getKenmarkHome() {
24
40
  return path.join(os.homedir(), ".kenmark");
@@ -28,10 +44,18 @@ function getStoreDir() {
28
44
  return path.join(getKenmarkHome(), "store", "skills");
29
45
  }
30
46
 
47
+ function getAgentStoreDir() {
48
+ return path.join(getKenmarkHome(), "store", "agents");
49
+ }
50
+
31
51
  function getManifestPath() {
32
52
  return path.join(getKenmarkHome(), "manifest.json");
33
53
  }
34
54
 
55
+ function getAgentManifestPath() {
56
+ return path.join(getKenmarkHome(), "agent-manifest.json");
57
+ }
58
+
35
59
  function buildGlobalTargets(homeDir = os.homedir()) {
36
60
  return {
37
61
  cursor: path.join(homeDir, ".cursor", "skills"),
@@ -77,11 +101,29 @@ function buildInventoryRoots(homeDir = os.homedir()) {
77
101
  ];
78
102
  }
79
103
 
104
+ function buildAgentInventoryRoots(homeDir = os.homedir()) {
105
+ return [
106
+ { id: "kenmark-store", path: path.join(homeDir, ".kenmark", "store", "agents") },
107
+ { id: "claude", path: path.join(homeDir, ".claude", "agents") },
108
+ { id: "cursor", path: path.join(homeDir, ".cursor", "agents") },
109
+ { id: "agents", path: path.join(homeDir, ".agents", "agents") },
110
+ { id: "gemini", path: path.join(homeDir, ".gemini", "agents") },
111
+ { id: "codex", path: path.join(homeDir, ".codex", "agents") },
112
+ { id: "opencode", path: path.join(homeDir, ".opencode", "agents") },
113
+ { id: "minimax", path: path.join(homeDir, ".minimax", "agents") }
114
+ ];
115
+ }
116
+
80
117
  function isVendoredMirror(relativePath) {
81
118
  const norm = String(relativePath || "").replace(/\\/g, "/");
82
119
  return VENDORED_PREFIXES.some((prefix) => norm.includes(prefix));
83
120
  }
84
121
 
122
+ function isVendoredAgent(relativePath) {
123
+ const norm = String(relativePath || "").replace(/\\/g, "/");
124
+ return AGENT_VENDORED_PREFIXES.some((prefix) => norm.includes(prefix));
125
+ }
126
+
85
127
  function safeRealpath(p) {
86
128
  try {
87
129
  return fs.realpathSync(p);
@@ -588,13 +630,19 @@ function uninstallKenmarkFromIdes(skillNames, targetMap, { keepStore = true, dry
588
630
 
589
631
  module.exports = {
590
632
  VENDORED_PREFIXES,
633
+ AGENT_VENDORED_PREFIXES,
634
+ AGENT_IDE_SCAN_PRIORITY,
591
635
  getKenmarkHome,
592
636
  getStoreDir,
637
+ getAgentStoreDir,
593
638
  getManifestPath,
639
+ getAgentManifestPath,
594
640
  buildGlobalTargets,
595
641
  buildProjectTargets,
596
642
  buildInventoryRoots,
643
+ buildAgentInventoryRoots,
597
644
  isVendoredMirror,
645
+ isVendoredAgent,
598
646
  safeRealpath,
599
647
  readManifest,
600
648
  writeManifest,
@@ -7,9 +7,12 @@ const {
7
7
  promptScope,
8
8
  promptIde,
9
9
  promptYesNo,
10
+ promptSelectPacks,
11
+ promptEccProfile,
10
12
  confirmPlan,
11
13
  banner
12
14
  } = require("./interactive");
15
+ const { loadCatalog } = require("./recommended-catalog");
13
16
 
14
17
  const repoRoot = path.resolve(__dirname, "..");
15
18
  const setupScript = path.join(__dirname, "setup-skills.js");
@@ -135,6 +138,8 @@ async function run() {
135
138
  let ideArg = args.ide;
136
139
  let installKenmark = !args.recommendedOnly;
137
140
  let installRecommended = !args.skipRecommended;
141
+ let selectedPacks = [];
142
+ let eccProfile = null;
138
143
 
139
144
  if (interactive) {
140
145
  if (!args.recommendedOnly && !args.skipRecommended) {
@@ -148,6 +153,25 @@ async function run() {
148
153
  installRecommended = await promptYesNo("Install only curated recommended packs?", true);
149
154
  }
150
155
  }
156
+ if (installRecommended) {
157
+ const catalog = loadCatalog();
158
+ const packs = catalog.packs || [];
159
+ if (packs.length === 0) {
160
+ console.log("No curated packs available; skipping recommended step.");
161
+ installRecommended = false;
162
+ } else {
163
+ selectedPacks = await promptSelectPacks(packs);
164
+ if (selectedPacks.length === 0) {
165
+ console.log("No packs chosen; skipping recommended step.");
166
+ installRecommended = false;
167
+ } else {
168
+ const eccPack = packs.find((p) => p.id === "ecc");
169
+ if (eccPack && selectedPacks.includes("ecc")) {
170
+ eccProfile = await promptEccProfile(eccPack, null);
171
+ }
172
+ }
173
+ }
174
+ }
151
175
  if (installKenmark || installRecommended) {
152
176
  if (!scope) scope = await promptScope("global");
153
177
  }
@@ -170,7 +194,9 @@ async function run() {
170
194
  plan.push(`Kenmark skills → ${scope} (${ideLabel})`);
171
195
  }
172
196
  if (installRecommended) {
173
- plan.push(`Recommended packs (defaults) ${scope} via npx`);
197
+ const label = selectedPacks.length > 0 ? selectedPacks.join(", ") : "defaults";
198
+ plan.push(`Recommended packs → ${scope}: ${label}`);
199
+ if (eccProfile) plan.push(` ECC profile: ${eccProfile}`);
174
200
  }
175
201
  plan.push("Tip: run init-brain in your agent chat to bootstrap brain/ in a repo");
176
202
 
@@ -212,8 +238,15 @@ async function run() {
212
238
  }
213
239
 
214
240
  if (installRecommended) {
215
- const recArgs = [scope === "project" ? "--project" : "--global", "-y"];
241
+ const recArgs = [scope === "project" ? "--project" : "--global"];
242
+ if (selectedPacks.length > 0) {
243
+ recArgs.push("--ids", selectedPacks.join(","));
244
+ } else {
245
+ recArgs.push("--all");
246
+ }
247
+ if (eccProfile) recArgs.push("--ecc-profile", eccProfile);
216
248
  if (args.dryRun) recArgs.push("--dry-run");
249
+ recArgs.push("-y");
217
250
  const result = runNode(recommendedScript, recArgs, args.dryRun, "Recommended packs");
218
251
  if (!args.dryRun && result.status !== 0) process.exit(result.status || 1);
219
252
  }
@@ -1,23 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
5
3
  const { spawnSync } = require("child_process");
6
4
  const {
7
5
  wantsInteractive,
8
6
  promptScope,
7
+ promptSelectPacks,
8
+ promptEccProfile,
9
9
  confirmPlan,
10
10
  banner
11
11
  } = require("./interactive");
12
- const readline = require("readline");
13
-
14
- const repoRoot = path.resolve(__dirname, "..");
15
- const catalogPath = path.join(
16
- repoRoot,
17
- "skills",
18
- "user-skills",
19
- "recommended-catalog.json"
20
- );
12
+ const { loadCatalog } = require("./recommended-catalog");
21
13
 
22
14
  function printUsage() {
23
15
  console.log("Usage: node scripts/skills-install-recommended.js [options]");
@@ -87,14 +79,6 @@ function parseArgs(argv) {
87
79
  return args;
88
80
  }
89
81
 
90
- function loadCatalog() {
91
- if (!fs.existsSync(catalogPath)) {
92
- console.error(`Catalog not found: ${catalogPath}`);
93
- process.exit(1);
94
- }
95
- return JSON.parse(fs.readFileSync(catalogPath, "utf8"));
96
- }
97
-
98
82
  function resolveInstallCommand(pack, scope, eccProfile) {
99
83
  const block = pack.install?.[scope];
100
84
  if (!block?.command) {
@@ -169,72 +153,6 @@ function verifyPack(pack, scope) {
169
153
  return result.status === 0;
170
154
  }
171
155
 
172
- function createRl() {
173
- return readline.createInterface({
174
- input: process.stdin,
175
- output: process.stdout
176
- });
177
- }
178
-
179
- function ask(rl, question) {
180
- return new Promise((resolve) => rl.question(question, (ans) => resolve(ans.trim())));
181
- }
182
-
183
- async function promptEccProfile(pack, preset) {
184
- if (preset) return preset;
185
- const profiles = pack.install?.profiles;
186
- if (!profiles?.length) {
187
- return pack.install?.defaultProfile || "core";
188
- }
189
- const rl = createRl();
190
- console.log("\nECC install profile:");
191
- profiles.forEach((p, i) => {
192
- console.log(` ${i + 1}) ${p.id} — ${p.description}`);
193
- });
194
- const defaultId = pack.install.defaultProfile || "core";
195
- const answer = await ask(rl, `Profile [1-${profiles.length} or id] (default ${defaultId}): `);
196
- rl.close();
197
- if (!answer) return defaultId;
198
- const num = parseInt(answer, 10);
199
- if (!Number.isNaN(num) && num >= 1 && num <= profiles.length) {
200
- return profiles[num - 1].id;
201
- }
202
- const byId = profiles.find((p) => p.id === answer);
203
- if (byId) return byId.id;
204
- console.error(`Unknown profile "${answer}", using ${defaultId}.`);
205
- return defaultId;
206
- }
207
-
208
- async function promptSelectPacks(packs) {
209
- const rl = createRl();
210
- console.log("\nSelect packs to install:\n");
211
- packs.forEach((p, i) => {
212
- const mark = p.defaultSelected ? " [default]" : "";
213
- console.log(` ${i + 1}) ${p.id}${mark} — ${p.name}`);
214
- });
215
- console.log("\nEnter: number(s) 1,2 · ids impeccable,ecc · all · defaults · Enter for defaults\n");
216
- const answer = await ask(rl, "Choice> ");
217
- rl.close();
218
- if (!answer) {
219
- return packs.filter((p) => p.defaultSelected).map((p) => p.id);
220
- }
221
- const lower = answer.toLowerCase();
222
- if (lower === "all") return packs.map((p) => p.id);
223
- if (lower === "defaults" || lower === "default" || lower === "d") {
224
- return packs.filter((p) => p.defaultSelected).map((p) => p.id);
225
- }
226
- const nums = lower.split(/[\s,]+/).filter(Boolean);
227
- const byNum = [];
228
- for (const part of nums) {
229
- const n = parseInt(part, 10);
230
- if (!Number.isNaN(n) && n >= 1 && n <= packs.length) {
231
- byNum.push(packs[n - 1].id);
232
- }
233
- }
234
- if (byNum.length > 0) return [...new Set(byNum)];
235
- return answer.split(",").map((s) => s.trim()).filter(Boolean);
236
- }
237
-
238
156
  async function run() {
239
157
  const args = parseArgs(process.argv.slice(2));
240
158
  if (args.help) {