@katipwork/skills 0.1.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/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @katipwork/skills
2
+
3
+ Curated agent skills with an interactive TUI installer.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ npx @katipwork/skills
9
+ ```
10
+
11
+ This launches an interactive TUI where you can:
12
+ 1. **Select skills** from the available catalog
13
+ 2. **Pick target agents** — Pi, Claude Code, Codex, GitHub Copilot, Hermes, Cursor, OpenCode
14
+ 3. **Choose install scope** — project or global
15
+ 4. **Choose install method** — symlink (recommended) or copy
16
+
17
+ ## Non-interactive usage
18
+
19
+ ```bash
20
+ # List available skills
21
+ npx @katipwork/skills --list
22
+
23
+ # Install specific skills to specific agents
24
+ npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y
25
+
26
+ # Install all skills to all detected agents
27
+ npx @katipwork/skills -i '*' --all-agents -y
28
+
29
+ # Install globally with symlinks
30
+ npx @katipwork/skills -i code-review --all-agents --global -y
31
+
32
+ # Copy instead of symlink
33
+ npx @katipwork/skills -i code-review -a pi --copy -y
34
+ ```
35
+
36
+ ## Options
37
+
38
+ | Option | Description |
39
+ |--------|-------------|
40
+ | `--help, -h` | Show help |
41
+ | `--list, -l` | List available skills |
42
+ | `--install, -i <skills...>` | Install specific skills (use `'*'` for all) |
43
+ | `--agent, -a <id>` | Target agent (repeatable) |
44
+ | `--all-agents` | Install to all detected agents |
45
+ | `--global, -g` | Install globally (`~/<agent>/skills/`) |
46
+ | `--copy` | Copy files instead of symlinking |
47
+ | `--yes, -y` | Skip confirmation prompts |
48
+
49
+ ## Supported agents
50
+
51
+ | ID | Agent | Project path | Global path |
52
+ |----|-------|-------------|-------------|
53
+ | `pi` | Pi | `.pi/skills/` | `~/.pi/agent/skills/` |
54
+ | `claude-code` | Claude Code | `.claude/skills/` | `~/.claude/skills/` |
55
+ | `codex` | Codex | `.agents/skills/` | `~/.codex/skills/` |
56
+ | `github-copilot` | GitHub Copilot | `.agents/skills/` | `~/.copilot/skills/` |
57
+ | `hermes-agent` | Hermes Agent | `.hermes/skills/` | `~/.hermes/skills/` |
58
+ | `cursor` | Cursor | `.agents/skills/` | `~/.cursor/skills/` |
59
+ | `opencode` | OpenCode | `.agents/skills/` | `~/.config/opencode/skills/` |
60
+
61
+ ## How it works
62
+
63
+ ### Symlink mode (recommended)
64
+
65
+ ```
66
+ skills/ your curated skills
67
+ ↓ copied
68
+ .agents/skills/<name>/ canonical source of truth
69
+ ↓ symlinked
70
+ .pi/skills/<name>/ agent picks it up from here
71
+ .claude/skills/<name>/ ...
72
+ ```
73
+
74
+ Single source of truth at `.agents/skills/`. Each agent gets a symlink. Update by re-running the installer.
75
+
76
+ ### Copy mode
77
+
78
+ ```
79
+ skills/ your curated skills
80
+ ↓ copied directly
81
+ .pi/skills/<name>/ independent copy
82
+ .claude/skills/<name>/ independent copy
83
+ ```
84
+
85
+ Each agent gets its own independent copy. Use when symlinks aren't supported.
86
+
87
+ ## Adding skills
88
+
89
+ Add skill directories under `skills/`:
90
+
91
+ ```
92
+ skills/
93
+ ├── engineering/
94
+ │ └── my-skill/
95
+ │ ├── SKILL.md # required: YAML frontmatter + markdown
96
+ │ └── agents/
97
+ │ └── openai.yaml # optional: Codex metadata
98
+ └── productivity/
99
+ └── another-skill/
100
+ └── SKILL.md
101
+ ```
102
+
103
+ Each `SKILL.md` must have YAML frontmatter:
104
+
105
+ ```yaml
106
+ ---
107
+ name: my-skill
108
+ description: What this skill does and when to use it
109
+ ---
110
+
111
+ # My Skill
112
+
113
+ Instructions for the agent...
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT
package/dist/cli.cjs ADDED
@@ -0,0 +1,669 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/tui.ts
27
+ var import_prompts = require("@inquirer/prompts");
28
+ var import_picocolors = __toESM(require("picocolors"), 1);
29
+
30
+ // src/agents.ts
31
+ var import_node_os = require("os");
32
+ var import_node_path = require("path");
33
+ var import_node_fs = require("fs");
34
+ var home = (0, import_node_os.homedir)();
35
+ var AGENTS = [
36
+ {
37
+ id: "pi",
38
+ displayName: "Pi",
39
+ projectSkillsDir: ".pi/skills",
40
+ globalSkillsDir: (0, import_node_path.join)(home, ".pi/agent/skills"),
41
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".pi/agent"))
42
+ },
43
+ {
44
+ id: "claude-code",
45
+ displayName: "Claude Code",
46
+ projectSkillsDir: ".claude/skills",
47
+ globalSkillsDir: (0, import_node_path.join)(home, ".claude/skills"),
48
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".claude"))
49
+ },
50
+ {
51
+ id: "codex",
52
+ displayName: "Codex",
53
+ projectSkillsDir: ".agents/skills",
54
+ globalSkillsDir: (0, import_node_path.join)(home, ".codex/skills"),
55
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".codex"))
56
+ },
57
+ {
58
+ id: "github-copilot",
59
+ displayName: "GitHub Copilot",
60
+ projectSkillsDir: ".agents/skills",
61
+ globalSkillsDir: (0, import_node_path.join)(home, ".copilot/skills"),
62
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".copilot"))
63
+ },
64
+ {
65
+ id: "hermes-agent",
66
+ displayName: "Hermes Agent",
67
+ projectSkillsDir: ".hermes/skills",
68
+ globalSkillsDir: (0, import_node_path.join)(home, ".hermes/skills"),
69
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".hermes"))
70
+ },
71
+ {
72
+ id: "cursor",
73
+ displayName: "Cursor",
74
+ projectSkillsDir: ".agents/skills",
75
+ globalSkillsDir: (0, import_node_path.join)(home, ".cursor/skills"),
76
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".cursor"))
77
+ },
78
+ {
79
+ id: "opencode",
80
+ displayName: "OpenCode",
81
+ projectSkillsDir: ".agents/skills",
82
+ globalSkillsDir: (0, import_node_path.join)(home, ".config/opencode/skills"),
83
+ detectInstalled: () => (0, import_node_fs.existsSync)((0, import_node_path.join)(home, ".config/opencode"))
84
+ }
85
+ ];
86
+ function getAgent(id) {
87
+ return AGENTS.find((a) => a.id === id);
88
+ }
89
+ function detectInstalledAgents() {
90
+ return AGENTS.filter((a) => a.detectInstalled());
91
+ }
92
+
93
+ // src/skills.ts
94
+ var import_node_fs2 = require("fs");
95
+ var import_promises = require("fs/promises");
96
+ var import_node_path2 = require("path");
97
+ function parseFrontmatter(content) {
98
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
99
+ if (!match) return null;
100
+ const data = {};
101
+ for (const line of match[1].split("\n")) {
102
+ const colonIdx = line.indexOf(":");
103
+ if (colonIdx === -1) continue;
104
+ const key = line.slice(0, colonIdx).trim();
105
+ const value = line.slice(colonIdx + 1).trim();
106
+ if (key && value) data[key] = value;
107
+ }
108
+ return { data };
109
+ }
110
+ function getSkillsRoot() {
111
+ const bundledPath = (0, import_node_path2.join)(__dirname, "..", "skills");
112
+ if ((0, import_node_fs2.existsSync)(bundledPath)) return bundledPath;
113
+ return (0, import_node_path2.join)(process.cwd(), "skills");
114
+ }
115
+ async function discoverSkills() {
116
+ const skillsRoot = getSkillsRoot();
117
+ const skills = [];
118
+ if (!(0, import_node_fs2.existsSync)(skillsRoot)) {
119
+ return skills;
120
+ }
121
+ async function walk(dir, category = "") {
122
+ let entries;
123
+ try {
124
+ entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
125
+ } catch {
126
+ return;
127
+ }
128
+ for (const entry of entries) {
129
+ if (!entry.isDirectory()) continue;
130
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
131
+ const fullPath = (0, import_node_path2.join)(dir, entry.name);
132
+ const skillMdPath = (0, import_node_path2.join)(fullPath, "SKILL.md");
133
+ if ((0, import_node_fs2.existsSync)(skillMdPath)) {
134
+ try {
135
+ const content = (0, import_node_fs2.readFileSync)(skillMdPath, "utf-8");
136
+ const parsed = parseFrontmatter(content);
137
+ if (parsed && parsed.data.name) {
138
+ skills.push({
139
+ name: parsed.data.name,
140
+ description: parsed.data.description || "",
141
+ dirPath: fullPath,
142
+ category: category || (0, import_node_path2.basename)((0, import_node_path2.dirname)(fullPath))
143
+ });
144
+ }
145
+ } catch {
146
+ }
147
+ } else {
148
+ const subCategory = category ? `${category}/${entry.name}` : entry.name;
149
+ await walk(fullPath, category || entry.name);
150
+ }
151
+ }
152
+ }
153
+ await walk(skillsRoot);
154
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
155
+ }
156
+
157
+ // src/installer.ts
158
+ var import_promises2 = require("fs/promises");
159
+ var import_node_path3 = require("path");
160
+ var import_node_os2 = require("os");
161
+ var home2 = (0, import_node_os2.homedir)();
162
+ function getCanonicalDir(global, cwd) {
163
+ const base = global ? home2 : cwd || process.cwd();
164
+ return (0, import_node_path3.join)(base, ".agents", "skills");
165
+ }
166
+ function getAgentSkillsDir(agent, global, cwd) {
167
+ const base = global ? home2 : cwd || process.cwd();
168
+ return global ? agent.globalSkillsDir : (0, import_node_path3.join)(base, agent.projectSkillsDir);
169
+ }
170
+ async function resolveParentSymlinks(p) {
171
+ const d = (0, import_node_path3.dirname)(p);
172
+ const b = (0, import_node_path3.basename)(p);
173
+ try {
174
+ return (0, import_node_path3.join)(await (0, import_promises2.realpath)(d), b);
175
+ } catch {
176
+ return p;
177
+ }
178
+ }
179
+ async function createSymlink(target, linkPath) {
180
+ try {
181
+ const resolvedTarget = (0, import_node_path3.resolve)(target);
182
+ const resolvedLinkPath = (0, import_node_path3.resolve)(linkPath);
183
+ try {
184
+ const [realTrg, realLnk] = await Promise.all([
185
+ (0, import_promises2.realpath)(resolvedTarget),
186
+ (0, import_promises2.realpath)(resolvedLinkPath)
187
+ ]);
188
+ if (realTrg === realLnk) return true;
189
+ } catch {
190
+ }
191
+ const realTgtWithParent = await resolveParentSymlinks(target);
192
+ const realLnkWithParent = await resolveParentSymlinks(linkPath);
193
+ if (realTgtWithParent === realLnkWithParent) return true;
194
+ try {
195
+ const stats = await (0, import_promises2.lstat)(linkPath);
196
+ if (stats.isSymbolicLink()) {
197
+ const existing = await (0, import_promises2.readlink)(linkPath);
198
+ if ((0, import_node_path3.resolve)((0, import_node_path3.dirname)(linkPath), existing) === resolvedTarget) return true;
199
+ await (0, import_promises2.rm)(linkPath);
200
+ } else {
201
+ await (0, import_promises2.rm)(linkPath, { recursive: true });
202
+ }
203
+ } catch {
204
+ }
205
+ const linkDir = (0, import_node_path3.dirname)(linkPath);
206
+ await (0, import_promises2.mkdir)(linkDir, { recursive: true });
207
+ const realLinkDir = await resolveParentSymlinks(linkDir);
208
+ const relPath = (0, import_node_path3.relative)(realLinkDir, target);
209
+ await (0, import_promises2.symlink)(relPath, linkPath);
210
+ return true;
211
+ } catch {
212
+ return false;
213
+ }
214
+ }
215
+ async function copyDir(src, dest) {
216
+ await (0, import_promises2.mkdir)(dest, { recursive: true });
217
+ await (0, import_promises2.cp)(src, dest, {
218
+ recursive: true,
219
+ dereference: true,
220
+ filter: (srcPath) => {
221
+ const name = (0, import_node_path3.basename)(srcPath);
222
+ return name !== ".git" && name !== "node_modules";
223
+ }
224
+ });
225
+ }
226
+ async function installSkill(skill, agent, options = {}) {
227
+ const isGlobal = options.global ?? false;
228
+ const cwd = options.cwd ?? process.cwd();
229
+ const mode = options.mode ?? "symlink";
230
+ const skillName = skill.name;
231
+ const canonicalDir = (0, import_node_path3.join)(getCanonicalDir(isGlobal, cwd), skillName);
232
+ const agentDir = (0, import_node_path3.join)(getAgentSkillsDir(agent, isGlobal, cwd), skillName);
233
+ try {
234
+ if (mode === "copy") {
235
+ await (0, import_promises2.rm)(agentDir, { recursive: true, force: true });
236
+ await copyDir(skill.dirPath, agentDir);
237
+ return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: "copy" };
238
+ }
239
+ await (0, import_promises2.rm)(canonicalDir, { recursive: true, force: true });
240
+ await copyDir(skill.dirPath, canonicalDir);
241
+ if (agent.projectSkillsDir === ".agents/skills" && !isGlobal) {
242
+ return {
243
+ skill: skillName,
244
+ agent: agent.id,
245
+ success: true,
246
+ path: canonicalDir,
247
+ mode: "symlink"
248
+ };
249
+ }
250
+ const ok = await createSymlink(canonicalDir, agentDir);
251
+ if (!ok) {
252
+ await (0, import_promises2.rm)(agentDir, { recursive: true, force: true });
253
+ await copyDir(skill.dirPath, agentDir);
254
+ }
255
+ return {
256
+ skill: skillName,
257
+ agent: agent.id,
258
+ success: true,
259
+ path: ok ? agentDir : agentDir,
260
+ mode: "symlink"
261
+ };
262
+ } catch (err) {
263
+ return {
264
+ skill: skillName,
265
+ agent: agent.id,
266
+ success: false,
267
+ path: agentDir,
268
+ mode,
269
+ error: err instanceof Error ? err.message : "Unknown error"
270
+ };
271
+ }
272
+ }
273
+ async function installSkills(skills, agents, options = {}) {
274
+ const results = [];
275
+ for (const skill of skills) {
276
+ for (const agent of agents) {
277
+ results.push(await installSkill(skill, agent, options));
278
+ }
279
+ }
280
+ return results;
281
+ }
282
+
283
+ // src/tui.ts
284
+ function spinner(message) {
285
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
286
+ let i = 0;
287
+ let running = true;
288
+ const timer = setInterval(() => {
289
+ process.stderr.write(`\r${frames[i++ % frames.length]} ${message}`);
290
+ }, 80);
291
+ return {
292
+ succeed(text) {
293
+ clearInterval(timer);
294
+ process.stderr.write(`\r${import_picocolors.default.green("\u2713")} ${text}
295
+ `);
296
+ running = false;
297
+ },
298
+ fail(text) {
299
+ clearInterval(timer);
300
+ process.stderr.write(`\r${import_picocolors.default.red("\u2717")} ${text}
301
+ `);
302
+ running = false;
303
+ },
304
+ stop() {
305
+ clearInterval(timer);
306
+ process.stderr.write("\r" + " ".repeat(message.length + 4) + "\r");
307
+ running = false;
308
+ }
309
+ };
310
+ }
311
+ var theme = {
312
+ style: {
313
+ highlight: (text) => import_picocolors.default.cyan(text)
314
+ }
315
+ };
316
+ async function selectSkills(skills) {
317
+ if (skills.length === 0) {
318
+ console.log(import_picocolors.default.yellow("No skills found in skills/ directory."));
319
+ return [];
320
+ }
321
+ const choices = skills.map((s) => ({
322
+ name: `${import_picocolors.default.bold(s.name)} ${import_picocolors.default.dim(`\u2014 ${s.description}`)}`,
323
+ value: s
324
+ }));
325
+ choices.unshift({
326
+ name: import_picocolors.default.italic("Select all skills"),
327
+ value: "__all__"
328
+ });
329
+ const selected = await (0, import_prompts.checkbox)({
330
+ message: "Select skills to install:",
331
+ choices,
332
+ pageSize: 15,
333
+ loop: false,
334
+ theme: {
335
+ ...theme,
336
+ helpMode: "auto"
337
+ }
338
+ });
339
+ if (selected.some((s) => s === "__all__")) {
340
+ return skills;
341
+ }
342
+ return selected;
343
+ }
344
+ async function selectAgents() {
345
+ const detected = detectInstalledAgents();
346
+ const detectedIds = new Set(detected.map((a) => a.id));
347
+ const choices = AGENTS.map((a) => ({
348
+ name: detectedIds.has(a.id) ? `${a.displayName} ${import_picocolors.default.green("(detected)")}` : a.displayName,
349
+ value: a,
350
+ checked: detectedIds.has(a.id)
351
+ }));
352
+ return (0, import_prompts.checkbox)({
353
+ message: "Select target agents:",
354
+ choices,
355
+ pageSize: 12,
356
+ loop: false,
357
+ theme: {
358
+ ...theme,
359
+ helpMode: "auto"
360
+ },
361
+ validate: (answer) => {
362
+ if (answer.length === 0) return "Select at least one agent.";
363
+ return true;
364
+ }
365
+ });
366
+ }
367
+ async function selectScope() {
368
+ return (0, import_prompts.select)({
369
+ message: "Install scope:",
370
+ choices: [
371
+ {
372
+ name: `${import_picocolors.default.bold("Project")} \u2014 install to ./<agent>/skills/ (shared with your team)`,
373
+ value: false
374
+ },
375
+ {
376
+ name: `${import_picocolors.default.bold("Global")} \u2014 install to ~/<agent>/skills/ (available everywhere)`,
377
+ value: true
378
+ }
379
+ ],
380
+ default: false
381
+ });
382
+ }
383
+ async function selectMode() {
384
+ return (0, import_prompts.select)({
385
+ message: "Install method:",
386
+ choices: [
387
+ {
388
+ name: `${import_picocolors.default.bold("Symlink")} ${import_picocolors.default.green("(recommended)")} \u2014 single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,
389
+ value: "symlink"
390
+ },
391
+ {
392
+ name: `${import_picocolors.default.bold("Copy")} \u2014 independent copies for each agent. Use when symlinks aren't supported.`,
393
+ value: "copy"
394
+ }
395
+ ],
396
+ default: "symlink"
397
+ });
398
+ }
399
+ async function confirmInstallation(skills, agents, global, mode) {
400
+ console.log("\n" + import_picocolors.default.cyan("\u2500\u2500\u2500 Installation summary \u2500\u2500\u2500"));
401
+ console.log(import_picocolors.default.white(` Skills: ${import_picocolors.default.bold(skills.map((s) => s.name).join(", "))}`));
402
+ console.log(import_picocolors.default.white(` Agents: ${import_picocolors.default.bold(agents.map((a) => a.displayName).join(", "))}`));
403
+ console.log(import_picocolors.default.white(` Scope: ${import_picocolors.default.bold(global ? "Global" : "Project")}`));
404
+ console.log(import_picocolors.default.white(` Method: ${import_picocolors.default.bold(mode === "symlink" ? "Symlink (canonical at .agents/skills/)" : "Copy")}`));
405
+ console.log("");
406
+ return (0, import_prompts.confirm)({
407
+ message: "Proceed with installation?",
408
+ default: true
409
+ });
410
+ }
411
+ function printResults(results) {
412
+ const succeeded = results.filter((r) => r.success);
413
+ const failed = results.filter((r) => !r.success);
414
+ console.log("");
415
+ if (succeeded.length > 0) {
416
+ console.log(import_picocolors.default.green(`\u2713 ${succeeded.length} skill(s) installed successfully:`));
417
+ for (const r of succeeded) {
418
+ console.log(import_picocolors.default.dim(` ${r.skill} \u2192 ${r.agent} (${r.mode}) \u2192 ${r.path}`));
419
+ }
420
+ }
421
+ if (failed.length > 0) {
422
+ console.log(import_picocolors.default.red(`\u2717 ${failed.length} skill(s) failed:`));
423
+ for (const r of failed) {
424
+ console.log(import_picocolors.default.red(` ${r.skill} \u2192 ${r.agent}: ${r.error}`));
425
+ }
426
+ }
427
+ if (succeeded.length > 0) {
428
+ console.log("");
429
+ console.log(import_picocolors.default.cyan("Tip:"));
430
+ console.log(import_picocolors.default.dim(" Skills are installed. Restart your agent for changes to take effect."));
431
+ console.log(import_picocolors.default.dim(" To update, re-run this installer or git pull + re-link."));
432
+ }
433
+ }
434
+ async function runTui() {
435
+ console.log("");
436
+ console.log(import_picocolors.default.bold(import_picocolors.default.cyan("@katipwork/skills")));
437
+ console.log(import_picocolors.default.dim("Curated agent skills installer"));
438
+ console.log("");
439
+ const discoverSpinner = spinner("Discovering skills...");
440
+ let skills;
441
+ try {
442
+ skills = await discoverSkills();
443
+ discoverSpinner.succeed(`Found ${skills.length} skill(s).`);
444
+ } catch (err) {
445
+ discoverSpinner.fail("Failed to discover skills.");
446
+ console.error(err);
447
+ process.exit(1);
448
+ }
449
+ if (skills.length === 0) {
450
+ console.log(import_picocolors.default.yellow("No skills found. Add SKILL.md files under skills/ and try again."));
451
+ process.exit(0);
452
+ }
453
+ const selectedSkills = await selectSkills(skills);
454
+ if (selectedSkills.length === 0) {
455
+ console.log(import_picocolors.default.yellow("No skills selected. Exiting."));
456
+ process.exit(0);
457
+ }
458
+ const selectedAgents = await selectAgents();
459
+ const global = await selectScope();
460
+ const mode = await selectMode();
461
+ const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);
462
+ if (!confirmed) {
463
+ console.log(import_picocolors.default.yellow("Installation cancelled."));
464
+ process.exit(0);
465
+ }
466
+ const installSpinner = spinner("Installing skills...");
467
+ let results;
468
+ try {
469
+ results = await installSkills(selectedSkills, selectedAgents, { global, mode });
470
+ installSpinner.stop();
471
+ } catch (err) {
472
+ installSpinner.fail("Installation failed.");
473
+ console.error(err);
474
+ process.exit(1);
475
+ }
476
+ printResults(results);
477
+ if (results.some((r) => !r.success)) {
478
+ process.exit(1);
479
+ }
480
+ }
481
+
482
+ // src/cli-args.ts
483
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
484
+ function showHelp() {
485
+ console.log(`
486
+ ${import_picocolors2.default.bold(import_picocolors2.default.cyan("@katipwork/skills"))} \u2014 Curated agent skills installer
487
+
488
+ ${import_picocolors2.default.bold("Usage:")}
489
+ npx @katipwork/skills Interactive TUI (default)
490
+ npx @katipwork/skills --list List available skills
491
+ npx @katipwork/skills --install <skills...> [options]
492
+
493
+ ${import_picocolors2.default.bold("Options:")}
494
+ --help, -h Show this help
495
+ --list, -l List available skills and exit
496
+ --install, -i Install specific skills (followed by skill names)
497
+ --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,
498
+ hermes-agent, cursor, opencode (can repeat)
499
+ --all-agents Install to all detected agents
500
+ --global, -g Install globally (~/<agent>/skills/)
501
+ --copy Copy files instead of symlinking
502
+ --yes, -y Skip confirmation prompt
503
+
504
+ ${import_picocolors2.default.bold("Examples:")}
505
+ npx @katipwork/skills
506
+ npx @katipwork/skills --list
507
+ npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y
508
+ npx @katipwork/skills -i '*' --all-agents --global
509
+
510
+ ${import_picocolors2.default.bold("Agents:")}
511
+ ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(", ")}
512
+
513
+ ${import_picocolors2.default.dim("Source: https://github.com/katipwork/skills")}
514
+ `);
515
+ }
516
+ function parseArgs(args2) {
517
+ const result = {
518
+ help: false,
519
+ list: false,
520
+ install: [],
521
+ agents: [],
522
+ allAgents: false,
523
+ global: false,
524
+ copy: false,
525
+ yes: false
526
+ };
527
+ let i = 0;
528
+ while (i < args2.length) {
529
+ const arg = args2[i];
530
+ switch (arg) {
531
+ case "--help":
532
+ case "-h":
533
+ result.help = true;
534
+ break;
535
+ case "--list":
536
+ case "-l":
537
+ result.list = true;
538
+ break;
539
+ case "--install":
540
+ case "-i":
541
+ i++;
542
+ while (i < args2.length && !args2[i].startsWith("-")) {
543
+ result.install.push(args2[i]);
544
+ i++;
545
+ }
546
+ i--;
547
+ break;
548
+ case "--agent":
549
+ case "-a":
550
+ i++;
551
+ while (i < args2.length && !args2[i].startsWith("-")) {
552
+ result.agents.push(args2[i]);
553
+ i++;
554
+ }
555
+ i--;
556
+ break;
557
+ case "--all-agents":
558
+ result.allAgents = true;
559
+ break;
560
+ case "--global":
561
+ case "-g":
562
+ result.global = true;
563
+ break;
564
+ case "--copy":
565
+ result.copy = true;
566
+ break;
567
+ case "--yes":
568
+ case "-y":
569
+ result.yes = true;
570
+ break;
571
+ default:
572
+ if (!arg.startsWith("-")) {
573
+ result.install.push(arg);
574
+ }
575
+ }
576
+ i++;
577
+ }
578
+ return result;
579
+ }
580
+ async function runCli(rawArgs) {
581
+ const args2 = parseArgs(rawArgs);
582
+ if (args2.help) {
583
+ showHelp();
584
+ process.exit(0);
585
+ }
586
+ if (args2.list) {
587
+ const skills = await discoverSkills();
588
+ if (skills.length === 0) {
589
+ console.log(import_picocolors2.default.yellow("No skills found."));
590
+ } else {
591
+ console.log(import_picocolors2.default.bold(`Available skills (${skills.length}):
592
+ `));
593
+ for (const s of skills) {
594
+ console.log(` ${import_picocolors2.default.bold(import_picocolors2.default.cyan(s.name))}`);
595
+ console.log(` ${import_picocolors2.default.dim(s.description)}`);
596
+ console.log(` category: ${s.category}`);
597
+ console.log("");
598
+ }
599
+ }
600
+ process.exit(0);
601
+ }
602
+ if (args2.install.length > 0) {
603
+ const allSkills = await discoverSkills();
604
+ let skillsToInstall = allSkills;
605
+ if (!args2.install.includes("*")) {
606
+ skillsToInstall = allSkills.filter(
607
+ (s) => args2.install.some((name) => s.name === name)
608
+ );
609
+ const notFound = args2.install.filter(
610
+ (name) => !allSkills.some((s) => s.name === name)
611
+ );
612
+ if (notFound.length > 0) {
613
+ console.log(import_picocolors2.default.yellow(`Warning: skills not found: ${notFound.join(", ")}`));
614
+ }
615
+ }
616
+ if (skillsToInstall.length === 0) {
617
+ console.log(import_picocolors2.default.red("No matching skills found to install."));
618
+ process.exit(1);
619
+ }
620
+ let agentsToUse = args2.agents.map((id) => getAgent(id)).filter((a) => !!a);
621
+ if (args2.allAgents) {
622
+ agentsToUse = AGENTS;
623
+ }
624
+ if (agentsToUse.length === 0) {
625
+ console.log(import_picocolors2.default.red("No agents specified. Use --agent <id> or --all-agents."));
626
+ process.exit(1);
627
+ }
628
+ const mode = args2.copy ? "copy" : "symlink";
629
+ console.log(import_picocolors2.default.cyan("\n\u2500\u2500\u2500 Installing \u2500\u2500\u2500"));
630
+ console.log(` Skills: ${import_picocolors2.default.bold(skillsToInstall.map((s) => s.name).join(", "))}`);
631
+ console.log(` Agents: ${import_picocolors2.default.bold(agentsToUse.map((a) => a.displayName).join(", "))}`);
632
+ console.log(` Scope: ${import_picocolors2.default.bold(args2.global ? "Global" : "Project")}`);
633
+ console.log(` Method: ${import_picocolors2.default.bold(mode)}`);
634
+ console.log("");
635
+ const results = await installSkills(skillsToInstall, agentsToUse, {
636
+ global: args2.global,
637
+ mode
638
+ });
639
+ const succeeded = results.filter((r) => r.success);
640
+ const failed = results.filter((r) => !r.success);
641
+ if (succeeded.length > 0) {
642
+ console.log(import_picocolors2.default.green(`\u2713 ${succeeded.length} installed successfully`));
643
+ }
644
+ if (failed.length > 0) {
645
+ console.log(import_picocolors2.default.red(`\u2717 ${failed.length} failed`));
646
+ for (const r of failed) {
647
+ console.log(import_picocolors2.default.red(` ${r.skill} \u2192 ${r.agent}: ${r.error}`));
648
+ }
649
+ process.exit(1);
650
+ }
651
+ process.exit(0);
652
+ }
653
+ showHelp();
654
+ }
655
+
656
+ // src/cli.ts
657
+ var args = process.argv.slice(2);
658
+ if (args.length > 0) {
659
+ runCli(args).catch((err) => {
660
+ console.error("Error:", err.message);
661
+ process.exit(1);
662
+ });
663
+ } else {
664
+ runTui().catch((err) => {
665
+ console.error("Unexpected error:", err);
666
+ process.exit(1);
667
+ });
668
+ }
669
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui.ts","../src/agents.ts","../src/skills.ts","../src/installer.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["import { search, checkbox, select, confirm } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { AGENTS, detectInstalledAgents, type AgentConfig } from './agents.js';\nimport { discoverSkills, type SkillMeta } from './skills.js';\nimport { installSkills, type InstallMode, type InstallResult } from './installer.js';\n\n// ─── simple spinner ────────────────────────────────────────────\n\nfunction spinner(message: string) {\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let i = 0;\n let running = true;\n const timer = setInterval(() => {\n process.stderr.write(`\\r${frames[i++ % frames.length]} ${message}`);\n }, 80);\n return {\n succeed(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.green('✓')} ${text}\\n`);\n running = false;\n },\n fail(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.red('✗')} ${text}\\n`);\n running = false;\n },\n stop() {\n clearInterval(timer);\n process.stderr.write('\\r' + ' '.repeat(message.length + 4) + '\\r');\n running = false;\n },\n };\n}\n\n// ─── theme helpers ──────────────────────────────────────────────\n\nconst theme = {\n style: {\n highlight: (text: string) => pc.cyan(text),\n },\n};\n\n// ─── steps ──────────────────────────────────────────────────────\n\nasync function selectSkills(skills: SkillMeta[]): Promise<SkillMeta[]> {\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found in skills/ directory.'));\n return [];\n }\n\n const choices = skills.map((s) => ({\n name: `${pc.bold(s.name)} ${pc.dim(`— ${s.description}`)}`,\n value: s,\n }));\n\n // Add a \"select all\" option\n choices.unshift({\n name: pc.italic('Select all skills'),\n value: '__all__' as unknown as SkillMeta,\n });\n\n const selected = await checkbox({\n message: 'Select skills to install:',\n choices,\n pageSize: 15,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n });\n\n // Handle \"select all\"\n if (selected.some((s: SkillMeta | string) => (s as unknown as string) === '__all__')) {\n return skills;\n }\n\n return selected as SkillMeta[];\n}\n\nasync function selectAgents(): Promise<AgentConfig[]> {\n const detected = detectInstalledAgents();\n const detectedIds = new Set(detected.map((a) => a.id));\n\n const choices = AGENTS.map((a) => ({\n name: detectedIds.has(a.id)\n ? `${a.displayName} ${pc.green('(detected)')}`\n : a.displayName,\n value: a,\n checked: detectedIds.has(a.id),\n }));\n\n return checkbox({\n message: 'Select target agents:',\n choices,\n pageSize: 12,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n validate: (answer) => {\n if (answer.length === 0) return 'Select at least one agent.';\n return true;\n },\n });\n}\n\nasync function selectScope(): Promise<boolean> {\n return select({\n message: 'Install scope:',\n choices: [\n {\n name: `${pc.bold('Project')} — install to ./<agent>/skills/ (shared with your team)`,\n value: false,\n },\n {\n name: `${pc.bold('Global')} — install to ~/<agent>/skills/ (available everywhere)`,\n value: true,\n },\n ],\n default: false,\n });\n}\n\nasync function selectMode(): Promise<InstallMode> {\n return select({\n message: 'Install method:',\n choices: [\n {\n name: `${pc.bold('Symlink')} ${pc.green('(recommended)')} — single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,\n value: 'symlink',\n },\n {\n name: `${pc.bold('Copy')} — independent copies for each agent. Use when symlinks aren't supported.`,\n value: 'copy',\n },\n ],\n default: 'symlink',\n });\n}\n\nasync function confirmInstallation(\n skills: SkillMeta[],\n agents: AgentConfig[],\n global: boolean,\n mode: InstallMode,\n): Promise<boolean> {\n console.log('\\n' + pc.cyan('─── Installation summary ───'));\n console.log(pc.white(` Skills: ${pc.bold(skills.map((s) => s.name).join(', '))}`));\n console.log(pc.white(` Agents: ${pc.bold(agents.map((a) => a.displayName).join(', '))}`));\n console.log(pc.white(` Scope: ${pc.bold(global ? 'Global' : 'Project')}`));\n console.log(pc.white(` Method: ${pc.bold(mode === 'symlink' ? 'Symlink (canonical at .agents/skills/)' : 'Copy')}`));\n console.log('');\n\n return confirm({\n message: 'Proceed with installation?',\n default: true,\n });\n}\n\nfunction printResults(results: InstallResult[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n console.log('');\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} skill(s) installed successfully:`));\n for (const r of succeeded) {\n console.log(pc.dim(` ${r.skill} → ${r.agent} (${r.mode}) → ${r.path}`));\n }\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} skill(s) failed:`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n }\n\n // Post-install hints\n if (succeeded.length > 0) {\n console.log('');\n console.log(pc.cyan('Tip:'));\n console.log(pc.dim(' Skills are installed. Restart your agent for changes to take effect.'));\n console.log(pc.dim(' To update, re-run this installer or git pull + re-link.'));\n }\n}\n\n// ─── main ───────────────────────────────────────────────────────\n\nexport async function runTui(): Promise<void> {\n console.log('');\n console.log(pc.bold(pc.cyan('@katipwork/skills')));\n console.log(pc.dim('Curated agent skills installer'));\n console.log('');\n\n // 1. Discover skills\n const discoverSpinner = spinner('Discovering skills...');\n let skills: SkillMeta[];\n try {\n skills = await discoverSkills();\n discoverSpinner.succeed(`Found ${skills.length} skill(s).`);\n } catch (err) {\n discoverSpinner.fail('Failed to discover skills.');\n console.error(err);\n process.exit(1);\n }\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found. Add SKILL.md files under skills/ and try again.'));\n process.exit(0);\n }\n\n // 2. Select skills\n const selectedSkills = await selectSkills(skills);\n if (selectedSkills.length === 0) {\n console.log(pc.yellow('No skills selected. Exiting.'));\n process.exit(0);\n }\n\n // 3. Select agents\n const selectedAgents = await selectAgents();\n\n // 4. Scope\n const global = await selectScope();\n\n // 5. Mode\n const mode = await selectMode();\n\n // 6. Confirm\n const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);\n if (!confirmed) {\n console.log(pc.yellow('Installation cancelled.'));\n process.exit(0);\n }\n\n // 7. Install\n const installSpinner = spinner('Installing skills...');\n let results: InstallResult[];\n try {\n results = await installSkills(selectedSkills, selectedAgents, { global, mode });\n installSpinner.stop();\n } catch (err) {\n installSpinner.fail('Installation failed.');\n console.error(err);\n process.exit(1);\n }\n\n printResults(results);\n\n if (results.some((r) => !r.success)) {\n process.exit(1);\n }\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nconst home = homedir();\n\nexport interface AgentConfig {\n /** CLI flag value, e.g. \"claude-code\" */\n id: string;\n /** Human-readable label */\n displayName: string;\n /** Relative path from project root for project-scoped installs */\n projectSkillsDir: string;\n /** Absolute path for global installs */\n globalSkillsDir: string;\n /** Quick check if agent is installed */\n detectInstalled: () => boolean;\n}\n\nexport type AgentId = (typeof AGENTS)[number]['id'];\n\n/**\n * All supported coding agents.\n * Each has a project-skills dir (relative to CWD) and a global-skills dir (absolute).\n */\nexport const AGENTS: AgentConfig[] = [\n {\n id: 'pi',\n displayName: 'Pi',\n projectSkillsDir: '.pi/skills',\n globalSkillsDir: join(home, '.pi/agent/skills'),\n detectInstalled: () => existsSync(join(home, '.pi/agent')),\n },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectSkillsDir: '.claude/skills',\n globalSkillsDir: join(home, '.claude/skills'),\n detectInstalled: () => existsSync(join(home, '.claude')),\n },\n {\n id: 'codex',\n displayName: 'Codex',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.codex/skills'),\n detectInstalled: () => existsSync(join(home, '.codex')),\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.copilot/skills'),\n detectInstalled: () => existsSync(join(home, '.copilot')),\n },\n {\n id: 'hermes-agent',\n displayName: 'Hermes Agent',\n projectSkillsDir: '.hermes/skills',\n globalSkillsDir: join(home, '.hermes/skills'),\n detectInstalled: () => existsSync(join(home, '.hermes')),\n },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.cursor/skills'),\n detectInstalled: () => existsSync(join(home, '.cursor')),\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.config/opencode/skills'),\n detectInstalled: () => existsSync(join(home, '.config/opencode')),\n },\n];\n\n/** Lookup agent config by id */\nexport function getAgent(id: string): AgentConfig | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n\n/** Detect which agents are installed on this machine */\nexport function detectInstalledAgents(): AgentConfig[] {\n return AGENTS.filter((a) => a.detectInstalled());\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { join, basename, dirname } from 'node:path';\n\n// In CJS, __dirname is a global pointing to the directory of the current file.\n// After build, __dirname is dist/ and skills/ is at ../skills.\ndeclare const __dirname: string;\n\nexport interface SkillMeta {\n /** Skill name from SKILL.md frontmatter */\n name: string;\n /** Description from SKILL.md frontmatter */\n description: string;\n /** Path to the skill directory on disk */\n dirPath: string;\n /** Which category/bucket the skill lives in, e.g. \"engineering\" */\n category: string;\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file.\n * Minimal parser that handles `---` delimited frontmatter.\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string> } | null {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n\n const data: Record<string, string> = {};\n for (const line of match[1].split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value) data[key] = value;\n }\n return { data };\n}\n\n/**\n * Find the skills/ directory.\n * When built: dist/ → ../skills\n * When running from source (tsx): src/ → ../skills\n * When installed globally: <package>/dist/ → ../skills\n */\nfunction getSkillsRoot(): string {\n // dist/ → ../skills (bundled case)\n const bundledPath = join(__dirname, '..', 'skills');\n if (existsSync(bundledPath)) return bundledPath;\n\n // CWD fallback\n return join(process.cwd(), 'skills');\n}\n\n/**\n * Discover all skills under skills/.\n * Skills can be at skills/<name>/SKILL.md or skills/<category>/<name>/SKILL.md.\n */\nexport async function discoverSkills(): Promise<SkillMeta[]> {\n const skillsRoot = getSkillsRoot();\n const skills: SkillMeta[] = [];\n\n if (!existsSync(skillsRoot)) {\n return skills;\n }\n\n async function walk(dir: string, category: string = ''): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n\n const fullPath = join(dir, entry.name);\n const skillMdPath = join(fullPath, 'SKILL.md');\n\n if (existsSync(skillMdPath)) {\n // This is a skill directory (has SKILL.md)\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const parsed = parseFrontmatter(content);\n if (parsed && parsed.data.name) {\n skills.push({\n name: parsed.data.name,\n description: parsed.data.description || '',\n dirPath: fullPath,\n category: category || basename(dirname(fullPath)),\n });\n }\n } catch {\n // skip broken skills\n }\n } else {\n // Recurse into subdirectories (category level)\n const subCategory = category ? `${category}/${entry.name}` : entry.name;\n await walk(fullPath, category || entry.name);\n }\n }\n }\n\n await walk(skillsRoot);\n return skills.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { existsSync } from 'node:fs';\nimport {\n mkdir,\n cp,\n symlink,\n rm,\n readlink,\n lstat,\n realpath,\n stat,\n} from 'node:fs/promises';\nimport { join, relative, dirname, resolve, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AgentConfig } from './agents.js';\nimport type { SkillMeta } from './skills.js';\n\nconst home = homedir();\n\nexport type InstallMode = 'symlink' | 'copy';\n\nexport interface InstallResult {\n skill: string;\n agent: string;\n success: boolean;\n path: string;\n mode: InstallMode;\n error?: string;\n}\n\n/** Canonical skills directory: .agents/skills/ */\nexport function getCanonicalDir(global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return join(base, '.agents', 'skills');\n}\n\nfunction getAgentSkillsDir(agent: AgentConfig, global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return global ? agent.globalSkillsDir : join(base, agent.projectSkillsDir);\n}\n\n/**\n * Resolve a symlink's parent directory to its real path.\n * Handles the case where e.g. ~/.claude/skills is a symlink to ~/.agents/skills.\n */\nasync function resolveParentSymlinks(p: string): Promise<string> {\n const d = dirname(p);\n const b = basename(p);\n try {\n return join(await realpath(d), b);\n } catch {\n return p;\n }\n}\n\n/**\n * Create a symlink at `linkPath` pointing to `target`.\n * Returns true on success, false if fallback to copy is needed.\n */\nasync function createSymlink(target: string, linkPath: string): Promise<boolean> {\n try {\n const resolvedTarget = resolve(target);\n const resolvedLinkPath = resolve(linkPath);\n\n // If they resolve to the same physical path, skip\n try {\n const [realTrg, realLnk] = await Promise.all([\n realpath(resolvedTarget),\n realpath(resolvedLinkPath),\n ]);\n if (realTrg === realLnk) return true;\n } catch {\n // paths may not exist yet — continue\n }\n\n // Check parent symlinks\n const realTgtWithParent = await resolveParentSymlinks(target);\n const realLnkWithParent = await resolveParentSymlinks(linkPath);\n if (realTgtWithParent === realLnkWithParent) return true;\n\n // Remove existing link/dir at target\n try {\n const stats = await lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const existing = await readlink(linkPath);\n if (resolve(dirname(linkPath), existing) === resolvedTarget) return true;\n await rm(linkPath);\n } else {\n await rm(linkPath, { recursive: true });\n }\n } catch {\n // Doesn't exist — fine\n }\n\n const linkDir = dirname(linkPath);\n await mkdir(linkDir, { recursive: true });\n\n const realLinkDir = await resolveParentSymlinks(linkDir);\n const relPath = relative(realLinkDir, target);\n\n await symlink(relPath, linkPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively copy a directory, skipping .git and node_modules.\n */\nasync function copyDir(src: string, dest: string): Promise<void> {\n await mkdir(dest, { recursive: true });\n\n // Use cp with recursive\n await cp(src, dest, {\n recursive: true,\n dereference: true,\n filter: (srcPath) => {\n const name = basename(srcPath);\n return name !== '.git' && name !== 'node_modules';\n },\n });\n}\n\n/**\n * Install a single skill for a single agent.\n */\nexport async function installSkill(\n skill: SkillMeta,\n agent: AgentConfig,\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult> {\n const isGlobal = options.global ?? false;\n const cwd = options.cwd ?? process.cwd();\n const mode = options.mode ?? 'symlink';\n\n const skillName = skill.name;\n const canonicalDir = join(getCanonicalDir(isGlobal, cwd), skillName);\n const agentDir = join(getAgentSkillsDir(agent, isGlobal, cwd), skillName);\n\n try {\n if (mode === 'copy') {\n // Copy directly to agent directory\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: 'copy' };\n }\n\n // Symlink mode: copy to canonical, then symlink from agent dir\n await rm(canonicalDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, canonicalDir);\n\n // For agents that share the universal .agents/skills dir, canonical is already correct\n if (agent.projectSkillsDir === '.agents/skills' && !isGlobal) {\n // Already installed at canonical location, nothing to symlink\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: canonicalDir,\n mode: 'symlink',\n };\n }\n\n const ok = await createSymlink(canonicalDir, agentDir);\n if (!ok) {\n // Fallback to copy\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n }\n\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: ok ? agentDir : agentDir,\n mode: 'symlink',\n };\n } catch (err) {\n return {\n skill: skillName,\n agent: agent.id,\n success: false,\n path: agentDir,\n mode,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Install multiple skills for multiple agents.\n */\nexport async function installSkills(\n skills: SkillMeta[],\n agents: AgentConfig[],\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult[]> {\n const results: InstallResult[] = [];\n for (const skill of skills) {\n for (const agent of agents) {\n results.push(await installSkill(skill, agent, options));\n }\n }\n return results;\n}\n","import pc from 'picocolors';\nimport { AGENTS, getAgent } from './agents.js';\nimport { discoverSkills } from './skills.js';\nimport { installSkills } from './installer.js';\n\nfunction showHelp(): void {\n console.log(`\n${pc.bold(pc.cyan('@katipwork/skills'))} — Curated agent skills installer\n\n${pc.bold('Usage:')}\n npx @katipwork/skills Interactive TUI (default)\n npx @katipwork/skills --list List available skills\n npx @katipwork/skills --install <skills...> [options]\n\n${pc.bold('Options:')}\n --help, -h Show this help\n --list, -l List available skills and exit\n --install, -i Install specific skills (followed by skill names)\n --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,\n hermes-agent, cursor, opencode (can repeat)\n --all-agents Install to all detected agents\n --global, -g Install globally (~/<agent>/skills/)\n --copy Copy files instead of symlinking\n --yes, -y Skip confirmation prompt\n\n${pc.bold('Examples:')}\n npx @katipwork/skills\n npx @katipwork/skills --list\n npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y\n npx @katipwork/skills -i '*' --all-agents --global\n\n${pc.bold('Agents:')}\n ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(', ')}\n\n${pc.dim('Source: https://github.com/katipwork/skills')}\n`);\n}\n\ninterface ParsedArgs {\n help: boolean;\n list: boolean;\n install: string[];\n agents: string[];\n allAgents: boolean;\n global: boolean;\n copy: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const result: ParsedArgs = {\n help: false,\n list: false,\n install: [],\n agents: [],\n allAgents: false,\n global: false,\n copy: false,\n yes: false,\n };\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n switch (arg) {\n case '--help':\n case '-h':\n result.help = true;\n break;\n case '--list':\n case '-l':\n result.list = true;\n break;\n case '--install':\n case '-i':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.install.push(args[i]);\n i++;\n }\n i--; // compensate for loop increment\n break;\n case '--agent':\n case '-a':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.agents.push(args[i]);\n i++;\n }\n i--;\n break;\n case '--all-agents':\n result.allAgents = true;\n break;\n case '--global':\n case '-g':\n result.global = true;\n break;\n case '--copy':\n result.copy = true;\n break;\n case '--yes':\n case '-y':\n result.yes = true;\n break;\n default:\n // Treat as skill name if not a flag\n if (!arg.startsWith('-')) {\n result.install.push(arg);\n }\n }\n i++;\n }\n\n return result;\n}\n\nexport async function runCli(rawArgs: string[]): Promise<void> {\n const args = parseArgs(rawArgs);\n\n if (args.help) {\n showHelp();\n process.exit(0);\n }\n\n if (args.list) {\n const skills = await discoverSkills();\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n } else {\n console.log(pc.bold(`Available skills (${skills.length}):\\n`));\n for (const s of skills) {\n console.log(` ${pc.bold(pc.cyan(s.name))}`);\n console.log(` ${pc.dim(s.description)}`);\n console.log(` category: ${s.category}`);\n console.log('');\n }\n }\n process.exit(0);\n }\n\n // Install mode\n if (args.install.length > 0) {\n const allSkills = await discoverSkills();\n let skillsToInstall = allSkills;\n\n // Filter by requested names unless '*' is specified\n if (!args.install.includes('*')) {\n skillsToInstall = allSkills.filter((s) =>\n args.install.some((name) => s.name === name),\n );\n const notFound = args.install.filter(\n (name) => !allSkills.some((s) => s.name === name),\n );\n if (notFound.length > 0) {\n console.log(pc.yellow(`Warning: skills not found: ${notFound.join(', ')}`));\n }\n }\n\n if (skillsToInstall.length === 0) {\n console.log(pc.red('No matching skills found to install.'));\n process.exit(1);\n }\n\n // Resolve agents\n let agentsToUse = args.agents\n .map((id) => getAgent(id))\n .filter((a): a is NonNullable<typeof a> => !!a);\n\n if (args.allAgents) {\n // Use all agents\n agentsToUse = AGENTS;\n }\n\n if (agentsToUse.length === 0) {\n console.log(pc.red('No agents specified. Use --agent <id> or --all-agents.'));\n process.exit(1);\n }\n\n const mode = args.copy ? 'copy' : 'symlink';\n\n console.log(pc.cyan('\\n─── Installing ───'));\n console.log(` Skills: ${pc.bold(skillsToInstall.map((s) => s.name).join(', '))}`);\n console.log(` Agents: ${pc.bold(agentsToUse.map((a) => a.displayName).join(', '))}`);\n console.log(` Scope: ${pc.bold(args.global ? 'Global' : 'Project')}`);\n console.log(` Method: ${pc.bold(mode)}`);\n console.log('');\n\n const results = await installSkills(skillsToInstall, agentsToUse, {\n global: args.global,\n mode,\n });\n\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} installed successfully`));\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} failed`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n process.exit(1);\n }\n process.exit(0);\n }\n\n // No recognized mode — show help\n showHelp();\n}\n","// Shebang is added by tsup banner at build time.\nimport { runTui } from './tui.js';\nimport { runCli } from './cli-args.js';\n\n// If CLI arguments are provided (beyond just the binary), run in non-interactive mode.\n// Otherwise, launch the TUI.\nconst args = process.argv.slice(2);\n\nif (args.length > 0) {\n runCli(args).catch((err) => {\n console.error('Error:', err.message);\n process.exit(1);\n });\n} else {\n runTui().catch((err) => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_promises","import_node_path","import_node_os","home","pc","import_picocolors","pc","args"]}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@katipwork/skills",
3
+ "version": "0.1.0",
4
+ "description": "Curated agent skills with an interactive TUI installer",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/katipwork/skills.git"
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "bin": {
12
+ "@katipwork/skills": "./dist/cli.cjs",
13
+ "skills": "./dist/cli.cjs"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "skills"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsx src/cli.ts",
22
+ "start": "node dist/cli.cjs"
23
+ },
24
+ "dependencies": {
25
+ "@inquirer/prompts": "^7.6.0",
26
+ "picocolors": "^1.1.1"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.15.0",
30
+ "tsup": "^8.5.0",
31
+ "tsx": "^4.19.0",
32
+ "typescript": "^5.8.0"
33
+ },
34
+ "packageManager": "npm@10.9.4"
35
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: code-review
3
+ description: Review changes since a fixed point (commit, branch, or tag) along two axes — Standards (does the code follow this repo's conventions?) and Spec (does it match the originating issue/PRD?). Use when reviewing a branch, PR, or work-in-progress changes.
4
+ ---
5
+
6
+ Review changes between `HEAD` and a fixed point the user supplies.
7
+
8
+ ## Process
9
+
10
+ 1. **Pin the fixed point** — ask the user for a commit SHA, branch, tag, or `main`. Resolve it and confirm the diff is non-empty.
11
+
12
+ 2. **Identify the spec** — look for issue references in commit messages (`#123`, `Closes #45`), PRD files under `docs/` or `specs/`, or ask the user.
13
+
14
+ 3. **Review on two axes in parallel:**
15
+ - **Standards** — does the diff follow documented coding conventions?
16
+ - **Spec** — does it implement what was asked?
17
+
18
+ Run both as sub-agents so they don't pollute each other's context.
19
+
20
+ 4. **Report** — present findings under `## Standards` and `## Spec` headings. Keep axes separate — a change can pass one and fail the other.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Code Review"
3
+ short_description: "Review a diff on standards and spec"
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: diagnosing-bugs
3
+ description: Systematic diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose," "debug this," or reports something broken, throwing, failing, or slow.
4
+ ---
5
+
6
+ ## Diagnosis Loop
7
+
8
+ Follow this cycle until the root cause is identified:
9
+
10
+ ### 1. Reproduce
11
+ - Confirm the bug is reproducible.
12
+ - Find the minimal reproduction steps.
13
+ - Note the exact observed vs expected behavior.
14
+
15
+ ### 2. Isolate
16
+ - Bisect the codebase — when did this start?
17
+ - Narrow the scope: which module, function, or line?
18
+ - Use `git bisect` for regressions.
19
+
20
+ ### 3. Hypothesize
21
+ - Propose a specific root-cause hypothesis.
22
+ - Design a test or probe that would confirm or refute it.
23
+
24
+ ### 4. Test
25
+ - Run the probe (log, breakpoint, test).
26
+ - If confirmed → fix. If refuted → return to step 3.
27
+
28
+ ### Guidelines
29
+ - **One hypothesis at a time.** Don't shotgun-debug.
30
+ - **Read the error message** — it often contains the answer.
31
+ - **Check recent changes first** — most bugs come from the last change.
32
+ - **Don't guess and check** — each probe should be deliberate.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Diagnosing Bugs"
3
+ short_description: "Systematic diagnosis for bugs and performance issues"
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: tdd
3
+ description: Test-driven development loop. Write a failing test first, then implement the minimal code to make it pass, then refactor. Use when building features or fixing bugs test-first, or when the user mentions "red-green-refactor".
4
+ ---
5
+
6
+ ## Red-Green-Refactor Loop
7
+
8
+ ### 1. Red — Write a failing test
9
+ - Write the smallest test that captures the desired behavior.
10
+ - The test must fail before any implementation code exists.
11
+ - Use the project's existing test framework and conventions.
12
+
13
+ ### 2. Green — Make it pass
14
+ - Write the **minimum** code to make the test pass.
15
+ - Don't over-engineer — no refactoring, no "future-proofing."
16
+ - If the test still fails, diagnose and fix the implementation.
17
+
18
+ ### 3. Refactor
19
+ - Clean up both test and production code.
20
+ - Remove duplication, improve names, extract helpers.
21
+ - Run tests to confirm nothing broke.
22
+
23
+ ### Guidelines
24
+ - **Test behavior, not implementation.** Avoid coupling tests to internal details.
25
+ - **One assertion per test** where practical.
26
+ - **Tests are documentation** — name them clearly.
27
+ - **Fast feedback** — keep tests quick; mock external dependencies.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "TDD"
3
+ short_description: "Test-driven development with red-green-refactor loop"
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: grilling
3
+ description: Grill the user relentlessly about a plan, decision, or idea to stress-test their thinking. Use when the user wants to pressure-test an idea, or mentions "grill me," "stress test," or "poke holes in this."
4
+ ---
5
+
6
+ ## Grilling Session
7
+
8
+ The goal is to find flaws, gaps, and unstated assumptions in the user's plan — not to be contrarian but to make the plan stronger.
9
+
10
+ ### Rules
11
+ 1. **Ask one question at a time.** Let the user answer before moving to the next.
12
+ 2. **Probe the edges.** What happens at scale? Under failure? With bad data?
13
+ 3. **Challenge assumptions.** "What makes you sure that...?"
14
+ 4. **Surface trade-offs.** "If you optimize for X, what happens to Y?"
15
+ 5. **Don't offer solutions yet.** The grilling phase is for finding problems.
16
+
17
+ ### Areas to probe
18
+ - **Scope:** What's in and out? What's the MVP vs nice-to-have?
19
+ - **Failure modes:** What breaks? What's the rollback plan?
20
+ - **Dependencies:** What does this depend on? What depends on this?
21
+ - **Alternatives:** What else did you consider? Why was this chosen?
22
+ - **Success criteria:** How will you know it worked?
23
+
24
+ ### End condition
25
+ Stop when the user says "that's enough" or when you've asked 5-8 questions without finding new gaps.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Grilling"
3
+ short_description: "Relentlessly question a plan to find gaps and assumptions"
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: handoff
3
+ description: Create a structured handoff document summarizing current work, decisions made, and next steps. Use when the user wants to hand off work to another developer or agent, or save context before switching tasks.
4
+ ---
5
+
6
+ ## Handoff Document
7
+
8
+ Create a concise document that captures everything the next person (human or agent) needs to continue the work.
9
+
10
+ ### Structure
11
+
12
+ 1. **What we're building** — one sentence on the goal.
13
+ 2. **Current state** — what's done, what's in progress, what's blocked.
14
+ 3. **Key decisions** — what was chosen, why, and what alternatives were considered.
15
+ 4. **Files to look at** — the 3-5 most important files.
16
+ 5. **Next steps** — the immediate next action, clearly scoped.
17
+ 6. **Gotchas** — surprising constraints, workarounds, or known issues.
18
+
19
+ ### Guidelines
20
+ - Write for someone with context about the project but not this specific task.
21
+ - Be honest about what's messy or incomplete.
22
+ - Don't bury the lede — put the most important info first.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Handoff"
3
+ short_description: "Create a structured handoff document for context transfer"
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: prototype
3
+ description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
4
+ ---
5
+
6
+ ## Throwaway Prototyping
7
+
8
+ The prototype exists to answer **one question**. It should be built as fast as possible and thrown away after the question is answered.
9
+
10
+ ### 1. Define the question
11
+ - What exactly are we trying to learn?
12
+ - What would a "yes" or "no" answer look like?
13
+
14
+ ### 2. Build the minimum
15
+ - Only the code needed to answer the question.
16
+ - No error handling, no tests, no polish.
17
+ - Fake anything that isn't the thing being tested.
18
+
19
+ ### 3. Run the experiment
20
+ - Exercise the prototype with the specific scenario.
21
+ - Record what happened.
22
+
23
+ ### 4. Decide
24
+ - Question answered? → Report findings, throw away code.
25
+ - Question not answered? → Refine the prototype or the question.
26
+
27
+ ### Red flags (abort)
28
+ - "Let me just add one more feature..."
29
+ - "This prototype is actually pretty good, maybe we should..."
30
+ - More than 30 minutes spent on the prototype.
31
+
32
+ **Reminder:** The prototype is garbage by design. The value is the learning, not the code.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Prototype"
3
+ short_description: "Build a throwaway prototype to answer one design question"