@smicolon/ai-kit 0.0.1

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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,901 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command6 } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { Command } from "commander";
8
+ import * as p from "@clack/prompts";
9
+ import pc from "picocolors";
10
+ import path5 from "path";
11
+
12
+ // src/tools.ts
13
+ var TOOL_REGISTRY = {
14
+ "claude-code": {
15
+ label: "Claude Code",
16
+ hint: "agents, skills, commands, rules, hooks",
17
+ skillsDir: ".claude/skills",
18
+ components: {
19
+ agents: ".claude/agents",
20
+ skills: ".claude/skills",
21
+ commands: ".claude/commands",
22
+ rules: ".claude/rules",
23
+ hooks: ".claude/hooks"
24
+ }
25
+ },
26
+ cursor: {
27
+ label: "Cursor",
28
+ hint: "skills, rules",
29
+ skillsDir: ".cursor/skills",
30
+ components: {
31
+ skills: ".cursor/skills",
32
+ rules: ".cursor/rules"
33
+ }
34
+ },
35
+ windsurf: {
36
+ label: "Windsurf",
37
+ hint: "skills, rules",
38
+ skillsDir: ".windsurf/skills",
39
+ components: {
40
+ skills: ".windsurf/skills",
41
+ rules: ".windsurf/rules"
42
+ }
43
+ },
44
+ copilot: {
45
+ label: "GitHub Copilot",
46
+ hint: "agents, skills",
47
+ skillsDir: ".github/skills",
48
+ components: {
49
+ agents: ".github/agents",
50
+ skills: ".github/skills"
51
+ }
52
+ },
53
+ codex: {
54
+ label: "OpenAI Codex",
55
+ hint: "agents, skills",
56
+ skillsDir: ".codex/skills",
57
+ components: {
58
+ agents: ".codex/agents",
59
+ skills: ".codex/skills"
60
+ }
61
+ },
62
+ cline: {
63
+ label: "Cline",
64
+ hint: "skills, rules",
65
+ skillsDir: ".cline/skills",
66
+ components: {
67
+ skills: ".cline/skills",
68
+ rules: ".cline/rules"
69
+ }
70
+ },
71
+ continue: {
72
+ label: "Continue",
73
+ hint: "skills, rules",
74
+ skillsDir: ".continue/skills",
75
+ components: {
76
+ skills: ".continue/skills",
77
+ rules: ".continue/rules"
78
+ }
79
+ },
80
+ gemini: {
81
+ label: "Gemini Code Assist",
82
+ hint: "agents, skills",
83
+ skillsDir: ".gemini/skills",
84
+ components: {
85
+ agents: ".gemini/agents",
86
+ skills: ".gemini/skills"
87
+ }
88
+ },
89
+ junie: {
90
+ label: "Junie",
91
+ hint: "skills, rules",
92
+ skillsDir: ".junie/skills",
93
+ components: {
94
+ skills: ".junie/skills",
95
+ rules: ".junie/rules"
96
+ }
97
+ },
98
+ kiro: {
99
+ label: "Kiro",
100
+ hint: "skills, rules",
101
+ skillsDir: ".kiro/skills",
102
+ components: {
103
+ skills: ".kiro/skills",
104
+ rules: ".kiro/rules"
105
+ }
106
+ },
107
+ amp: {
108
+ label: "Amp",
109
+ hint: "agents, skills",
110
+ skillsDir: ".amp/skills",
111
+ components: {
112
+ agents: ".amp/agents",
113
+ skills: ".amp/skills"
114
+ }
115
+ },
116
+ antigravity: {
117
+ label: "Antigravity",
118
+ hint: "skills, rules",
119
+ skillsDir: ".antigravity/skills",
120
+ components: {
121
+ skills: ".antigravity/skills",
122
+ rules: ".antigravity/rules"
123
+ }
124
+ },
125
+ augment: {
126
+ label: "Augment",
127
+ hint: "skills, rules",
128
+ skillsDir: ".augment/skills",
129
+ components: {
130
+ skills: ".augment/skills",
131
+ rules: ".augment/rules"
132
+ }
133
+ },
134
+ "roo-code": {
135
+ label: "Roo Code",
136
+ hint: "skills, rules",
137
+ skillsDir: ".roo-code/skills",
138
+ components: {
139
+ skills: ".roo-code/skills",
140
+ rules: ".roo-code/rules"
141
+ }
142
+ },
143
+ "amazon-q": {
144
+ label: "Amazon Q",
145
+ hint: "skills, rules",
146
+ skillsDir: ".amazon-q/skills",
147
+ components: {
148
+ skills: ".amazon-q/skills",
149
+ rules: ".amazon-q/rules"
150
+ }
151
+ }
152
+ };
153
+ var TOOL_IDS = Object.keys(TOOL_REGISTRY);
154
+ var CANONICAL_SKILLS_DIR = ".agents/skills";
155
+
156
+ // src/discovery.ts
157
+ import fs from "fs";
158
+ import path from "path";
159
+ function findMarketplaceJson(startDir) {
160
+ let dir = startDir;
161
+ while (true) {
162
+ const candidate = path.join(dir, ".claude-plugin", "marketplace.json");
163
+ if (fs.existsSync(candidate)) return candidate;
164
+ const parent = path.dirname(dir);
165
+ if (parent === dir) return null;
166
+ dir = parent;
167
+ }
168
+ }
169
+ function resolvePack(plugin, marketplaceDir) {
170
+ const sourceDir = path.resolve(marketplaceDir, plugin.source);
171
+ const resolveFiles = (files) => (files ?? []).map((f) => path.resolve(sourceDir, f));
172
+ let rules = [];
173
+ const rulesDir = path.join(sourceDir, "rules");
174
+ if (fs.existsSync(rulesDir)) {
175
+ rules = fs.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).map((f) => path.join(rulesDir, f));
176
+ }
177
+ const skills = (plugin.skills ?? []).map((s) => {
178
+ const skillMdPath = path.resolve(sourceDir, s);
179
+ return path.dirname(skillMdPath);
180
+ });
181
+ return {
182
+ name: plugin.name,
183
+ version: plugin.version,
184
+ description: plugin.description,
185
+ sourceDir,
186
+ agents: resolveFiles(plugin.agents),
187
+ commands: resolveFiles(plugin.commands),
188
+ skills,
189
+ rules,
190
+ hooks: resolveFiles(plugin.hooks),
191
+ mcpServers: plugin.mcpServers ? path.resolve(sourceDir, plugin.mcpServers) : void 0
192
+ };
193
+ }
194
+ function discoverPacks(startDir) {
195
+ const start = startDir ?? path.dirname(new URL(import.meta.url).pathname);
196
+ const marketplacePath = findMarketplaceJson(start);
197
+ if (!marketplacePath) {
198
+ throw new Error(
199
+ "Could not find .claude-plugin/marketplace.json. Make sure ai-kit is installed correctly."
200
+ );
201
+ }
202
+ const marketplaceDir = path.dirname(path.dirname(marketplacePath));
203
+ const raw = JSON.parse(fs.readFileSync(marketplacePath, "utf-8"));
204
+ return raw.plugins.map((p2) => resolvePack(p2, marketplaceDir)).filter((p2) => fs.existsSync(p2.sourceDir));
205
+ }
206
+ function findPack(name, startDir) {
207
+ return discoverPacks(startDir).find((p2) => p2.name === name);
208
+ }
209
+
210
+ // src/config.ts
211
+ import fs2 from "fs";
212
+ import path2 from "path";
213
+ var CONFIG_FILE = ".ai-kit.json";
214
+ function configPath(projectDir) {
215
+ return path2.join(projectDir, CONFIG_FILE);
216
+ }
217
+ function readConfig(projectDir) {
218
+ const fp = configPath(projectDir);
219
+ if (!fs2.existsSync(fp)) return null;
220
+ return JSON.parse(fs2.readFileSync(fp, "utf-8"));
221
+ }
222
+ function writeConfig(projectDir, config) {
223
+ fs2.writeFileSync(configPath(projectDir), JSON.stringify(config, null, 2) + "\n");
224
+ }
225
+ function mergeInstall(config, result) {
226
+ const components = {};
227
+ for (const [type, count] of Object.entries(result.installed)) {
228
+ if (count > 0) {
229
+ components[type] = [`${count} files`];
230
+ }
231
+ }
232
+ return {
233
+ ...config,
234
+ packs: {
235
+ ...config.packs,
236
+ [result.pack]: {
237
+ version: config.packs[result.pack]?.version ?? "0.0.0",
238
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
239
+ components,
240
+ files: result.files
241
+ }
242
+ }
243
+ };
244
+ }
245
+ function removePack(config, packName) {
246
+ const { [packName]: _, ...rest } = config.packs;
247
+ return { ...config, packs: rest };
248
+ }
249
+ function createDefaultConfig(tools) {
250
+ return {
251
+ version: "1",
252
+ tools,
253
+ packs: {}
254
+ };
255
+ }
256
+
257
+ // src/gitignore.ts
258
+ import fs3 from "fs";
259
+ import path3 from "path";
260
+ var MANAGED_COMMENT = "# AI coding tools (managed by @smicolon/ai-kit)";
261
+ function findGitRoot(startDir) {
262
+ let dir = startDir;
263
+ while (true) {
264
+ if (fs3.existsSync(path3.join(dir, ".git"))) return dir;
265
+ const parent = path3.dirname(dir);
266
+ if (parent === dir) return null;
267
+ dir = parent;
268
+ }
269
+ }
270
+ function appendToGitignore(gitignorePath, entries) {
271
+ const existing = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf-8") : "";
272
+ const lines = existing.split("\n");
273
+ const newEntries = entries.filter(
274
+ (entry) => !lines.some(
275
+ (line) => line.trim() === entry.replace(/\/$/, "") || line.trim() === entry
276
+ )
277
+ );
278
+ if (newEntries.length === 0) return;
279
+ const hasBlock = lines.includes(MANAGED_COMMENT);
280
+ let updated;
281
+ if (hasBlock) {
282
+ const blockIdx = lines.indexOf(MANAGED_COMMENT);
283
+ let endIdx = blockIdx + 1;
284
+ while (endIdx < lines.length && lines[endIdx].trim() !== "" && !lines[endIdx].startsWith("#")) {
285
+ endIdx++;
286
+ }
287
+ lines.splice(endIdx, 0, ...newEntries);
288
+ updated = lines.join("\n");
289
+ } else {
290
+ const block = `
291
+ ${MANAGED_COMMENT}
292
+ ${newEntries.join("\n")}
293
+ `;
294
+ updated = existing.endsWith("\n") ? existing + block : existing + "\n" + block;
295
+ }
296
+ fs3.writeFileSync(gitignorePath, updated);
297
+ }
298
+ function updateGitignore(projectDir, dirs) {
299
+ if (dirs.length === 0) return;
300
+ const entries = dirs.map((d) => d.endsWith("/") ? d : `${d}/`);
301
+ const projectGitignore = path3.join(projectDir, ".gitignore");
302
+ appendToGitignore(projectGitignore, entries);
303
+ const gitRoot = findGitRoot(projectDir);
304
+ if (gitRoot && path3.resolve(gitRoot) !== path3.resolve(projectDir)) {
305
+ const relFromRoot = path3.relative(gitRoot, projectDir);
306
+ const rootEntries = entries.map((e) => `${relFromRoot}/${e}`);
307
+ appendToGitignore(path3.join(gitRoot, ".gitignore"), rootEntries);
308
+ }
309
+ }
310
+
311
+ // src/installer.ts
312
+ import fs5 from "fs";
313
+ import path4 from "path";
314
+
315
+ // src/converters/cursor-mdc.ts
316
+ import fs4 from "fs";
317
+ function convertToMdc(mdFilePath, packName) {
318
+ const content = fs4.readFileSync(mdFilePath, "utf-8");
319
+ const { frontmatter, body } = parseFrontmatter(content);
320
+ const paths = frontmatter.paths;
321
+ const globs = paths && paths.length > 0 ? paths.length === 1 ? paths[0] : paths.join(", ") : "";
322
+ const headingMatch = body.match(/^#\s+(.+)$/m);
323
+ const description = headingMatch ? headingMatch[1].trim() : `${packName} rule`;
324
+ const mdcFrontmatter = [
325
+ "---",
326
+ `description: ${description}`,
327
+ ...globs ? [`globs: ${globs}`] : [],
328
+ "---"
329
+ ].join("\n");
330
+ return `${mdcFrontmatter}
331
+ ${body}`;
332
+ }
333
+ function parseFrontmatter(content) {
334
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
335
+ if (!match) {
336
+ return { frontmatter: {}, body: content };
337
+ }
338
+ const yamlStr = match[1];
339
+ const body = match[2];
340
+ const frontmatter = {};
341
+ let currentKey = "";
342
+ const currentArray = [];
343
+ for (const line of yamlStr.split("\n")) {
344
+ const keyMatch = line.match(/^(\w+):(.*)$/);
345
+ if (keyMatch) {
346
+ if (currentKey && currentArray.length > 0) {
347
+ frontmatter[currentKey] = [...currentArray];
348
+ currentArray.length = 0;
349
+ }
350
+ currentKey = keyMatch[1];
351
+ const value = keyMatch[2].trim();
352
+ if (value) {
353
+ frontmatter[currentKey] = value;
354
+ currentKey = "";
355
+ }
356
+ } else {
357
+ const itemMatch = line.match(/^\s+-\s+"?(.+?)"?\s*$/);
358
+ if (itemMatch) {
359
+ currentArray.push(itemMatch[1]);
360
+ }
361
+ }
362
+ }
363
+ if (currentKey && currentArray.length > 0) {
364
+ frontmatter[currentKey] = [...currentArray];
365
+ }
366
+ return { frontmatter, body };
367
+ }
368
+
369
+ // src/installer.ts
370
+ var trackedFiles = [];
371
+ var currentProjectDir = "";
372
+ function trackFile(absPath) {
373
+ trackedFiles.push(path4.relative(currentProjectDir, absPath));
374
+ }
375
+ function ensureDir(dir) {
376
+ fs5.mkdirSync(dir, { recursive: true });
377
+ }
378
+ function copyFile(src, dest) {
379
+ ensureDir(path4.dirname(dest));
380
+ fs5.copyFileSync(src, dest);
381
+ trackFile(dest);
382
+ }
383
+ function copyDir(src, dest) {
384
+ ensureDir(dest);
385
+ for (const entry of fs5.readdirSync(src, { withFileTypes: true })) {
386
+ const srcPath = path4.join(src, entry.name);
387
+ const destPath = path4.join(dest, entry.name);
388
+ if (entry.isDirectory()) {
389
+ copyDir(srcPath, destPath);
390
+ } else {
391
+ fs5.copyFileSync(srcPath, destPath);
392
+ trackFile(destPath);
393
+ }
394
+ }
395
+ }
396
+ function createSymlink(target, linkPath) {
397
+ ensureDir(path4.dirname(linkPath));
398
+ if (fs5.existsSync(linkPath)) {
399
+ const stat = fs5.lstatSync(linkPath);
400
+ if (stat.isSymbolicLink()) fs5.unlinkSync(linkPath);
401
+ else return;
402
+ }
403
+ const type = process.platform === "win32" ? "junction" : void 0;
404
+ fs5.symlinkSync(target, linkPath, type);
405
+ trackFile(linkPath);
406
+ }
407
+ function installSkills(skillDirs, tools, projectDir) {
408
+ if (skillDirs.length === 0 || tools.length === 0) return 0;
409
+ const skillTools = tools.filter((t) => TOOL_REGISTRY[t].components.skills);
410
+ if (skillTools.length === 0) return 0;
411
+ const canonicalBase = path4.join(projectDir, CANONICAL_SKILLS_DIR);
412
+ let count = 0;
413
+ for (const skillDir of skillDirs) {
414
+ const skillName = path4.basename(skillDir);
415
+ const canonicalDest = path4.join(canonicalBase, skillName);
416
+ if (!fs5.existsSync(canonicalDest)) {
417
+ copyDir(skillDir, canonicalDest);
418
+ count++;
419
+ }
420
+ }
421
+ for (const toolId of skillTools) {
422
+ const toolSkillsDir = path4.join(projectDir, TOOL_REGISTRY[toolId].skillsDir);
423
+ if (path4.resolve(toolSkillsDir) === path4.resolve(canonicalBase)) continue;
424
+ ensureDir(toolSkillsDir);
425
+ for (const skillDir of skillDirs) {
426
+ const skillName = path4.basename(skillDir);
427
+ const canonicalDest = path4.join(canonicalBase, skillName);
428
+ const linkPath = path4.join(toolSkillsDir, skillName);
429
+ const relTarget = path4.relative(path4.dirname(linkPath), canonicalDest);
430
+ createSymlink(relTarget, linkPath);
431
+ }
432
+ }
433
+ return count;
434
+ }
435
+ function installMdFiles(files, componentType, tools, projectDir, packName) {
436
+ if (files.length === 0) return 0;
437
+ for (const toolId of tools) {
438
+ const targetDir = TOOL_REGISTRY[toolId].components[componentType];
439
+ if (!targetDir) continue;
440
+ const dest = path4.join(projectDir, targetDir);
441
+ ensureDir(dest);
442
+ for (const file of files) {
443
+ if (componentType === "rules" && toolId === "cursor") {
444
+ const mdcContent = convertToMdc(file, packName ?? "unknown");
445
+ const mdcName = path4.basename(file, ".md") + ".mdc";
446
+ const destPath = path4.join(dest, mdcName);
447
+ ensureDir(path4.dirname(destPath));
448
+ fs5.writeFileSync(destPath, mdcContent);
449
+ trackFile(destPath);
450
+ } else {
451
+ copyFile(file, path4.join(dest, path4.basename(file)));
452
+ }
453
+ }
454
+ }
455
+ return files.length;
456
+ }
457
+ function installHooks(hookFiles, tools, projectDir) {
458
+ if (hookFiles.length === 0) return 0;
459
+ if (!tools.includes("claude-code")) return 0;
460
+ let count = 0;
461
+ const targetHooksDir = path4.join(projectDir, ".claude", "hooks");
462
+ for (const hookFile of hookFiles) {
463
+ if (!fs5.existsSync(hookFile)) continue;
464
+ const raw = JSON.parse(fs5.readFileSync(hookFile, "utf-8"));
465
+ if (!raw.hooks || Object.keys(raw.hooks).length === 0) continue;
466
+ const hookSourceDir = path4.dirname(hookFile);
467
+ const scriptFiles = findScriptFiles(hookSourceDir);
468
+ for (const script of scriptFiles) {
469
+ const relPath = path4.relative(hookSourceDir, script);
470
+ const destPath = path4.join(targetHooksDir, relPath);
471
+ copyFile(script, destPath);
472
+ fs5.chmodSync(destPath, 493);
473
+ }
474
+ const rewritten = JSON.stringify(raw.hooks).replace(/\$\{CLAUDE_PLUGIN_ROOT\}\/hooks/g, ".claude/hooks");
475
+ const projectHooksPath = path4.join(projectDir, ".claude", "hooks.json");
476
+ let existing = {};
477
+ if (fs5.existsSync(projectHooksPath)) {
478
+ const parsed = JSON.parse(fs5.readFileSync(projectHooksPath, "utf-8"));
479
+ existing = parsed.hooks ?? parsed;
480
+ }
481
+ const newHooks = JSON.parse(rewritten);
482
+ for (const [event, handlers] of Object.entries(newHooks)) {
483
+ if (!existing[event]) existing[event] = [];
484
+ existing[event].push(...handlers);
485
+ }
486
+ ensureDir(path4.dirname(projectHooksPath));
487
+ fs5.writeFileSync(
488
+ projectHooksPath,
489
+ JSON.stringify({ hooks: existing }, null, 2) + "\n"
490
+ );
491
+ trackFile(projectHooksPath);
492
+ count++;
493
+ }
494
+ return count;
495
+ }
496
+ function findScriptFiles(dir) {
497
+ const scripts = [];
498
+ if (!fs5.existsSync(dir)) return scripts;
499
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
500
+ const fullPath = path4.join(dir, entry.name);
501
+ if (entry.isDirectory()) {
502
+ scripts.push(...findScriptFiles(fullPath));
503
+ } else if (/\.(sh|js|ts)$/.test(entry.name)) {
504
+ scripts.push(fullPath);
505
+ }
506
+ }
507
+ return scripts;
508
+ }
509
+ function installPack(options) {
510
+ const { pack, tools, filter, projectDir } = options;
511
+ trackedFiles = [];
512
+ currentProjectDir = projectDir;
513
+ const installed = {
514
+ agents: 0,
515
+ skills: 0,
516
+ commands: 0,
517
+ rules: 0,
518
+ hooks: 0
519
+ };
520
+ const should = (type) => !filter || filter.includes(type);
521
+ if (should("agents")) {
522
+ installed.agents = installMdFiles(pack.agents, "agents", tools, projectDir);
523
+ }
524
+ if (should("commands")) {
525
+ installed.commands = installMdFiles(pack.commands, "commands", tools, projectDir);
526
+ }
527
+ if (should("rules")) {
528
+ installed.rules = installMdFiles(pack.rules, "rules", tools, projectDir, pack.name);
529
+ }
530
+ if (should("skills")) {
531
+ installed.skills = installSkills(pack.skills, tools, projectDir);
532
+ }
533
+ if (should("hooks")) {
534
+ installed.hooks = installHooks(pack.hooks, tools, projectDir);
535
+ }
536
+ return { pack: pack.name, tools, installed, files: [...trackedFiles] };
537
+ }
538
+ function removePack2(projectDir, files) {
539
+ let removed = 0;
540
+ for (const relFile of files) {
541
+ const absPath = path4.join(projectDir, relFile);
542
+ if (!fs5.existsSync(absPath) && !isSymlink(absPath)) continue;
543
+ if (isSymlink(absPath)) {
544
+ fs5.unlinkSync(absPath);
545
+ } else if (fs5.statSync(absPath).isDirectory()) {
546
+ fs5.rmSync(absPath, { recursive: true });
547
+ } else {
548
+ fs5.unlinkSync(absPath);
549
+ }
550
+ removed++;
551
+ let parent = path4.dirname(absPath);
552
+ while (parent !== projectDir && parent.length > projectDir.length) {
553
+ try {
554
+ const entries = fs5.readdirSync(parent);
555
+ if (entries.length === 0) {
556
+ fs5.rmdirSync(parent);
557
+ } else {
558
+ break;
559
+ }
560
+ } catch {
561
+ break;
562
+ }
563
+ parent = path4.dirname(parent);
564
+ }
565
+ }
566
+ return removed;
567
+ }
568
+ function isSymlink(p2) {
569
+ try {
570
+ return fs5.lstatSync(p2).isSymbolicLink();
571
+ } catch {
572
+ return false;
573
+ }
574
+ }
575
+ function getWrittenDirs(tools, hadSkills) {
576
+ const dirs = /* @__PURE__ */ new Set();
577
+ if (hadSkills) {
578
+ dirs.add(CANONICAL_SKILLS_DIR);
579
+ }
580
+ for (const toolId of tools) {
581
+ const config = TOOL_REGISTRY[toolId];
582
+ for (const dir of Object.values(config.components)) {
583
+ const topLevel = dir.split("/")[0];
584
+ dirs.add(topLevel);
585
+ }
586
+ }
587
+ return [...dirs];
588
+ }
589
+
590
+ // src/commands/init.ts
591
+ var initCommand = new Command("init").description("Interactive first-time setup").option("--cwd <dir>", "Project directory (for monorepo sub-packages)").action(async (opts) => {
592
+ p.intro(pc.bgCyan(pc.black(" ai-kit ")));
593
+ const projectDir = opts.cwd ? path5.resolve(opts.cwd) : process.cwd();
594
+ const existing = readConfig(projectDir);
595
+ if (existing) {
596
+ const action = await p.select({
597
+ message: "ai-kit is already configured. What would you like to do?",
598
+ options: [
599
+ { value: "reconfigure", label: "Reconfigure from scratch" },
600
+ { value: "add", label: "Add more packs" },
601
+ { value: "cancel", label: "Cancel" }
602
+ ]
603
+ });
604
+ if (p.isCancel(action) || action === "cancel") {
605
+ p.outro("Cancelled.");
606
+ return;
607
+ }
608
+ if (action === "add") {
609
+ p.outro(`Run ${pc.cyan("ai-kit add <pack>")} to add packs.`);
610
+ return;
611
+ }
612
+ }
613
+ const toolSelection = await p.multiselect({
614
+ message: "Which AI coding tools do you use?",
615
+ options: TOOL_IDS.map((id) => ({
616
+ value: id,
617
+ label: TOOL_REGISTRY[id].label,
618
+ hint: TOOL_REGISTRY[id].hint
619
+ })),
620
+ required: true
621
+ });
622
+ if (p.isCancel(toolSelection)) {
623
+ p.outro("Cancelled.");
624
+ return;
625
+ }
626
+ const selectedTools = toolSelection;
627
+ let packs;
628
+ try {
629
+ packs = discoverPacks();
630
+ } catch {
631
+ p.log.error("Could not find marketplace.json. Is ai-kit installed correctly?");
632
+ p.outro("Setup failed.");
633
+ return;
634
+ }
635
+ const packSelection = await p.multiselect({
636
+ message: "What's your stack?",
637
+ options: packs.map((pack) => ({
638
+ value: pack.name,
639
+ label: pack.name,
640
+ hint: pack.description
641
+ })),
642
+ required: true
643
+ });
644
+ if (p.isCancel(packSelection)) {
645
+ p.outro("Cancelled.");
646
+ return;
647
+ }
648
+ const selectedPackNames = packSelection;
649
+ const componentChoice = await p.select({
650
+ message: "What components should be installed?",
651
+ options: [
652
+ { value: "all", label: "Everything", hint: "agents, skills, commands, rules, hooks" },
653
+ { value: "skills", label: "Skills only", hint: "auto-enforcing convention skills" },
654
+ { value: "pick", label: "Let me pick" }
655
+ ]
656
+ });
657
+ if (p.isCancel(componentChoice)) {
658
+ p.outro("Cancelled.");
659
+ return;
660
+ }
661
+ let filter;
662
+ if (componentChoice === "skills") {
663
+ filter = ["skills"];
664
+ } else if (componentChoice === "pick") {
665
+ const components = await p.multiselect({
666
+ message: "Select component types:",
667
+ options: [
668
+ { value: "agents", label: "Agents", hint: "specialized AI agents" },
669
+ { value: "skills", label: "Skills", hint: "auto-enforcing conventions" },
670
+ { value: "commands", label: "Commands", hint: "slash commands" },
671
+ { value: "rules", label: "Rules", hint: "path-specific rules" },
672
+ { value: "hooks", label: "Hooks", hint: "lifecycle hooks (Claude Code only)" }
673
+ ],
674
+ required: true
675
+ });
676
+ if (p.isCancel(components)) {
677
+ p.outro("Cancelled.");
678
+ return;
679
+ }
680
+ filter = components;
681
+ }
682
+ const s = p.spinner();
683
+ s.start("Installing packs...");
684
+ let config = createDefaultConfig(selectedTools);
685
+ const selectedPacks = packs.filter((p2) => selectedPackNames.includes(p2.name));
686
+ let hadSkills = false;
687
+ for (const pack of selectedPacks) {
688
+ const result = installPack({
689
+ pack,
690
+ tools: selectedTools,
691
+ filter,
692
+ projectDir
693
+ });
694
+ config = mergeInstall(config, result);
695
+ if (result.installed.skills > 0) hadSkills = true;
696
+ }
697
+ writeConfig(projectDir, config);
698
+ updateGitignore(projectDir, getWrittenDirs(selectedTools, hadSkills));
699
+ s.stop("Done!");
700
+ p.log.success(`Installed ${selectedPacks.length} pack(s) for ${selectedTools.length} tool(s):`);
701
+ for (const pack of selectedPacks) {
702
+ p.log.message(` ${pc.green("+")} ${pack.name} ${pc.dim(`v${pack.version}`)}`);
703
+ }
704
+ p.outro(pc.dim("Config saved to .ai-kit.json"));
705
+ });
706
+
707
+ // src/commands/add.ts
708
+ import { Command as Command2 } from "commander";
709
+ import path6 from "path";
710
+ import pc2 from "picocolors";
711
+ var addCommand = new Command2("add").description("Add a pack to your project").argument("<pack>", "Pack name (e.g., django, nextjs)").option("--skills-only", "Only install skills").option("--agents-only", "Only install agents").option("--rules-only", "Only install rules").option("--commands-only", "Only install commands").option("--hooks-only", "Only install hooks").option("--tools <tools>", "Comma-separated tool IDs (overrides config)").option("--cwd <dir>", "Project directory (for monorepo sub-packages)").action(async (packName, opts) => {
712
+ const projectDir = opts.cwd ? path6.resolve(opts.cwd) : process.cwd();
713
+ const config = readConfig(projectDir);
714
+ if (!config) {
715
+ console.error(
716
+ pc2.red("No .ai-kit.json found. Run ") + pc2.cyan("ai-kit init") + pc2.red(" first.")
717
+ );
718
+ process.exit(1);
719
+ }
720
+ const pack = findPack(packName);
721
+ if (!pack) {
722
+ const available = discoverPacks().map((p2) => p2.name);
723
+ console.error(
724
+ pc2.red(`Pack "${packName}" not found.`) + "\nAvailable: " + available.join(", ")
725
+ );
726
+ process.exit(1);
727
+ }
728
+ const tools = opts.tools ? opts.tools.split(",").map((t) => t.trim()) : config.tools;
729
+ let filter;
730
+ if (opts.skillsOnly) filter = ["skills"];
731
+ else if (opts.agentsOnly) filter = ["agents"];
732
+ else if (opts.rulesOnly) filter = ["rules"];
733
+ else if (opts.commandsOnly) filter = ["commands"];
734
+ else if (opts.hooksOnly) filter = ["hooks"];
735
+ const result = installPack({ pack, tools, filter, projectDir });
736
+ const updated = mergeInstall(config, result);
737
+ updated.packs[pack.name].version = pack.version;
738
+ writeConfig(projectDir, updated);
739
+ updateGitignore(projectDir, getWrittenDirs(tools, result.installed.skills > 0));
740
+ const parts = [];
741
+ for (const [type, count] of Object.entries(result.installed)) {
742
+ if (count > 0) parts.push(`${count} ${type}`);
743
+ }
744
+ console.log(
745
+ pc2.green(`+ ${pack.name}`) + pc2.dim(` v${pack.version}`) + (parts.length ? ` (${parts.join(", ")})` : "")
746
+ );
747
+ console.log(pc2.dim(` \u2192 ${tools.map((t) => t).join(", ")}`));
748
+ });
749
+
750
+ // src/commands/list.ts
751
+ import { Command as Command3 } from "commander";
752
+ import pc3 from "picocolors";
753
+ var listCommand = new Command3("list").description("List available or installed packs").option("--installed", "Show only installed packs").option("--cwd <dir>", "Project directory").action((opts) => {
754
+ const projectDir = opts.cwd ? opts.cwd : process.cwd();
755
+ if (opts.installed) {
756
+ const config2 = readConfig(projectDir);
757
+ if (!config2 || Object.keys(config2.packs).length === 0) {
758
+ console.log(pc3.dim("No packs installed. Run ") + pc3.cyan("ai-kit init") + pc3.dim(" to get started."));
759
+ return;
760
+ }
761
+ console.log(pc3.bold("Installed packs:\n"));
762
+ for (const [name, pack] of Object.entries(config2.packs)) {
763
+ const parts = Object.entries(pack.components).filter(([, v]) => v && v.length > 0).map(([type, v]) => `${v[0].replace(" files", "")} ${type}`);
764
+ console.log(` ${pc3.green(name)} ${pc3.dim(`v${pack.version}`)} ${pc3.dim(parts.join(", "))}`);
765
+ }
766
+ console.log(`
767
+ ${pc3.dim(`Tools: ${config2.tools.join(", ")}`)}`);
768
+ return;
769
+ }
770
+ let packs;
771
+ try {
772
+ packs = discoverPacks();
773
+ } catch {
774
+ console.error(pc3.red("Could not find marketplace.json."));
775
+ process.exit(1);
776
+ }
777
+ const config = readConfig(projectDir);
778
+ const installedNames = config ? Object.keys(config.packs) : [];
779
+ console.log(pc3.bold("Available packs:\n"));
780
+ for (const pack of packs) {
781
+ const installed = installedNames.includes(pack.name);
782
+ const badge = installed ? pc3.green(" [installed]") : "";
783
+ const counts = [];
784
+ if (pack.agents.length) counts.push(`${pack.agents.length} agents`);
785
+ if (pack.skills.length) counts.push(`${pack.skills.length} skills`);
786
+ if (pack.commands.length) counts.push(`${pack.commands.length} commands`);
787
+ if (pack.rules.length) counts.push(`${pack.rules.length} rules`);
788
+ if (pack.hooks.length) counts.push(`${pack.hooks.length} hooks`);
789
+ console.log(
790
+ ` ${pc3.cyan(pack.name)}${badge} ${pc3.dim(`v${pack.version}`)}
791
+ ${pack.description}
792
+ ${pc3.dim(counts.join(", "))}`
793
+ );
794
+ }
795
+ console.log(`
796
+ ${pc3.dim(`${packs.length} packs available`)}`);
797
+ });
798
+
799
+ // src/commands/remove.ts
800
+ import { Command as Command4 } from "commander";
801
+ import path7 from "path";
802
+ import pc4 from "picocolors";
803
+ var removeCommand = new Command4("remove").description("Remove a pack from your project").argument("<pack>", "Pack name to remove").option("--cwd <dir>", "Project directory").action((packName, opts) => {
804
+ const projectDir = opts.cwd ? path7.resolve(opts.cwd) : process.cwd();
805
+ const config = readConfig(projectDir);
806
+ if (!config) {
807
+ console.error(pc4.red("No .ai-kit.json found."));
808
+ process.exit(1);
809
+ }
810
+ const packConfig = config.packs[packName];
811
+ if (!packConfig) {
812
+ console.error(
813
+ pc4.red(`Pack "${packName}" is not installed.`) + "\nInstalled: " + Object.keys(config.packs).join(", ")
814
+ );
815
+ process.exit(1);
816
+ }
817
+ const files = packConfig.files ?? [];
818
+ if (files.length === 0) {
819
+ console.log(pc4.yellow(`No tracked files for "${packName}". Removing from config only.`));
820
+ } else {
821
+ const removed = removePack2(projectDir, files);
822
+ console.log(pc4.green(`Removed ${removed} file(s) for ${packName}`));
823
+ }
824
+ const updated = removePack(config, packName);
825
+ writeConfig(projectDir, updated);
826
+ console.log(pc4.dim("Config updated."));
827
+ });
828
+
829
+ // src/commands/update.ts
830
+ import { Command as Command5 } from "commander";
831
+ import path8 from "path";
832
+ import pc5 from "picocolors";
833
+ var updateCommand = new Command5("update").description("Update installed packs").argument("[pack]", "Pack name (omit to update all)").option("--cwd <dir>", "Project directory").action((packName, opts) => {
834
+ const projectDir = opts.cwd ? path8.resolve(opts.cwd) : process.cwd();
835
+ const config = readConfig(projectDir);
836
+ if (!config) {
837
+ console.error(pc5.red("No .ai-kit.json found. Run ") + pc5.cyan("ai-kit init") + pc5.red(" first."));
838
+ process.exit(1);
839
+ }
840
+ const packsToUpdate = packName ? [packName] : Object.keys(config.packs);
841
+ if (packsToUpdate.length === 0) {
842
+ console.log(pc5.dim("No packs installed."));
843
+ return;
844
+ }
845
+ let updated = 0;
846
+ let skipped = 0;
847
+ let hadSkills = false;
848
+ for (const name of packsToUpdate) {
849
+ const installed = config.packs[name];
850
+ if (!installed) {
851
+ console.log(pc5.yellow(`"${name}" is not installed, skipping.`));
852
+ skipped++;
853
+ continue;
854
+ }
855
+ const available = findPack(name);
856
+ if (!available) {
857
+ console.log(pc5.yellow(`"${name}" not found in marketplace, skipping.`));
858
+ skipped++;
859
+ continue;
860
+ }
861
+ if (available.version === installed.version) {
862
+ console.log(pc5.dim(` ${name} v${installed.version} \u2014 already up to date`));
863
+ skipped++;
864
+ continue;
865
+ }
866
+ if (installed.files && installed.files.length > 0) {
867
+ removePack2(projectDir, installed.files);
868
+ }
869
+ const result = installPack({
870
+ pack: available,
871
+ tools: config.tools,
872
+ projectDir
873
+ });
874
+ const merged = mergeInstall(config, result);
875
+ merged.packs[name].version = available.version;
876
+ Object.assign(config, merged);
877
+ if (result.installed.skills > 0) hadSkills = true;
878
+ console.log(
879
+ pc5.green(` ${name}`) + ` ${pc5.dim(installed.version)} \u2192 ${pc5.cyan(available.version)}`
880
+ );
881
+ updated++;
882
+ }
883
+ writeConfig(projectDir, config);
884
+ updateGitignore(projectDir, getWrittenDirs(config.tools, hadSkills));
885
+ if (updated > 0) {
886
+ console.log(pc5.green(`
887
+ Updated ${updated} pack(s).`));
888
+ }
889
+ if (skipped > 0 && updated === 0) {
890
+ console.log(pc5.dim("\nAll packs are up to date."));
891
+ }
892
+ });
893
+
894
+ // src/index.ts
895
+ var program = new Command6().name("ai-kit").description("AI coding tool pack manager").version("0.0.1");
896
+ program.addCommand(initCommand);
897
+ program.addCommand(addCommand);
898
+ program.addCommand(listCommand);
899
+ program.addCommand(removeCommand);
900
+ program.addCommand(updateCommand);
901
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@smicolon/ai-kit",
3
+ "version": "0.0.1",
4
+ "description": "AI coding tool pack manager for Smicolon standards",
5
+ "type": "module",
6
+ "bin": {
7
+ "ai-kit": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": ["dist", "packs"],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "dependencies": {
20
+ "@clack/prompts": "^1.0.1",
21
+ "commander": "^14.0.3",
22
+ "picocolors": "^1.1.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.2.3",
26
+ "tsup": "^8.5.1",
27
+ "typescript": "^5.9.3"
28
+ }
29
+ }