@ez-cook/nano-peaces 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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,665 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/constants.ts
7
+ var VERSION = "0.1.0";
8
+ var CONFIG_DIR = ".nano-peaces";
9
+ var CONFIG_FILE = "config.json";
10
+ var CONTEXT_FILE = "context.json";
11
+ var AGENT_TYPES = ["antigravity", "claude-code", "cursor", "generic"];
12
+
13
+ // src/commands/init.ts
14
+ import * as p from "@clack/prompts";
15
+
16
+ // src/fingerprint/detect.ts
17
+ import fs from "fs";
18
+ import path from "path";
19
+ function readPackageJson(projectRoot) {
20
+ const pkgPath = path.join(projectRoot, "package.json");
21
+ if (!fs.existsSync(pkgPath)) return null;
22
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
23
+ }
24
+ function getAllDeps(pkg) {
25
+ return [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})];
26
+ }
27
+ function hasDep(deps, name) {
28
+ return deps.some((d) => d === name || d.startsWith(name.replace("*", "")));
29
+ }
30
+ function hasAnyDep(deps, names) {
31
+ return names.some((name) => hasDep(deps, name));
32
+ }
33
+ function fileExists(projectRoot, ...parts) {
34
+ return fs.existsSync(path.join(projectRoot, ...parts));
35
+ }
36
+ function detectFramework(deps) {
37
+ if (hasDep(deps, "next")) return "nextjs";
38
+ if (hasDep(deps, "astro")) return "astro";
39
+ if (hasDep(deps, "@remix-run/react") || hasDep(deps, "@remix-run/node")) return "remix";
40
+ if (hasDep(deps, "vite")) return "vite";
41
+ if (hasDep(deps, "react-scripts")) return "cra";
42
+ return "unknown";
43
+ }
44
+ function detectUILibraries(deps, projectRoot) {
45
+ const libs = [];
46
+ const hasShadcnSignals = hasAnyDep(deps, ["radix-ui", "@radix-ui/react-slot", "@radix-ui/react-dialog"]) && hasDep(deps, "tailwindcss");
47
+ const hasComponentsJson = fileExists(projectRoot, "components.json");
48
+ if (hasComponentsJson || hasShadcnSignals) {
49
+ libs.push("shadcn");
50
+ }
51
+ if (hasDep(deps, "@carbon/react")) libs.push("carbon");
52
+ if (hasDep(deps, "@mantine/core")) libs.push("mantine");
53
+ if (hasDep(deps, "@headlessui/react")) libs.push("headless-ui");
54
+ if (hasDep(deps, "daisyui")) libs.push("daisyui");
55
+ if (hasDep(deps, "@ark-ui/react")) libs.push("ark-ui");
56
+ return libs;
57
+ }
58
+ function detectStyling(deps) {
59
+ if (hasDep(deps, "tailwindcss")) return "tailwind";
60
+ if (hasDep(deps, "styled-components") || hasDep(deps, "@emotion/styled"))
61
+ return "styled-components";
62
+ if (hasDep(deps, "sass") || hasDep(deps, "node-sass")) return "sass";
63
+ return "unknown";
64
+ }
65
+ function detectLanguage(projectRoot) {
66
+ if (fileExists(projectRoot, "tsconfig.json")) return "typescript";
67
+ return "javascript";
68
+ }
69
+ function detectRouter(deps, projectRoot) {
70
+ if (hasDep(deps, "next")) {
71
+ if (fileExists(projectRoot, "app") || fileExists(projectRoot, "src", "app")) return "app-router";
72
+ if (fileExists(projectRoot, "pages") || fileExists(projectRoot, "src", "pages"))
73
+ return "pages-router";
74
+ return "app-router";
75
+ }
76
+ if (hasDep(deps, "@tanstack/react-router")) return "tanstack-router";
77
+ if (hasDep(deps, "react-router") || hasDep(deps, "react-router-dom")) return "react-router";
78
+ return "unknown";
79
+ }
80
+ function detectStateManagement(deps) {
81
+ const state = [];
82
+ if (hasDep(deps, "zustand")) state.push("zustand");
83
+ if (hasDep(deps, "jotai")) state.push("jotai");
84
+ if (hasDep(deps, "@reduxjs/toolkit") || hasDep(deps, "redux")) state.push("redux");
85
+ if (hasDep(deps, "recoil")) state.push("recoil");
86
+ if (hasDep(deps, "valtio")) state.push("valtio");
87
+ return state;
88
+ }
89
+ function detectFormLibrary(deps) {
90
+ if (hasDep(deps, "react-hook-form")) return "react-hook-form";
91
+ if (hasDep(deps, "formik")) return "formik";
92
+ return null;
93
+ }
94
+ function recommendSkills(fingerprint) {
95
+ const skills = [];
96
+ if (fingerprint.uiLibraries.includes("shadcn")) {
97
+ skills.push("shadcn-ui");
98
+ }
99
+ if (fingerprint.uiLibraries.includes("carbon")) {
100
+ skills.push("carbon-design");
101
+ }
102
+ if (fingerprint.uiLibraries.length > 1) {
103
+ skills.push("ui-selector");
104
+ }
105
+ return skills;
106
+ }
107
+ function createFingerprint(projectRoot) {
108
+ const pkg = readPackageJson(projectRoot);
109
+ if (!pkg) {
110
+ return {
111
+ framework: "unknown",
112
+ uiLibraries: [],
113
+ styling: "unknown",
114
+ language: detectLanguage(projectRoot),
115
+ router: "unknown",
116
+ stateManagement: [],
117
+ formLibrary: null,
118
+ recommendedSkills: []
119
+ };
120
+ }
121
+ const deps = getAllDeps(pkg);
122
+ const base = {
123
+ framework: detectFramework(deps),
124
+ uiLibraries: detectUILibraries(deps, projectRoot),
125
+ styling: detectStyling(deps),
126
+ language: detectLanguage(projectRoot),
127
+ router: detectRouter(deps, projectRoot),
128
+ stateManagement: detectStateManagement(deps),
129
+ formLibrary: detectFormLibrary(deps)
130
+ };
131
+ return {
132
+ ...base,
133
+ recommendedSkills: recommendSkills(base)
134
+ };
135
+ }
136
+
137
+ // src/adapters/antigravity.ts
138
+ import path2 from "path";
139
+ var AntigravityAdapter = class {
140
+ name = "antigravity";
141
+ getOutputDir(_projectRoot) {
142
+ return ".agent/skills";
143
+ }
144
+ transform(source) {
145
+ const outputs = [];
146
+ const baseDir = path2.join(".agent", "skills", source.id);
147
+ outputs.push({
148
+ path: path2.join(baseDir, "SKILL.md"),
149
+ content: source.skillMd
150
+ });
151
+ for (const [chunkPath, chunkContent] of source.chunks) {
152
+ outputs.push({
153
+ path: path2.join(baseDir, chunkPath),
154
+ content: chunkContent
155
+ });
156
+ }
157
+ const registryOutput = {
158
+ path: path2.join(".agent", "skills", "registry.json"),
159
+ content: JSON.stringify(
160
+ {
161
+ version: "0.1.0",
162
+ skills: [
163
+ {
164
+ id: source.registry.id,
165
+ description: source.registry.description,
166
+ skillPath: `${source.id}/SKILL.md`,
167
+ signals: source.registry.signals,
168
+ priority: source.registry.priority
169
+ }
170
+ ]
171
+ },
172
+ null,
173
+ 2
174
+ )
175
+ };
176
+ outputs.push(registryOutput);
177
+ return outputs;
178
+ }
179
+ getInstructions() {
180
+ return [
181
+ "\u2713 Skills installed to .agent/skills/",
182
+ "",
183
+ "Antigravity will automatically detect and load these skills.",
184
+ "The agent reads registry.json first, then loads SKILL.md on match.",
185
+ "Deep chunks are loaded on-demand based on the Context Router table."
186
+ ].join("\n");
187
+ }
188
+ };
189
+
190
+ // src/adapters/claude-code.ts
191
+ var ClaudeCodeAdapter = class {
192
+ name = "claude-code";
193
+ getOutputDir(_projectRoot) {
194
+ return ".";
195
+ }
196
+ transform(source) {
197
+ const sections = [];
198
+ sections.push(`## UI Skills: ${source.id}
199
+ `);
200
+ sections.push(stripFrontmatter(source.skillMd));
201
+ for (const [chunkPath, chunkContent] of source.chunks) {
202
+ sections.push(`
203
+ ---
204
+
205
+ <!-- ${chunkPath} -->
206
+ `);
207
+ sections.push(chunkContent);
208
+ }
209
+ return [
210
+ {
211
+ path: "CLAUDE.md",
212
+ content: sections.join("\n")
213
+ }
214
+ ];
215
+ }
216
+ getInstructions() {
217
+ return [
218
+ "\u2713 Skills appended to CLAUDE.md",
219
+ "",
220
+ "Claude Code reads CLAUDE.md automatically on every interaction.",
221
+ "All skill content has been inlined into a single document."
222
+ ].join("\n");
223
+ }
224
+ };
225
+ function stripFrontmatter(md) {
226
+ const match = md.match(/^---\n[\s\S]*?\n---\n(.*)$/s);
227
+ return match ? match[1].trim() : md;
228
+ }
229
+
230
+ // src/adapters/cursor.ts
231
+ var CursorAdapter = class {
232
+ name = "cursor";
233
+ getOutputDir(_projectRoot) {
234
+ return ".cursor/rules";
235
+ }
236
+ transform(source) {
237
+ const sections = [];
238
+ sections.push("---");
239
+ sections.push(`description: ${source.registry.description || source.id + " skill"}`);
240
+ sections.push(`globs: ${JSON.stringify(source.registry.signals.filePatterns || ["**/*.tsx"])}`);
241
+ sections.push("alwaysApply: false");
242
+ sections.push("---\n");
243
+ sections.push(stripFrontmatter2(source.skillMd));
244
+ for (const [, chunkContent] of source.chunks) {
245
+ sections.push(`
246
+ ---
247
+ `);
248
+ sections.push(chunkContent);
249
+ }
250
+ return [
251
+ {
252
+ path: `.cursor/rules/${source.id}.mdc`,
253
+ content: sections.join("\n")
254
+ }
255
+ ];
256
+ }
257
+ getInstructions() {
258
+ return [
259
+ "\u2713 Skills installed to .cursor/rules/",
260
+ "",
261
+ "Cursor will load rules automatically based on glob patterns.",
262
+ "Rules activate when you work on matching files."
263
+ ].join("\n");
264
+ }
265
+ };
266
+ function stripFrontmatter2(md) {
267
+ const match = md.match(/^---\n[\s\S]*?\n---\n(.*)$/s);
268
+ return match ? match[1].trim() : md;
269
+ }
270
+
271
+ // src/adapters/generic.ts
272
+ import path3 from "path";
273
+ var GenericAdapter = class {
274
+ name = "generic";
275
+ getOutputDir(_projectRoot) {
276
+ return ".ai/skills";
277
+ }
278
+ transform(source) {
279
+ const outputs = [];
280
+ const baseDir = path3.join(".ai", "skills", source.id);
281
+ outputs.push({
282
+ path: path3.join(baseDir, "SKILL.md"),
283
+ content: source.skillMd
284
+ });
285
+ for (const [chunkPath, chunkContent] of source.chunks) {
286
+ outputs.push({
287
+ path: path3.join(baseDir, chunkPath),
288
+ content: chunkContent
289
+ });
290
+ }
291
+ outputs.push({
292
+ path: path3.join(".ai", "skills", "registry.json"),
293
+ content: JSON.stringify(
294
+ {
295
+ version: "0.1.0",
296
+ skills: [
297
+ {
298
+ id: source.registry.id,
299
+ description: source.registry.description,
300
+ skillPath: `${source.id}/SKILL.md`,
301
+ signals: source.registry.signals,
302
+ priority: source.registry.priority
303
+ }
304
+ ]
305
+ },
306
+ null,
307
+ 2
308
+ )
309
+ });
310
+ return outputs;
311
+ }
312
+ getInstructions() {
313
+ return [
314
+ "\u2713 Skills installed to .ai/skills/",
315
+ "",
316
+ "Point your AI agent to .ai/skills/registry.json to discover skills.",
317
+ "Chunks are loaded on-demand based on the Context Router in SKILL.md."
318
+ ].join("\n");
319
+ }
320
+ };
321
+
322
+ // src/adapters/registry.ts
323
+ var adapters = {
324
+ antigravity: () => new AntigravityAdapter(),
325
+ "claude-code": () => new ClaudeCodeAdapter(),
326
+ cursor: () => new CursorAdapter(),
327
+ generic: () => new GenericAdapter()
328
+ };
329
+ function getAdapter(agentType) {
330
+ const factory = adapters[agentType];
331
+ if (!factory) {
332
+ throw new Error(`Unknown agent type: ${agentType}`);
333
+ }
334
+ return factory();
335
+ }
336
+
337
+ // src/utils/skills.ts
338
+ import fs2 from "fs";
339
+ import path4 from "path";
340
+ function getSkillsDir() {
341
+ let dir = path4.dirname(new URL(import.meta.url).pathname);
342
+ for (let i = 0; i < 10; i++) {
343
+ const candidate = path4.join(dir, "packages", "skills");
344
+ if (fs2.existsSync(candidate)) return candidate;
345
+ const sibling = path4.join(dir, "skills");
346
+ if (fs2.existsSync(sibling)) return sibling;
347
+ dir = path4.dirname(dir);
348
+ }
349
+ throw new Error("Could not locate skills directory. Is nano-peaces installed correctly?");
350
+ }
351
+ function loadRegistry() {
352
+ const skillsDir = getSkillsDir();
353
+ const registryPath = path4.join(skillsDir, "registry.json");
354
+ if (!fs2.existsSync(registryPath)) {
355
+ throw new Error(`Registry not found at ${registryPath}`);
356
+ }
357
+ return JSON.parse(fs2.readFileSync(registryPath, "utf-8"));
358
+ }
359
+ function loadSkillSource(skillId) {
360
+ const skillsDir = getSkillsDir();
361
+ const registry = loadRegistry();
362
+ const entry = registry.skills.find((s) => s.id === skillId);
363
+ if (!entry) {
364
+ throw new Error(`Skill "${skillId}" not found in registry.`);
365
+ }
366
+ const skillMdPath = path4.join(skillsDir, entry.skillPath);
367
+ if (!fs2.existsSync(skillMdPath)) {
368
+ throw new Error(`SKILL.md not found at ${skillMdPath}`);
369
+ }
370
+ const skillMd = fs2.readFileSync(skillMdPath, "utf-8");
371
+ const chunks = /* @__PURE__ */ new Map();
372
+ const skillDir = path4.join(skillsDir, skillId);
373
+ const chunksDir = path4.join(skillDir, "chunks");
374
+ if (fs2.existsSync(chunksDir)) {
375
+ loadChunksRecursive(chunksDir, "chunks", chunks);
376
+ }
377
+ return {
378
+ id: skillId,
379
+ skillMd,
380
+ chunks,
381
+ registry: entry
382
+ };
383
+ }
384
+ function loadChunksRecursive(dir, relativePath, chunks) {
385
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
386
+ for (const entry of entries) {
387
+ const fullPath = path4.join(dir, entry.name);
388
+ const relPath = path4.join(relativePath, entry.name);
389
+ if (entry.isDirectory()) {
390
+ loadChunksRecursive(fullPath, relPath, chunks);
391
+ } else if (entry.name.endsWith(".md")) {
392
+ chunks.set(relPath, fs2.readFileSync(fullPath, "utf-8"));
393
+ }
394
+ }
395
+ }
396
+ function getAvailableSkills() {
397
+ const registry = loadRegistry();
398
+ return registry.skills;
399
+ }
400
+
401
+ // src/utils/files.ts
402
+ import fs3 from "fs";
403
+ import path5 from "path";
404
+ function writeSkillFiles(outputs, projectRoot) {
405
+ for (const output of outputs) {
406
+ const fullPath = path5.join(projectRoot, output.path);
407
+ fs3.mkdirSync(path5.dirname(fullPath), { recursive: true });
408
+ fs3.writeFileSync(fullPath, output.content, "utf-8");
409
+ }
410
+ }
411
+ function saveConfig(config, projectRoot) {
412
+ const configDir = path5.join(projectRoot, CONFIG_DIR);
413
+ fs3.mkdirSync(configDir, { recursive: true });
414
+ fs3.writeFileSync(path5.join(configDir, CONFIG_FILE), JSON.stringify(config, null, 2), "utf-8");
415
+ }
416
+ function loadConfig(projectRoot) {
417
+ const configPath = path5.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
418
+ if (!fs3.existsSync(configPath)) return null;
419
+ return JSON.parse(fs3.readFileSync(configPath, "utf-8"));
420
+ }
421
+ function saveContext(fingerprint, projectRoot) {
422
+ const configDir = path5.join(projectRoot, CONFIG_DIR);
423
+ fs3.mkdirSync(configDir, { recursive: true });
424
+ fs3.writeFileSync(
425
+ path5.join(configDir, CONTEXT_FILE),
426
+ JSON.stringify(fingerprint, null, 2),
427
+ "utf-8"
428
+ );
429
+ }
430
+ function updateGitignore(projectRoot, _agentDir) {
431
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
432
+ const entriesToAdd = [CONFIG_DIR];
433
+ let content = "";
434
+ if (fs3.existsSync(gitignorePath)) {
435
+ content = fs3.readFileSync(gitignorePath, "utf-8");
436
+ }
437
+ const linesToAdd = [];
438
+ for (const entry of entriesToAdd) {
439
+ if (!content.includes(entry)) {
440
+ linesToAdd.push(entry);
441
+ }
442
+ }
443
+ if (linesToAdd.length > 0) {
444
+ const section = `
445
+ # nano-peaces config
446
+ ${linesToAdd.join("\n")}
447
+ `;
448
+ fs3.writeFileSync(gitignorePath, content.trimEnd() + "\n" + section, "utf-8");
449
+ }
450
+ }
451
+
452
+ // src/commands/init.ts
453
+ async function initCommand(options) {
454
+ p.intro("nano-peaces init");
455
+ const projectRoot = process.cwd();
456
+ const s = p.spinner();
457
+ s.start("Scanning project...");
458
+ const fingerprint = createFingerprint(projectRoot);
459
+ s.stop("Project scanned");
460
+ const detectedParts = [];
461
+ if (fingerprint.framework !== "unknown") detectedParts.push(fingerprint.framework);
462
+ if (fingerprint.language === "typescript") detectedParts.push("TypeScript");
463
+ if (fingerprint.styling !== "unknown") detectedParts.push(fingerprint.styling);
464
+ for (const lib of fingerprint.uiLibraries) detectedParts.push(lib);
465
+ if (detectedParts.length > 0) {
466
+ p.log.info(`Detected: ${detectedParts.join(" + ")}`);
467
+ } else {
468
+ p.log.warn("Could not detect project stack. Skills will still work.");
469
+ }
470
+ let agentType;
471
+ if (options.agent && AGENT_TYPES.includes(options.agent)) {
472
+ agentType = options.agent;
473
+ } else if (options.yes) {
474
+ agentType = "antigravity";
475
+ p.log.info(`Agent: ${agentType} (default)`);
476
+ } else {
477
+ const selected = await p.select({
478
+ message: "Which AI agent are you using?",
479
+ options: [
480
+ { value: "antigravity", label: "Antigravity", hint: ".agent/skills/" },
481
+ { value: "claude-code", label: "Claude Code", hint: "CLAUDE.md" },
482
+ { value: "cursor", label: "Cursor", hint: ".cursor/rules/" },
483
+ { value: "generic", label: "Generic", hint: ".ai/skills/" }
484
+ ]
485
+ });
486
+ if (p.isCancel(selected)) {
487
+ p.cancel("Init cancelled.");
488
+ process.exit(0);
489
+ }
490
+ agentType = selected;
491
+ }
492
+ const availableSkills = getAvailableSkills();
493
+ let selectedSkillIds;
494
+ if (options.yes) {
495
+ selectedSkillIds = fingerprint.recommendedSkills.length > 0 ? fingerprint.recommendedSkills.filter((id) => availableSkills.some((s2) => s2.id === id)) : availableSkills.map((s2) => s2.id);
496
+ p.log.info(`Skills: ${selectedSkillIds.join(", ")}`);
497
+ } else {
498
+ const skillOptions = availableSkills.map((skill) => ({
499
+ value: skill.id,
500
+ label: skill.id,
501
+ hint: skill.description || ""
502
+ }));
503
+ if (skillOptions.length === 0) {
504
+ p.log.error("No skills available. This might be an installation issue.");
505
+ process.exit(1);
506
+ }
507
+ if (skillOptions.length === 1) {
508
+ selectedSkillIds = [skillOptions[0].value];
509
+ p.log.info(`Skill: ${selectedSkillIds[0]}`);
510
+ } else {
511
+ const selected = await p.multiselect({
512
+ message: "Which skills do you want to install?",
513
+ options: skillOptions,
514
+ initialValues: fingerprint.recommendedSkills.filter(
515
+ (id) => availableSkills.some((s2) => s2.id === id)
516
+ ),
517
+ required: true
518
+ });
519
+ if (p.isCancel(selected)) {
520
+ p.cancel("Init cancelled.");
521
+ process.exit(0);
522
+ }
523
+ selectedSkillIds = selected;
524
+ }
525
+ }
526
+ const adapter = getAdapter(agentType);
527
+ s.start("Installing skills...");
528
+ for (const skillId of selectedSkillIds) {
529
+ try {
530
+ const source = loadSkillSource(skillId);
531
+ const outputs = adapter.transform(source);
532
+ writeSkillFiles(outputs, projectRoot);
533
+ } catch (err) {
534
+ s.stop(`Failed to install skill: ${skillId}`);
535
+ p.log.error(err instanceof Error ? err.message : String(err));
536
+ process.exit(1);
537
+ }
538
+ }
539
+ s.stop(`Installed ${selectedSkillIds.length} skill(s)`);
540
+ saveConfig(
541
+ {
542
+ agent: agentType,
543
+ skills: selectedSkillIds,
544
+ version: VERSION,
545
+ autoUpdate: false
546
+ },
547
+ projectRoot
548
+ );
549
+ saveContext(fingerprint, projectRoot);
550
+ updateGitignore(projectRoot, adapter.getOutputDir(projectRoot));
551
+ p.log.message(adapter.getInstructions());
552
+ p.outro("Done! Your AI agent now has UI superpowers.");
553
+ }
554
+
555
+ // src/commands/add.ts
556
+ import * as p2 from "@clack/prompts";
557
+ async function addCommand(skillId, options) {
558
+ p2.intro("nano-peaces add");
559
+ const projectRoot = process.cwd();
560
+ const config = loadConfig(projectRoot);
561
+ const availableSkills = getAvailableSkills();
562
+ let agentType;
563
+ if (options.agent && AGENT_TYPES.includes(options.agent)) {
564
+ agentType = options.agent;
565
+ } else if (config) {
566
+ agentType = config.agent;
567
+ } else {
568
+ p2.log.error("No config found. Run `nano-peaces init` first, or pass --agent.");
569
+ process.exit(1);
570
+ }
571
+ const adapter = getAdapter(agentType);
572
+ let skillIds;
573
+ if (options.all) {
574
+ const fingerprint = createFingerprint(projectRoot);
575
+ skillIds = fingerprint.recommendedSkills.length > 0 ? fingerprint.recommendedSkills.filter((id) => availableSkills.some((s2) => s2.id === id)) : availableSkills.map((s2) => s2.id);
576
+ } else if (skillId) {
577
+ if (!availableSkills.some((s2) => s2.id === skillId)) {
578
+ p2.log.error(`Skill "${skillId}" not found.`);
579
+ p2.log.info(`Available: ${availableSkills.map((s2) => s2.id).join(", ")}`);
580
+ process.exit(1);
581
+ }
582
+ skillIds = [skillId];
583
+ } else {
584
+ const selected = await p2.multiselect({
585
+ message: "Which skills do you want to add?",
586
+ options: availableSkills.map((s2) => ({
587
+ value: s2.id,
588
+ label: s2.id,
589
+ hint: s2.description || ""
590
+ })),
591
+ required: true
592
+ });
593
+ if (p2.isCancel(selected)) {
594
+ p2.cancel("Cancelled.");
595
+ process.exit(0);
596
+ }
597
+ skillIds = selected;
598
+ }
599
+ const s = p2.spinner();
600
+ s.start("Adding skills...");
601
+ for (const id of skillIds) {
602
+ try {
603
+ const source = loadSkillSource(id);
604
+ const outputs = adapter.transform(source);
605
+ writeSkillFiles(outputs, projectRoot);
606
+ } catch (err) {
607
+ s.stop(`Failed to add skill: ${id}`);
608
+ p2.log.error(err instanceof Error ? err.message : String(err));
609
+ process.exit(1);
610
+ }
611
+ }
612
+ s.stop(`Added ${skillIds.length} skill(s): ${skillIds.join(", ")}`);
613
+ if (config) {
614
+ const updatedSkills = [.../* @__PURE__ */ new Set([...config.skills, ...skillIds])];
615
+ saveConfig({ ...config, skills: updatedSkills }, projectRoot);
616
+ }
617
+ p2.outro("Done!");
618
+ }
619
+
620
+ // src/commands/list.ts
621
+ import * as p3 from "@clack/prompts";
622
+ async function listCommand() {
623
+ p3.intro("nano-peaces list");
624
+ const projectRoot = process.cwd();
625
+ const config = loadConfig(projectRoot);
626
+ const installedSkills = config?.skills ?? [];
627
+ const availableSkills = getAvailableSkills();
628
+ if (availableSkills.length === 0) {
629
+ p3.log.warn("No skills available in registry.");
630
+ p3.outro("Check your installation.");
631
+ return;
632
+ }
633
+ p3.log.info("Available skills:\n");
634
+ for (const skill of availableSkills) {
635
+ const isInstalled = installedSkills.includes(skill.id);
636
+ const status = isInstalled ? "\u25CF" : "\u25CB";
637
+ const tag = isInstalled ? " (installed)" : "";
638
+ const desc = skill.description ? ` ${skill.description}` : "";
639
+ p3.log.message(` ${status} ${skill.id}${tag}${desc}`);
640
+ }
641
+ p3.log.message("");
642
+ if (installedSkills.length > 0) {
643
+ p3.log.info(
644
+ `${installedSkills.length} installed, ${availableSkills.length - installedSkills.length} available`
645
+ );
646
+ }
647
+ p3.outro("Use `nano-peaces add <skill>` to install a skill.");
648
+ }
649
+
650
+ // src/commands/update.ts
651
+ import * as p4 from "@clack/prompts";
652
+ async function updateCommand() {
653
+ p4.intro("nano-peaces update");
654
+ p4.log.warn("Update command coming in v0.2.0");
655
+ p4.outro("Stay tuned!");
656
+ }
657
+
658
+ // src/index.ts
659
+ var program = new Command();
660
+ program.name("nano-peaces").description("The missing UI skills pack for AI agents").version(VERSION);
661
+ program.command("init").description("Initialize nano-peaces in your project").option("-a, --agent <type>", "AI agent type (antigravity|claude-code|cursor|generic)").option("-y, --yes", "Skip prompts, use defaults").action(initCommand);
662
+ program.command("add [skill-id]").description("Add a UI skill to your project").option("--all", "Add all recommended skills").option("-a, --agent <type>", "AI agent type (antigravity|claude-code|cursor|generic)").action(addCommand);
663
+ program.command("list").description("List available UI skills").action(listCommand);
664
+ program.command("update").description("Update installed skills to latest").action(updateCommand);
665
+ program.parse();