@tdsoft-tech/aikit 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,4901 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/utils/paths.ts
22
+ import { homedir } from "os";
23
+ import { join } from "path";
24
+ import { existsSync } from "fs";
25
+ var paths;
26
+ var init_paths = __esm({
27
+ "src/utils/paths.ts"() {
28
+ "use strict";
29
+ init_esm_shims();
30
+ paths = {
31
+ /**
32
+ * Get the global AIKit configuration directory
33
+ * ~/.config/aikit/ on Unix, %APPDATA%/aikit/ on Windows
34
+ */
35
+ globalConfig() {
36
+ const base = process.platform === "win32" ? process.env.APPDATA || join(homedir(), "AppData", "Roaming") : join(homedir(), ".config");
37
+ return join(base, "aikit");
38
+ },
39
+ /**
40
+ * Get the project-level AIKit configuration directory
41
+ */
42
+ projectConfig(projectPath) {
43
+ const base = projectPath || process.cwd();
44
+ return join(base, ".aikit");
45
+ },
46
+ /**
47
+ * Get the OpenCode configuration directory
48
+ */
49
+ opencodeConfig() {
50
+ const base = process.platform === "win32" ? process.env.APPDATA || join(homedir(), "AppData", "Roaming") : join(homedir(), ".config");
51
+ return join(base, "opencode");
52
+ },
53
+ /**
54
+ * Get the Beads directory for the current project
55
+ */
56
+ beadsDir(projectPath) {
57
+ const base = projectPath || process.cwd();
58
+ return join(base, ".beads");
59
+ },
60
+ /**
61
+ * Check if a project has AIKit initialized
62
+ */
63
+ hasProjectConfig(projectPath) {
64
+ return existsSync(this.projectConfig(projectPath));
65
+ },
66
+ /**
67
+ * Check if global AIKit is initialized
68
+ */
69
+ hasGlobalConfig() {
70
+ return existsSync(this.globalConfig());
71
+ },
72
+ /**
73
+ * Get effective config path (project takes precedence over global)
74
+ */
75
+ effectiveConfig(projectPath) {
76
+ if (this.hasProjectConfig(projectPath)) {
77
+ return this.projectConfig(projectPath);
78
+ }
79
+ if (this.hasGlobalConfig()) {
80
+ return this.globalConfig();
81
+ }
82
+ return null;
83
+ },
84
+ /**
85
+ * Get skills directory
86
+ */
87
+ skills(configPath) {
88
+ return join(configPath, "skills");
89
+ },
90
+ /**
91
+ * Get agents directory
92
+ */
93
+ agents(configPath) {
94
+ return join(configPath, "agents");
95
+ },
96
+ /**
97
+ * Get commands directory
98
+ */
99
+ commands(configPath) {
100
+ return join(configPath, "commands");
101
+ },
102
+ /**
103
+ * Get tools directory
104
+ */
105
+ tools(configPath) {
106
+ return join(configPath, "tools");
107
+ },
108
+ /**
109
+ * Get plugins directory
110
+ */
111
+ plugins(configPath) {
112
+ return join(configPath, "plugins");
113
+ },
114
+ /**
115
+ * Get memory directory
116
+ */
117
+ memory(configPath) {
118
+ return join(configPath, "memory");
119
+ }
120
+ };
121
+ }
122
+ });
123
+
124
+ // src/utils/logger.ts
125
+ import chalk from "chalk";
126
+ var logger;
127
+ var init_logger = __esm({
128
+ "src/utils/logger.ts"() {
129
+ "use strict";
130
+ init_esm_shims();
131
+ logger = {
132
+ info(...args) {
133
+ console.log(chalk.blue("\u2139"), ...args);
134
+ },
135
+ success(...args) {
136
+ console.log(chalk.green("\u2713"), ...args);
137
+ },
138
+ warn(...args) {
139
+ console.log(chalk.yellow("\u26A0"), ...args);
140
+ },
141
+ error(...args) {
142
+ console.error(chalk.red("\u2716"), ...args);
143
+ },
144
+ debug(...args) {
145
+ if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
146
+ console.log(chalk.gray("\u22EF"), ...args);
147
+ }
148
+ },
149
+ step(step, total, message) {
150
+ console.log(chalk.cyan(`[${step}/${total}]`), message);
151
+ },
152
+ header(message) {
153
+ console.log(chalk.bold.underline(`
154
+ ${message}
155
+ `));
156
+ },
157
+ list(items, prefix = "\u2022") {
158
+ for (const item of items) {
159
+ console.log(` ${prefix} ${item}`);
160
+ }
161
+ }
162
+ };
163
+ }
164
+ });
165
+
166
+ // src/core/tools/figma-mcp.ts
167
+ var figma_mcp_exports = {};
168
+ __export(figma_mcp_exports, {
169
+ FigmaMcpClient: () => FigmaMcpClient
170
+ });
171
+ import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
172
+ import { join as join5 } from "path";
173
+ import { existsSync as existsSync2 } from "fs";
174
+ var FigmaMcpClient;
175
+ var init_figma_mcp = __esm({
176
+ "src/core/tools/figma-mcp.ts"() {
177
+ "use strict";
178
+ init_esm_shims();
179
+ init_logger();
180
+ FigmaMcpClient = class {
181
+ apiKey;
182
+ configManager;
183
+ constructor(apiKey, configManager) {
184
+ this.apiKey = apiKey;
185
+ this.configManager = configManager;
186
+ }
187
+ /**
188
+ * Fetch helper with simple retry/backoff for 429/5xx
189
+ */
190
+ async fetchWithRetry(url, options, label, retries = 3, backoffMs = 1500) {
191
+ let attempt = 0;
192
+ let lastError;
193
+ while (attempt <= retries) {
194
+ try {
195
+ const res = await fetch(url, options);
196
+ if (res.ok) return res;
197
+ if (res.status === 429 || res.status >= 500) {
198
+ const retryAfter = Number(res.headers.get("retry-after")) || 0;
199
+ const delay = retryAfter > 0 ? retryAfter * 1e3 : backoffMs * (attempt + 1);
200
+ logger.warn(`${label} failed (status ${res.status}), retrying in ${Math.round(delay / 1e3)}s...`);
201
+ await new Promise((r) => setTimeout(r, delay));
202
+ attempt += 1;
203
+ continue;
204
+ }
205
+ const text = await res.text();
206
+ throw new Error(`${label} error: ${res.status} ${res.statusText}
207
+ ${text}`);
208
+ } catch (err) {
209
+ lastError = err;
210
+ logger.warn(`${label} network error, attempt ${attempt + 1}/${retries + 1}: ${err instanceof Error ? err.message : String(err)}`);
211
+ if (attempt >= retries) break;
212
+ await new Promise((r) => setTimeout(r, backoffMs * (attempt + 1)));
213
+ attempt += 1;
214
+ }
215
+ }
216
+ throw lastError instanceof Error ? lastError : new Error(`${label} failed after retries`);
217
+ }
218
+ /**
219
+ * Extract file key from Figma URL
220
+ */
221
+ extractFileKey(url) {
222
+ const match = url.match(/figma\.com\/design\/([a-zA-Z0-9]+)/);
223
+ return match ? match[1] : null;
224
+ }
225
+ /**
226
+ * Extract node ID from Figma URL
227
+ */
228
+ extractNodeId(url) {
229
+ const match = url.match(/[?&]node-id=([^&]+)/);
230
+ if (!match) return null;
231
+ let nodeId = decodeURIComponent(match[1]);
232
+ if (nodeId.includes("-") && !nodeId.includes(":")) {
233
+ nodeId = nodeId.replace(/-/g, ":");
234
+ }
235
+ return nodeId;
236
+ }
237
+ /**
238
+ * Get Figma file data using API
239
+ */
240
+ async getFileData(url) {
241
+ const fileKey = this.extractFileKey(url);
242
+ if (!fileKey) {
243
+ throw new Error(`Invalid Figma URL: ${url}`);
244
+ }
245
+ const nodeId = this.extractNodeId(url);
246
+ const apiUrl = nodeId ? `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${encodeURIComponent(nodeId)}` : `https://api.figma.com/v1/files/${fileKey}`;
247
+ const response = await this.fetchWithRetry(apiUrl, {
248
+ headers: {
249
+ "X-Figma-Token": this.apiKey
250
+ }
251
+ }, "Figma file fetch");
252
+ const data = await response.json();
253
+ if (nodeId) {
254
+ const nodes = data.nodes;
255
+ const nodeData = Object.values(nodes)[0];
256
+ if (!nodeData) {
257
+ throw new Error(`Node not found: ${nodeId}`);
258
+ }
259
+ return {
260
+ document: nodeData.document,
261
+ components: {},
262
+ styles: {}
263
+ };
264
+ }
265
+ return data;
266
+ }
267
+ /**
268
+ * Extract design tokens from Figma file
269
+ */
270
+ async extractDesignTokens(url, downloadAssets = true, assetsDir) {
271
+ const fileData = await this.getFileData(url);
272
+ const fileKey = this.extractFileKey(url);
273
+ if (!fileKey) {
274
+ throw new Error(`Invalid Figma URL: ${url}`);
275
+ }
276
+ const tokens = {
277
+ colors: [],
278
+ typography: [],
279
+ spacing: {
280
+ unit: 8,
281
+ // Default 8px grid
282
+ scale: []
283
+ },
284
+ components: [],
285
+ screens: [],
286
+ breakpoints: [375, 768, 1024, 1280, 1920]
287
+ // Common breakpoints
288
+ };
289
+ const colorMap = /* @__PURE__ */ new Map();
290
+ this.extractColors(fileData.document, colorMap);
291
+ tokens.colors = Array.from(colorMap.entries()).map(([name, hex]) => ({
292
+ name,
293
+ hex,
294
+ rgba: hex
295
+ // Simplified
296
+ }));
297
+ const typographyMap = /* @__PURE__ */ new Map();
298
+ this.extractTypography(fileData.document, typographyMap);
299
+ tokens.typography = Array.from(typographyMap.values());
300
+ Object.values(fileData.components).forEach((component) => {
301
+ tokens.components.push({
302
+ name: component.name,
303
+ type: "component",
304
+ description: component.description
305
+ });
306
+ });
307
+ this.extractScreens(fileData.document, tokens.screens);
308
+ tokens.structure = this.extractStructure(fileData.document);
309
+ if (downloadAssets && tokens.structure) {
310
+ try {
311
+ tokens.assets = await this.downloadAssets(
312
+ fileKey,
313
+ fileData.document,
314
+ assetsDir || "./assets/images"
315
+ );
316
+ } catch (error) {
317
+ logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
318
+ }
319
+ }
320
+ return tokens;
321
+ }
322
+ /**
323
+ * Recursively extract colors from nodes
324
+ */
325
+ extractColors(node, colorMap) {
326
+ if (node.fills && Array.isArray(node.fills)) {
327
+ node.fills.forEach((fill) => {
328
+ if (fill.type === "SOLID" && fill.color) {
329
+ const { r, g, b, a } = fill.color;
330
+ const hex = this.rgbaToHex(r, g, b, a);
331
+ const name = node.name || "Color";
332
+ if (!colorMap.has(hex)) {
333
+ colorMap.set(hex, hex);
334
+ }
335
+ }
336
+ });
337
+ }
338
+ if (node.strokes && Array.isArray(node.strokes)) {
339
+ node.strokes.forEach((stroke) => {
340
+ if (stroke.type === "SOLID" && stroke.color) {
341
+ const { r, g, b, a } = stroke.color;
342
+ const hex = this.rgbaToHex(r, g, b, a);
343
+ if (!colorMap.has(hex)) {
344
+ colorMap.set(hex, hex);
345
+ }
346
+ }
347
+ });
348
+ }
349
+ if (node.children) {
350
+ node.children.forEach((child) => this.extractColors(child, colorMap));
351
+ }
352
+ }
353
+ /**
354
+ * Recursively extract typography from nodes
355
+ */
356
+ extractTypography(node, typographyMap) {
357
+ if (node.type === "TEXT" && node.style) {
358
+ const key = `${node.style.fontFamily}-${node.style.fontSize}-${node.style.fontWeight}`;
359
+ if (!typographyMap.has(key)) {
360
+ typographyMap.set(key, {
361
+ name: `${node.style.fontSize}px ${node.style.fontFamily}`,
362
+ fontFamily: node.style.fontFamily || "Inter",
363
+ fontSize: node.style.fontSize || 16,
364
+ fontWeight: node.style.fontWeight || 400,
365
+ lineHeight: node.style.lineHeightPx || node.style.fontSize || 16,
366
+ letterSpacing: node.style.letterSpacing
367
+ });
368
+ }
369
+ }
370
+ if (node.children) {
371
+ node.children.forEach((child) => this.extractTypography(child, typographyMap));
372
+ }
373
+ }
374
+ /**
375
+ * Extract screens/frames with detailed information
376
+ */
377
+ extractScreens(node, screens) {
378
+ if (node.type === "FRAME" || node.type === "COMPONENT") {
379
+ if (node.absoluteBoundingBox) {
380
+ const isMainScreen = node.absoluteBoundingBox.width >= 800 && node.absoluteBoundingBox.height >= 400;
381
+ if (isMainScreen) {
382
+ screens.push({
383
+ id: node.id,
384
+ name: node.name,
385
+ width: node.absoluteBoundingBox.width,
386
+ height: node.absoluteBoundingBox.height,
387
+ type: node.type,
388
+ childrenCount: node.children?.length || 0
389
+ });
390
+ }
391
+ }
392
+ }
393
+ if (node.children) {
394
+ node.children.forEach((child) => this.extractScreens(child, screens));
395
+ }
396
+ }
397
+ /**
398
+ * Extract structure, content, and layout from nodes
399
+ */
400
+ extractStructure(node, depth = 0) {
401
+ const nodes = [];
402
+ const hierarchyLines = [];
403
+ const processNode = (n, level = 0) => {
404
+ const indent = " ".repeat(level);
405
+ const childIds = [];
406
+ const nodeData = {
407
+ id: n.id,
408
+ name: n.name || "Unnamed",
409
+ type: n.type
410
+ };
411
+ if (n.absoluteBoundingBox) {
412
+ nodeData.position = {
413
+ x: n.absoluteBoundingBox.x,
414
+ y: n.absoluteBoundingBox.y,
415
+ width: n.absoluteBoundingBox.width,
416
+ height: n.absoluteBoundingBox.height
417
+ };
418
+ }
419
+ if (n.type === "TEXT" && n.characters) {
420
+ nodeData.content = n.characters;
421
+ }
422
+ const styles = {};
423
+ if (n.fills && Array.isArray(n.fills)) {
424
+ const solidFill = n.fills.find((f) => f.type === "SOLID" && f.color);
425
+ if (solidFill && solidFill.color) {
426
+ const { r, g, b, a } = solidFill.color;
427
+ styles.backgroundColor = this.rgbaToHex(r, g, b, a);
428
+ }
429
+ }
430
+ if (n.style) {
431
+ if (n.style.fontFamily) styles.fontFamily = n.style.fontFamily;
432
+ if (n.style.fontSize) styles.fontSize = n.style.fontSize;
433
+ if (n.style.fontWeight) styles.fontWeight = n.style.fontWeight;
434
+ if (n.style.lineHeightPx) styles.lineHeight = n.style.lineHeightPx;
435
+ if (n.fills && Array.isArray(n.fills)) {
436
+ const textFill = n.fills.find((f) => f.type === "SOLID" && f.color);
437
+ if (textFill && textFill.color) {
438
+ const { r, g, b, a } = textFill.color;
439
+ styles.color = this.rgbaToHex(r, g, b, a);
440
+ }
441
+ }
442
+ }
443
+ if (n.layoutMode) {
444
+ styles.layout = n.layoutMode;
445
+ }
446
+ if (n.itemSpacing !== void 0) {
447
+ styles.gap = n.itemSpacing;
448
+ }
449
+ if (n.paddingLeft || n.paddingRight || n.paddingTop || n.paddingBottom) {
450
+ styles.padding = {
451
+ top: n.paddingTop || 0,
452
+ right: n.paddingRight || 0,
453
+ bottom: n.paddingBottom || 0,
454
+ left: n.paddingLeft || 0
455
+ };
456
+ }
457
+ if (Object.keys(styles).length > 0) {
458
+ nodeData.styles = styles;
459
+ }
460
+ if (n.children && n.children.length > 0) {
461
+ n.children.forEach((child) => {
462
+ const childNodeIds = processNode(child, level + 1);
463
+ childIds.push(child.id);
464
+ childIds.push(...childNodeIds);
465
+ });
466
+ nodeData.children = n.children.map((c) => c.id);
467
+ }
468
+ nodes.push(nodeData);
469
+ const typeLabel = n.type.toLowerCase();
470
+ const nameLabel = n.name || "Unnamed";
471
+ const contentPreview = n.type === "TEXT" && n.characters ? `: "${n.characters.substring(0, 50)}${n.characters.length > 50 ? "..." : ""}"` : "";
472
+ const sizeLabel = n.absoluteBoundingBox ? ` [${Math.round(n.absoluteBoundingBox.width)}\xD7${Math.round(n.absoluteBoundingBox.height)}]` : "";
473
+ hierarchyLines.push(`${indent}${typeLabel} "${nameLabel}"${contentPreview}${sizeLabel}`);
474
+ return [n.id, ...childIds];
475
+ };
476
+ processNode(node, depth);
477
+ return {
478
+ nodes,
479
+ hierarchy: hierarchyLines.join("\n")
480
+ };
481
+ }
482
+ /**
483
+ * Find all nodes that can be exported as images (optionally filtered by screen)
484
+ */
485
+ findImageNodes(node, imageNodes = [], screenId, isWithinScreen = false) {
486
+ const currentIsScreen = node.id === screenId;
487
+ const nowWithinScreen = isWithinScreen || currentIsScreen;
488
+ if (screenId && !nowWithinScreen && node.type !== "PAGE") {
489
+ if (node.children) {
490
+ node.children.forEach((child) => this.findImageNodes(child, imageNodes, screenId, false));
491
+ }
492
+ return;
493
+ }
494
+ const exportableTypes = ["VECTOR", "COMPONENT", "INSTANCE", "FRAME", "GROUP", "RECTANGLE", "ELLIPSE"];
495
+ const hasImageFill = node.fills?.some((fill) => fill.type === "IMAGE" || fill.imageRef);
496
+ const isExportable = exportableTypes.includes(node.type) || hasImageFill;
497
+ if (isExportable && node.absoluteBoundingBox) {
498
+ const minSize = 16;
499
+ if (node.absoluteBoundingBox.width >= minSize && node.absoluteBoundingBox.height >= minSize) {
500
+ imageNodes.push({
501
+ id: node.id,
502
+ name: node.name || "Unnamed",
503
+ type: node.type,
504
+ width: node.absoluteBoundingBox.width,
505
+ height: node.absoluteBoundingBox.height
506
+ });
507
+ }
508
+ }
509
+ if (node.children) {
510
+ node.children.forEach((child) => this.findImageNodes(child, imageNodes, screenId, nowWithinScreen));
511
+ }
512
+ }
513
+ /**
514
+ * Download images/assets from Figma (optionally filtered by screen)
515
+ */
516
+ async downloadAssets(fileKey, rootNode, assetsDir, screenId) {
517
+ const imageNodes = [];
518
+ this.findImageNodes(rootNode, imageNodes, screenId);
519
+ if (imageNodes.length === 0) {
520
+ logger.info("No image nodes found to download");
521
+ return [];
522
+ }
523
+ logger.info(`Found ${imageNodes.length} image nodes to download`);
524
+ const nodesToDownload = imageNodes.slice(0, 50);
525
+ const nodeIds = nodesToDownload.map((n) => n.id).join(",");
526
+ const imageUrl = `https://api.figma.com/v1/images/${fileKey}?ids=${encodeURIComponent(nodeIds)}&format=png&scale=2`;
527
+ const response = await this.fetchWithRetry(imageUrl, {
528
+ headers: {
529
+ "X-Figma-Token": this.apiKey
530
+ }
531
+ }, "Figma images listing");
532
+ const imageData = await response.json();
533
+ const images = imageData.images;
534
+ const fullAssetsDir = assetsDir.startsWith("/") ? assetsDir : join5(process.cwd(), assetsDir);
535
+ if (!existsSync2(fullAssetsDir)) {
536
+ await mkdir3(fullAssetsDir, { recursive: true });
537
+ }
538
+ const downloadedAssets = [];
539
+ for (const node of nodesToDownload) {
540
+ const imageUrl2 = images[node.id];
541
+ if (!imageUrl2) {
542
+ logger.warn(`No image URL returned for node ${node.id} (${node.name})`);
543
+ continue;
544
+ }
545
+ try {
546
+ const imageResponse = await this.fetchWithRetry(
547
+ imageUrl2,
548
+ {},
549
+ `Download image ${node.id}`
550
+ );
551
+ const imageBuffer = await imageResponse.arrayBuffer();
552
+ const safeName = node.name.replace(/[^a-z0-9]/gi, "_").toLowerCase().substring(0, 50);
553
+ const extension = "png";
554
+ const filename = `${safeName}_${node.id.substring(0, 8)}.${extension}`;
555
+ const filePath = join5(fullAssetsDir, filename);
556
+ await writeFile3(filePath, Buffer.from(imageBuffer));
557
+ downloadedAssets.push({
558
+ nodeId: node.id,
559
+ nodeName: node.name,
560
+ nodeType: node.type,
561
+ format: extension,
562
+ path: filePath,
563
+ url: imageUrl2,
564
+ width: node.width,
565
+ height: node.height
566
+ });
567
+ logger.info(`Downloaded: ${filename} (${node.name})`);
568
+ } catch (error) {
569
+ logger.warn(`Error downloading image for node ${node.id}: ${error instanceof Error ? error.message : String(error)}`);
570
+ }
571
+ }
572
+ logger.info(`Downloaded ${downloadedAssets.length} assets to ${fullAssetsDir}`);
573
+ return downloadedAssets;
574
+ }
575
+ /**
576
+ * Convert RGBA to hex
577
+ */
578
+ rgbaToHex(r, g, b, a = 1) {
579
+ const toHex = (n) => {
580
+ const hex = Math.round(n * 255).toString(16);
581
+ return hex.length === 1 ? "0" + hex : hex;
582
+ };
583
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}${a < 1 ? toHex(a) : ""}`;
584
+ }
585
+ };
586
+ }
587
+ });
588
+
589
+ // src/core/tools/figma-screen-developer.ts
590
+ var figma_screen_developer_exports = {};
591
+ __export(figma_screen_developer_exports, {
592
+ checkCurrentCodeStatus: () => checkCurrentCodeStatus,
593
+ compareCodeWithFigma: () => compareCodeWithFigma
594
+ });
595
+ import { readFile as readFile4, readdir as readdir3 } from "fs/promises";
596
+ import { join as join6 } from "path";
597
+ import { existsSync as existsSync3 } from "fs";
598
+ async function checkCurrentCodeStatus(projectPath = process.cwd()) {
599
+ const status = {
600
+ hasHTML: false,
601
+ htmlFile: void 0,
602
+ hasCSS: false,
603
+ cssFiles: [],
604
+ hasAssets: false,
605
+ assetCount: 0,
606
+ sections: []
607
+ };
608
+ try {
609
+ const htmlFiles = ["index.html", "index.htm", "main.html"].filter(
610
+ (file) => existsSync3(join6(projectPath, file))
611
+ );
612
+ if (htmlFiles.length > 0) {
613
+ status.hasHTML = true;
614
+ status.htmlFile = htmlFiles[0];
615
+ try {
616
+ const htmlContent = await readFile4(join6(projectPath, htmlFiles[0]), "utf-8");
617
+ const sectionMatches = htmlContent.match(/<(section|div|header|footer|main|article|aside)[^>]*(?:id|class)=["']([^"']+)["']/gi);
618
+ if (sectionMatches) {
619
+ status.sections = sectionMatches.map((match) => {
620
+ const idMatch = match.match(/id=["']([^"']+)["']/i);
621
+ const classMatch = match.match(/class=["']([^"']+)["']/i);
622
+ return idMatch ? idMatch[1] : classMatch ? classMatch[1].split(" ")[0] : "";
623
+ }).filter(Boolean);
624
+ }
625
+ } catch (e) {
626
+ }
627
+ }
628
+ const stylesDir = join6(projectPath, "styles");
629
+ if (existsSync3(stylesDir)) {
630
+ try {
631
+ const files = await readdir3(stylesDir);
632
+ const cssFiles = files.filter((f) => f.endsWith(".css"));
633
+ if (cssFiles.length > 0) {
634
+ status.hasCSS = true;
635
+ status.cssFiles = cssFiles.map((f) => join6(stylesDir, f));
636
+ }
637
+ } catch (e) {
638
+ }
639
+ }
640
+ const assetsDir = join6(projectPath, "assets", "images");
641
+ if (existsSync3(assetsDir)) {
642
+ try {
643
+ const files = await readdir3(assetsDir);
644
+ const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|svg|webp)$/i.test(f));
645
+ if (imageFiles.length > 0) {
646
+ status.hasAssets = true;
647
+ status.assetCount = imageFiles.length;
648
+ }
649
+ } catch (e) {
650
+ }
651
+ }
652
+ } catch (error) {
653
+ logger.warn(`Error checking code status: ${error instanceof Error ? error.message : String(error)}`);
654
+ }
655
+ return status;
656
+ }
657
+ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd()) {
658
+ const codeStatus = await checkCurrentCodeStatus(projectPath);
659
+ const result = {
660
+ missingSections: [],
661
+ missingAssets: [],
662
+ needsUpdate: false,
663
+ recommendations: []
664
+ };
665
+ const selectedScreen = figmaTokens.screens?.find((s) => s.id === selectedScreenId);
666
+ if (!selectedScreen) {
667
+ result.recommendations.push("Selected screen not found in Figma design");
668
+ return result;
669
+ }
670
+ const figmaSections = [];
671
+ if (figmaTokens.structure?.nodes) {
672
+ const screenNode = figmaTokens.structure.nodes.find((n) => n.id === selectedScreenId);
673
+ if (screenNode?.children) {
674
+ screenNode.children.forEach((childId) => {
675
+ const childNode = figmaTokens.structure.nodes.find((n) => n.id === childId);
676
+ if (childNode && (childNode.type === "FRAME" || childNode.type === "COMPONENT")) {
677
+ const sectionName = childNode.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-");
678
+ figmaSections.push(sectionName);
679
+ }
680
+ });
681
+ }
682
+ }
683
+ const existingSections = codeStatus.sections.map((s) => s.toLowerCase());
684
+ result.missingSections = figmaSections.filter(
685
+ (s) => !existingSections.some((existing) => existing.includes(s) || s.includes(existing))
686
+ );
687
+ if (codeStatus.assetCount === 0) {
688
+ result.missingAssets.push("All assets need to be downloaded");
689
+ result.needsUpdate = true;
690
+ }
691
+ if (!codeStatus.hasHTML) {
692
+ result.recommendations.push("Create index.html with HTML5 structure");
693
+ }
694
+ if (!codeStatus.hasCSS) {
695
+ result.recommendations.push("Create CSS files (variables.css, base.css, components.css)");
696
+ }
697
+ if (result.missingSections.length > 0) {
698
+ result.recommendations.push(`Implement missing sections: ${result.missingSections.join(", ")}`);
699
+ }
700
+ if (result.missingAssets.length > 0) {
701
+ result.recommendations.push("Download required assets from Figma");
702
+ }
703
+ return result;
704
+ }
705
+ var init_figma_screen_developer = __esm({
706
+ "src/core/tools/figma-screen-developer.ts"() {
707
+ "use strict";
708
+ init_esm_shims();
709
+ init_logger();
710
+ }
711
+ });
712
+
713
+ // src/core/memory.ts
714
+ var memory_exports = {};
715
+ __export(memory_exports, {
716
+ MemoryManager: () => MemoryManager
717
+ });
718
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, access as access2, constants as constants2 } from "fs/promises";
719
+ import { join as join7 } from "path";
720
+ var MemoryManager;
721
+ var init_memory = __esm({
722
+ "src/core/memory.ts"() {
723
+ "use strict";
724
+ init_esm_shims();
725
+ init_paths();
726
+ MemoryManager = class {
727
+ config;
728
+ constructor(config) {
729
+ this.config = config;
730
+ }
731
+ /**
732
+ * List all memory entries
733
+ */
734
+ async list() {
735
+ const memories = [];
736
+ const memoryPath = paths.memory(this.config.configPath);
737
+ const subDirs = ["observations", "handoffs", "research"];
738
+ for (const subDir of subDirs) {
739
+ const dirPath = join7(memoryPath, subDir);
740
+ try {
741
+ const { readdir: readdir7 } = await import("fs/promises");
742
+ const files = await readdir7(dirPath);
743
+ for (const file of files) {
744
+ if (!file.endsWith(".md")) continue;
745
+ const content = await readFile5(join7(dirPath, file), "utf-8");
746
+ const summary = this.extractSummary(content);
747
+ memories.push({
748
+ key: `${subDir}/${file.replace(".md", "")}`,
749
+ content,
750
+ summary,
751
+ createdAt: /* @__PURE__ */ new Date(),
752
+ // Would get from file stats
753
+ updatedAt: /* @__PURE__ */ new Date(),
754
+ type: subDir
755
+ });
756
+ }
757
+ } catch {
758
+ }
759
+ }
760
+ return memories;
761
+ }
762
+ /**
763
+ * Read a memory entry
764
+ */
765
+ async read(key) {
766
+ const memoryPath = paths.memory(this.config.configPath);
767
+ let filePath;
768
+ if (key.includes("/")) {
769
+ filePath = join7(memoryPath, `${key}.md`);
770
+ } else {
771
+ const subDirs = ["observations", "handoffs", "research", "_templates"];
772
+ for (const subDir of subDirs) {
773
+ const testPath = join7(memoryPath, subDir, `${key}.md`);
774
+ try {
775
+ await access2(testPath, constants2.R_OK);
776
+ filePath = testPath;
777
+ break;
778
+ } catch {
779
+ continue;
780
+ }
781
+ }
782
+ filePath = filePath || join7(memoryPath, `${key}.md`);
783
+ }
784
+ try {
785
+ return await readFile5(filePath, "utf-8");
786
+ } catch {
787
+ return null;
788
+ }
789
+ }
790
+ /**
791
+ * Update a memory entry
792
+ */
793
+ async update(key, content, options) {
794
+ const memoryPath = paths.memory(this.config.configPath);
795
+ const type = options?.type || "custom";
796
+ let filePath;
797
+ if (key.includes("/")) {
798
+ filePath = join7(memoryPath, `${key}.md`);
799
+ } else {
800
+ const subDir = type === "observation" ? "observations" : type === "handoff" ? "handoffs" : type === "research" ? "research" : "";
801
+ filePath = join7(memoryPath, subDir, `${key}.md`);
802
+ }
803
+ const { dirname: dirname2 } = await import("path");
804
+ await mkdir4(dirname2(filePath), { recursive: true });
805
+ if (options?.append) {
806
+ try {
807
+ const existing = await readFile5(filePath, "utf-8");
808
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
809
+ content = `${existing}
810
+
811
+ ---
812
+ _Updated: ${timestamp}_
813
+
814
+ ${content}`;
815
+ } catch {
816
+ }
817
+ }
818
+ await writeFile4(filePath, content);
819
+ }
820
+ /**
821
+ * Create a handoff bundle
822
+ */
823
+ async createHandoff(summary) {
824
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
825
+ const key = `handoffs/${timestamp}`;
826
+ const content = `# Handoff: ${(/* @__PURE__ */ new Date()).toLocaleString()}
827
+
828
+ ## Completed
829
+ ${summary.completed.map((item) => `- [x] ${item}`).join("\n") || "- None"}
830
+
831
+ ## In Progress
832
+ ${summary.inProgress.map((item) => `- [ ] ${item}`).join("\n") || "- None"}
833
+
834
+ ## Remaining
835
+ ${summary.remaining.map((item) => `- [ ] ${item}`).join("\n") || "- None"}
836
+
837
+ ## Context
838
+ ${summary.context || "No additional context."}
839
+
840
+ ## Next Steps
841
+ ${summary.nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "- Continue from where left off"}
842
+ `;
843
+ await this.update(key, content, { type: "handoff" });
844
+ return key;
845
+ }
846
+ /**
847
+ * Get the latest handoff
848
+ */
849
+ async getLatestHandoff() {
850
+ const memories = await this.list();
851
+ const handoffs = memories.filter((m) => m.type === "handoff");
852
+ if (handoffs.length === 0) return null;
853
+ handoffs.sort((a, b) => b.key.localeCompare(a.key));
854
+ return handoffs[0];
855
+ }
856
+ /**
857
+ * Create an observation
858
+ */
859
+ async createObservation(title, observation) {
860
+ const slug = title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
861
+ const key = `observations/${slug}`;
862
+ const content = `# ${title}
863
+
864
+ ## What
865
+ ${observation.what}
866
+
867
+ ## Why
868
+ ${observation.why}
869
+
870
+ ## Impact
871
+ ${observation.impact}
872
+
873
+ ${observation.tags?.length ? `## Tags
874
+ ${observation.tags.map((t) => `- ${t}`).join("\n")}` : ""}
875
+
876
+ ---
877
+ _Created: ${(/* @__PURE__ */ new Date()).toISOString()}_
878
+ `;
879
+ await this.update(key, content, { type: "observation" });
880
+ return key;
881
+ }
882
+ /**
883
+ * Save research findings
884
+ */
885
+ async saveResearch(topic, findings) {
886
+ const slug = topic.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
887
+ const key = `research/${slug}`;
888
+ const content = `# Research: ${topic}
889
+
890
+ ## Summary
891
+ ${findings.summary}
892
+
893
+ ## Sources
894
+ ${findings.sources.map((s) => `- ${s}`).join("\n")}
895
+
896
+ ## Recommendations
897
+ ${findings.recommendations.map((r, i) => `${i + 1}. ${r}`).join("\n")}
898
+
899
+ ${findings.codeExamples ? `## Code Examples
900
+ \`\`\`
901
+ ${findings.codeExamples}
902
+ \`\`\`` : ""}
903
+
904
+ ---
905
+ _Researched: ${(/* @__PURE__ */ new Date()).toISOString()}_
906
+ `;
907
+ await this.update(key, content, { type: "research" });
908
+ return key;
909
+ }
910
+ /**
911
+ * Extract a summary from content (first paragraph or heading)
912
+ */
913
+ extractSummary(content) {
914
+ const lines = content.split("\n").filter((l) => l.trim());
915
+ for (const line of lines) {
916
+ if (!line.startsWith("#") && line.trim().length > 0) {
917
+ return line.slice(0, 100) + (line.length > 100 ? "..." : "");
918
+ }
919
+ }
920
+ if (lines[0]?.startsWith("#")) {
921
+ return lines[0].replace(/^#+\s*/, "");
922
+ }
923
+ return "No summary available";
924
+ }
925
+ };
926
+ }
927
+ });
928
+
929
+ // src/core/tool-config.ts
930
+ var tool_config_exports = {};
931
+ __export(tool_config_exports, {
932
+ ToolConfigManager: () => ToolConfigManager
933
+ });
934
+ import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8, access as access4, constants as constants4 } from "fs/promises";
935
+ import { join as join11 } from "path";
936
+ import { z as z3 } from "zod";
937
+ var ToolConfigSchema, REGISTERED_TOOLS, ToolConfigManager;
938
+ var init_tool_config = __esm({
939
+ "src/core/tool-config.ts"() {
940
+ "use strict";
941
+ init_esm_shims();
942
+ ToolConfigSchema = z3.object({
943
+ name: z3.string(),
944
+ status: z3.enum(["ready", "needs_config", "error"]),
945
+ description: z3.string(),
946
+ configMethod: z3.enum(["oauth", "manual", "none"]),
947
+ config: z3.record(z3.unknown()).optional(),
948
+ errorMessage: z3.string().optional()
949
+ });
950
+ REGISTERED_TOOLS = [
951
+ {
952
+ name: "figma-analysis",
953
+ description: "Analyze Figma designs and extract design tokens using Figma API",
954
+ configMethod: "oauth"
955
+ }
956
+ // Add more tools here as needed
957
+ ];
958
+ ToolConfigManager = class {
959
+ config;
960
+ toolsConfigPath;
961
+ constructor(config) {
962
+ this.config = config;
963
+ this.toolsConfigPath = join11(this.config.configPath, "config", "tools.json");
964
+ }
965
+ /**
966
+ * Get all registered tools with their current status
967
+ */
968
+ async listTools() {
969
+ const savedConfigs = await this.loadConfigs();
970
+ const tools = [];
971
+ for (const tool of REGISTERED_TOOLS) {
972
+ const saved = savedConfigs[tool.name];
973
+ const toolConfig = {
974
+ ...tool,
975
+ status: this.determineStatus(tool, saved),
976
+ config: saved?.config,
977
+ errorMessage: saved?.errorMessage
978
+ };
979
+ tools.push(toolConfig);
980
+ }
981
+ return tools;
982
+ }
983
+ /**
984
+ * Get configuration for a specific tool
985
+ */
986
+ async getToolConfig(toolName) {
987
+ const tools = await this.listTools();
988
+ return tools.find((t) => t.name === toolName) || null;
989
+ }
990
+ /**
991
+ * Update tool configuration
992
+ */
993
+ async updateToolConfig(toolName, updates) {
994
+ const savedConfigs = await this.loadConfigs();
995
+ const tool = REGISTERED_TOOLS.find((t) => t.name === toolName);
996
+ if (!tool) {
997
+ throw new Error(`Tool not found: ${toolName}`);
998
+ }
999
+ const existing = savedConfigs[toolName] || {};
1000
+ savedConfigs[toolName] = {
1001
+ ...existing,
1002
+ ...updates
1003
+ };
1004
+ await this.saveConfigs(savedConfigs);
1005
+ }
1006
+ /**
1007
+ * Check if a tool is ready to use
1008
+ */
1009
+ async isToolReady(toolName) {
1010
+ const toolConfig = await this.getToolConfig(toolName);
1011
+ return toolConfig?.status === "ready";
1012
+ }
1013
+ /**
1014
+ * Get API key for a tool (if configured)
1015
+ */
1016
+ async getApiKey(toolName) {
1017
+ const toolConfig = await this.getToolConfig(toolName);
1018
+ if (toolConfig?.config?.apiKey && typeof toolConfig.config.apiKey === "string") {
1019
+ return toolConfig.config.apiKey;
1020
+ }
1021
+ return null;
1022
+ }
1023
+ /**
1024
+ * Determine tool status based on configuration
1025
+ */
1026
+ determineStatus(tool, saved) {
1027
+ if (tool.configMethod === "none") {
1028
+ return "ready";
1029
+ }
1030
+ if (saved?.errorMessage) {
1031
+ return "error";
1032
+ }
1033
+ if (tool.configMethod === "oauth" || tool.configMethod === "manual") {
1034
+ if (saved?.config?.apiKey && typeof saved.config.apiKey === "string" && saved.config.apiKey.length > 0) {
1035
+ return "ready";
1036
+ }
1037
+ return "needs_config";
1038
+ }
1039
+ return "needs_config";
1040
+ }
1041
+ /**
1042
+ * Load saved configurations
1043
+ */
1044
+ async loadConfigs() {
1045
+ try {
1046
+ await access4(this.toolsConfigPath, constants4.R_OK);
1047
+ const content = await readFile7(this.toolsConfigPath, "utf-8");
1048
+ return JSON.parse(content);
1049
+ } catch {
1050
+ return {};
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Save configurations
1055
+ */
1056
+ async saveConfigs(configs) {
1057
+ const configDir = join11(this.config.configPath, "config");
1058
+ await mkdir8(configDir, { recursive: true });
1059
+ await writeFile8(this.toolsConfigPath, JSON.stringify(configs, null, 2));
1060
+ }
1061
+ };
1062
+ }
1063
+ });
1064
+
1065
+ // src/core/auth/figma-oauth.ts
1066
+ var figma_oauth_exports = {};
1067
+ __export(figma_oauth_exports, {
1068
+ FigmaOAuth: () => FigmaOAuth
1069
+ });
1070
+ import open from "open";
1071
+ var FigmaOAuth;
1072
+ var init_figma_oauth = __esm({
1073
+ "src/core/auth/figma-oauth.ts"() {
1074
+ "use strict";
1075
+ init_esm_shims();
1076
+ init_logger();
1077
+ FigmaOAuth = class {
1078
+ configManager;
1079
+ constructor(configManager) {
1080
+ this.configManager = configManager;
1081
+ }
1082
+ /**
1083
+ * Start OAuth flow for Figma
1084
+ *
1085
+ * Since Figma uses Personal Access Tokens (not OAuth 2.0),
1086
+ * we'll open browser to token creation page and guide user
1087
+ */
1088
+ async authenticate() {
1089
+ console.log("\n\u{1F510} Figma Authentication\n");
1090
+ console.log("Figma uses Personal Access Tokens for API access.");
1091
+ console.log("We will open your browser to create a token.\n");
1092
+ const tokenUrl = "https://www.figma.com/developers/api#access-tokens";
1093
+ console.log(`Opening: ${tokenUrl}`);
1094
+ try {
1095
+ await open(tokenUrl);
1096
+ } catch (error) {
1097
+ console.log("\n\u26A0\uFE0F Could not open browser automatically.");
1098
+ console.log(`Please visit: ${tokenUrl}`);
1099
+ }
1100
+ console.log("\n\u{1F4CB} Instructions:");
1101
+ console.log('1. In the browser, scroll to "Personal access tokens" section');
1102
+ console.log('2. Click "Create new token"');
1103
+ console.log('3. Give it a name (e.g., "AIKit")');
1104
+ console.log("4. Copy the token (you won't see it again!)");
1105
+ console.log("5. Paste it here when prompted\n");
1106
+ const { default: inquirer } = await import("inquirer");
1107
+ const { token } = await inquirer.prompt([
1108
+ {
1109
+ type: "password",
1110
+ name: "token",
1111
+ message: "Paste your Figma Personal Access Token:",
1112
+ validate: (input) => {
1113
+ if (!input || input.trim().length === 0) {
1114
+ return "Token cannot be empty";
1115
+ }
1116
+ if (input.length < 20) {
1117
+ return "Token seems too short. Please check and try again.";
1118
+ }
1119
+ return true;
1120
+ }
1121
+ }
1122
+ ]);
1123
+ const trimmedToken = token.trim();
1124
+ await this.configManager.updateToolConfig("figma-analysis", {
1125
+ config: {
1126
+ apiKey: trimmedToken
1127
+ },
1128
+ status: "ready"
1129
+ });
1130
+ logger.success("Figma token saved successfully!");
1131
+ return trimmedToken;
1132
+ }
1133
+ /**
1134
+ * Alternative: Manual token input
1135
+ */
1136
+ async authenticateManual() {
1137
+ const { default: inquirer } = await import("inquirer");
1138
+ console.log("\n\u{1F510} Figma Authentication (Manual)\n");
1139
+ console.log("To get your Figma Personal Access Token:");
1140
+ console.log("1. Visit: https://www.figma.com/developers/api#access-tokens");
1141
+ console.log('2. Scroll to "Personal access tokens"');
1142
+ console.log('3. Click "Create new token"');
1143
+ console.log("4. Copy the token and paste it below\n");
1144
+ const { token } = await inquirer.prompt([
1145
+ {
1146
+ type: "password",
1147
+ name: "token",
1148
+ message: "Enter your Figma Personal Access Token:",
1149
+ validate: (input) => {
1150
+ if (!input || input.trim().length === 0) {
1151
+ return "Token cannot be empty";
1152
+ }
1153
+ return true;
1154
+ }
1155
+ }
1156
+ ]);
1157
+ const trimmedToken = token.trim();
1158
+ await this.configManager.updateToolConfig("figma-analysis", {
1159
+ config: {
1160
+ apiKey: trimmedToken
1161
+ },
1162
+ status: "ready"
1163
+ });
1164
+ logger.success("Figma token saved successfully!");
1165
+ return trimmedToken;
1166
+ }
1167
+ /**
1168
+ * Test if token is valid by making a simple API call
1169
+ */
1170
+ async validateToken(token) {
1171
+ try {
1172
+ const response = await fetch("https://api.figma.com/v1/me", {
1173
+ headers: {
1174
+ "X-Figma-Token": token
1175
+ }
1176
+ });
1177
+ if (response.ok) {
1178
+ const data = await response.json();
1179
+ logger.success(`Token validated! Logged in as: ${data.email || "Unknown"}`);
1180
+ return true;
1181
+ } else {
1182
+ logger.error(`Token validation failed: ${response.status} ${response.statusText}`);
1183
+ return false;
1184
+ }
1185
+ } catch (error) {
1186
+ logger.error(`Token validation error: ${error instanceof Error ? error.message : String(error)}`);
1187
+ return false;
1188
+ }
1189
+ }
1190
+ };
1191
+ }
1192
+ });
1193
+
1194
+ // src/cli.ts
1195
+ init_esm_shims();
1196
+ import { Command } from "commander";
1197
+ import chalk2 from "chalk";
1198
+
1199
+ // src/index.ts
1200
+ init_esm_shims();
1201
+
1202
+ // src/core/config.ts
1203
+ init_esm_shims();
1204
+ init_paths();
1205
+ import { readFile, access, constants } from "fs/promises";
1206
+ import { join as join2 } from "path";
1207
+ import { z } from "zod";
1208
+ var ConfigSchema = z.object({
1209
+ version: z.string(),
1210
+ skills: z.object({
1211
+ enabled: z.boolean().default(true),
1212
+ directory: z.string().optional()
1213
+ }).default({}),
1214
+ agents: z.object({
1215
+ enabled: z.boolean().default(true),
1216
+ default: z.string().default("build")
1217
+ }).default({}),
1218
+ commands: z.object({
1219
+ enabled: z.boolean().default(true)
1220
+ }).default({}),
1221
+ tools: z.object({
1222
+ enabled: z.boolean().default(true)
1223
+ }).default({}),
1224
+ plugins: z.object({
1225
+ enabled: z.boolean().default(true),
1226
+ autoload: z.array(z.string()).optional()
1227
+ }).default({}),
1228
+ memory: z.object({
1229
+ enabled: z.boolean().default(true),
1230
+ maxSize: z.number().optional()
1231
+ }).default({}),
1232
+ beads: z.object({
1233
+ enabled: z.boolean().default(true),
1234
+ autoInit: z.boolean().default(false)
1235
+ }).default({}),
1236
+ antiHallucination: z.object({
1237
+ enabled: z.boolean().default(true),
1238
+ specFile: z.string().default("spec.md"),
1239
+ reviewFile: z.string().default("review.md")
1240
+ }).default({}),
1241
+ mcp: z.object({
1242
+ context7: z.boolean().default(false),
1243
+ githubGrep: z.boolean().default(false),
1244
+ gkg: z.boolean().default(false)
1245
+ }).optional()
1246
+ });
1247
+ var Config = class {
1248
+ config;
1249
+ constructor(config) {
1250
+ this.config = config;
1251
+ }
1252
+ get() {
1253
+ return this.config;
1254
+ }
1255
+ get skills() {
1256
+ return this.config.skills;
1257
+ }
1258
+ get agents() {
1259
+ return this.config.agents;
1260
+ }
1261
+ get commands() {
1262
+ return this.config.commands;
1263
+ }
1264
+ get tools() {
1265
+ return this.config.tools;
1266
+ }
1267
+ get plugins() {
1268
+ return this.config.plugins;
1269
+ }
1270
+ get memory() {
1271
+ return this.config.memory;
1272
+ }
1273
+ get beads() {
1274
+ return this.config.beads;
1275
+ }
1276
+ get antiHallucination() {
1277
+ return this.config.antiHallucination;
1278
+ }
1279
+ get configPath() {
1280
+ return this.config.configPath;
1281
+ }
1282
+ get projectPath() {
1283
+ return this.config.projectPath;
1284
+ }
1285
+ /**
1286
+ * Get path to a specific resource
1287
+ */
1288
+ getPath(resource) {
1289
+ return paths[resource](this.configPath);
1290
+ }
1291
+ };
1292
+ async function loadConfig(projectPath) {
1293
+ const project = projectPath || process.cwd();
1294
+ const projectConfigPath = paths.projectConfig(project);
1295
+ const globalConfigPath = paths.globalConfig();
1296
+ let configPath;
1297
+ let configData = {};
1298
+ try {
1299
+ await access(join2(globalConfigPath, "aikit.json"), constants.R_OK);
1300
+ const globalContent = await readFile(join2(globalConfigPath, "aikit.json"), "utf-8");
1301
+ configData = JSON.parse(globalContent);
1302
+ configPath = globalConfigPath;
1303
+ } catch {
1304
+ configPath = projectConfigPath;
1305
+ }
1306
+ try {
1307
+ await access(join2(projectConfigPath, "aikit.json"), constants.R_OK);
1308
+ const projectContent = await readFile(join2(projectConfigPath, "aikit.json"), "utf-8");
1309
+ const projectData = JSON.parse(projectContent);
1310
+ configData = deepMerge(configData, projectData);
1311
+ configPath = projectConfigPath;
1312
+ } catch {
1313
+ }
1314
+ if (!configData.version) {
1315
+ configData.version = "0.1.0";
1316
+ }
1317
+ const parsed = ConfigSchema.parse(configData);
1318
+ return new Config({
1319
+ ...parsed,
1320
+ configPath,
1321
+ projectPath: project
1322
+ });
1323
+ }
1324
+ function deepMerge(base, override) {
1325
+ const result = { ...base };
1326
+ for (const key in override) {
1327
+ const baseValue = base[key];
1328
+ const overrideValue = override[key];
1329
+ if (typeof baseValue === "object" && baseValue !== null && typeof overrideValue === "object" && overrideValue !== null && !Array.isArray(baseValue) && !Array.isArray(overrideValue)) {
1330
+ result[key] = deepMerge(
1331
+ baseValue,
1332
+ overrideValue
1333
+ );
1334
+ } else if (overrideValue !== void 0) {
1335
+ result[key] = overrideValue;
1336
+ }
1337
+ }
1338
+ return result;
1339
+ }
1340
+
1341
+ // src/core/skills.ts
1342
+ init_esm_shims();
1343
+ init_paths();
1344
+ import { readFile as readFile2, readdir, writeFile, mkdir } from "fs/promises";
1345
+ import { join as join3, basename, extname } from "path";
1346
+ import matter from "gray-matter";
1347
+ var SkillEngine = class {
1348
+ config;
1349
+ skillsCache = /* @__PURE__ */ new Map();
1350
+ constructor(config) {
1351
+ this.config = config;
1352
+ }
1353
+ /**
1354
+ * List all available skills
1355
+ */
1356
+ async listSkills() {
1357
+ const skills = [];
1358
+ const globalSkillsPath = paths.skills(paths.globalConfig());
1359
+ try {
1360
+ const globalSkills = await this.loadSkillsFromDir(globalSkillsPath);
1361
+ skills.push(...globalSkills);
1362
+ } catch {
1363
+ }
1364
+ const projectSkillsPath = paths.skills(this.config.configPath);
1365
+ if (projectSkillsPath !== globalSkillsPath) {
1366
+ try {
1367
+ const projectSkills = await this.loadSkillsFromDir(projectSkillsPath);
1368
+ for (const skill of projectSkills) {
1369
+ const existingIndex = skills.findIndex((s) => s.name === skill.name);
1370
+ if (existingIndex >= 0) {
1371
+ skills[existingIndex] = skill;
1372
+ } else {
1373
+ skills.push(skill);
1374
+ }
1375
+ }
1376
+ } catch {
1377
+ }
1378
+ }
1379
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
1380
+ }
1381
+ /**
1382
+ * Get a specific skill by name
1383
+ */
1384
+ async getSkill(name) {
1385
+ if (this.skillsCache.has(name)) {
1386
+ return this.skillsCache.get(name);
1387
+ }
1388
+ const skills = await this.listSkills();
1389
+ const skill = skills.find((s) => s.name === name);
1390
+ if (skill) {
1391
+ this.skillsCache.set(name, skill);
1392
+ }
1393
+ return skill || null;
1394
+ }
1395
+ /**
1396
+ * Find skills matching a query
1397
+ */
1398
+ async findSkills(query) {
1399
+ const skills = await this.listSkills();
1400
+ if (!query) {
1401
+ return skills;
1402
+ }
1403
+ const lowerQuery = query.toLowerCase();
1404
+ return skills.filter(
1405
+ (skill) => skill.name.toLowerCase().includes(lowerQuery) || skill.description.toLowerCase().includes(lowerQuery) || skill.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)) || skill.category.toLowerCase().includes(lowerQuery)
1406
+ );
1407
+ }
1408
+ /**
1409
+ * Create a new skill
1410
+ */
1411
+ async createSkill(name, options) {
1412
+ const configPath = options?.global ? paths.globalConfig() : this.config.configPath;
1413
+ const skillsDir = paths.skills(configPath);
1414
+ await mkdir(skillsDir, { recursive: true });
1415
+ const fileName = `${name.replace(/\s+/g, "-").toLowerCase()}.md`;
1416
+ const filePath = join3(skillsDir, fileName);
1417
+ const frontmatter = {
1418
+ name,
1419
+ description: options?.description || `Use when you need to ${name}`,
1420
+ useWhen: options?.useWhen || `The user asks you to ${name}`,
1421
+ category: options?.category || "custom",
1422
+ tags: options?.tags || ["custom"]
1423
+ };
1424
+ const content = options?.content || `## ${name}
1425
+
1426
+ ### Overview
1427
+ Describe what this skill does.
1428
+
1429
+ ### Workflow
1430
+
1431
+ #### Step 1: Understand the Task
1432
+ - Gather context
1433
+ - Clarify requirements
1434
+
1435
+ #### Step 2: Plan the Approach
1436
+ - Break down into sub-tasks
1437
+ - Identify dependencies
1438
+
1439
+ #### Step 3: Execute
1440
+ - Follow TDD principles
1441
+ - Write tests first
1442
+
1443
+ #### Step 4: Verify
1444
+ - Run all tests
1445
+ - Check for regressions
1446
+
1447
+ ### Checklist
1448
+ - [ ] Requirements understood
1449
+ - [ ] Tests written
1450
+ - [ ] Implementation complete
1451
+ - [ ] All tests passing
1452
+ - [ ] Code reviewed
1453
+ `;
1454
+ const fileContent = matter.stringify(content, frontmatter);
1455
+ await writeFile(filePath, fileContent);
1456
+ const skill = {
1457
+ name,
1458
+ description: frontmatter.description,
1459
+ useWhen: frontmatter.useWhen,
1460
+ category: frontmatter.category,
1461
+ tags: frontmatter.tags,
1462
+ content,
1463
+ filePath
1464
+ };
1465
+ this.skillsCache.set(name, skill);
1466
+ return skill;
1467
+ }
1468
+ /**
1469
+ * Sync global skills to project directory
1470
+ */
1471
+ async syncSkillsToProject() {
1472
+ const globalSkillsPath = paths.skills(paths.globalConfig());
1473
+ const projectSkillsPath = paths.skills(this.config.configPath);
1474
+ if (globalSkillsPath === projectSkillsPath) {
1475
+ return { count: 0, synced: [] };
1476
+ }
1477
+ const globalSkills = await this.loadSkillsFromDir(globalSkillsPath);
1478
+ await mkdir(projectSkillsPath, { recursive: true });
1479
+ const synced = [];
1480
+ for (const skill of globalSkills) {
1481
+ const fileName = `${skill.name.replace(/\s+/g, "-").toLowerCase()}.md`;
1482
+ const destPath = join3(projectSkillsPath, fileName);
1483
+ const srcContent = await readFile2(skill.filePath, "utf-8");
1484
+ await writeFile(destPath, srcContent);
1485
+ synced.push(skill.name);
1486
+ }
1487
+ return { count: synced.length, synced };
1488
+ }
1489
+ /**
1490
+ * Format skill for agent consumption
1491
+ */
1492
+ formatForAgent(skill) {
1493
+ return `# Skill: ${skill.name}
1494
+
1495
+ ## When to Use
1496
+ ${skill.useWhen}
1497
+
1498
+ ## Description
1499
+ ${skill.description}
1500
+
1501
+ ## Workflow
1502
+ ${skill.content}
1503
+
1504
+ ---
1505
+ **IMPORTANT**: Follow this workflow step by step. Do not skip steps.
1506
+ `;
1507
+ }
1508
+ /**
1509
+ * Load skills from a directory
1510
+ */
1511
+ async loadSkillsFromDir(dir) {
1512
+ const files = await readdir(dir);
1513
+ const skills = [];
1514
+ for (const file of files) {
1515
+ if (extname(file) !== ".md") continue;
1516
+ const filePath = join3(dir, file);
1517
+ const content = await readFile2(filePath, "utf-8");
1518
+ const { data, content: body } = matter(content);
1519
+ const frontmatter = data;
1520
+ const name = frontmatter.name || basename(file, ".md");
1521
+ skills.push({
1522
+ name,
1523
+ description: frontmatter.description || "",
1524
+ useWhen: frontmatter.useWhen || "",
1525
+ category: frontmatter.category || "general",
1526
+ tags: frontmatter.tags || [],
1527
+ content: body.trim(),
1528
+ filePath
1529
+ });
1530
+ }
1531
+ return skills;
1532
+ }
1533
+ };
1534
+
1535
+ // src/core/agents.ts
1536
+ init_esm_shims();
1537
+ var DEFAULT_AGENTS = [
1538
+ {
1539
+ name: "planner",
1540
+ displayName: "@planner",
1541
+ description: "Strategic planning and multi-agent coordination",
1542
+ useWhen: "Complex tasks requiring architecture decisions, multi-step planning, or coordination between specialists",
1543
+ capabilities: [
1544
+ "Break down complex tasks into sub-tasks",
1545
+ "Coordinate between specialist agents",
1546
+ "Make architecture decisions",
1547
+ "Create implementation plans"
1548
+ ],
1549
+ systemPrompt: `You are a strategic planner agent. Your role is to:
1550
+
1551
+ 1. ANALYZE the task and understand the full scope
1552
+ 2. BREAK DOWN complex tasks into smaller, manageable sub-tasks
1553
+ 3. DELEGATE to appropriate specialist agents
1554
+ 4. COORDINATE the overall workflow
1555
+ 5. VERIFY completion of the overall goal
1556
+
1557
+ When delegating:
1558
+ - Use @build for implementation tasks
1559
+ - Use @scout for external research
1560
+ - Use @review for code review and security audits
1561
+ - Use @explore for codebase navigation
1562
+ - Use @vision for visual analysis
1563
+
1564
+ Always create a clear plan before delegating. Track progress and ensure all sub-tasks complete successfully.`,
1565
+ delegatesTo: ["build", "scout", "review", "explore", "vision"]
1566
+ },
1567
+ {
1568
+ name: "build",
1569
+ displayName: "@build",
1570
+ description: "Primary execution agent for implementing features",
1571
+ useWhen: "Implementing features, writing code, making changes to the codebase",
1572
+ capabilities: [
1573
+ "Write production code",
1574
+ "Write tests",
1575
+ "Refactor code",
1576
+ "Fix bugs",
1577
+ "Implement features"
1578
+ ],
1579
+ systemPrompt: `You are the build agent. Your role is to implement code changes.
1580
+
1581
+ Follow these principles:
1582
+ 1. TEST-DRIVEN DEVELOPMENT: Write tests before implementation
1583
+ 2. INCREMENTAL CHANGES: Make small, focused commits
1584
+ 3. VERIFY: Run tests and type checks after each change
1585
+ 4. DOCUMENT: Add comments for complex logic
1586
+
1587
+ Before starting:
1588
+ - Understand the requirements fully
1589
+ - Check existing patterns in the codebase
1590
+ - Plan the implementation approach
1591
+
1592
+ After completing:
1593
+ - Ensure all tests pass
1594
+ - Run linting and type checks
1595
+ - Create a clear commit message`,
1596
+ delegatesTo: ["review", "explore"]
1597
+ },
1598
+ {
1599
+ name: "rush",
1600
+ displayName: "@rush",
1601
+ description: "Fast execution with minimal planning",
1602
+ useWhen: "Quick fixes, hotfixes, simple edits that need minimal planning",
1603
+ capabilities: [
1604
+ "Quick bug fixes",
1605
+ "Simple refactoring",
1606
+ "Minor changes",
1607
+ "Hotfixes"
1608
+ ],
1609
+ systemPrompt: `You are the rush agent. Your role is to make quick, focused changes.
1610
+
1611
+ Guidelines:
1612
+ 1. ACT FAST: Minimal planning, direct execution
1613
+ 2. FOCUS: One change at a time
1614
+ 3. VERIFY: Quick sanity check after change
1615
+ 4. MINIMAL SCOPE: Don't expand beyond the immediate task
1616
+
1617
+ Use for:
1618
+ - Typo fixes
1619
+ - Simple bug fixes
1620
+ - Minor adjustments
1621
+ - Urgent hotfixes`,
1622
+ delegatesTo: []
1623
+ },
1624
+ {
1625
+ name: "review",
1626
+ displayName: "@review",
1627
+ description: "Code review, debugging, and security audits",
1628
+ useWhen: "Reviewing code quality, finding bugs, security review, debugging issues",
1629
+ capabilities: [
1630
+ "Code review",
1631
+ "Security audit",
1632
+ "Performance analysis",
1633
+ "Bug finding",
1634
+ "Best practices enforcement"
1635
+ ],
1636
+ systemPrompt: `You are the review agent. Your role is to review and improve code quality.
1637
+
1638
+ Review checklist:
1639
+ 1. CORRECTNESS: Does the code do what it's supposed to?
1640
+ 2. SECURITY: Are there any security vulnerabilities?
1641
+ 3. PERFORMANCE: Are there performance issues?
1642
+ 4. MAINTAINABILITY: Is the code clean and readable?
1643
+ 5. TESTS: Are there adequate tests?
1644
+ 6. PATTERNS: Does it follow project conventions?
1645
+
1646
+ When reviewing:
1647
+ - Be specific about issues
1648
+ - Suggest fixes, not just problems
1649
+ - Prioritize by severity
1650
+ - Check for edge cases`,
1651
+ delegatesTo: []
1652
+ },
1653
+ {
1654
+ name: "scout",
1655
+ displayName: "@scout",
1656
+ description: "External research - library docs, GitHub patterns, frameworks",
1657
+ useWhen: "Researching external libraries, finding code patterns on GitHub, learning about frameworks",
1658
+ capabilities: [
1659
+ "Web research",
1660
+ "GitHub code search",
1661
+ "Documentation lookup",
1662
+ "Framework exploration",
1663
+ "Best practices research"
1664
+ ],
1665
+ systemPrompt: `You are the scout agent. Your role is to research external resources.
1666
+
1667
+ Research strategy:
1668
+ 1. UNDERSTAND what information is needed
1669
+ 2. SEARCH appropriate sources (docs, GitHub, web)
1670
+ 3. EVALUATE quality and relevance of findings
1671
+ 4. SUMMARIZE key findings concisely
1672
+ 5. PROVIDE actionable recommendations
1673
+
1674
+ Sources to use:
1675
+ - Official documentation
1676
+ - GitHub code examples
1677
+ - Stack Overflow (verified answers)
1678
+ - Framework guides
1679
+ - Community best practices
1680
+
1681
+ Always cite your sources and verify information accuracy.`,
1682
+ delegatesTo: []
1683
+ },
1684
+ {
1685
+ name: "explore",
1686
+ displayName: "@explore",
1687
+ description: "Fast codebase navigation and pattern search",
1688
+ useWhen: "Finding files, understanding codebase structure, searching for patterns in code",
1689
+ capabilities: [
1690
+ "File discovery",
1691
+ "Pattern search",
1692
+ "Codebase navigation",
1693
+ "Structure analysis",
1694
+ "Dependency mapping"
1695
+ ],
1696
+ systemPrompt: `You are the explore agent. Your role is to navigate and understand the codebase.
1697
+
1698
+ Exploration techniques:
1699
+ 1. FILE STRUCTURE: Understand project organization
1700
+ 2. GREP SEARCH: Find specific patterns or usages
1701
+ 3. DEPENDENCY ANALYSIS: Map relationships between modules
1702
+ 4. PATTERN DISCOVERY: Find existing patterns to follow
1703
+ 5. QUICK CONTEXT: Gather just enough context for the task
1704
+
1705
+ Focus on speed and accuracy. Provide concise summaries of findings.`,
1706
+ delegatesTo: []
1707
+ },
1708
+ {
1709
+ name: "vision",
1710
+ displayName: "@vision",
1711
+ description: "Multimodal analysis - mockups, PDFs, diagrams",
1712
+ useWhen: "Analyzing images, mockups, screenshots, PDFs, or diagrams",
1713
+ capabilities: [
1714
+ "Image analysis",
1715
+ "Mockup interpretation",
1716
+ "PDF extraction",
1717
+ "Diagram understanding",
1718
+ "UI/UX analysis"
1719
+ ],
1720
+ systemPrompt: `You are the vision agent. Your role is to analyze visual content.
1721
+
1722
+ Analysis approach:
1723
+ 1. OBSERVE: Carefully examine the visual content
1724
+ 2. EXTRACT: Identify key information, text, structure
1725
+ 3. INTERPRET: Understand the intent and requirements
1726
+ 4. TRANSLATE: Convert visual specs to actionable tasks
1727
+ 5. VALIDATE: Ensure accurate interpretation
1728
+
1729
+ For mockups:
1730
+ - Identify components and layout
1731
+ - Note colors, spacing, typography
1732
+ - Extract interaction patterns
1733
+
1734
+ For diagrams:
1735
+ - Understand relationships
1736
+ - Map to code structure
1737
+ - Note data flow`,
1738
+ delegatesTo: []
1739
+ }
1740
+ ];
1741
+ var AgentManager = class {
1742
+ config;
1743
+ agents;
1744
+ constructor(config) {
1745
+ this.config = config;
1746
+ this.agents = /* @__PURE__ */ new Map();
1747
+ for (const agent of DEFAULT_AGENTS) {
1748
+ this.agents.set(agent.name, agent);
1749
+ }
1750
+ }
1751
+ /**
1752
+ * List all available agents
1753
+ */
1754
+ listAgents() {
1755
+ return Array.from(this.agents.values());
1756
+ }
1757
+ /**
1758
+ * Get a specific agent
1759
+ */
1760
+ getAgent(name) {
1761
+ return this.agents.get(name);
1762
+ }
1763
+ /**
1764
+ * Get the default agent
1765
+ */
1766
+ getDefaultAgent() {
1767
+ const defaultName = this.config.agents.default;
1768
+ return this.agents.get(defaultName) || this.agents.get("build");
1769
+ }
1770
+ /**
1771
+ * Decide which agent should handle a task
1772
+ */
1773
+ decideAgent(task, _context) {
1774
+ const lowerTask = task.toLowerCase();
1775
+ for (const [name, agent] of this.agents) {
1776
+ if (lowerTask.includes(`@${name}`)) {
1777
+ return {
1778
+ agent,
1779
+ reason: `Explicitly requested @${name}`,
1780
+ shouldDelegate: false
1781
+ };
1782
+ }
1783
+ }
1784
+ if (this.matchesKeywords(lowerTask, ["plan", "architect", "design", "coordinate", "complex"])) {
1785
+ return {
1786
+ agent: this.agents.get("planner"),
1787
+ reason: "Task requires planning and coordination",
1788
+ shouldDelegate: true
1789
+ };
1790
+ }
1791
+ if (this.matchesKeywords(lowerTask, ["research", "docs", "documentation", "library", "framework", "how to"])) {
1792
+ return {
1793
+ agent: this.agents.get("scout"),
1794
+ reason: "Task requires external research",
1795
+ shouldDelegate: false
1796
+ };
1797
+ }
1798
+ if (this.matchesKeywords(lowerTask, ["review", "audit", "security", "check", "debug"])) {
1799
+ return {
1800
+ agent: this.agents.get("review"),
1801
+ reason: "Task requires code review or debugging",
1802
+ shouldDelegate: false
1803
+ };
1804
+ }
1805
+ if (this.matchesKeywords(lowerTask, ["find", "search", "where", "locate", "explore"])) {
1806
+ return {
1807
+ agent: this.agents.get("explore"),
1808
+ reason: "Task requires codebase exploration",
1809
+ shouldDelegate: false
1810
+ };
1811
+ }
1812
+ if (this.matchesKeywords(lowerTask, ["image", "mockup", "screenshot", "pdf", "diagram", "visual"])) {
1813
+ return {
1814
+ agent: this.agents.get("vision"),
1815
+ reason: "Task requires visual analysis",
1816
+ shouldDelegate: false
1817
+ };
1818
+ }
1819
+ if (this.matchesKeywords(lowerTask, ["fix quickly", "hotfix", "urgent", "quick fix", "typo"])) {
1820
+ return {
1821
+ agent: this.agents.get("rush"),
1822
+ reason: "Task is a quick fix",
1823
+ shouldDelegate: false
1824
+ };
1825
+ }
1826
+ return {
1827
+ agent: this.agents.get("build"),
1828
+ reason: "Default to build agent for implementation",
1829
+ shouldDelegate: false
1830
+ };
1831
+ }
1832
+ /**
1833
+ * Format agent instructions for prompt
1834
+ */
1835
+ formatAgentPrompt(agent) {
1836
+ return `# Agent: ${agent.displayName}
1837
+
1838
+ ${agent.systemPrompt}
1839
+
1840
+ ## Capabilities
1841
+ ${agent.capabilities.map((c) => `- ${c}`).join("\n")}
1842
+
1843
+ ${agent.delegatesTo.length > 0 ? `## Can Delegate To
1844
+ ${agent.delegatesTo.map((a) => `- @${a}`).join("\n")}` : ""}
1845
+ `;
1846
+ }
1847
+ matchesKeywords(text, keywords) {
1848
+ return keywords.some((kw) => text.includes(kw));
1849
+ }
1850
+ };
1851
+
1852
+ // src/core/commands.ts
1853
+ init_esm_shims();
1854
+ init_paths();
1855
+ import { readFile as readFile3, readdir as readdir2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1856
+ import { join as join4, basename as basename2, extname as extname2 } from "path";
1857
+ import matter2 from "gray-matter";
1858
+ var DEFAULT_COMMANDS = [
1859
+ // Core Workflow Commands (Beads integration)
1860
+ {
1861
+ name: "create",
1862
+ description: "Create a new Beads task for tracking",
1863
+ category: "core",
1864
+ usage: "/create <task description>",
1865
+ examples: ["/create Add user authentication", "/create Fix navigation bug"],
1866
+ content: `Create a new task in the Beads system (.beads/ directory).
1867
+
1868
+ ## Workflow
1869
+ 1. Create a new bead file with unique ID
1870
+ 2. Add task description, status, and notes
1871
+ 3. Set status to "in-progress"
1872
+ 4. Initialize working notes section
1873
+
1874
+ ## Required Output
1875
+ - Task ID for reference
1876
+ - Confirmation of task creation
1877
+ - Next steps`
1878
+ },
1879
+ {
1880
+ name: "plan",
1881
+ description: "Create a detailed implementation plan",
1882
+ category: "core",
1883
+ usage: "/plan <feature or task>",
1884
+ examples: ["/plan user authentication system", "/plan refactor database layer"],
1885
+ content: `Create a comprehensive plan before implementation.
1886
+
1887
+ ## Workflow
1888
+ 1. UNDERSTAND: Clarify requirements through Socratic questioning
1889
+ 2. RESEARCH: Check existing patterns and dependencies
1890
+ 3. BREAK DOWN: Create 2-5 minute sub-tasks with:
1891
+ - Exact file paths
1892
+ - Expected changes
1893
+ - Verification steps
1894
+ 4. DOCUMENT: Write plan to memory/plans/
1895
+
1896
+ ## Output Format
1897
+ \`\`\`markdown
1898
+ # Plan: [Feature Name]
1899
+
1900
+ ## Overview
1901
+ Brief description of the goal.
1902
+
1903
+ ## Tasks
1904
+ 1. [ ] Task 1 - file.ts
1905
+ 2. [ ] Task 2 - component.tsx
1906
+ ...
1907
+
1908
+ ## Dependencies
1909
+ - List dependencies
1910
+
1911
+ ## Risks
1912
+ - Potential issues
1913
+
1914
+ ## Verification
1915
+ - How to verify completion
1916
+ \`\`\``
1917
+ },
1918
+ {
1919
+ name: "implement",
1920
+ description: "Implement a planned task with TDD",
1921
+ category: "core",
1922
+ usage: "/implement <task reference>",
1923
+ examples: ["/implement task-001", '/implement "add login form"'],
1924
+ content: `Implement a task following TDD principles.
1925
+
1926
+ ## Workflow
1927
+ 1. LOAD: Get task details from .beads/ or plan
1928
+ 2. TEST: Write failing tests first (RED)
1929
+ 3. IMPLEMENT: Write minimal code to pass (GREEN)
1930
+ 4. REFACTOR: Clean up while keeping tests green
1931
+ 5. VERIFY: Run full test suite
1932
+
1933
+ ## Hard Gates
1934
+ Before marking complete:
1935
+ - [ ] All new tests pass
1936
+ - [ ] No regressions
1937
+ - [ ] Type check passes
1938
+ - [ ] Linting passes`
1939
+ },
1940
+ {
1941
+ name: "finish",
1942
+ description: "Complete a task with quality gates",
1943
+ category: "core",
1944
+ usage: "/finish [task-id]",
1945
+ examples: ["/finish", "/finish task-001"],
1946
+ content: `Complete the current task with mandatory quality checks.
1947
+
1948
+ ## Hard Gates (Must ALL Pass)
1949
+ 1. \`npm run typecheck\` - No type errors
1950
+ 2. \`npm run test\` - All tests pass
1951
+ 3. \`npm run lint\` - No linting errors
1952
+ 4. \`npm run build\` - Build succeeds
1953
+
1954
+ ## Workflow
1955
+ 1. Run all quality gates
1956
+ 2. If any fail, report issues and stop
1957
+ 3. If all pass, update task status to "completed"
1958
+ 4. Create summary of changes
1959
+ 5. Suggest commit message`
1960
+ },
1961
+ {
1962
+ name: "handoff",
1963
+ description: "Create handoff bundle for session continuity",
1964
+ category: "core",
1965
+ usage: "/handoff",
1966
+ examples: ["/handoff"],
1967
+ content: `Create a handoff bundle for context transfer to next session.
1968
+
1969
+ ## Workflow
1970
+ 1. Summarize current progress
1971
+ 2. Document:
1972
+ - What was completed
1973
+ - What remains
1974
+ - Current blockers
1975
+ - Key decisions made
1976
+ 3. Save to memory/handoffs/[timestamp].md
1977
+
1978
+ ## Output Format
1979
+ \`\`\`markdown
1980
+ # Handoff: [Date/Time]
1981
+
1982
+ ## Completed
1983
+ - List of completed items
1984
+
1985
+ ## In Progress
1986
+ - Current work state
1987
+
1988
+ ## Remaining
1989
+ - What still needs to be done
1990
+
1991
+ ## Context
1992
+ - Important context for next session
1993
+
1994
+ ## Next Steps
1995
+ - Recommended actions
1996
+ \`\`\``
1997
+ },
1998
+ {
1999
+ name: "resume",
2000
+ description: "Resume from last handoff",
2001
+ category: "core",
2002
+ usage: "/resume",
2003
+ examples: ["/resume"],
2004
+ content: `Resume work from the most recent handoff.
2005
+
2006
+ ## Workflow
2007
+ 1. Load latest handoff from memory/handoffs/
2008
+ 2. Display summary to user
2009
+ 3. Propose next actions
2010
+ 4. Continue from where left off`
2011
+ },
2012
+ // Quick Actions
2013
+ {
2014
+ name: "fix",
2015
+ description: "Quick fix for an issue",
2016
+ category: "quick",
2017
+ usage: "/fix <issue description>",
2018
+ examples: ["/fix button not clickable", "/fix type error in auth.ts"],
2019
+ content: `Quick fix with minimal ceremony.
2020
+
2021
+ ## Workflow
2022
+ 1. Identify the issue
2023
+ 2. Make minimal change to fix
2024
+ 3. Verify fix works
2025
+ 4. Run affected tests`
2026
+ },
2027
+ {
2028
+ name: "fix-types",
2029
+ description: "Fix TypeScript type errors",
2030
+ category: "quick",
2031
+ usage: "/fix-types [file]",
2032
+ examples: ["/fix-types", "/fix-types src/auth.ts"],
2033
+ content: `Fix TypeScript type errors systematically.
2034
+
2035
+ ## Workflow
2036
+ 1. Run \`npm run typecheck\`
2037
+ 2. Parse error output
2038
+ 3. Fix each error in dependency order
2039
+ 4. Verify all types pass`
2040
+ },
2041
+ {
2042
+ name: "fix-ci",
2043
+ description: "Fix CI/CD pipeline failures",
2044
+ category: "quick",
2045
+ usage: "/fix-ci",
2046
+ examples: ["/fix-ci"],
2047
+ content: `Diagnose and fix CI failures.
2048
+
2049
+ ## Workflow
2050
+ 1. Check CI logs for errors
2051
+ 2. Reproduce locally if possible
2052
+ 3. Fix issues in order:
2053
+ - Type errors
2054
+ - Test failures
2055
+ - Lint errors
2056
+ - Build errors
2057
+ 4. Verify full CI pipeline locally`
2058
+ },
2059
+ {
2060
+ name: "commit",
2061
+ description: "Create a well-formatted commit",
2062
+ category: "quick",
2063
+ usage: "/commit [message]",
2064
+ examples: ["/commit", '/commit "feat: add login"'],
2065
+ content: `Create a conventional commit.
2066
+
2067
+ ## Workflow
2068
+ 1. Stage changes: \`git add -A\`
2069
+ 2. Generate commit message following conventional commits:
2070
+ - feat: New feature
2071
+ - fix: Bug fix
2072
+ - docs: Documentation
2073
+ - refactor: Code refactoring
2074
+ - test: Adding tests
2075
+ - chore: Maintenance
2076
+ 3. Commit with message`
2077
+ },
2078
+ {
2079
+ name: "pr",
2080
+ description: "Create a pull request",
2081
+ category: "quick",
2082
+ usage: "/pr [title]",
2083
+ examples: ["/pr", '/pr "Add user authentication"'],
2084
+ content: `Create a pull request with proper description.
2085
+
2086
+ ## Workflow
2087
+ 1. Push current branch
2088
+ 2. Generate PR description:
2089
+ - Summary of changes
2090
+ - Related issues
2091
+ - Testing done
2092
+ - Screenshots if UI changes
2093
+ 3. Create PR via GitHub CLI or provide URL`
2094
+ },
2095
+ // Research & Analysis
2096
+ {
2097
+ name: "research",
2098
+ description: "Deep research on a topic",
2099
+ category: "research",
2100
+ usage: "/research <topic>",
2101
+ examples: ["/research React Server Components", "/research OAuth 2.0 best practices"],
2102
+ content: `Conduct thorough research and document findings.
2103
+
2104
+ ## Workflow
2105
+ 1. Search documentation and resources
2106
+ 2. Find code examples and patterns
2107
+ 3. Evaluate options and trade-offs
2108
+ 4. Document findings in memory/research/
2109
+
2110
+ ## Output
2111
+ - Summary of findings
2112
+ - Recommended approach
2113
+ - Code examples
2114
+ - Links to resources`
2115
+ },
2116
+ {
2117
+ name: "analyze-project",
2118
+ description: "Analyze project structure and patterns",
2119
+ category: "research",
2120
+ usage: "/analyze-project",
2121
+ examples: ["/analyze-project"],
2122
+ content: `Comprehensive project analysis.
2123
+
2124
+ ## Workflow
2125
+ 1. Scan directory structure
2126
+ 2. Identify:
2127
+ - Tech stack
2128
+ - Architecture patterns
2129
+ - Key dependencies
2130
+ - Coding conventions
2131
+ 3. Document findings in AGENTS.md`
2132
+ },
2133
+ {
2134
+ name: "review-codebase",
2135
+ description: "Review codebase quality",
2136
+ category: "research",
2137
+ usage: "/review-codebase [path]",
2138
+ examples: ["/review-codebase", "/review-codebase src/"],
2139
+ content: `Review codebase for quality issues.
2140
+
2141
+ ## Workflow
2142
+ 1. Check code quality metrics
2143
+ 2. Identify:
2144
+ - Code smells
2145
+ - Security issues
2146
+ - Performance concerns
2147
+ - Test coverage gaps
2148
+ 3. Prioritize findings
2149
+ 4. Suggest improvements`
2150
+ },
2151
+ // Design & Planning
2152
+ {
2153
+ name: "design",
2154
+ description: "Design a feature or system",
2155
+ category: "design",
2156
+ usage: "/design <feature>",
2157
+ examples: ["/design notification system", "/design API gateway"],
2158
+ content: `Design a feature with thorough planning.
2159
+
2160
+ ## Workflow
2161
+ 1. Requirements gathering (Socratic questioning)
2162
+ 2. Research existing solutions
2163
+ 3. Design options with trade-offs
2164
+ 4. Choose approach
2165
+ 5. Document design in memory/
2166
+
2167
+ ## Output
2168
+ - Design document
2169
+ - Architecture diagrams (described)
2170
+ - API contracts
2171
+ - Data models`
2172
+ },
2173
+ {
2174
+ name: "brainstorm",
2175
+ description: "Brainstorm ideas for a problem",
2176
+ category: "design",
2177
+ usage: "/brainstorm <problem>",
2178
+ examples: ["/brainstorm user retention", "/brainstorm performance optimization"],
2179
+ content: `Collaborative brainstorming session.
2180
+
2181
+ ## Workflow
2182
+ 1. Define the problem clearly
2183
+ 2. Generate diverse ideas (no judgement)
2184
+ 3. Group related ideas
2185
+ 4. Evaluate feasibility
2186
+ 5. Select top candidates`
2187
+ },
2188
+ // Git & Version Control
2189
+ {
2190
+ name: "branch",
2191
+ description: "Create a new feature branch",
2192
+ category: "git",
2193
+ usage: "/branch <name>",
2194
+ examples: ["/branch feat/auth", "/branch fix/navigation-bug"],
2195
+ content: `Create and switch to a new branch.
2196
+
2197
+ ## Workflow
2198
+ 1. Ensure clean working directory
2199
+ 2. Pull latest main/master
2200
+ 3. Create branch with naming convention:
2201
+ - feat/* for features
2202
+ - fix/* for bug fixes
2203
+ - refactor/* for refactoring
2204
+ - docs/* for documentation
2205
+ 4. Switch to new branch`
2206
+ },
2207
+ {
2208
+ name: "merge",
2209
+ description: "Merge current branch to target",
2210
+ category: "git",
2211
+ usage: "/merge [target]",
2212
+ examples: ["/merge", "/merge main"],
2213
+ content: `Merge current branch to target.
2214
+
2215
+ ## Workflow
2216
+ 1. Run quality gates first
2217
+ 2. Commit any pending changes
2218
+ 3. Switch to target branch
2219
+ 4. Pull latest
2220
+ 5. Merge feature branch
2221
+ 6. Resolve conflicts if any
2222
+ 7. Push`
2223
+ },
2224
+ // Utilities
2225
+ {
2226
+ name: "status",
2227
+ description: "Show current status overview",
2228
+ category: "utility",
2229
+ usage: "/status",
2230
+ examples: ["/status"],
2231
+ content: `Display comprehensive status.
2232
+
2233
+ ## Shows
2234
+ - Current task (from Beads)
2235
+ - Git status
2236
+ - Active branch
2237
+ - Pending changes
2238
+ - Test status
2239
+ - Recent activity`
2240
+ },
2241
+ {
2242
+ name: "help",
2243
+ description: "Show available commands",
2244
+ category: "utility",
2245
+ usage: "/help [command]",
2246
+ examples: ["/help", "/help plan"],
2247
+ content: `Display help information.
2248
+
2249
+ If no command specified, list all available commands.
2250
+ If command specified, show detailed help for that command.`
2251
+ },
2252
+ {
2253
+ name: "analyze-figma",
2254
+ description: "Analyze Figma design and extract design tokens using Figma API",
2255
+ category: "design",
2256
+ usage: "/analyze-figma <figma-url>",
2257
+ examples: [
2258
+ "/analyze-figma https://www.figma.com/design/...",
2259
+ "/analyze-figma [figma-url]"
2260
+ ],
2261
+ content: `Analyze a Figma design and extract all design tokens automatically using Figma API.
2262
+
2263
+ ## Workflow
2264
+
2265
+ **Step 1: Extract URL from User Input**
2266
+
2267
+ The Figma URL is provided in the SAME message as the command. Extract it:
2268
+ - Check the full user input message
2269
+ - Look for URL pattern: \`https://www.figma.com/design/...\` or \`http://www.figma.com/design/...\`
2270
+ - Extract the ENTIRE URL including all query parameters
2271
+ - If URL not found in current message, check previous messages
2272
+
2273
+ **Step 2: Check Tool Configuration**
2274
+
2275
+ Before calling the tool, verify that Figma tool is configured:
2276
+ - If not configured, inform user to run: \`aikit skills figma-analysis config\`
2277
+ - The tool requires a Figma Personal Access Token
2278
+
2279
+ **Step 3: Call MCP Tool**
2280
+
2281
+ **CRITICAL**: You MUST use the MCP tool \`tool_read_figma_design\`, NOT web fetch!
2282
+
2283
+ **The correct tool name is**: \`tool_read_figma_design\` (exposed via MCP)
2284
+
2285
+ **DO NOT use**:
2286
+ - \u274C \`read_figma_design\` (wrong - missing "tool_" prefix)
2287
+ - \u274C \`figma-analysis/read_figma_design\` (wrong format)
2288
+ - \u274C Web fetch (file requires authentication)
2289
+
2290
+ **DO use**:
2291
+ - \u2705 \`tool_read_figma_design\` (correct MCP tool name)
2292
+
2293
+ Use the MCP tool:
2294
+ \`\`\`
2295
+ Use MCP tool: tool_read_figma_design
2296
+ Arguments: { "url": "[extracted URL]" }
2297
+ \`\`\`
2298
+
2299
+ The tool has the Figma API token configured and will authenticate automatically.
2300
+
2301
+ This tool will:
2302
+ 1. Validate the Figma URL format
2303
+ 2. Check if Figma tool is configured
2304
+ 3. Call Figma API to fetch design data
2305
+ 4. Extract design tokens:
2306
+ - Colors (from fills and strokes)
2307
+ - Typography (font families, sizes, weights, line heights)
2308
+ - Spacing system (8px grid detection)
2309
+ - Components (from Figma components)
2310
+ - Screens/Frames (dimensions and names)
2311
+ - Breakpoints (common responsive breakpoints)
2312
+ 5. Return formatted markdown with all extracted tokens
2313
+
2314
+ **Step 4: Format and Save**
2315
+
2316
+ Format extracted tokens as structured markdown:
2317
+ \`\`\`markdown
2318
+ # Figma Design Analysis
2319
+
2320
+ **Source**: [Figma URL]
2321
+ **Analyzed**: [Date]
2322
+
2323
+ ## Screens/Pages
2324
+ - [List all screens]
2325
+
2326
+ ## Color Palette
2327
+ ### Primary Colors
2328
+ - Color Name: #hexcode
2329
+ [Continue for all colors]
2330
+
2331
+ ## Typography
2332
+ ### Font Families
2333
+ - Primary: Font Name
2334
+ [Continue]
2335
+
2336
+ ### Font Sizes
2337
+ - Heading 1: 48px
2338
+ [Continue for all sizes]
2339
+
2340
+ ## Spacing System
2341
+ - Base unit: 8px
2342
+ - Values used: [list]
2343
+
2344
+ ## Component Structure
2345
+ - Header: [description]
2346
+ [Continue for all components]
2347
+
2348
+ ## Layout Grid
2349
+ - Container max-width: [value]
2350
+ - Columns: [value]
2351
+
2352
+ ## Responsive Breakpoints
2353
+ - Mobile: [range]
2354
+ - Tablet: [range]
2355
+ - Desktop: [range]
2356
+
2357
+ ## Assets Needed
2358
+ ### Images
2359
+ - [List]
2360
+
2361
+ ### Icons
2362
+ - [List]
2363
+
2364
+ ### Fonts
2365
+ - [List]
2366
+ \`\`\`
2367
+
2368
+ Save to memory using memory-update tool:
2369
+ \`\`\`
2370
+ Use tool: memory-update
2371
+ Arguments: {
2372
+ "key": "research/figma-analysis",
2373
+ "content": "[formatted markdown]"
2374
+ }
2375
+ \`\`\`
2376
+
2377
+ **Step 5: Report Results**
2378
+
2379
+ Summarize what was extracted:
2380
+ - Number of colors found
2381
+ - Number of typography styles
2382
+ - Number of components
2383
+ - Number of screens/frames
2384
+ - Confirm save location: \`memory/research/figma-analysis.md\`
2385
+
2386
+ ## Important Notes
2387
+
2388
+ - **DO NOT** ask user to provide URL again - extract it from input
2389
+ - **DO NOT** wait - start immediately after extracting URL
2390
+ - The URL is in the SAME message as the command
2391
+ - The tool uses Figma API, so the file must be accessible with your API token
2392
+ - If the tool returns an error about configuration, guide user to run: \`aikit skills figma-analysis config\`
2393
+ - If the tool returns an error about access, verify the file is accessible with your token
2394
+
2395
+ The analysis will be saved automatically for later reference.`
2396
+ },
2397
+ {
2398
+ name: "refactor",
2399
+ description: "Refactor code to improve structure without changing behavior",
2400
+ category: "quick",
2401
+ usage: "/refactor [file or pattern]",
2402
+ examples: ["/refactor src/utils.ts", "/refactor duplicate code"],
2403
+ content: `Refactor code following best practices.
2404
+
2405
+ ## Workflow
2406
+ 1. Ensure tests are in place
2407
+ 2. Identify refactoring opportunities
2408
+ 3. Apply refactoring incrementally
2409
+ 4. Run tests after each change
2410
+ 5. Verify no behavior changes`
2411
+ },
2412
+ {
2413
+ name: "test",
2414
+ description: "Run tests and show results",
2415
+ category: "utility",
2416
+ usage: "/test [pattern]",
2417
+ examples: ["/test", "/test auth", "/test --watch"],
2418
+ content: `Run test suite and display results.
2419
+
2420
+ ## Workflow
2421
+ 1. Run test command: \`npm run test\`
2422
+ 2. Parse and display results
2423
+ 3. Show coverage if available
2424
+ 4. Highlight failures
2425
+ 5. Suggest fixes for failures`
2426
+ },
2427
+ {
2428
+ name: "lint",
2429
+ description: "Run linter and fix issues",
2430
+ category: "quick",
2431
+ usage: "/lint [--fix]",
2432
+ examples: ["/lint", "/lint --fix"],
2433
+ content: `Run linter and optionally fix issues.
2434
+
2435
+ ## Workflow
2436
+ 1. Run linter: \`npm run lint\`
2437
+ 2. Parse errors and warnings
2438
+ 3. If --fix flag, run auto-fix
2439
+ 4. Report remaining issues
2440
+ 5. Suggest manual fixes if needed`
2441
+ },
2442
+ {
2443
+ name: "deploy",
2444
+ description: "Deploy application to production",
2445
+ category: "utility",
2446
+ usage: "/deploy [environment]",
2447
+ examples: ["/deploy", "/deploy staging", "/deploy production"],
2448
+ content: `Deploy application with quality checks.
2449
+
2450
+ ## Workflow
2451
+ 1. Run quality gates (test, lint, build)
2452
+ 2. Check for uncommitted changes
2453
+ 3. Build production bundle
2454
+ 4. Deploy to target environment
2455
+ 5. Verify deployment success`
2456
+ },
2457
+ {
2458
+ name: "rollback",
2459
+ description: "Rollback to previous deployment",
2460
+ category: "utility",
2461
+ usage: "/rollback [version]",
2462
+ examples: ["/rollback", "/rollback v1.2.3"],
2463
+ content: `Rollback to previous version.
2464
+
2465
+ ## Workflow
2466
+ 1. Identify current version
2467
+ 2. List available versions
2468
+ 3. Confirm rollback target
2469
+ 4. Execute rollback
2470
+ 5. Verify rollback success`
2471
+ },
2472
+ {
2473
+ name: "logs",
2474
+ description: "View application logs",
2475
+ category: "utility",
2476
+ usage: "/logs [--tail] [--follow]",
2477
+ examples: ["/logs", "/logs --tail 100", "/logs --follow"],
2478
+ content: `View and filter application logs.
2479
+
2480
+ ## Workflow
2481
+ 1. Determine log location
2482
+ 2. Apply filters if specified
2483
+ 3. Display logs
2484
+ 4. If --follow, stream updates
2485
+ 5. Format for readability`
2486
+ }
2487
+ ];
2488
+ var CommandRunner = class {
2489
+ config;
2490
+ commandsCache = /* @__PURE__ */ new Map();
2491
+ constructor(config) {
2492
+ this.config = config;
2493
+ }
2494
+ /**
2495
+ * List all available commands
2496
+ */
2497
+ async listCommands() {
2498
+ const commands = [];
2499
+ for (const cmd of DEFAULT_COMMANDS) {
2500
+ commands.push({
2501
+ ...cmd,
2502
+ filePath: "built-in"
2503
+ });
2504
+ }
2505
+ const globalCommandsPath = paths.commands(paths.globalConfig());
2506
+ try {
2507
+ const globalCommands = await this.loadCommandsFromDir(globalCommandsPath);
2508
+ commands.push(...globalCommands);
2509
+ } catch {
2510
+ }
2511
+ const projectCommandsPath = paths.commands(this.config.configPath);
2512
+ if (projectCommandsPath !== globalCommandsPath) {
2513
+ try {
2514
+ const projectCommands = await this.loadCommandsFromDir(projectCommandsPath);
2515
+ for (const cmd of projectCommands) {
2516
+ const existingIndex = commands.findIndex((c) => c.name === cmd.name);
2517
+ if (existingIndex >= 0) {
2518
+ commands[existingIndex] = cmd;
2519
+ } else {
2520
+ commands.push(cmd);
2521
+ }
2522
+ }
2523
+ } catch {
2524
+ }
2525
+ }
2526
+ return commands.sort((a, b) => a.name.localeCompare(b.name));
2527
+ }
2528
+ /**
2529
+ * Get a specific command
2530
+ */
2531
+ async getCommand(name) {
2532
+ if (this.commandsCache.has(name)) {
2533
+ return this.commandsCache.get(name);
2534
+ }
2535
+ const commands = await this.listCommands();
2536
+ const command = commands.find((c) => c.name === name);
2537
+ if (command) {
2538
+ this.commandsCache.set(name, command);
2539
+ }
2540
+ return command || null;
2541
+ }
2542
+ /**
2543
+ * Create a custom command
2544
+ */
2545
+ async createCommand(name, options) {
2546
+ const configPath = options?.global ? paths.globalConfig() : this.config.configPath;
2547
+ const category = options?.category || "utility";
2548
+ const commandsDir = join4(paths.commands(configPath), category);
2549
+ await mkdir2(commandsDir, { recursive: true });
2550
+ const fileName = `${name}.md`;
2551
+ const filePath = join4(commandsDir, fileName);
2552
+ const frontmatter = {
2553
+ name,
2554
+ description: options?.description || `Custom command: ${name}`,
2555
+ category,
2556
+ usage: options?.usage || `/${name}`,
2557
+ examples: options?.examples || [`/${name}`]
2558
+ };
2559
+ const content = options?.content || `## /${name}
2560
+
2561
+ Describe what this command does.
2562
+
2563
+ ## Workflow
2564
+ 1. Step 1
2565
+ 2. Step 2
2566
+ 3. Step 3
2567
+ `;
2568
+ const fileContent = matter2.stringify(content, frontmatter);
2569
+ await writeFile2(filePath, fileContent);
2570
+ const command = {
2571
+ name,
2572
+ description: frontmatter.description,
2573
+ category,
2574
+ usage: frontmatter.usage,
2575
+ examples: frontmatter.examples,
2576
+ content,
2577
+ filePath
2578
+ };
2579
+ this.commandsCache.set(name, command);
2580
+ return command;
2581
+ }
2582
+ /**
2583
+ * Format command for agent consumption
2584
+ */
2585
+ formatForAgent(command) {
2586
+ return `# Command: /${command.name}
2587
+
2588
+ ## Usage
2589
+ \`${command.usage}\`
2590
+
2591
+ ## Description
2592
+ ${command.description}
2593
+
2594
+ ## Examples
2595
+ ${command.examples.map((e) => `- \`${e}\``).join("\n")}
2596
+
2597
+ ## Workflow
2598
+ ${command.content}
2599
+ `;
2600
+ }
2601
+ /**
2602
+ * Load commands from directory (recursively)
2603
+ */
2604
+ async loadCommandsFromDir(dir) {
2605
+ const commands = [];
2606
+ const processDir = async (currentDir, category) => {
2607
+ try {
2608
+ const entries = await readdir2(currentDir, { withFileTypes: true });
2609
+ for (const entry of entries) {
2610
+ const fullPath = join4(currentDir, entry.name);
2611
+ if (entry.isDirectory()) {
2612
+ await processDir(fullPath, entry.name);
2613
+ } else if (extname2(entry.name) === ".md") {
2614
+ const content = await readFile3(fullPath, "utf-8");
2615
+ const { data, content: body } = matter2(content);
2616
+ const frontmatter = data;
2617
+ const name = frontmatter.name || basename2(entry.name, ".md");
2618
+ commands.push({
2619
+ name,
2620
+ description: frontmatter.description || "",
2621
+ category: frontmatter.category || category,
2622
+ usage: frontmatter.usage || `/${name}`,
2623
+ examples: frontmatter.examples || [],
2624
+ content: body.trim(),
2625
+ filePath: fullPath
2626
+ });
2627
+ }
2628
+ }
2629
+ } catch {
2630
+ return;
2631
+ }
2632
+ };
2633
+ await processDir(dir, "custom");
2634
+ return commands;
2635
+ }
2636
+ };
2637
+
2638
+ // src/core/tools.ts
2639
+ init_esm_shims();
2640
+ init_paths();
2641
+ init_logger();
2642
+ import { readdir as readdir4, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
2643
+ import { join as join8, extname as extname3 } from "path";
2644
+ import { z as z2 } from "zod";
2645
+ var ToolArgSchema = z2.object({
2646
+ type: z2.enum(["string", "number", "boolean", "array", "object"]),
2647
+ description: z2.string(),
2648
+ required: z2.boolean().optional().default(true),
2649
+ default: z2.any().optional()
2650
+ });
2651
+ var BUILT_IN_TOOLS = [
2652
+ {
2653
+ name: "websearch",
2654
+ description: "Search the web for documentation, articles, and current information",
2655
+ args: {
2656
+ query: {
2657
+ type: "string",
2658
+ description: "The search query",
2659
+ required: true
2660
+ },
2661
+ numResults: {
2662
+ type: "number",
2663
+ description: "Number of results to return (default: 5)",
2664
+ required: false,
2665
+ default: 5
2666
+ }
2667
+ },
2668
+ async execute({ query }) {
2669
+ return `Web search results for: "${query}"
2670
+
2671
+ Note: Configure a search provider in AIKit settings for actual results.`;
2672
+ }
2673
+ },
2674
+ {
2675
+ name: "codesearch",
2676
+ description: "Search GitHub for code patterns and examples across millions of repositories",
2677
+ args: {
2678
+ query: {
2679
+ type: "string",
2680
+ description: "The code pattern or search query",
2681
+ required: true
2682
+ },
2683
+ language: {
2684
+ type: "string",
2685
+ description: "Programming language to filter by",
2686
+ required: false
2687
+ }
2688
+ },
2689
+ async execute({ query, language }) {
2690
+ const langFilter = language ? ` in ${language}` : "";
2691
+ return `GitHub code search for: "${query}"${langFilter}
2692
+
2693
+ Note: Configure GitHub integration in AIKit settings for actual results.`;
2694
+ }
2695
+ },
2696
+ {
2697
+ name: "memory-read",
2698
+ description: "Read from persistent memory (project or global)",
2699
+ args: {
2700
+ key: {
2701
+ type: "string",
2702
+ description: "The memory key to read",
2703
+ required: true
2704
+ }
2705
+ },
2706
+ async execute({ key }) {
2707
+ return `Memory read: ${key}`;
2708
+ }
2709
+ },
2710
+ {
2711
+ name: "memory-update",
2712
+ description: "Update persistent memory with new information",
2713
+ args: {
2714
+ key: {
2715
+ type: "string",
2716
+ description: "The memory key to update",
2717
+ required: true
2718
+ },
2719
+ content: {
2720
+ type: "string",
2721
+ description: "The content to write",
2722
+ required: true
2723
+ },
2724
+ append: {
2725
+ type: "boolean",
2726
+ description: "Whether to append to existing content (default: true)",
2727
+ required: false,
2728
+ default: true
2729
+ }
2730
+ },
2731
+ async execute({ key, append = true }) {
2732
+ return `Memory updated: ${key} (append: ${append})`;
2733
+ }
2734
+ },
2735
+ {
2736
+ name: "find_skills",
2737
+ description: "Find available workflow skills",
2738
+ args: {
2739
+ query: {
2740
+ type: "string",
2741
+ description: "Optional search query to filter skills",
2742
+ required: false
2743
+ }
2744
+ },
2745
+ async execute({ query }) {
2746
+ return `Skills matching: ${query || "all"}`;
2747
+ }
2748
+ },
2749
+ {
2750
+ name: "use_skill",
2751
+ description: "Load and use a specific skill workflow",
2752
+ args: {
2753
+ name: {
2754
+ type: "string",
2755
+ description: "Name of the skill to use",
2756
+ required: true
2757
+ }
2758
+ },
2759
+ async execute({ name }) {
2760
+ return `Loading skill: ${name}`;
2761
+ }
2762
+ },
2763
+ {
2764
+ name: "read_figma_design",
2765
+ description: "Read and analyze a Figma design using Figma API. Extracts design tokens including colors, typography, spacing, components, and layout.",
2766
+ args: {
2767
+ url: {
2768
+ type: "string",
2769
+ description: "Figma design URL to analyze (must start with https://www.figma.com/design/)",
2770
+ required: true
2771
+ }
2772
+ },
2773
+ async execute({ url }, context) {
2774
+ if (!url || typeof url !== "string") {
2775
+ return "Error: Invalid URL provided";
2776
+ }
2777
+ if (!url.startsWith("https://www.figma.com/design/") && !url.startsWith("http://www.figma.com/design/")) {
2778
+ return `Error: Invalid Figma URL format. URL must start with https://www.figma.com/design/
2779
+
2780
+ Provided URL: ${url}`;
2781
+ }
2782
+ const configManager = context?.toolConfigManager;
2783
+ if (!configManager) {
2784
+ return `Error: Tool configuration manager not available. This usually means the MCP server isn't properly initialized. Please restart OpenCode.
2785
+
2786
+ If the issue persists, configure Figma tool manually: aikit skills figma-analysis config`;
2787
+ }
2788
+ const isReady = await configManager.isToolReady("figma-analysis");
2789
+ if (!isReady) {
2790
+ const toolConfig = await configManager.getToolConfig("figma-analysis");
2791
+ if (toolConfig?.status === "error") {
2792
+ return `Error: Figma tool configuration error: ${toolConfig.errorMessage || "Unknown error"}
2793
+
2794
+ Please reconfigure: aikit skills figma-analysis config`;
2795
+ }
2796
+ return `Error: Figma tool is not configured. Please run: aikit skills figma-analysis config
2797
+
2798
+ This will guide you through setting up your Figma Personal Access Token.`;
2799
+ }
2800
+ const apiKey = await configManager.getApiKey("figma-analysis");
2801
+ if (!apiKey) {
2802
+ return `Error: Figma API key not found. Please run: aikit skills figma-analysis config`;
2803
+ }
2804
+ try {
2805
+ const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
2806
+ const client = new FigmaMcpClient2(apiKey, configManager);
2807
+ const assetsDir = "./assets/images";
2808
+ const tokens = await client.extractDesignTokens(url, false, assetsDir);
2809
+ let result = `# Figma Design Analysis
2810
+
2811
+ `;
2812
+ result += `**URL**: ${url}
2813
+
2814
+ `;
2815
+ result += `## Design Structure & Content
2816
+
2817
+ `;
2818
+ if (tokens.structure) {
2819
+ result += `### Node Hierarchy (${tokens.structure.nodes.length} nodes)
2820
+
2821
+ `;
2822
+ result += `\`\`\`
2823
+ ${tokens.structure.hierarchy}
2824
+ \`\`\`
2825
+
2826
+ `;
2827
+ const textNodes = tokens.structure.nodes.filter((n) => n.type === "TEXT" && n.content);
2828
+ if (textNodes.length > 0) {
2829
+ result += `### Text Content (${textNodes.length} text elements)
2830
+
2831
+ `;
2832
+ textNodes.slice(0, 20).forEach((node) => {
2833
+ const preview = node.content && node.content.length > 100 ? node.content.substring(0, 100) + "..." : node.content;
2834
+ result += `- **${node.name}**: "${preview}"
2835
+ `;
2836
+ if (node.styles) {
2837
+ result += ` - Style: ${node.styles.fontFamily || "N/A"} ${node.styles.fontSize || "N/A"}px, weight ${node.styles.fontWeight || "N/A"}
2838
+ `;
2839
+ }
2840
+ });
2841
+ if (textNodes.length > 20) {
2842
+ result += `
2843
+ ... and ${textNodes.length - 20} more text elements
2844
+ `;
2845
+ }
2846
+ result += `
2847
+ `;
2848
+ }
2849
+ const frameNodes = tokens.structure.nodes.filter((n) => n.type === "FRAME" || n.type === "COMPONENT");
2850
+ if (frameNodes.length > 0) {
2851
+ result += `### Layout Structure (${frameNodes.length} frames/components)
2852
+
2853
+ `;
2854
+ frameNodes.slice(0, 15).forEach((node) => {
2855
+ result += `- **${node.name}** (${node.type})
2856
+ `;
2857
+ if (node.position) {
2858
+ result += ` - Position: x=${Math.round(node.position.x)}, y=${Math.round(node.position.y)}
2859
+ `;
2860
+ result += ` - Size: ${Math.round(node.position.width)}\xD7${Math.round(node.position.height)}px
2861
+ `;
2862
+ }
2863
+ if (node.styles?.layout) {
2864
+ result += ` - Layout: ${node.styles.layout}${node.styles.gap ? `, gap: ${node.styles.gap}px` : ""}
2865
+ `;
2866
+ }
2867
+ if (node.children && node.children.length > 0) {
2868
+ result += ` - Children: ${node.children.length}
2869
+ `;
2870
+ }
2871
+ });
2872
+ if (frameNodes.length > 15) {
2873
+ result += `
2874
+ ... and ${frameNodes.length - 15} more frames/components
2875
+ `;
2876
+ }
2877
+ result += `
2878
+ `;
2879
+ }
2880
+ }
2881
+ result += `## Design Tokens
2882
+
2883
+ `;
2884
+ if (tokens.colors.length > 0) {
2885
+ result += `### Colors (${tokens.colors.length} found)
2886
+
2887
+ `;
2888
+ tokens.colors.slice(0, 30).forEach((color) => {
2889
+ result += `- \`${color.hex}\`
2890
+ `;
2891
+ });
2892
+ if (tokens.colors.length > 30) {
2893
+ result += `
2894
+ ... and ${tokens.colors.length - 30} more colors
2895
+ `;
2896
+ }
2897
+ result += `
2898
+ `;
2899
+ }
2900
+ if (tokens.typography.length > 0) {
2901
+ result += `### Typography (${tokens.typography.length} styles)
2902
+
2903
+ `;
2904
+ tokens.typography.forEach((typography) => {
2905
+ result += `- **${typography.name}**: ${typography.fontFamily}, ${typography.fontSize}px, weight ${typography.fontWeight}, line-height ${typography.lineHeight}px
2906
+ `;
2907
+ });
2908
+ result += `
2909
+ `;
2910
+ }
2911
+ result += `### Spacing System
2912
+
2913
+ `;
2914
+ result += `- Base unit: ${tokens.spacing.unit}px
2915
+ `;
2916
+ result += `- Scale: ${tokens.spacing.scale.length > 0 ? tokens.spacing.scale.join(", ") : "Not detected"}
2917
+
2918
+ `;
2919
+ if (tokens.components.length > 0) {
2920
+ result += `### Components (${tokens.components.length} found)
2921
+
2922
+ `;
2923
+ tokens.components.forEach((component) => {
2924
+ result += `- **${component.name}**: ${component.type}${component.description ? ` - ${component.description}` : ""}
2925
+ `;
2926
+ });
2927
+ result += `
2928
+ `;
2929
+ }
2930
+ if (tokens.screens.length > 0) {
2931
+ result += `## Available Screens/Frames (${tokens.screens.length} found)
2932
+
2933
+ `;
2934
+ result += `**Please confirm which screen(s) you want to develop:**
2935
+
2936
+ `;
2937
+ tokens.screens.forEach((screen, index) => {
2938
+ result += `${index + 1}. **${screen.name}**
2939
+ `;
2940
+ result += ` - Size: ${screen.width}\xD7${screen.height}px
2941
+ `;
2942
+ result += ` - Type: ${screen.type}
2943
+ `;
2944
+ if (screen.childrenCount) {
2945
+ result += ` - Components: ${screen.childrenCount}
2946
+ `;
2947
+ }
2948
+ result += ` - ID: \`${screen.id}\`
2949
+
2950
+ `;
2951
+ });
2952
+ result += `
2953
+ **To proceed, simply reply with the screen number(s) or name(s) you want to develop.**
2954
+ `;
2955
+ result += `Example: "1" or "Main Page" or "1, 2, 3"
2956
+
2957
+ `;
2958
+ }
2959
+ result += `### Responsive Breakpoints
2960
+
2961
+ `;
2962
+ result += `- ${tokens.breakpoints.join("px, ")}px
2963
+
2964
+ `;
2965
+ if (tokens.assets && tokens.assets.length > 0) {
2966
+ result += `## Downloaded Assets (${tokens.assets.length} files)
2967
+
2968
+ `;
2969
+ result += `All assets have been downloaded to: \`${tokens.assets[0].path.split("/").slice(0, -1).join("/")}\`
2970
+
2971
+ `;
2972
+ result += `### Image Files
2973
+
2974
+ `;
2975
+ tokens.assets.forEach((asset) => {
2976
+ const relativePath = asset.path.replace(process.cwd() + "/", "");
2977
+ result += `- **${asset.nodeName}** (${asset.nodeType})
2978
+ `;
2979
+ result += ` - File: \`${relativePath}\`
2980
+ `;
2981
+ if (asset.width && asset.height) {
2982
+ result += ` - Size: ${Math.round(asset.width)}\xD7${Math.round(asset.height)}px
2983
+ `;
2984
+ }
2985
+ result += ` - Format: ${asset.format.toUpperCase()}
2986
+ `;
2987
+ result += ` - Usage: \`<img src="${relativePath}" alt="${asset.nodeName}" />\`
2988
+
2989
+ `;
2990
+ });
2991
+ }
2992
+ result += `## Implementation Guide
2993
+
2994
+ `;
2995
+ result += `### Structure Analysis
2996
+ `;
2997
+ result += `The design contains ${tokens.structure?.nodes.length || 0} nodes organized in a hierarchical structure.
2998
+ `;
2999
+ result += `Use the node hierarchy above to understand:
3000
+ `;
3001
+ result += `1. **Component structure** - How elements are organized
3002
+ `;
3003
+ result += `2. **Text content** - All text content from TEXT nodes
3004
+ `;
3005
+ result += `3. **Layout properties** - Flex direction, gaps, padding
3006
+ `;
3007
+ result += `4. **Positioning** - Exact x, y, width, height values
3008
+
3009
+ `;
3010
+ result += `### Next Steps
3011
+
3012
+ `;
3013
+ result += `1. Review the structure hierarchy to understand component organization
3014
+ `;
3015
+ result += `2. Extract text content from TEXT nodes for HTML content
3016
+ `;
3017
+ result += `3. Use position and size data for pixel-perfect CSS
3018
+ `;
3019
+ result += `4. Use layout properties (HORIZONTAL/VERTICAL) for flexbox/grid
3020
+ `;
3021
+ result += `5. Use extracted design tokens (colors, typography) for styling
3022
+ `;
3023
+ result += `6. Save this analysis: \`memory-update("research/figma-analysis", "[this analysis]")\`
3024
+ `;
3025
+ return result;
3026
+ } catch (error) {
3027
+ const errorMessage = error instanceof Error ? error.message : String(error);
3028
+ return `Error analyzing Figma design: ${errorMessage}
3029
+
3030
+ Please check:
3031
+ 1. The Figma URL is correct and accessible
3032
+ 2. Your Figma API token has proper permissions
3033
+ 3. The design file is shared with your account`;
3034
+ }
3035
+ }
3036
+ },
3037
+ {
3038
+ name: "analyze_figma",
3039
+ description: "Analyze a Figma design URL and extract all design tokens automatically. The URL should be provided in the user input after the command.",
3040
+ args: {
3041
+ url: {
3042
+ type: "string",
3043
+ description: "Figma design URL to analyze",
3044
+ required: true
3045
+ }
3046
+ },
3047
+ async execute({ url }) {
3048
+ return `Figma analysis tool called for: ${url}
3049
+
3050
+ Next steps:
3051
+ 1. Use @vision agent to analyze the design
3052
+ 2. Extract all design tokens
3053
+ 3. Save to memory/research/figma-analysis.md`;
3054
+ }
3055
+ },
3056
+ {
3057
+ name: "develop_figma_screen",
3058
+ description: "Smart workflow to develop a specific Figma screen: check current code, pull needed assets, plan, and develop. User just needs to confirm the screen number/name.",
3059
+ args: {
3060
+ figmaUrl: {
3061
+ type: "string",
3062
+ description: "Figma design URL",
3063
+ required: true
3064
+ },
3065
+ screenId: {
3066
+ type: "string",
3067
+ description: "Screen ID or name to develop (from read_figma_design output)",
3068
+ required: true
3069
+ }
3070
+ },
3071
+ async execute({ figmaUrl, screenId }, context) {
3072
+ const configManager = context?.toolConfigManager;
3073
+ if (!configManager) {
3074
+ return "Error: Tool configuration manager not available.";
3075
+ }
3076
+ const isReady = await configManager.isToolReady("figma-analysis");
3077
+ if (!isReady) {
3078
+ return "Error: Figma tool is not configured. Please run: aikit skills figma-analysis config";
3079
+ }
3080
+ const apiKey = await configManager.getApiKey("figma-analysis");
3081
+ if (!apiKey) {
3082
+ return "Error: Figma API key not found.";
3083
+ }
3084
+ try {
3085
+ const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
3086
+ const { checkCurrentCodeStatus: checkCurrentCodeStatus2, compareCodeWithFigma: compareCodeWithFigma2 } = await Promise.resolve().then(() => (init_figma_screen_developer(), figma_screen_developer_exports));
3087
+ const client = new FigmaMcpClient2(apiKey, configManager);
3088
+ const tokens = await client.extractDesignTokens(figmaUrl, false, "./assets/images");
3089
+ const screenIdStr = String(screenId);
3090
+ const selectedScreen = tokens.screens?.find(
3091
+ (s) => s.id === screenIdStr || s.name.toLowerCase() === screenIdStr.toLowerCase()
3092
+ );
3093
+ if (!selectedScreen) {
3094
+ return `Error: Screen "${screenId}" not found. Available screens:
3095
+ ${tokens.screens?.map((s, i) => `${i + 1}. ${s.name} (ID: ${s.id})`).join("\n") || "None"}`;
3096
+ }
3097
+ const codeStatus = await checkCurrentCodeStatus2();
3098
+ const comparison = await compareCodeWithFigma2(tokens, selectedScreen.id);
3099
+ let downloadedAssets = [];
3100
+ const fileKey = client.extractFileKey(figmaUrl);
3101
+ if (fileKey) {
3102
+ try {
3103
+ const fileData = await client.getFileData(figmaUrl);
3104
+ const projectPath = process.cwd();
3105
+ const assetsDir = join8(projectPath, "assets", "images");
3106
+ const assets = await client.downloadAssets(fileKey, fileData.document, assetsDir, selectedScreen.id);
3107
+ downloadedAssets = assets || [];
3108
+ logger.info(`Downloaded ${downloadedAssets.length} assets for screen ${selectedScreen.name}`);
3109
+ } catch (error) {
3110
+ logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
3111
+ downloadedAssets = [];
3112
+ }
3113
+ }
3114
+ let result = `# Development Plan for Screen: ${selectedScreen.name}
3115
+
3116
+ `;
3117
+ result += `## Current Code Status
3118
+
3119
+ `;
3120
+ result += `- HTML: ${codeStatus.hasHTML ? `\u2705 ${codeStatus.htmlFile}` : "\u274C Not found"}
3121
+ `;
3122
+ result += `- CSS: ${codeStatus.hasCSS ? `\u2705 ${codeStatus.cssFiles.length} files` : "\u274C Not found"}
3123
+ `;
3124
+ result += `- Assets: ${codeStatus.hasAssets ? `\u2705 ${codeStatus.assetCount} files` : "\u274C Not found"}
3125
+ `;
3126
+ result += `- Existing Sections: ${codeStatus.sections.length > 0 ? codeStatus.sections.join(", ") : "None"}
3127
+
3128
+ `;
3129
+ result += `## Comparison with Figma Design
3130
+
3131
+ `;
3132
+ result += `- Missing Sections: ${comparison.missingSections.length > 0 ? comparison.missingSections.join(", ") : "None"}
3133
+ `;
3134
+ result += `- Missing Assets: ${comparison.missingAssets.length > 0 ? comparison.missingAssets.join(", ") : "None"}
3135
+ `;
3136
+ result += `- Needs Update: ${comparison.needsUpdate ? "Yes" : "No"}
3137
+
3138
+ `;
3139
+ result += `## Recommendations
3140
+
3141
+ `;
3142
+ comparison.recommendations.forEach((rec, i) => {
3143
+ result += `${i + 1}. ${rec}
3144
+ `;
3145
+ });
3146
+ result += `
3147
+ `;
3148
+ result += `## Downloaded Assets (${downloadedAssets.length})
3149
+
3150
+ `;
3151
+ if (downloadedAssets.length > 0) {
3152
+ downloadedAssets.forEach((asset) => {
3153
+ const relativePath = asset.path.replace(process.cwd() + "/", "");
3154
+ result += `- **${asset.nodeName}** (${asset.nodeType})
3155
+ `;
3156
+ result += ` - File: \`${relativePath}\`
3157
+ `;
3158
+ if (asset.width && asset.height) {
3159
+ result += ` - Size: ${Math.round(asset.width)}\xD7${Math.round(asset.height)}px
3160
+ `;
3161
+ }
3162
+ result += ` - Usage: \`<img src="${relativePath}" alt="${asset.nodeName}" />\`
3163
+
3164
+ `;
3165
+ });
3166
+ result += `
3167
+ **\u26A0\uFE0F IMPORTANT: Use downloaded assets above instead of placeholder images!**
3168
+ `;
3169
+ result += `Replace all Unsplash/placeholder image URLs with the downloaded asset paths.
3170
+
3171
+ `;
3172
+ } else {
3173
+ result += `**No assets downloaded.** This may indicate:
3174
+ `;
3175
+ result += `1. The screen doesn't contain exportable image nodes
3176
+ `;
3177
+ result += `2. Assets download failed (check logs)
3178
+ `;
3179
+ result += `3. Figma API permissions issue
3180
+
3181
+ `;
3182
+ }
3183
+ result += `
3184
+ ## Next Steps
3185
+
3186
+ `;
3187
+ result += `1. Review the plan above
3188
+ `;
3189
+ result += `2. Use @build agent to implement missing sections
3190
+ `;
3191
+ result += `3. Use extracted design tokens for CSS variables
3192
+ `;
3193
+ result += `4. Use downloaded assets in HTML
3194
+ `;
3195
+ result += `5. Verify pixel-perfect match with Figma design
3196
+ `;
3197
+ return result;
3198
+ } catch (error) {
3199
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
3200
+ }
3201
+ }
3202
+ },
3203
+ {
3204
+ name: "list_session",
3205
+ description: "List previous sessions to discover what happened and when. Use this before read_session to find the right context.",
3206
+ args: {
3207
+ limit: {
3208
+ type: "number",
3209
+ description: "Maximum number of sessions to return (default: 10)",
3210
+ required: false,
3211
+ default: 10
3212
+ }
3213
+ },
3214
+ async execute({ limit = 10 }, context) {
3215
+ const config = context?.config;
3216
+ if (!config) {
3217
+ return "Error: Configuration not available";
3218
+ }
3219
+ const limitNum = typeof limit === "number" ? limit : 10;
3220
+ try {
3221
+ const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
3222
+ const memory = new MemoryManager2(config);
3223
+ const memories = await memory.list();
3224
+ const handoffs = memories.filter((m) => m.type === "handoff").sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()).slice(0, limitNum);
3225
+ if (handoffs.length === 0) {
3226
+ return "No previous sessions found. Use /handoff to create a session handoff.";
3227
+ }
3228
+ let result = `# Previous Sessions (${handoffs.length})
3229
+
3230
+ `;
3231
+ handoffs.forEach((handoff, index) => {
3232
+ const sessionId = handoff.key.replace("handoffs/", "");
3233
+ result += `${index + 1}. **${sessionId}**
3234
+ `;
3235
+ result += ` - Updated: ${handoff.updatedAt.toLocaleString()}
3236
+ `;
3237
+ result += ` - Summary: ${handoff.summary}
3238
+
3239
+ `;
3240
+ });
3241
+ result += `
3242
+ Use \`read_session\` with a session ID to load full context.`;
3243
+ return result;
3244
+ } catch (error) {
3245
+ return `Error listing sessions: ${error instanceof Error ? error.message : String(error)}`;
3246
+ }
3247
+ }
3248
+ },
3249
+ {
3250
+ name: "read_session",
3251
+ description: "Load context from a previous session. Returns session summary, user tasks, and file changes.",
3252
+ args: {
3253
+ sessionId: {
3254
+ type: "string",
3255
+ description: 'Session ID from list_session (e.g., "2024-01-15T10-30-00")',
3256
+ required: true
3257
+ }
3258
+ },
3259
+ async execute({ sessionId }, context) {
3260
+ const config = context?.config;
3261
+ if (!config) {
3262
+ return "Error: Configuration not available";
3263
+ }
3264
+ try {
3265
+ const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
3266
+ const memory = new MemoryManager2(config);
3267
+ const content = await memory.read(`handoffs/${sessionId}`);
3268
+ if (!content) {
3269
+ return `Session not found: ${sessionId}
3270
+
3271
+ Use \`list_session\` to see available sessions.`;
3272
+ }
3273
+ return `# Session Context: ${sessionId}
3274
+
3275
+ ${content}
3276
+
3277
+ ---
3278
+
3279
+ This context has been loaded. Use /resume to continue from this point.`;
3280
+ } catch (error) {
3281
+ return `Error reading session: ${error instanceof Error ? error.message : String(error)}`;
3282
+ }
3283
+ }
3284
+ }
3285
+ ];
3286
+ var ToolRegistry = class {
3287
+ config;
3288
+ tools = /* @__PURE__ */ new Map();
3289
+ toolConfigManager;
3290
+ constructor(config) {
3291
+ this.config = config;
3292
+ for (const tool of BUILT_IN_TOOLS) {
3293
+ this.tools.set(tool.name, tool);
3294
+ }
3295
+ }
3296
+ /**
3297
+ * Set tool config manager (for tools that need configuration)
3298
+ */
3299
+ setToolConfigManager(manager) {
3300
+ this.toolConfigManager = manager;
3301
+ }
3302
+ /**
3303
+ * List all available tools
3304
+ */
3305
+ async listTools() {
3306
+ await this.loadCustomTools();
3307
+ return Array.from(this.tools.values());
3308
+ }
3309
+ /**
3310
+ * Get a specific tool
3311
+ */
3312
+ getTool(name) {
3313
+ return this.tools.get(name);
3314
+ }
3315
+ /**
3316
+ * Register a new tool
3317
+ */
3318
+ registerTool(tool) {
3319
+ this.tools.set(tool.name, tool);
3320
+ }
3321
+ /**
3322
+ * Execute a tool
3323
+ */
3324
+ async executeTool(name, args, context) {
3325
+ const tool = this.tools.get(name);
3326
+ if (!tool) {
3327
+ throw new Error(`Tool not found: ${name}`);
3328
+ }
3329
+ for (const [argName, argDef] of Object.entries(tool.args)) {
3330
+ if (argDef.required && args[argName] === void 0) {
3331
+ throw new Error(`Missing required argument: ${argName}`);
3332
+ }
3333
+ }
3334
+ const mergedContext = {
3335
+ ...context,
3336
+ toolConfigManager: context?.toolConfigManager || this.toolConfigManager
3337
+ };
3338
+ if (tool.execute.length === 2) {
3339
+ return await tool.execute(args, mergedContext);
3340
+ }
3341
+ return await tool.execute(args);
3342
+ }
3343
+ /**
3344
+ * Create a custom tool
3345
+ */
3346
+ async createTool(name, options) {
3347
+ const configPath = options.global ? paths.globalConfig() : this.config.configPath;
3348
+ const toolsDir = paths.tools(configPath);
3349
+ await mkdir5(toolsDir, { recursive: true });
3350
+ const fileName = `${name}.ts`;
3351
+ const filePath = join8(toolsDir, fileName);
3352
+ const argsSchema = Object.entries(options.args).map(([argName, arg]) => ` ${argName}: {
3353
+ type: '${arg.type}',
3354
+ description: '${arg.description}',
3355
+ required: ${arg.required ?? true},
3356
+ }`).join(",\n");
3357
+ const content = `import { defineTool } from 'aikit';
3358
+
3359
+ export default defineTool({
3360
+ name: '${name}',
3361
+ description: '${options.description}',
3362
+ args: {
3363
+ ${argsSchema}
3364
+ },
3365
+ async execute(args) {
3366
+ ${options.code}
3367
+ }
3368
+ });
3369
+ `;
3370
+ await writeFile5(filePath, content);
3371
+ }
3372
+ /**
3373
+ * Format tool for agent consumption
3374
+ */
3375
+ formatForAgent(tool) {
3376
+ const argsDesc = Object.entries(tool.args).map(([name, arg]) => ` - ${name} (${arg.type}${arg.required ? ", required" : ""}): ${arg.description}`).join("\n");
3377
+ return `## Tool: ${tool.name}
3378
+
3379
+ ${tool.description}
3380
+
3381
+ ### Arguments
3382
+ ${argsDesc}
3383
+ `;
3384
+ }
3385
+ /**
3386
+ * Load custom tools from disk
3387
+ */
3388
+ async loadCustomTools() {
3389
+ const globalToolsPath = paths.tools(paths.globalConfig());
3390
+ await this.loadToolsFromDir(globalToolsPath);
3391
+ const projectToolsPath = paths.tools(this.config.configPath);
3392
+ if (projectToolsPath !== globalToolsPath) {
3393
+ await this.loadToolsFromDir(projectToolsPath);
3394
+ }
3395
+ }
3396
+ async loadToolsFromDir(dir) {
3397
+ let files;
3398
+ try {
3399
+ files = await readdir4(dir);
3400
+ } catch {
3401
+ return;
3402
+ }
3403
+ for (const file of files) {
3404
+ if (extname3(file) !== ".ts" && extname3(file) !== ".js") continue;
3405
+ const filePath = join8(dir, file);
3406
+ try {
3407
+ const toolModule = await import(`file://${filePath}`);
3408
+ const tool = toolModule.default;
3409
+ if (tool?.name && typeof tool.execute === "function") {
3410
+ tool.filePath = filePath;
3411
+ this.tools.set(tool.name, tool);
3412
+ }
3413
+ } catch (error) {
3414
+ logger.warn(`Failed to load tool from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
3415
+ }
3416
+ }
3417
+ }
3418
+ };
3419
+
3420
+ // src/core/plugins.ts
3421
+ init_esm_shims();
3422
+ init_paths();
3423
+ init_logger();
3424
+ import { readdir as readdir5, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
3425
+ import { join as join9, basename as basename3, extname as extname4 } from "path";
3426
+ var PluginSystem = class {
3427
+ config;
3428
+ plugins = /* @__PURE__ */ new Map();
3429
+ loadedPlugins = /* @__PURE__ */ new Map();
3430
+ eventQueue = [];
3431
+ processing = false;
3432
+ constructor(config) {
3433
+ this.config = config;
3434
+ }
3435
+ /**
3436
+ * Initialize and load all plugins
3437
+ */
3438
+ async initialize() {
3439
+ await this.loadPlugins();
3440
+ for (const [name, info] of this.plugins) {
3441
+ if (info.enabled) {
3442
+ try {
3443
+ const handlers = await this.initializePlugin(info);
3444
+ this.loadedPlugins.set(name, handlers);
3445
+ info.handlers = handlers;
3446
+ } catch (error) {
3447
+ logger.warn(`Failed to initialize plugin ${name}: ${error instanceof Error ? error.message : String(error)}`);
3448
+ }
3449
+ }
3450
+ }
3451
+ }
3452
+ /**
3453
+ * List all available plugins
3454
+ */
3455
+ async listPlugins() {
3456
+ await this.loadPlugins();
3457
+ return Array.from(this.plugins.values());
3458
+ }
3459
+ /**
3460
+ * Enable a plugin
3461
+ */
3462
+ async enablePlugin(name) {
3463
+ const plugin = this.plugins.get(name);
3464
+ if (plugin) {
3465
+ plugin.enabled = true;
3466
+ if (!this.loadedPlugins.has(name)) {
3467
+ const handlers = await this.initializePlugin(plugin);
3468
+ this.loadedPlugins.set(name, handlers);
3469
+ plugin.handlers = handlers;
3470
+ }
3471
+ }
3472
+ }
3473
+ /**
3474
+ * Disable a plugin
3475
+ */
3476
+ disablePlugin(name) {
3477
+ const plugin = this.plugins.get(name);
3478
+ if (plugin) {
3479
+ plugin.enabled = false;
3480
+ this.loadedPlugins.delete(name);
3481
+ plugin.handlers = void 0;
3482
+ }
3483
+ }
3484
+ /**
3485
+ * Emit an event to all plugins
3486
+ */
3487
+ async emit(event) {
3488
+ this.eventQueue.push(event);
3489
+ if (!this.processing) {
3490
+ await this.processEventQueue();
3491
+ }
3492
+ }
3493
+ /**
3494
+ * Execute before hooks for tool execution
3495
+ */
3496
+ async executeBeforeHooks(_toolName, input) {
3497
+ let result = input;
3498
+ for (const handlers of this.loadedPlugins.values()) {
3499
+ if (handlers["tool.execute.before"]) {
3500
+ result = await handlers["tool.execute.before"](result);
3501
+ }
3502
+ }
3503
+ return result;
3504
+ }
3505
+ /**
3506
+ * Execute after hooks for tool execution
3507
+ */
3508
+ async executeAfterHooks(_toolName, input, output) {
3509
+ let result = output;
3510
+ for (const handlers of this.loadedPlugins.values()) {
3511
+ if (handlers["tool.execute.after"]) {
3512
+ result = await handlers["tool.execute.after"](input, result);
3513
+ }
3514
+ }
3515
+ return result;
3516
+ }
3517
+ /**
3518
+ * Create a new plugin
3519
+ */
3520
+ async createPlugin(name, options) {
3521
+ const configPath = options.global ? paths.globalConfig() : this.config.configPath;
3522
+ const pluginsDir = paths.plugins(configPath);
3523
+ await mkdir6(pluginsDir, { recursive: true });
3524
+ const fileName = `${name}.ts`;
3525
+ const filePath = join9(pluginsDir, fileName);
3526
+ const content = `import { Plugin } from 'aikit';
3527
+
3528
+ /**
3529
+ * ${options.description || `Custom plugin: ${name}`}
3530
+ */
3531
+ export const ${toPascalCase(name)}Plugin: Plugin = async ({ project, config, emit }) => {
3532
+ return {
3533
+ event: async ({ event }) => {
3534
+ ${options.code}
3535
+ }
3536
+ };
3537
+ };
3538
+
3539
+ export default ${toPascalCase(name)}Plugin;
3540
+ `;
3541
+ await writeFile6(filePath, content);
3542
+ }
3543
+ /**
3544
+ * Process event queue
3545
+ */
3546
+ async processEventQueue() {
3547
+ this.processing = true;
3548
+ while (this.eventQueue.length > 0) {
3549
+ const event = this.eventQueue.shift();
3550
+ for (const handlers of this.loadedPlugins.values()) {
3551
+ if (handlers.event) {
3552
+ try {
3553
+ await handlers.event(event);
3554
+ } catch (error) {
3555
+ logger.warn(`Plugin error handling event ${event.type}: ${error instanceof Error ? error.message : String(error)}`);
3556
+ }
3557
+ }
3558
+ }
3559
+ }
3560
+ this.processing = false;
3561
+ }
3562
+ /**
3563
+ * Load plugins from disk
3564
+ */
3565
+ async loadPlugins() {
3566
+ this.registerBuiltInPlugins();
3567
+ const globalPluginsPath = paths.plugins(paths.globalConfig());
3568
+ await this.loadPluginsFromDir(globalPluginsPath);
3569
+ const projectPluginsPath = paths.plugins(this.config.configPath);
3570
+ if (projectPluginsPath !== globalPluginsPath) {
3571
+ await this.loadPluginsFromDir(projectPluginsPath);
3572
+ }
3573
+ }
3574
+ registerBuiltInPlugins() {
3575
+ this.plugins.set("enforcer", {
3576
+ name: "enforcer",
3577
+ description: "Warns when session idles with TODO items remaining",
3578
+ enabled: true,
3579
+ filePath: "built-in"
3580
+ });
3581
+ this.plugins.set("compactor", {
3582
+ name: "compactor",
3583
+ description: "Warns when context usage reaches 70%, 85%, 95%",
3584
+ enabled: true,
3585
+ filePath: "built-in"
3586
+ });
3587
+ this.plugins.set("truncator", {
3588
+ name: "truncator",
3589
+ description: "Auto-truncates tool output to preserve context space",
3590
+ enabled: true,
3591
+ filePath: "built-in"
3592
+ });
3593
+ this.plugins.set("notification", {
3594
+ name: "notification",
3595
+ description: "OS notifications when OpenCode completes a session",
3596
+ enabled: true,
3597
+ filePath: "built-in"
3598
+ });
3599
+ this.plugins.set("session-management", {
3600
+ name: "session-management",
3601
+ description: "Cross-session context transfer based on handoffs",
3602
+ enabled: true,
3603
+ filePath: "built-in"
3604
+ });
3605
+ }
3606
+ async loadPluginsFromDir(dir) {
3607
+ let files;
3608
+ try {
3609
+ files = await readdir5(dir);
3610
+ } catch {
3611
+ return;
3612
+ }
3613
+ for (const file of files) {
3614
+ if (extname4(file) !== ".ts" && extname4(file) !== ".js") continue;
3615
+ const filePath = join9(dir, file);
3616
+ const name = basename3(file, extname4(file));
3617
+ this.plugins.set(name, {
3618
+ name,
3619
+ description: `Custom plugin: ${name}`,
3620
+ enabled: true,
3621
+ filePath
3622
+ });
3623
+ }
3624
+ }
3625
+ async initializePlugin(info) {
3626
+ if (info.filePath === "built-in") {
3627
+ return this.getBuiltInPluginHandlers(info.name);
3628
+ }
3629
+ try {
3630
+ const pluginModule = await import(`file://${info.filePath}`);
3631
+ const factory = pluginModule.default;
3632
+ const context = {
3633
+ project: {
3634
+ path: this.config.projectPath,
3635
+ name: basename3(this.config.projectPath)
3636
+ },
3637
+ config: this.config,
3638
+ emit: this.emit.bind(this)
3639
+ };
3640
+ return await factory(context);
3641
+ } catch (error) {
3642
+ logger.warn(`Failed to load plugin ${info.name}: ${error instanceof Error ? error.message : String(error)}`);
3643
+ return {};
3644
+ }
3645
+ }
3646
+ getBuiltInPluginHandlers(name) {
3647
+ switch (name) {
3648
+ case "enforcer":
3649
+ return {
3650
+ event: async (event) => {
3651
+ if (event.type === "session.idle") {
3652
+ logger.info("[Enforcer] Session idle - check for remaining work");
3653
+ }
3654
+ }
3655
+ };
3656
+ case "compactor":
3657
+ return {
3658
+ event: async (event) => {
3659
+ const usage = event.properties?.contextUsage;
3660
+ if (usage && usage > 70) {
3661
+ logger.info(`[Compactor] Context usage at ${usage}%`);
3662
+ }
3663
+ }
3664
+ };
3665
+ case "truncator":
3666
+ return {
3667
+ "tool.execute.after": async (_input, output) => {
3668
+ if (typeof output === "string" && output.length > 5e4) {
3669
+ return output.slice(0, 5e4) + "\n\n[Output truncated - exceeded 50KB limit]";
3670
+ }
3671
+ return output;
3672
+ }
3673
+ };
3674
+ case "notification":
3675
+ return {
3676
+ event: async (event) => {
3677
+ if (event.type === "session.idle") {
3678
+ try {
3679
+ const { exec: exec2 } = await import("child_process");
3680
+ const { promisify: promisify2 } = await import("util");
3681
+ const execAsync2 = promisify2(exec2);
3682
+ const platform = process.platform;
3683
+ const summary = event.properties?.summary || "Session completed";
3684
+ if (platform === "darwin") {
3685
+ await execAsync2(`osascript -e 'display notification "${summary}" with title "OpenCode Session Complete"'`);
3686
+ } else if (platform === "linux") {
3687
+ await execAsync2(`notify-send "OpenCode Session Complete" "${summary}"`);
3688
+ } else if (platform === "win32") {
3689
+ await execAsync2(`powershell -Command "New-BurntToastNotification -Text 'OpenCode Session Complete', '${summary}'"`);
3690
+ }
3691
+ } catch (error) {
3692
+ logger.warn(`[Notification] Failed to send notification: ${error instanceof Error ? error.message : String(error)}`);
3693
+ }
3694
+ }
3695
+ }
3696
+ };
3697
+ case "session-management":
3698
+ return {
3699
+ event: async (event) => {
3700
+ if (event.type === "session.idle") {
3701
+ logger.info("[Session Management] Session idle - context saved for next session");
3702
+ }
3703
+ }
3704
+ };
3705
+ default:
3706
+ return {};
3707
+ }
3708
+ }
3709
+ };
3710
+ function toPascalCase(str) {
3711
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
3712
+ }
3713
+
3714
+ // src/index.ts
3715
+ init_memory();
3716
+
3717
+ // src/core/beads.ts
3718
+ init_esm_shims();
3719
+ init_paths();
3720
+ init_logger();
3721
+ import { readFile as readFile6, writeFile as writeFile7, readdir as readdir6, access as access3, constants as constants3, mkdir as mkdir7 } from "fs/promises";
3722
+ import { join as join10 } from "path";
3723
+ import { exec } from "child_process";
3724
+ import { promisify } from "util";
3725
+ var execAsync = promisify(exec);
3726
+ var BeadsIntegration = class {
3727
+ projectPath;
3728
+ constructor(projectPath) {
3729
+ this.projectPath = projectPath || process.cwd();
3730
+ }
3731
+ /**
3732
+ * Check if Beads CLI is installed
3733
+ */
3734
+ async isInstalled() {
3735
+ try {
3736
+ await execAsync("bd --version");
3737
+ return true;
3738
+ } catch {
3739
+ return false;
3740
+ }
3741
+ }
3742
+ /**
3743
+ * Get Beads version
3744
+ */
3745
+ async getVersion() {
3746
+ try {
3747
+ const { stdout } = await execAsync("bd --version");
3748
+ const match = stdout.match(/bd version (\S+)/);
3749
+ return match?.[1] || null;
3750
+ } catch {
3751
+ return null;
3752
+ }
3753
+ }
3754
+ /**
3755
+ * Check if Beads is initialized in project
3756
+ */
3757
+ async isInitialized() {
3758
+ const beadsDir = paths.beadsDir(this.projectPath);
3759
+ try {
3760
+ await access3(beadsDir, constants3.R_OK);
3761
+ return true;
3762
+ } catch {
3763
+ return false;
3764
+ }
3765
+ }
3766
+ /**
3767
+ * Initialize Beads in project
3768
+ */
3769
+ async init() {
3770
+ try {
3771
+ await execAsync("bd init", { cwd: this.projectPath });
3772
+ logger.success("Beads initialized");
3773
+ return true;
3774
+ } catch (error) {
3775
+ logger.error("Failed to initialize Beads:", error);
3776
+ return false;
3777
+ }
3778
+ }
3779
+ /**
3780
+ * Get current status
3781
+ */
3782
+ async getStatus() {
3783
+ const installed = await this.isInstalled();
3784
+ const version = await this.getVersion();
3785
+ const initialized = await this.isInitialized();
3786
+ let activeTasks = 0;
3787
+ let completedTasks = 0;
3788
+ let currentTask;
3789
+ if (initialized) {
3790
+ const beads = await this.listBeads();
3791
+ activeTasks = beads.filter((b) => b.status === "in-progress" || b.status === "todo").length;
3792
+ completedTasks = beads.filter((b) => b.status === "completed").length;
3793
+ const active = beads.find((b) => b.status === "in-progress");
3794
+ currentTask = active?.title;
3795
+ }
3796
+ return {
3797
+ installed,
3798
+ version: version || void 0,
3799
+ initialized,
3800
+ activeTasks,
3801
+ completedTasks,
3802
+ currentTask
3803
+ };
3804
+ }
3805
+ /**
3806
+ * List all beads in the project
3807
+ */
3808
+ async listBeads() {
3809
+ const beadsDir = paths.beadsDir(this.projectPath);
3810
+ const beads = [];
3811
+ try {
3812
+ const files = await readdir6(beadsDir);
3813
+ for (const file of files) {
3814
+ if (!file.match(/^bead-\d+\.md$/)) continue;
3815
+ const content = await readFile6(join10(beadsDir, file), "utf-8");
3816
+ const bead = this.parseBeadFile(file, content);
3817
+ if (bead) beads.push(bead);
3818
+ }
3819
+ } catch {
3820
+ }
3821
+ return beads.sort((a, b) => a.id.localeCompare(b.id));
3822
+ }
3823
+ /**
3824
+ * Get a specific bead
3825
+ */
3826
+ async getBead(id) {
3827
+ const beadsDir = paths.beadsDir(this.projectPath);
3828
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3829
+ try {
3830
+ const content = await readFile6(join10(beadsDir, fileName), "utf-8");
3831
+ return this.parseBeadFile(fileName, content);
3832
+ } catch {
3833
+ return null;
3834
+ }
3835
+ }
3836
+ /**
3837
+ * Create a new bead
3838
+ */
3839
+ async createBead(title, description) {
3840
+ const beadsDir = paths.beadsDir(this.projectPath);
3841
+ await mkdir7(beadsDir, { recursive: true });
3842
+ const beads = await this.listBeads();
3843
+ const maxId = beads.reduce((max, b) => {
3844
+ const num = parseInt(b.id.replace("bead-", ""), 10);
3845
+ return num > max ? num : max;
3846
+ }, 0);
3847
+ const id = `bead-${String(maxId + 1).padStart(3, "0")}`;
3848
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3849
+ const content = `---
3850
+ id: ${id}
3851
+ title: ${title}
3852
+ status: in-progress
3853
+ created: ${now}
3854
+ updated: ${now}
3855
+ ---
3856
+
3857
+ # ${title}
3858
+
3859
+ ## Description
3860
+ ${description}
3861
+
3862
+ ## Notes
3863
+
3864
+
3865
+ ## Checklist
3866
+ - [ ] Requirements understood
3867
+ - [ ] Implementation complete
3868
+ - [ ] Tests passing
3869
+ - [ ] Code reviewed
3870
+
3871
+ ## Progress
3872
+
3873
+ `;
3874
+ await writeFile7(join10(beadsDir, `${id}.md`), content);
3875
+ return {
3876
+ id,
3877
+ title,
3878
+ description,
3879
+ status: "in-progress",
3880
+ createdAt: new Date(now),
3881
+ updatedAt: new Date(now),
3882
+ notes: []
3883
+ };
3884
+ }
3885
+ /**
3886
+ * Update bead status
3887
+ */
3888
+ async updateBeadStatus(id, status) {
3889
+ const beadsDir = paths.beadsDir(this.projectPath);
3890
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3891
+ const filePath = join10(beadsDir, fileName);
3892
+ try {
3893
+ let content = await readFile6(filePath, "utf-8");
3894
+ content = content.replace(
3895
+ /status:\s*\w+/,
3896
+ `status: ${status}`
3897
+ );
3898
+ content = content.replace(
3899
+ /updated:\s*.+/,
3900
+ `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
3901
+ );
3902
+ await writeFile7(filePath, content);
3903
+ return true;
3904
+ } catch {
3905
+ return false;
3906
+ }
3907
+ }
3908
+ /**
3909
+ * Add note to bead
3910
+ */
3911
+ async addNote(id, note) {
3912
+ const beadsDir = paths.beadsDir(this.projectPath);
3913
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3914
+ const filePath = join10(beadsDir, fileName);
3915
+ try {
3916
+ let content = await readFile6(filePath, "utf-8");
3917
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
3918
+ if (notesMatch) {
3919
+ const timestamp = (/* @__PURE__ */ new Date()).toLocaleString();
3920
+ const newNote = `- [${timestamp}] ${note}`;
3921
+ content = content.replace(
3922
+ notesMatch[0],
3923
+ `## Notes
3924
+ ${notesMatch[1]}${newNote}
3925
+ `
3926
+ );
3927
+ }
3928
+ content = content.replace(
3929
+ /updated:\s*.+/,
3930
+ `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
3931
+ );
3932
+ await writeFile7(filePath, content);
3933
+ return true;
3934
+ } catch {
3935
+ return false;
3936
+ }
3937
+ }
3938
+ /**
3939
+ * Complete a bead with quality gates
3940
+ */
3941
+ async completeBead(id) {
3942
+ const gates = [
3943
+ { name: "Type Check", command: "npm run typecheck" },
3944
+ { name: "Tests", command: "npm run test" },
3945
+ { name: "Lint", command: "npm run lint" },
3946
+ { name: "Build", command: "npm run build" }
3947
+ ];
3948
+ const results = [];
3949
+ for (const gate of gates) {
3950
+ try {
3951
+ await execAsync(gate.command, { cwd: this.projectPath });
3952
+ results.push({ name: gate.name, passed: true });
3953
+ logger.success(`${gate.name}: passed`);
3954
+ } catch (error) {
3955
+ results.push({
3956
+ name: gate.name,
3957
+ passed: false,
3958
+ error: error instanceof Error ? error.message.slice(0, 200) : "Failed"
3959
+ });
3960
+ logger.error(`${gate.name}: failed`);
3961
+ }
3962
+ }
3963
+ const allPassed = results.every((r) => r.passed);
3964
+ if (allPassed) {
3965
+ await this.updateBeadStatus(id, "completed");
3966
+ await this.addNote(id, "Task completed - all quality gates passed");
3967
+ } else {
3968
+ await this.addNote(id, "Completion attempted but quality gates failed");
3969
+ }
3970
+ return {
3971
+ success: allPassed,
3972
+ gates: results
3973
+ };
3974
+ }
3975
+ /**
3976
+ * Get current active bead
3977
+ */
3978
+ async getCurrentBead() {
3979
+ const beads = await this.listBeads();
3980
+ return beads.find((b) => b.status === "in-progress") || null;
3981
+ }
3982
+ /**
3983
+ * Parse a bead file
3984
+ */
3985
+ parseBeadFile(fileName, content) {
3986
+ try {
3987
+ const id = fileName.replace(".md", "");
3988
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
3989
+ const frontmatter = frontmatterMatch?.[1] || "";
3990
+ const getValue = (key) => {
3991
+ const match = frontmatter.match(new RegExp(`${key}:\\s*(.+)`));
3992
+ return match?.[1]?.trim() || "";
3993
+ };
3994
+ const titleMatch = content.match(/^# (.+)/m);
3995
+ const title = getValue("title") || titleMatch?.[1] || id;
3996
+ const descMatch = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
3997
+ const description = descMatch?.[1]?.trim() || "";
3998
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
3999
+ const notesContent = notesMatch?.[1] || "";
4000
+ const notes = notesContent.split("\n").filter((line) => line.trim().startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
4001
+ return {
4002
+ id,
4003
+ title,
4004
+ description,
4005
+ status: getValue("status") || "todo",
4006
+ createdAt: new Date(getValue("created") || Date.now()),
4007
+ updatedAt: new Date(getValue("updated") || Date.now()),
4008
+ notes
4009
+ };
4010
+ } catch {
4011
+ return null;
4012
+ }
4013
+ }
4014
+ };
4015
+
4016
+ // src/core/anti-hallucination.ts
4017
+ init_esm_shims();
4018
+ init_logger();
4019
+
4020
+ // src/index.ts
4021
+ init_logger();
4022
+ init_paths();
4023
+ var VERSION = "0.1.0";
4024
+
4025
+ // src/cli.ts
4026
+ init_memory();
4027
+ init_logger();
4028
+ init_paths();
4029
+ var program = new Command();
4030
+ program.name("aikit").description("Open-source AI coding agent toolkit for OpenCode").version(VERSION);
4031
+ program.command("init").description("Initialize AIKit configuration").option("-g, --global", "Initialize global configuration").option("-p, --project", "Initialize project-level configuration").action(async (options) => {
4032
+ const configDir = options.global ? paths.globalConfig() : paths.projectConfig();
4033
+ console.log(chalk2.bold("\n\u{1F680} AIKit Setup\n"));
4034
+ logger.info(`Initializing AIKit in ${configDir}...`);
4035
+ try {
4036
+ await initializeConfig(configDir, options.global);
4037
+ logger.success("\u2713 Configuration created");
4038
+ if (!options.global) {
4039
+ const config = await loadConfig();
4040
+ const engine = new SkillEngine(config);
4041
+ const result = await engine.syncSkillsToProject();
4042
+ if (result.count > 0) {
4043
+ logger.success(`\u2713 Synced ${result.count} skills`);
4044
+ }
4045
+ const opencodePath = paths.opencodeConfig();
4046
+ await installToOpenCode(opencodePath);
4047
+ console.log(chalk2.bold("\n\u2728 AIKit is ready!\n"));
4048
+ console.log("Usage in OpenCode:");
4049
+ console.log(chalk2.cyan(" /skills") + " - List all available skills");
4050
+ console.log(chalk2.cyan(" /plan") + " - Create implementation plan");
4051
+ console.log(chalk2.cyan(" /tdd") + " - Test-driven development");
4052
+ console.log(chalk2.cyan(" /debug") + " - Systematic debugging");
4053
+ console.log(chalk2.cyan(" /review") + " - Code review checklist");
4054
+ console.log(chalk2.cyan(" /git") + " - Git workflow");
4055
+ console.log(chalk2.cyan(" /frontend-aesthetics") + " - UI/UX guidelines");
4056
+ console.log("\nPress " + chalk2.bold("Ctrl+K") + " in OpenCode to see all commands.\n");
4057
+ }
4058
+ } catch (error) {
4059
+ logger.error("Failed to initialize AIKit:", error);
4060
+ process.exit(1);
4061
+ }
4062
+ });
4063
+ program.command("install").description("Install AIKit to OpenCode configuration").action(async () => {
4064
+ logger.info("Installing AIKit to OpenCode...");
4065
+ try {
4066
+ const opencodePath = paths.opencodeConfig();
4067
+ await installToOpenCode(opencodePath);
4068
+ logger.success("AIKit installed to OpenCode!");
4069
+ } catch (error) {
4070
+ logger.error("Failed to install:", error);
4071
+ process.exit(1);
4072
+ }
4073
+ });
4074
+ var skillsCmd = program.command("skills").description("Manage skills");
4075
+ skillsCmd.command("list").description("List available skills and tools with their configuration status").action(async () => {
4076
+ const config = await loadConfig();
4077
+ const engine = new SkillEngine(config);
4078
+ const skills = await engine.listSkills();
4079
+ const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
4080
+ const toolConfigManager = new ToolConfigManager2(config);
4081
+ const tools = await toolConfigManager.listTools();
4082
+ console.log(chalk2.bold("\n\u{1F4DA} Available Skills:\n"));
4083
+ for (const skill of skills) {
4084
+ console.log(` ${chalk2.cyan(skill.name)} - ${skill.description}`);
4085
+ }
4086
+ console.log(chalk2.bold("\n\u{1F527} Available Tools:\n"));
4087
+ for (const tool of tools) {
4088
+ let statusIcon = " ";
4089
+ let statusText = "";
4090
+ if (tool.status === "ready") {
4091
+ statusIcon = chalk2.green("\u2713");
4092
+ statusText = chalk2.gray("(ready)");
4093
+ } else if (tool.status === "needs_config") {
4094
+ statusIcon = chalk2.yellow("\u26A0");
4095
+ statusText = chalk2.yellow("(needs config)");
4096
+ } else if (tool.status === "error") {
4097
+ statusIcon = chalk2.red("\u2717");
4098
+ statusText = chalk2.red("(error)");
4099
+ }
4100
+ console.log(` ${statusIcon} ${chalk2.cyan(tool.name)} - ${tool.description} ${statusText}`);
4101
+ if (tool.errorMessage) {
4102
+ console.log(` ${chalk2.red("Error:")} ${tool.errorMessage}`);
4103
+ }
4104
+ }
4105
+ console.log();
4106
+ console.log(chalk2.gray('Tip: Use "aikit skills <tool-name> config" to configure a tool\n'));
4107
+ });
4108
+ skillsCmd.command("show <name>").description("Show skill details").action(async (name) => {
4109
+ const config = await loadConfig();
4110
+ const engine = new SkillEngine(config);
4111
+ const skill = await engine.getSkill(name);
4112
+ if (!skill) {
4113
+ logger.error(`Skill not found: ${name}`);
4114
+ process.exit(1);
4115
+ }
4116
+ console.log(chalk2.bold(`
4117
+ \u{1F4D6} Skill: ${skill.name}
4118
+ `));
4119
+ console.log(chalk2.gray(skill.description));
4120
+ console.log(chalk2.bold("\nWorkflow:"));
4121
+ console.log(skill.content);
4122
+ });
4123
+ skillsCmd.command("create <name>").description("Create a new skill").action(async (name) => {
4124
+ const config = await loadConfig();
4125
+ const engine = new SkillEngine(config);
4126
+ await engine.createSkill(name);
4127
+ logger.success(`Skill created: ${name}`);
4128
+ });
4129
+ skillsCmd.command("sync").description("Sync global skills to project").action(async () => {
4130
+ const config = await loadConfig();
4131
+ const engine = new SkillEngine(config);
4132
+ const result = await engine.syncSkillsToProject();
4133
+ if (result.count === 0) {
4134
+ logger.info("Skills already in sync or no global skills to sync");
4135
+ } else {
4136
+ console.log(chalk2.bold(`
4137
+ \u2713 Synced ${result.count} skills to project:
4138
+ `));
4139
+ for (const skill of result.synced) {
4140
+ console.log(` ${chalk2.cyan("\u2022")} ${skill}`);
4141
+ }
4142
+ console.log();
4143
+ }
4144
+ });
4145
+ skillsCmd.command("config <tool-name>").description("Configure a tool (e.g., config figma-analysis)").action(async (toolName) => {
4146
+ const config = await loadConfig();
4147
+ const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
4148
+ const toolConfigManager = new ToolConfigManager2(config);
4149
+ const tool = await toolConfigManager.getToolConfig(toolName);
4150
+ if (!tool) {
4151
+ logger.error(`Tool not found: ${toolName}`);
4152
+ console.log(chalk2.gray("\nAvailable tools:"));
4153
+ const tools = await toolConfigManager.listTools();
4154
+ for (const t of tools) {
4155
+ console.log(` - ${chalk2.cyan(t.name)}`);
4156
+ }
4157
+ console.log();
4158
+ process.exit(1);
4159
+ }
4160
+ console.log(chalk2.bold(`
4161
+ \u{1F527} Configuring: ${tool.name}
4162
+ `));
4163
+ console.log(chalk2.gray(tool.description));
4164
+ console.log();
4165
+ if (tool.configMethod === "oauth") {
4166
+ if (toolName === "figma-analysis") {
4167
+ const { FigmaOAuth: FigmaOAuth2 } = await Promise.resolve().then(() => (init_figma_oauth(), figma_oauth_exports));
4168
+ const oauth = new FigmaOAuth2(toolConfigManager);
4169
+ try {
4170
+ const token = await oauth.authenticate();
4171
+ console.log(chalk2.gray("\nValidating token..."));
4172
+ const isValid = await oauth.validateToken(token);
4173
+ if (isValid) {
4174
+ logger.success(`
4175
+ \u2705 ${tool.name} configured successfully!`);
4176
+ console.log(chalk2.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
4177
+ } else {
4178
+ await toolConfigManager.updateToolConfig(toolName, {
4179
+ status: "error",
4180
+ errorMessage: "Token validation failed"
4181
+ });
4182
+ logger.error("Token validation failed. Please try again.");
4183
+ process.exit(1);
4184
+ }
4185
+ } catch (error) {
4186
+ await toolConfigManager.updateToolConfig(toolName, {
4187
+ status: "error",
4188
+ errorMessage: error instanceof Error ? error.message : String(error)
4189
+ });
4190
+ logger.error(`Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
4191
+ process.exit(1);
4192
+ }
4193
+ } else {
4194
+ logger.error(`OAuth flow not implemented for tool: ${toolName}`);
4195
+ process.exit(1);
4196
+ }
4197
+ } else if (tool.configMethod === "manual") {
4198
+ logger.info("Manual configuration not yet implemented");
4199
+ process.exit(1);
4200
+ } else {
4201
+ logger.info(`Tool ${tool.name} does not require configuration`);
4202
+ }
4203
+ });
4204
+ skillsCmd.command("*").description("Configure a tool (e.g., figma-analysis config)").allowUnknownOption().action(async () => {
4205
+ const args = process.argv.slice(process.argv.indexOf("skills") + 1);
4206
+ const toolName = args[0];
4207
+ const action = args[1];
4208
+ if (action === "config" && toolName) {
4209
+ const config = await loadConfig();
4210
+ const { ToolConfigManager: ToolConfigManager2 } = await Promise.resolve().then(() => (init_tool_config(), tool_config_exports));
4211
+ const toolConfigManager = new ToolConfigManager2(config);
4212
+ const tool = await toolConfigManager.getToolConfig(toolName);
4213
+ if (!tool) {
4214
+ logger.error(`Tool not found: ${toolName}`);
4215
+ console.log(chalk2.gray("\nAvailable tools:"));
4216
+ const tools = await toolConfigManager.listTools();
4217
+ for (const t of tools) {
4218
+ console.log(` - ${chalk2.cyan(t.name)}`);
4219
+ }
4220
+ console.log();
4221
+ console.log(chalk2.gray("Tip: If you meant to show a skill, use: aikit skills show <name>"));
4222
+ console.log();
4223
+ process.exit(1);
4224
+ }
4225
+ console.log(chalk2.bold(`
4226
+ \u{1F527} Configuring: ${tool.name}
4227
+ `));
4228
+ console.log(chalk2.gray(tool.description));
4229
+ console.log();
4230
+ if (tool.configMethod === "oauth") {
4231
+ if (toolName === "figma-analysis") {
4232
+ const { FigmaOAuth: FigmaOAuth2 } = await Promise.resolve().then(() => (init_figma_oauth(), figma_oauth_exports));
4233
+ const oauth = new FigmaOAuth2(toolConfigManager);
4234
+ try {
4235
+ const token = await oauth.authenticate();
4236
+ console.log(chalk2.gray("\nValidating token..."));
4237
+ const isValid = await oauth.validateToken(token);
4238
+ if (isValid) {
4239
+ logger.success(`
4240
+ \u2705 ${tool.name} configured successfully!`);
4241
+ console.log(chalk2.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
4242
+ } else {
4243
+ await toolConfigManager.updateToolConfig(toolName, {
4244
+ status: "error",
4245
+ errorMessage: "Token validation failed"
4246
+ });
4247
+ logger.error("Token validation failed. Please try again.");
4248
+ process.exit(1);
4249
+ }
4250
+ } catch (error) {
4251
+ await toolConfigManager.updateToolConfig(toolName, {
4252
+ status: "error",
4253
+ errorMessage: error instanceof Error ? error.message : String(error)
4254
+ });
4255
+ logger.error(`Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
4256
+ process.exit(1);
4257
+ }
4258
+ } else {
4259
+ logger.error(`OAuth flow not implemented for tool: ${toolName}`);
4260
+ process.exit(1);
4261
+ }
4262
+ } else if (tool.configMethod === "manual") {
4263
+ logger.info("Manual configuration not yet implemented");
4264
+ process.exit(1);
4265
+ } else {
4266
+ logger.info(`Tool ${tool.name} does not require configuration`);
4267
+ }
4268
+ } else {
4269
+ logger.error(`Unknown command: ${toolName || "unknown"} ${action || ""}`);
4270
+ console.log(chalk2.gray("\nAvailable commands:"));
4271
+ console.log(" aikit skills list - List all skills and tools");
4272
+ console.log(" aikit skills show <name> - Show skill details");
4273
+ console.log(" aikit skills config <tool-name> - Configure a tool");
4274
+ console.log(" aikit skills <tool-name> config - Configure a tool (alternative syntax)");
4275
+ console.log();
4276
+ process.exit(1);
4277
+ }
4278
+ });
4279
+ var agentsCmd = program.command("agents").description("Manage agents");
4280
+ agentsCmd.command("list").description("List available agents").action(async () => {
4281
+ const config = await loadConfig();
4282
+ const manager = new AgentManager(config);
4283
+ const agents = manager.listAgents();
4284
+ console.log(chalk2.bold("\n\u{1F916} Available Agents:\n"));
4285
+ for (const agent of agents) {
4286
+ console.log(` ${chalk2.cyan(`@${agent.name}`)} - ${agent.description}`);
4287
+ console.log(chalk2.gray(` Use when: ${agent.useWhen}`));
4288
+ }
4289
+ console.log();
4290
+ });
4291
+ var commandsCmd = program.command("commands").description("Manage commands");
4292
+ commandsCmd.command("list").description("List available commands").action(async () => {
4293
+ const config = await loadConfig();
4294
+ const runner = new CommandRunner(config);
4295
+ const commands = await runner.listCommands();
4296
+ console.log(chalk2.bold("\n\u26A1 Available Commands:\n"));
4297
+ const groups = groupBy(commands, (c) => c.category);
4298
+ for (const [category, cmds] of Object.entries(groups)) {
4299
+ console.log(chalk2.bold.yellow(`
4300
+ ${category}:`));
4301
+ for (const cmd of cmds) {
4302
+ console.log(` ${chalk2.cyan(`/${cmd.name}`)} - ${cmd.description}`);
4303
+ }
4304
+ }
4305
+ console.log();
4306
+ });
4307
+ var toolsCmd = program.command("tools").description("Manage custom tools");
4308
+ toolsCmd.command("list").description("List available tools").action(async () => {
4309
+ const config = await loadConfig();
4310
+ const registry = new ToolRegistry(config);
4311
+ const tools = await registry.listTools();
4312
+ console.log(chalk2.bold("\n\u{1F527} Available Tools:\n"));
4313
+ for (const tool of tools) {
4314
+ console.log(` ${chalk2.cyan(tool.name)} - ${tool.description}`);
4315
+ }
4316
+ console.log();
4317
+ });
4318
+ var pluginsCmd = program.command("plugins").description("Manage plugins");
4319
+ pluginsCmd.command("list").description("List available plugins").action(async () => {
4320
+ const config = await loadConfig();
4321
+ const system = new PluginSystem(config);
4322
+ const plugins = await system.listPlugins();
4323
+ console.log(chalk2.bold("\n\u{1F50C} Available Plugins:\n"));
4324
+ for (const plugin of plugins) {
4325
+ const status = plugin.enabled ? chalk2.green("\u2713") : chalk2.gray("\u25CB");
4326
+ console.log(` ${status} ${chalk2.cyan(plugin.name)} - ${plugin.description}`);
4327
+ }
4328
+ console.log();
4329
+ });
4330
+ var memoryCmd = program.command("memory").description("Manage persistent memory");
4331
+ memoryCmd.command("list").description("List memory entries").action(async () => {
4332
+ const config = await loadConfig();
4333
+ const memory = new MemoryManager(config);
4334
+ const entries = await memory.list();
4335
+ console.log(chalk2.bold("\n\u{1F9E0} Memory Entries:\n"));
4336
+ for (const entry of entries) {
4337
+ console.log(` ${chalk2.cyan(entry.key)} - ${entry.summary}`);
4338
+ console.log(chalk2.gray(` Updated: ${entry.updatedAt}`));
4339
+ }
4340
+ console.log();
4341
+ });
4342
+ memoryCmd.command("read <key>").description("Read a memory entry").action(async (key) => {
4343
+ const config = await loadConfig();
4344
+ const memory = new MemoryManager(config);
4345
+ const content = await memory.read(key);
4346
+ if (!content) {
4347
+ logger.error(`Memory entry not found: ${key}`);
4348
+ process.exit(1);
4349
+ }
4350
+ console.log(content);
4351
+ });
4352
+ var beadsCmd = program.command("beads").description("Beads task management integration");
4353
+ beadsCmd.command("status").description("Show current Beads status").action(async () => {
4354
+ const beads = new BeadsIntegration();
4355
+ const status = await beads.getStatus();
4356
+ console.log(chalk2.bold("\n\u{1F4FF} Beads Status:\n"));
4357
+ console.log(` Active tasks: ${status.activeTasks}`);
4358
+ console.log(` Completed: ${status.completedTasks}`);
4359
+ console.log(` Current: ${status.currentTask || "None"}`);
4360
+ console.log();
4361
+ });
4362
+ program.command("status").description("Show AIKit status").action(async () => {
4363
+ console.log(chalk2.bold(`
4364
+ \u{1F680} AIKit v${VERSION}
4365
+ `));
4366
+ try {
4367
+ const config = await loadConfig();
4368
+ console.log(chalk2.green("\u2713 Configuration loaded"));
4369
+ const skillEngine = new SkillEngine(config);
4370
+ const skills = await skillEngine.listSkills();
4371
+ console.log(` Skills: ${skills.length}`);
4372
+ const agentManager = new AgentManager(config);
4373
+ const agents = agentManager.listAgents();
4374
+ console.log(` Agents: ${agents.length}`);
4375
+ const commandRunner = new CommandRunner(config);
4376
+ const commands = await commandRunner.listCommands();
4377
+ console.log(` Commands: ${commands.length}`);
4378
+ const toolRegistry = new ToolRegistry(config);
4379
+ const tools = await toolRegistry.listTools();
4380
+ console.log(` Tools: ${tools.length}`);
4381
+ const beads = new BeadsIntegration();
4382
+ const beadsStatus = await beads.isInstalled();
4383
+ console.log(` Beads: ${beadsStatus ? chalk2.green("Installed") : chalk2.yellow("Not installed")}`);
4384
+ } catch (error) {
4385
+ console.log(chalk2.yellow('\u26A0 AIKit not initialized. Run "aikit init" to get started.'));
4386
+ }
4387
+ console.log();
4388
+ });
4389
+ async function initializeConfig(configDir, _isGlobal) {
4390
+ const { mkdir: mkdir9, writeFile: writeFile9 } = await import("fs/promises");
4391
+ const { join: join12 } = await import("path");
4392
+ const dirs = [
4393
+ "",
4394
+ "skills",
4395
+ "agents",
4396
+ "commands",
4397
+ "commands/build",
4398
+ "commands/git",
4399
+ "commands/plan",
4400
+ "commands/research",
4401
+ "tools",
4402
+ "plugins",
4403
+ "memory",
4404
+ "memory/_templates",
4405
+ "memory/handoffs",
4406
+ "memory/observations",
4407
+ "memory/research"
4408
+ ];
4409
+ for (const dir of dirs) {
4410
+ await mkdir9(join12(configDir, dir), { recursive: true });
4411
+ }
4412
+ const defaultConfig = {
4413
+ version: VERSION,
4414
+ skills: { enabled: true },
4415
+ agents: { enabled: true, default: "build" },
4416
+ commands: { enabled: true },
4417
+ tools: { enabled: true },
4418
+ plugins: { enabled: true },
4419
+ memory: { enabled: true },
4420
+ beads: { enabled: true },
4421
+ antiHallucination: { enabled: true }
4422
+ };
4423
+ await writeFile9(
4424
+ join12(configDir, "aikit.json"),
4425
+ JSON.stringify(defaultConfig, null, 2)
4426
+ );
4427
+ const agentsMd = `# AIKit Agent Rules
4428
+
4429
+ ## Build Commands
4430
+ - \`npm run build\` - Build the project
4431
+ - \`npm run test\` - Run tests
4432
+ - \`npm run lint\` - Run linting
4433
+
4434
+ ## Code Style
4435
+ - Use 2 spaces for indentation
4436
+ - Use single quotes for strings
4437
+ - Add trailing commas
4438
+
4439
+ ## Naming Conventions
4440
+ - Variables: camelCase
4441
+ - Components: PascalCase
4442
+ - Files: kebab-case
4443
+
4444
+ ## Project-Specific Rules
4445
+ Add your project-specific rules here.
4446
+ `;
4447
+ await writeFile9(join12(configDir, "AGENTS.md"), agentsMd);
4448
+ }
4449
+ async function configureMcpServer(projectPath) {
4450
+ const { mkdir: mkdir9, writeFile: writeFile9, readFile: readFile8 } = await import("fs/promises");
4451
+ const { join: join12 } = await import("path");
4452
+ const { existsSync: existsSync4 } = await import("fs");
4453
+ const { homedir: homedir2 } = await import("os");
4454
+ const { fileURLToPath: fileURLToPath2 } = await import("url");
4455
+ const { dirname: dirname2 } = await import("path");
4456
+ const currentFile = fileURLToPath2(import.meta.url);
4457
+ const currentDir = dirname2(currentFile);
4458
+ const aikitPath = join12(currentDir, "..");
4459
+ const mcpServerPath = join12(aikitPath, "dist", "mcp-server.js");
4460
+ const configLocations = [
4461
+ // Global config (most common)
4462
+ join12(homedir2(), ".config", "opencode", "opencode.json"),
4463
+ // Project-level config
4464
+ join12(projectPath, ".opencode", "opencode.json"),
4465
+ // Alternative global location
4466
+ join12(homedir2(), ".opencode", "opencode.json")
4467
+ ];
4468
+ const mcpServerConfig = {
4469
+ type: "local",
4470
+ command: ["node", mcpServerPath],
4471
+ environment: {}
4472
+ };
4473
+ for (const configPath of configLocations) {
4474
+ try {
4475
+ const configDir = join12(configPath, "..");
4476
+ await mkdir9(configDir, { recursive: true });
4477
+ let config = {};
4478
+ if (existsSync4(configPath)) {
4479
+ try {
4480
+ const existing = await readFile8(configPath, "utf-8");
4481
+ config = JSON.parse(existing);
4482
+ } catch {
4483
+ config = {};
4484
+ }
4485
+ }
4486
+ if (!config.mcp) {
4487
+ config.mcp = {};
4488
+ }
4489
+ config.mcp.aikit = mcpServerConfig;
4490
+ await writeFile9(configPath, JSON.stringify(config, null, 2));
4491
+ logger.success(`
4492
+ \u2705 MCP server configured: ${configPath}`);
4493
+ logger.info(` Server: node ${mcpServerPath}`);
4494
+ return;
4495
+ } catch (error) {
4496
+ continue;
4497
+ }
4498
+ }
4499
+ const instructionsPath = join12(projectPath, ".opencode", "MCP_SETUP.md");
4500
+ await mkdir9(join12(projectPath, ".opencode"), { recursive: true });
4501
+ await writeFile9(instructionsPath, `# AIKit MCP Server Configuration
4502
+
4503
+ ## Automatic Setup Failed
4504
+
4505
+ Please manually configure the MCP server in OpenCode.
4506
+
4507
+ ## Configuration
4508
+
4509
+ Add this to your OpenCode configuration file (\`~/.config/opencode/opencode.json\`):
4510
+
4511
+ \`\`\`json
4512
+ {
4513
+ "mcpServers": {
4514
+ "aikit": {
4515
+ "command": "node",
4516
+ "args": ["${mcpServerPath}"],
4517
+ "env": {}
4518
+ }
4519
+ }
4520
+ }
4521
+ \`\`\`
4522
+
4523
+ ## After Configuration
4524
+
4525
+ 1. Restart OpenCode completely
4526
+ 2. OpenCode will automatically start the MCP server
4527
+ 3. Tools will be available via MCP protocol
4528
+ 4. You can use tools like \`tool_read_figma_design\` directly
4529
+
4530
+ ## Verify
4531
+
4532
+ After restarting OpenCode, check:
4533
+ - MCP server is running (check OpenCode settings)
4534
+ - Tools are discoverable (OpenCode should list them)
4535
+ - You can call tools via MCP protocol
4536
+ `);
4537
+ logger.warn(`
4538
+ \u26A0\uFE0F Could not auto-configure MCP server. See: ${instructionsPath}`);
4539
+ }
4540
+ async function installToOpenCode(_opencodePath) {
4541
+ const { mkdir: mkdir9, writeFile: writeFile9, access: access5 } = await import("fs/promises");
4542
+ const { join: join12 } = await import("path");
4543
+ const projectPath = process.cwd();
4544
+ const opencodeCommandDir = join12(projectPath, ".opencode", "command");
4545
+ const aikitDir = join12(projectPath, ".aikit");
4546
+ const opencodeAgentDir = join12(paths.opencodeConfig(), "agent");
4547
+ await mkdir9(opencodeCommandDir, { recursive: true });
4548
+ await mkdir9(join12(aikitDir, "skills"), { recursive: true });
4549
+ await mkdir9(opencodeAgentDir, { recursive: true });
4550
+ const agentFiles = {
4551
+ agent: `---
4552
+ description: General-purpose default agent (OpenCode compatibility).
4553
+ mode: subagent
4554
+ tools:
4555
+ "*": true
4556
+ ---
4557
+
4558
+ Use for quick tasks when no specialized agent is needed.`,
4559
+ planner: `---
4560
+ description: Strategic planner; breaks down work and coordinates specialist agents.
4561
+ mode: subagent
4562
+ tools:
4563
+ "*": true
4564
+ ---
4565
+
4566
+ Use when the task is complex or multi-step. Delegates to @build for implementation,
4567
+ @scout for research, @review for code review/security, @explore for codebase navigation,
4568
+ and @vision for visual analysis.`,
4569
+ build: `---
4570
+ description: Primary builder; writes code, tests, and implements features.
4571
+ mode: subagent
4572
+ tools:
4573
+ "*": true
4574
+ ---
4575
+
4576
+ Use for feature implementation, refactors, and bug fixes. Prefer TDD, small steps,
4577
+ and run checks after changes. Delegates to @review for audits and @explore for context.`,
4578
+ rush: `---
4579
+ description: Fast execution for small/urgent changes with minimal planning.
4580
+ mode: subagent
4581
+ tools:
4582
+ "*": true
4583
+ ---
4584
+
4585
+ Use for quick fixes, hotfixes, or tiny edits. Keep scope minimal and verify quickly.`,
4586
+ review: `---
4587
+ description: Code review and quality/security auditing agent.
4588
+ mode: subagent
4589
+ tools:
4590
+ "*": true
4591
+ ---
4592
+
4593
+ Use to review correctness, security, performance, maintainability, and tests. Be specific
4594
+ about issues and suggest concrete fixes.`,
4595
+ scout: `---
4596
+ description: Research agent for external docs, patterns, and references.
4597
+ mode: subagent
4598
+ tools:
4599
+ "*": true
4600
+ ---
4601
+
4602
+ Use to look up docs, examples, best practices. Summarize findings concisely and cite sources.`,
4603
+ explore: `---
4604
+ description: Codebase navigation agent (search, grep, structure understanding).
4605
+ mode: subagent
4606
+ tools:
4607
+ "*": true
4608
+ ---
4609
+
4610
+ Use to locate files, patterns, dependencies, and gather quick context in the repo.`,
4611
+ vision: `---
4612
+ description: Visual analysis agent for mockups, screenshots, PDFs, diagrams.
4613
+ mode: subagent
4614
+ tools:
4615
+ "*": true
4616
+ ---
4617
+
4618
+ Use to interpret visual assets (components, layout, colors, typography) and translate to tasks.`
4619
+ };
4620
+ for (const [name, content] of Object.entries(agentFiles)) {
4621
+ const filePath = join12(opencodeAgentDir, `${name}.md`);
4622
+ try {
4623
+ await access5(filePath);
4624
+ } catch {
4625
+ await writeFile9(filePath, content, "utf8");
4626
+ }
4627
+ }
4628
+ const config = await loadConfig();
4629
+ const skillEngine = new SkillEngine(config);
4630
+ const commandRunner = new CommandRunner(config);
4631
+ const skills = await skillEngine.listSkills();
4632
+ const commands = await commandRunner.listCommands();
4633
+ const opencodeCommands = {};
4634
+ const skillsList = skills.map((s) => `| \`/${s.name.replace(/\s+/g, "-")}\` | ${s.description} |`).join("\n");
4635
+ opencodeCommands["skills"] = `List all available AIKit skills and how to use them.
4636
+
4637
+ READ .aikit/AGENTS.md
4638
+
4639
+ ## Available Skills
4640
+
4641
+ | Command | Description |
4642
+ |---------|-------------|
4643
+ ${skillsList}
4644
+
4645
+ Type any command to use that skill. For example: \`/test-driven-development\` or \`/tdd\`.`;
4646
+ for (const skill of skills) {
4647
+ const commandName = skill.name.replace(/\s+/g, "-").toLowerCase();
4648
+ const skillPath = skill.filePath;
4649
+ const relativePath = skillPath.startsWith(projectPath) ? skillPath.replace(projectPath, "").replace(/\\/g, "/").replace(/^\//, "") : `.aikit/skills/${skill.name.replace(/\s+/g, "-").toLowerCase()}.md`;
4650
+ const useWhen = skill.useWhen || `The user asks you to ${skill.name}`;
4651
+ opencodeCommands[commandName] = `Use the **${skill.name} skill** ${useWhen.toLowerCase()}.
4652
+
4653
+ READ ${relativePath}
4654
+
4655
+ ## Description
4656
+ ${skill.description}
4657
+
4658
+ ## When to Use
4659
+ ${useWhen}
4660
+
4661
+ ## Workflow
4662
+ ${skill.content.split("\n").slice(0, 20).join("\n")}${skill.content.split("\n").length > 20 ? "\n\n... (see full skill file for complete workflow)" : ""}
4663
+
4664
+ **IMPORTANT**: Follow this skill's workflow step by step. Do not skip steps.
4665
+ Complete the checklist at the end of the skill.`;
4666
+ }
4667
+ for (const cmd of commands) {
4668
+ if (opencodeCommands[cmd.name]) continue;
4669
+ const commandName = cmd.name.replace(/\//g, "").replace(/\s+/g, "-");
4670
+ const examples = cmd.examples.map((e) => `- \`${e}\``).join("\n");
4671
+ if (cmd.name === "analyze-figma") {
4672
+ opencodeCommands[commandName] = `# Command: /analyze-figma
4673
+
4674
+ ## Description
4675
+ ${cmd.description}
4676
+
4677
+ ## Usage
4678
+ \`${cmd.usage}\`
4679
+
4680
+ ## Examples
4681
+ ${examples}
4682
+
4683
+ ## \u26A0\uFE0F CRITICAL: Extract URL FIRST!
4684
+
4685
+ **BEFORE ANYTHING ELSE**: Look at the user's FULL input message (all lines) and find the Figma URL. It's ALWAYS there - never ask for it!
4686
+
4687
+ **The URL pattern**: Look for text containing \`figma.com/design/\` anywhere in the user's message.
4688
+
4689
+ **Example of what user input looks like**:
4690
+ \`\`\`
4691
+ /analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
4692
+ Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
4693
+ \`\`\`
4694
+
4695
+ **Extract the complete URL** (combine if split):
4696
+ \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
4697
+
4698
+ ## Workflow
4699
+
4700
+ **IMPORTANT**: When user provides a Figma URL, you MUST immediately:
4701
+
4702
+ **Step 1: Extract URL from User Input**
4703
+
4704
+ **CRITICAL**: The URL is ALWAYS in the user's input message! DO NOT ask for it - just extract it!
4705
+
4706
+ **MANDATORY**: You MUST extract the URL before proceeding. This is not optional!
4707
+
4708
+ **How to Extract**:
4709
+ 1. **Read the ENTIRE user input message** - look at ALL lines, not just the first line
4710
+ 2. **Search for ANY text containing** \`figma.com/design/\` - this is the URL
4711
+ 3. **URL may appear in different formats**:
4712
+ - On same line: \`/analyze-figma https://www.figma.com/design/...\`
4713
+ - Split across lines:
4714
+ \`\`\`
4715
+ /analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
4716
+ Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
4717
+ \`\`\`
4718
+ - Just the URL: \`https://www.figma.com/design/...\`
4719
+ 4. **Extract the COMPLETE URL**:
4720
+ - Start from \`https://\` or \`http://\`
4721
+ - Include everything until the end of the line or next whitespace
4722
+ - If URL is split, combine ALL parts into one complete URL
4723
+ 5. **Include ALL query parameters**: \`?node-id=...\`, \`&t=...\`, etc.
4724
+
4725
+ **REAL EXAMPLE**:
4726
+ \`\`\`
4727
+ User input:
4728
+ /analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
4729
+ Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
4730
+ \`\`\`
4731
+
4732
+ **Extract as**:
4733
+ \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
4734
+
4735
+ **CRITICAL RULES**:
4736
+ - \u2705 DO: Read the ENTIRE user message (all lines)
4737
+ - \u2705 DO: Look for \`figma.com/design/\` anywhere in the message
4738
+ - \u2705 DO: Combine split lines into one URL
4739
+ - \u274C DO NOT: Ask user for URL - it's ALWAYS in the input
4740
+ - \u274C DO NOT: Skip this step - URL extraction is MANDATORY
4741
+ - \u274C DO NOT: Proceed without extracting URL first
4742
+
4743
+ **If you think URL is not found**:
4744
+ 1. Re-read the user's message line by line
4745
+ 2. Look for ANY mention of "figma.com"
4746
+ 3. Check if URL is split across multiple lines
4747
+ 4. The URL is definitely there - find it!
4748
+ - If URL not found in current message, check previous messages
4749
+
4750
+ **Step 2: Check Tool Configuration**
4751
+
4752
+ Before calling the tool, verify that Figma tool is configured:
4753
+ - If not configured, inform user to run: \`aikit skills figma-analysis config\`
4754
+ - The tool requires a Figma Personal Access Token
4755
+
4756
+ **Step 3: Call MCP Tool read_figma_design**
4757
+
4758
+ Use the MCP tool \`read_figma_design\` (or \`tool_read_figma_design\` via MCP) with the extracted URL:
4759
+ \`\`\`
4760
+ Use tool: read_figma_design
4761
+ Arguments: { "url": "[extracted URL]" }
4762
+ \`\`\`
4763
+
4764
+ **This tool will automatically**:
4765
+ 1. Validate the Figma URL format
4766
+ 2. Check if Figma tool is configured
4767
+ 3. Call Figma API to fetch design data
4768
+ 4. Extract design tokens:
4769
+ - Colors (from fills and strokes, converted to hex)
4770
+ - Typography (font families, sizes, weights, line heights)
4771
+ - Spacing system (8px grid detection)
4772
+ - Components (from Figma components)
4773
+ - Screens/Frames (dimensions and names)
4774
+ - Breakpoints (common responsive breakpoints)
4775
+ 5. Return formatted markdown with all extracted tokens
4776
+
4777
+ **Step 4: Format and Save**
4778
+
4779
+ Format extracted tokens as structured markdown and save using memory-update tool:
4780
+ \`\`\`
4781
+ Use tool: memory-update
4782
+ Arguments: {
4783
+ "key": "research/figma-analysis",
4784
+ "content": "[formatted markdown with all tokens]"
4785
+ }
4786
+ \`\`\`
4787
+
4788
+ **Step 5: Report Results**
4789
+
4790
+ Report what was extracted:
4791
+ - Number of screens found
4792
+ - Number of colors in palette
4793
+ - Typography styles found
4794
+ - Components identified
4795
+ - Confirm save location: \`memory/research/figma-analysis.md\`
4796
+
4797
+ ## Critical Instructions
4798
+
4799
+ - **DO NOT** ask user to "share the Figma URL" - they already provided it in the command
4800
+ - **DO NOT** wait for confirmation - just start analyzing immediately
4801
+ - **DO** extract URL from full user input message
4802
+ - **DO** call MCP tool \`read_figma_design\` immediately
4803
+ - **DO** use browser MCP to navigate and snapshot
4804
+ - **DO** extract everything automatically without asking
4805
+ - **DO** save to memory automatically
4806
+
4807
+ ## Error Handling
4808
+
4809
+ If the tool returns an error:
4810
+ 1. **If "needs config"**: Guide user to run \`aikit skills figma-analysis config\`
4811
+ 2. **If API error**:
4812
+ - Verify the Figma URL is correct and accessible
4813
+ - Ensure your API token has access to the file
4814
+ - Check if the file is public or you have permission to access it
4815
+ 3. **If URL invalid**: Re-check the extracted URL format
4816
+
4817
+ ## How to Parse URL from Command
4818
+
4819
+ **CRITICAL**: The Figma URL is provided in the SAME message as the command!
4820
+
4821
+ Example:
4822
+ - User input: \`/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
4823
+ - The URL is: \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
4824
+
4825
+ **Extract the URL** from the command input:
4826
+ - Everything after \`/analyze-figma \` (note the space) is the URL
4827
+ - The URL starts with \`https://\` or \`http://\`
4828
+ - Extract the ENTIRE URL including all query parameters
4829
+ - Check the FULL user message, not just command name
4830
+
4831
+ ## Example Usage
4832
+
4833
+ User input: \`/analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
4834
+
4835
+ **Step 1: Extract URL**
4836
+ - Check full user input message
4837
+ - Extract: \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1\`
4838
+
4839
+ **Step 2: Call MCP Tool**
4840
+ \`\`\`
4841
+ Use tool: read_figma_design
4842
+ Arguments: { "url": "https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?node-id=0-1&t=70yZa7w5wSyjDhYj-1" }
4843
+ \`\`\`
4844
+
4845
+ **Step 3: Tool automatically extracts tokens via Figma API**
4846
+
4847
+ **Step 4: Save to memory using memory-update tool**
4848
+
4849
+ **Step 5: Report results to user**
4850
+
4851
+ ## Important Reminders
4852
+
4853
+ - The URL is ALREADY in the command input - extract it from full message!
4854
+ - URL may be split across multiple lines - combine them into one complete URL
4855
+ - Do NOT ask for URL again - it's always in the input
4856
+ - Do NOT wait - start immediately
4857
+ - Use MCP tool \`read_figma_design\` which uses Figma API directly`;
4858
+ } else {
4859
+ opencodeCommands[commandName] = `# Command: /${cmd.name}
4860
+
4861
+ ## Description
4862
+ ${cmd.description}
4863
+
4864
+ ## Usage
4865
+ \`${cmd.usage}\`
4866
+
4867
+ ## Examples
4868
+ ${examples}
4869
+
4870
+ ## Workflow
4871
+ ${cmd.content}
4872
+
4873
+ **Category**: ${cmd.category}`;
4874
+ }
4875
+ }
4876
+ let count = 0;
4877
+ for (const [name, content] of Object.entries(opencodeCommands)) {
4878
+ const filePath = join12(opencodeCommandDir, `${name}.md`);
4879
+ await writeFile9(filePath, content.trim());
4880
+ logger.info(` \u2713 Created /${name} command`);
4881
+ count++;
4882
+ }
4883
+ logger.success(`
4884
+ Created ${count} OpenCode commands in .opencode/command/`);
4885
+ await configureMcpServer(projectPath);
4886
+ logger.info("\nUsage in OpenCode:");
4887
+ logger.info(" Press Ctrl+K to open command picker");
4888
+ logger.info(" Or type /skills to see all available skills");
4889
+ logger.info(` Available: ${skills.length} skills, ${commands.length} commands`);
4890
+ logger.info(" MCP server configured - tools available via MCP protocol");
4891
+ }
4892
+ function groupBy(array, keyFn) {
4893
+ return array.reduce((acc, item) => {
4894
+ const key = keyFn(item);
4895
+ if (!acc[key]) acc[key] = [];
4896
+ acc[key].push(item);
4897
+ return acc;
4898
+ }, {});
4899
+ }
4900
+ program.parse();
4901
+ //# sourceMappingURL=cli.js.map