@tdsoft-tech/aikit 0.1.31 → 0.1.33

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/index.js CHANGED
@@ -144,6 +144,34 @@ var init_paths = __esm({
144
144
  */
145
145
  claudeAgents(project) {
146
146
  return join(this.claudeConfig(project ? "project" : "user"), "agents");
147
+ },
148
+ /**
149
+ * Get Cursor configuration directory
150
+ */
151
+ cursorConfig(scope) {
152
+ if (scope === "project") {
153
+ return join(process.cwd(), ".cursor");
154
+ }
155
+ const base = process.platform === "win32" ? join(homedir(), "AppData", "Local", "Cursor") : join(homedir(), ".cursor");
156
+ return base;
157
+ },
158
+ /**
159
+ * Get Cursor commands directory
160
+ */
161
+ cursorCommands(project) {
162
+ return join(this.cursorConfig(project ? "project" : "user"), "commands");
163
+ },
164
+ /**
165
+ * Get Cursor skills directory
166
+ */
167
+ cursorSkills(project) {
168
+ return join(this.cursorConfig(project ? "project" : "user"), "skills");
169
+ },
170
+ /**
171
+ * Get Cursor agents directory
172
+ */
173
+ cursorAgents(project) {
174
+ return join(this.cursorConfig(project ? "project" : "user"), "agents");
147
175
  }
148
176
  };
149
177
  }
@@ -176,6 +204,756 @@ var init_version = __esm({
176
204
  }
177
205
  });
178
206
 
207
+ // src/utils/logger.ts
208
+ import chalk from "chalk";
209
+ var logger;
210
+ var init_logger = __esm({
211
+ "src/utils/logger.ts"() {
212
+ "use strict";
213
+ init_esm_shims();
214
+ logger = {
215
+ info(...args) {
216
+ console.log(chalk.blue("\u2139"), ...args);
217
+ },
218
+ success(...args) {
219
+ console.log(chalk.green("\u2713"), ...args);
220
+ },
221
+ warn(...args) {
222
+ console.log(chalk.yellow("\u26A0"), ...args);
223
+ },
224
+ error(...args) {
225
+ console.error(chalk.red("\u2716"), ...args);
226
+ },
227
+ debug(...args) {
228
+ if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
229
+ console.log(chalk.gray("\u22EF"), ...args);
230
+ }
231
+ },
232
+ step(step, total, message) {
233
+ console.log(chalk.cyan(`[${step}/${total}]`), message);
234
+ },
235
+ header(message) {
236
+ console.log(chalk.bold.underline(`
237
+ ${message}
238
+ `));
239
+ },
240
+ list(items, prefix = "\u2022") {
241
+ for (const item of items) {
242
+ console.log(` ${prefix} ${item}`);
243
+ }
244
+ }
245
+ };
246
+ }
247
+ });
248
+
249
+ // src/core/database/schema.ts
250
+ function initializeSchema(db) {
251
+ try {
252
+ logger.info("Initializing Figma database schema...");
253
+ db.pragma("foreign_keys = ON");
254
+ db.pragma("journal_mode = WAL");
255
+ Object.entries(SCHEMA_SQL).forEach(([tableName, sql]) => {
256
+ logger.debug(`Creating table: ${tableName}`);
257
+ db.exec(sql);
258
+ });
259
+ Object.entries(INDEX_SQL).forEach(([indexName, sql]) => {
260
+ logger.debug(`Creating index: ${indexName}`);
261
+ db.exec(sql);
262
+ });
263
+ Object.entries(TRIGGER_SQL).forEach(([triggerName, sql]) => {
264
+ logger.debug(`Creating trigger: ${triggerName}`);
265
+ db.exec(sql);
266
+ });
267
+ const currentVersion = getCurrentSchemaVersion(db);
268
+ if (currentVersion < DATABASE_VERSION) {
269
+ db.prepare("INSERT OR REPLACE INTO schema_version (version) VALUES (?)").run(DATABASE_VERSION);
270
+ logger.info(`Schema updated to version ${DATABASE_VERSION}`);
271
+ }
272
+ logger.info("Database schema initialized successfully");
273
+ } catch (error) {
274
+ logger.error(`Failed to initialize database schema: ${error instanceof Error ? error.message : String(error)}`);
275
+ throw error;
276
+ }
277
+ }
278
+ function getCurrentSchemaVersion(db) {
279
+ try {
280
+ const result = db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
281
+ return result?.version || 0;
282
+ } catch {
283
+ return 0;
284
+ }
285
+ }
286
+ var DATABASE_VERSION, SCHEMA_SQL, INDEX_SQL, TRIGGER_SQL;
287
+ var init_schema = __esm({
288
+ "src/core/database/schema.ts"() {
289
+ "use strict";
290
+ init_esm_shims();
291
+ init_logger();
292
+ DATABASE_VERSION = 1;
293
+ SCHEMA_SQL = {
294
+ // Files table - stores Figma file metadata
295
+ figma_files: `
296
+ CREATE TABLE IF NOT EXISTS figma_files (
297
+ id TEXT PRIMARY KEY,
298
+ url TEXT NOT NULL UNIQUE,
299
+ name TEXT,
300
+ file_key TEXT NOT NULL,
301
+ last_analyzed DATETIME,
302
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
303
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
304
+ )
305
+ `,
306
+ // Screens table - stores screen/frame information
307
+ figma_screens: `
308
+ CREATE TABLE IF NOT EXISTS figma_screens (
309
+ id TEXT PRIMARY KEY,
310
+ file_id TEXT NOT NULL,
311
+ name TEXT NOT NULL,
312
+ width INTEGER,
313
+ height INTEGER,
314
+ type TEXT,
315
+ description TEXT,
316
+ children_count INTEGER,
317
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
318
+ )
319
+ `,
320
+ // Nodes table - stores hierarchical component structure
321
+ figma_nodes: `
322
+ CREATE TABLE IF NOT EXISTS figma_nodes (
323
+ id TEXT PRIMARY KEY,
324
+ file_id TEXT NOT NULL,
325
+ screen_id TEXT,
326
+ parent_id TEXT,
327
+ name TEXT NOT NULL,
328
+ type TEXT NOT NULL,
329
+ content TEXT,
330
+ position_x REAL,
331
+ position_y REAL,
332
+ width REAL,
333
+ height REAL,
334
+ styles TEXT, -- JSON string
335
+ children_ids TEXT, -- JSON string array
336
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE,
337
+ FOREIGN KEY (screen_id) REFERENCES figma_screens(id) ON DELETE CASCADE,
338
+ FOREIGN KEY (parent_id) REFERENCES figma_nodes(id) ON DELETE CASCADE
339
+ )
340
+ `,
341
+ // Design tokens table - stores extracted design tokens
342
+ design_tokens: `
343
+ CREATE TABLE IF NOT EXISTS design_tokens (
344
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
345
+ file_id TEXT NOT NULL,
346
+ type TEXT NOT NULL, -- 'color', 'typography', 'spacing', 'component'
347
+ name TEXT NOT NULL,
348
+ value TEXT NOT NULL,
349
+ category TEXT,
350
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
351
+ )
352
+ `,
353
+ // Assets table - stores downloadable assets
354
+ figma_assets: `
355
+ CREATE TABLE IF NOT EXISTS figma_assets (
356
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
357
+ file_id TEXT NOT NULL,
358
+ node_id TEXT NOT NULL,
359
+ node_name TEXT,
360
+ node_type TEXT,
361
+ format TEXT, -- 'png', 'svg', 'jpg'
362
+ file_path TEXT,
363
+ url TEXT,
364
+ width INTEGER,
365
+ height INTEGER,
366
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
367
+ )
368
+ `,
369
+ // Schema version tracking
370
+ schema_version: `
371
+ CREATE TABLE IF NOT EXISTS schema_version (
372
+ version INTEGER PRIMARY KEY,
373
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
374
+ )
375
+ `
376
+ };
377
+ INDEX_SQL = {
378
+ // Files indexes
379
+ idx_files_url: "CREATE INDEX IF NOT EXISTS idx_files_url ON figma_files(url)",
380
+ idx_files_file_key: "CREATE INDEX IF NOT EXISTS idx_files_file_key ON figma_files(file_key)",
381
+ idx_files_last_analyzed: "CREATE INDEX IF NOT EXISTS idx_files_last_analyzed ON figma_files(last_analyzed)",
382
+ // Screens indexes
383
+ idx_screens_file_id: "CREATE INDEX IF NOT EXISTS idx_screens_file_id ON figma_screens(file_id)",
384
+ idx_screens_type: "CREATE INDEX IF NOT EXISTS idx_screens_type ON figma_screens(type)",
385
+ // Nodes indexes
386
+ idx_nodes_file_id: "CREATE INDEX IF NOT EXISTS idx_nodes_file_id ON figma_nodes(file_id)",
387
+ idx_nodes_screen_id: "CREATE INDEX IF NOT EXISTS idx_nodes_screen_id ON figma_nodes(screen_id)",
388
+ idx_nodes_parent_id: "CREATE INDEX IF NOT EXISTS idx_nodes_parent_id ON figma_nodes(parent_id)",
389
+ idx_nodes_type: "CREATE INDEX IF NOT EXISTS idx_nodes_type ON figma_nodes(type)",
390
+ // Design tokens indexes
391
+ idx_tokens_file_id: "CREATE INDEX IF NOT EXISTS idx_tokens_file_id ON design_tokens(file_id)",
392
+ idx_tokens_type: "CREATE INDEX IF NOT EXISTS idx_tokens_type ON design_tokens(type)",
393
+ idx_tokens_file_type: "CREATE INDEX IF NOT EXISTS idx_tokens_file_type ON design_tokens(file_id, type)",
394
+ // Assets indexes
395
+ idx_assets_file_id: "CREATE INDEX IF NOT EXISTS idx_assets_file_id ON figma_assets(file_id)",
396
+ idx_assets_node_id: "CREATE INDEX IF NOT EXISTS idx_assets_node_id ON figma_assets(node_id)",
397
+ idx_assets_format: "CREATE INDEX IF NOT EXISTS idx_assets_format ON figma_assets(format)"
398
+ };
399
+ TRIGGER_SQL = {
400
+ // Update timestamp trigger for files table
401
+ trigger_files_updated_at: `
402
+ CREATE TRIGGER IF NOT EXISTS trigger_files_updated_at
403
+ AFTER UPDATE ON figma_files
404
+ FOR EACH ROW
405
+ BEGIN
406
+ UPDATE figma_files SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
407
+ END
408
+ `
409
+ };
410
+ }
411
+ });
412
+
413
+ // src/core/database/figma-db.ts
414
+ import Database from "better-sqlite3";
415
+ var FigmaDatabase;
416
+ var init_figma_db = __esm({
417
+ "src/core/database/figma-db.ts"() {
418
+ "use strict";
419
+ init_esm_shims();
420
+ init_logger();
421
+ init_schema();
422
+ FigmaDatabase = class {
423
+ db;
424
+ constructor(dbPath = ":memory:") {
425
+ try {
426
+ this.db = new Database(dbPath);
427
+ initializeSchema(this.db);
428
+ logger.info(`Figma database initialized at: ${dbPath}`);
429
+ } catch (error) {
430
+ logger.error(`Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`);
431
+ throw error;
432
+ }
433
+ }
434
+ /**
435
+ * Export database data to JSON
436
+ */
437
+ async exportData(fileId) {
438
+ try {
439
+ const files = fileId ? [await this.getFile(fileId)].filter(Boolean) : await this.listFiles();
440
+ const fileIds = files.map((f) => f.id);
441
+ let screens = [];
442
+ let nodes = [];
443
+ let tokens = [];
444
+ let assets = [];
445
+ for (const fId of fileIds) {
446
+ screens.push(...await this.getScreensByFile(fId));
447
+ nodes.push(...await this.getNodesByFile(fId));
448
+ tokens.push(...await this.getDesignTokensByFile(fId));
449
+ assets.push(...await this.getAssetsByFile(fId));
450
+ }
451
+ return {
452
+ files,
453
+ screens,
454
+ nodes,
455
+ tokens,
456
+ assets,
457
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
458
+ };
459
+ } catch (error) {
460
+ logger.error(`Failed to export data: ${error instanceof Error ? error.message : String(error)}`);
461
+ throw error;
462
+ }
463
+ }
464
+ /**
465
+ * Import database data from JSON
466
+ */
467
+ async importData(data) {
468
+ try {
469
+ const stats = { files: 0, screens: 0, nodes: 0, tokens: 0, assets: 0 };
470
+ for (const file of data.files) {
471
+ await this.upsertFile(file);
472
+ stats.files++;
473
+ }
474
+ for (const screen of data.screens) {
475
+ await this.upsertScreen(screen);
476
+ stats.screens++;
477
+ }
478
+ for (const node of data.nodes) {
479
+ await this.upsertNode(node);
480
+ stats.nodes++;
481
+ }
482
+ for (const token of data.tokens) {
483
+ await this.upsertDesignToken(token);
484
+ stats.tokens++;
485
+ }
486
+ for (const asset of data.assets) {
487
+ await this.upsertAsset(asset);
488
+ stats.assets++;
489
+ }
490
+ logger.info(`Imported ${stats.files} files, ${stats.screens} screens, ${stats.nodes} nodes, ${stats.tokens} tokens, ${stats.assets} assets`);
491
+ return stats;
492
+ } catch (error) {
493
+ logger.error(`Failed to import data: ${error instanceof Error ? error.message : String(error)}`);
494
+ throw error;
495
+ }
496
+ }
497
+ /**
498
+ * Utility Operations
499
+ */
500
+ async upsertFile(file) {
501
+ try {
502
+ const lastAnalyzed = file.last_analyzed instanceof Date ? file.last_analyzed.toISOString() : file.last_analyzed || null;
503
+ const existing = this.db.prepare("SELECT id FROM figma_files WHERE id = ?").get(file.id);
504
+ if (existing) {
505
+ this.db.prepare(`
506
+ UPDATE figma_files
507
+ SET url = ?, name = ?, file_key = ?, last_analyzed = ?, updated_at = CURRENT_TIMESTAMP
508
+ WHERE id = ?
509
+ `).run(file.url, file.name || null, file.file_key, lastAnalyzed, file.id);
510
+ return { id: file.id, created: false, updated: true };
511
+ } else {
512
+ this.db.prepare(`
513
+ INSERT INTO figma_files (id, url, name, file_key, last_analyzed)
514
+ VALUES (?, ?, ?, ?, ?)
515
+ `).run(file.id, file.url, file.name || null, file.file_key, lastAnalyzed);
516
+ return { id: file.id, created: true, updated: false };
517
+ }
518
+ } catch (error) {
519
+ logger.error(`Failed to upsert file: ${error instanceof Error ? error.message : String(error)}`);
520
+ throw error;
521
+ }
522
+ }
523
+ async getFile(id) {
524
+ try {
525
+ const row = this.db.prepare("SELECT * FROM figma_files WHERE id = ?").get(id);
526
+ if (!row) return null;
527
+ return {
528
+ ...row,
529
+ created_at: new Date(row.created_at),
530
+ updated_at: new Date(row.updated_at),
531
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
532
+ };
533
+ } catch (error) {
534
+ logger.error(`Failed to get file: ${error instanceof Error ? error.message : String(error)}`);
535
+ return null;
536
+ }
537
+ }
538
+ async getFileByUrl(url) {
539
+ try {
540
+ const row = this.db.prepare("SELECT * FROM figma_files WHERE url = ?").get(url);
541
+ if (!row) return null;
542
+ return {
543
+ ...row,
544
+ created_at: new Date(row.created_at),
545
+ updated_at: new Date(row.updated_at),
546
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
547
+ };
548
+ } catch (error) {
549
+ logger.error(`Failed to get file by URL: ${error instanceof Error ? error.message : String(error)}`);
550
+ return null;
551
+ }
552
+ }
553
+ async deleteFile(id) {
554
+ try {
555
+ const result = this.db.prepare("DELETE FROM figma_files WHERE id = ?").run(id);
556
+ return result.changes > 0;
557
+ } catch (error) {
558
+ logger.error(`Failed to delete file: ${error instanceof Error ? error.message : String(error)}`);
559
+ return false;
560
+ }
561
+ }
562
+ async listFiles(options) {
563
+ try {
564
+ const limit = options?.limit || 50;
565
+ const offset = options?.offset || 0;
566
+ const orderBy = options?.orderBy || "updated_at";
567
+ const orderDirection = options?.orderDirection || "DESC";
568
+ const query = `
569
+ SELECT * FROM figma_files
570
+ ORDER BY ${orderBy} ${orderDirection}
571
+ LIMIT ? OFFSET ?
572
+ `;
573
+ const rows = this.db.prepare(query).all(limit, offset);
574
+ return rows.map((row) => ({
575
+ ...row,
576
+ created_at: new Date(row.created_at),
577
+ updated_at: new Date(row.updated_at),
578
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
579
+ }));
580
+ } catch (error) {
581
+ logger.error(`Failed to list files: ${error instanceof Error ? error.message : String(error)}`);
582
+ return [];
583
+ }
584
+ }
585
+ /**
586
+ * Screen Operations
587
+ */
588
+ async upsertScreen(screen) {
589
+ try {
590
+ const existing = this.db.prepare("SELECT id FROM figma_screens WHERE id = ?").get(screen.id);
591
+ if (existing) {
592
+ this.db.prepare(`
593
+ UPDATE figma_screens
594
+ SET file_id = ?, name = ?, width = ?, height = ?, type = ?, description = ?, children_count = ?
595
+ WHERE id = ?
596
+ `).run(
597
+ screen.file_id,
598
+ screen.name,
599
+ screen.width || null,
600
+ screen.height || null,
601
+ screen.type || null,
602
+ screen.description || null,
603
+ screen.children_count || null,
604
+ screen.id
605
+ );
606
+ return { id: screen.id, created: false, updated: true };
607
+ } else {
608
+ this.db.prepare(`
609
+ INSERT INTO figma_screens (id, file_id, name, width, height, type, description, children_count)
610
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
611
+ `).run(
612
+ screen.id,
613
+ screen.file_id,
614
+ screen.name,
615
+ screen.width || null,
616
+ screen.height || null,
617
+ screen.type || null,
618
+ screen.description || null,
619
+ screen.children_count || null
620
+ );
621
+ return { id: screen.id, created: true, updated: false };
622
+ }
623
+ } catch (error) {
624
+ logger.error(`Failed to upsert screen: ${error instanceof Error ? error.message : String(error)}`);
625
+ throw error;
626
+ }
627
+ }
628
+ async getScreen(id) {
629
+ try {
630
+ const row = this.db.prepare("SELECT * FROM figma_screens WHERE id = ?").get(id);
631
+ return row || null;
632
+ } catch (error) {
633
+ logger.error(`Failed to get screen: ${error instanceof Error ? error.message : String(error)}`);
634
+ return null;
635
+ }
636
+ }
637
+ async getScreensByFile(fileId) {
638
+ try {
639
+ const rows = this.db.prepare("SELECT * FROM figma_screens WHERE file_id = ? ORDER BY name").all(fileId);
640
+ return rows;
641
+ } catch (error) {
642
+ logger.error(`Failed to get screens by file: ${error instanceof Error ? error.message : String(error)}`);
643
+ return [];
644
+ }
645
+ }
646
+ async deleteScreen(id) {
647
+ try {
648
+ const result = this.db.prepare("DELETE FROM figma_screens WHERE id = ?").run(id);
649
+ return result.changes > 0;
650
+ } catch (error) {
651
+ logger.error(`Failed to delete screen: ${error instanceof Error ? error.message : String(error)}`);
652
+ return false;
653
+ }
654
+ }
655
+ /**
656
+ * Node Operations
657
+ */
658
+ async upsertNode(node) {
659
+ try {
660
+ const existing = this.db.prepare("SELECT id FROM figma_nodes WHERE id = ?").get(node.id);
661
+ if (existing) {
662
+ this.db.prepare(`
663
+ UPDATE figma_nodes
664
+ SET file_id = ?, screen_id = ?, parent_id = ?, name = ?, type = ?, content = ?,
665
+ position_x = ?, position_y = ?, width = ?, height = ?, styles = ?, children_ids = ?
666
+ WHERE id = ?
667
+ `).run(
668
+ node.file_id,
669
+ node.screen_id || null,
670
+ node.parent_id || null,
671
+ node.name,
672
+ node.type,
673
+ node.content || null,
674
+ node.position_x || null,
675
+ node.position_y || null,
676
+ node.width || null,
677
+ node.height || null,
678
+ node.styles || null,
679
+ node.children_ids || null,
680
+ node.id
681
+ );
682
+ return { id: node.id, created: false, updated: true };
683
+ } else {
684
+ this.db.prepare(`
685
+ INSERT INTO figma_nodes (id, file_id, screen_id, parent_id, name, type, content,
686
+ position_x, position_y, width, height, styles, children_ids)
687
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
688
+ `).run(
689
+ node.id,
690
+ node.file_id,
691
+ node.screen_id || null,
692
+ node.parent_id || null,
693
+ node.name,
694
+ node.type,
695
+ node.content || null,
696
+ node.position_x || null,
697
+ node.position_y || null,
698
+ node.width || null,
699
+ node.height || null,
700
+ node.styles || null,
701
+ node.children_ids || null
702
+ );
703
+ return { id: node.id, created: true, updated: false };
704
+ }
705
+ } catch (error) {
706
+ logger.error(`Failed to upsert node: ${error instanceof Error ? error.message : String(error)}`);
707
+ throw error;
708
+ }
709
+ }
710
+ async getNode(id) {
711
+ try {
712
+ const row = this.db.prepare("SELECT * FROM figma_nodes WHERE id = ?").get(id);
713
+ return row || null;
714
+ } catch (error) {
715
+ logger.error(`Failed to get node: ${error instanceof Error ? error.message : String(error)}`);
716
+ return null;
717
+ }
718
+ }
719
+ async getNodesByScreen(screenId) {
720
+ try {
721
+ const rows = this.db.prepare("SELECT * FROM figma_nodes WHERE screen_id = ? ORDER BY name").all(screenId);
722
+ return rows;
723
+ } catch (error) {
724
+ logger.error(`Failed to get nodes by screen: ${error instanceof Error ? error.message : String(error)}`);
725
+ return [];
726
+ }
727
+ }
728
+ async getNodesByFile(fileId) {
729
+ try {
730
+ const rows = this.db.prepare("SELECT * FROM figma_nodes WHERE file_id = ? ORDER BY name").all(fileId);
731
+ return rows;
732
+ } catch (error) {
733
+ logger.error(`Failed to get nodes by file: ${error instanceof Error ? error.message : String(error)}`);
734
+ return [];
735
+ }
736
+ }
737
+ async deleteNode(id) {
738
+ try {
739
+ const result = this.db.prepare("DELETE FROM figma_nodes WHERE id = ?").run(id);
740
+ return result.changes > 0;
741
+ } catch (error) {
742
+ logger.error(`Failed to delete node: ${error instanceof Error ? error.message : String(error)}`);
743
+ return false;
744
+ }
745
+ }
746
+ /**
747
+ * Design Token Operations
748
+ */
749
+ async upsertDesignToken(token) {
750
+ try {
751
+ const existing = this.db.prepare(`
752
+ SELECT id FROM design_tokens
753
+ WHERE file_id = ? AND type = ? AND name = ?
754
+ `).get(token.file_id, token.type, token.name);
755
+ if (existing) {
756
+ this.db.prepare(`
757
+ UPDATE design_tokens
758
+ SET value = ?, category = ?
759
+ WHERE id = ?
760
+ `).run(token.value, token.category || null, existing.id);
761
+ return { id: existing.id.toString(), created: false, updated: true };
762
+ } else {
763
+ const result = this.db.prepare(`
764
+ INSERT INTO design_tokens (file_id, type, name, value, category)
765
+ VALUES (?, ?, ?, ?, ?)
766
+ `).run(token.file_id, token.type, token.name, token.value, token.category || null);
767
+ return { id: result.lastInsertRowid.toString(), created: true, updated: false };
768
+ }
769
+ } catch (error) {
770
+ logger.error(`Failed to upsert design token: ${error instanceof Error ? error.message : String(error)}`);
771
+ throw error;
772
+ }
773
+ }
774
+ async getDesignTokensByFile(fileId, type) {
775
+ try {
776
+ const query = type ? "SELECT * FROM design_tokens WHERE file_id = ? AND type = ? ORDER BY name" : "SELECT * FROM design_tokens WHERE file_id = ? ORDER BY type, name";
777
+ const rows = type ? this.db.prepare(query).all(fileId, type) : this.db.prepare(query).all(fileId);
778
+ return rows;
779
+ } catch (error) {
780
+ logger.error(`Failed to get design tokens: ${error instanceof Error ? error.message : String(error)}`);
781
+ return [];
782
+ }
783
+ }
784
+ async deleteDesignTokensByFile(fileId) {
785
+ try {
786
+ const result = this.db.prepare("DELETE FROM design_tokens WHERE file_id = ?").run(fileId);
787
+ return result.changes > 0;
788
+ } catch (error) {
789
+ logger.error(`Failed to delete design tokens: ${error instanceof Error ? error.message : String(error)}`);
790
+ return false;
791
+ }
792
+ }
793
+ /**
794
+ * Asset Operations
795
+ */
796
+ async upsertAsset(asset) {
797
+ try {
798
+ const existing = this.db.prepare(`
799
+ SELECT id FROM figma_assets
800
+ WHERE file_id = ? AND node_id = ?
801
+ `).get(asset.file_id, asset.node_id);
802
+ if (existing) {
803
+ this.db.prepare(`
804
+ UPDATE figma_assets
805
+ SET node_name = ?, node_type = ?, format = ?, file_path = ?, url = ?, width = ?, height = ?
806
+ WHERE id = ?
807
+ `).run(
808
+ asset.node_name || null,
809
+ asset.node_type || null,
810
+ asset.format || null,
811
+ asset.file_path || null,
812
+ asset.url || null,
813
+ asset.width || null,
814
+ asset.height || null,
815
+ existing.id
816
+ );
817
+ return { id: existing.id.toString(), created: false, updated: true };
818
+ } else {
819
+ const result = this.db.prepare(`
820
+ INSERT INTO figma_assets (file_id, node_id, node_name, node_type, format, file_path, url, width, height)
821
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
822
+ `).run(
823
+ asset.file_id,
824
+ asset.node_id,
825
+ asset.node_name || null,
826
+ asset.node_type || null,
827
+ asset.format || null,
828
+ asset.file_path || null,
829
+ asset.url || null,
830
+ asset.width || null,
831
+ asset.height || null
832
+ );
833
+ return { id: result.lastInsertRowid.toString(), created: true, updated: false };
834
+ }
835
+ } catch (error) {
836
+ logger.error(`Failed to upsert asset: ${error instanceof Error ? error.message : String(error)}`);
837
+ throw error;
838
+ }
839
+ }
840
+ async getAssetsByFile(fileId) {
841
+ try {
842
+ const rows = this.db.prepare("SELECT * FROM figma_assets WHERE file_id = ? ORDER BY node_name").all(fileId);
843
+ return rows;
844
+ } catch (error) {
845
+ logger.error(`Failed to get assets by file: ${error instanceof Error ? error.message : String(error)}`);
846
+ return [];
847
+ }
848
+ }
849
+ async deleteAssetsByFile(fileId) {
850
+ try {
851
+ const result = this.db.prepare("DELETE FROM figma_assets WHERE file_id = ?").run(fileId);
852
+ return result.changes > 0;
853
+ } catch (error) {
854
+ logger.error(`Failed to delete assets by file: ${error instanceof Error ? error.message : String(error)}`);
855
+ return false;
856
+ }
857
+ }
858
+ /**
859
+ * Cache invalidation logic
860
+ */
861
+ async isCacheValid(url, maxAgeHours = 24) {
862
+ try {
863
+ const file = await this.getFileByUrl(url);
864
+ if (!file || !file.last_analyzed) return false;
865
+ const now = /* @__PURE__ */ new Date();
866
+ const ageHours = (now.getTime() - file.last_analyzed.getTime()) / (1e3 * 60 * 60);
867
+ return ageHours < maxAgeHours;
868
+ } catch (error) {
869
+ logger.error(`Failed to check cache validity: ${error instanceof Error ? error.message : String(error)}`);
870
+ return false;
871
+ }
872
+ }
873
+ /**
874
+ * Clear cache for a specific file
875
+ */
876
+ async clearFileCache(url) {
877
+ try {
878
+ const file = await this.getFileByUrl(url);
879
+ if (!file) return false;
880
+ await this.deleteDesignTokensByFile(file.id);
881
+ await this.deleteAssetsByFile(file.id);
882
+ const nodes = await this.getNodesByFile(file.id);
883
+ for (const node of nodes) {
884
+ await this.deleteNode(node.id);
885
+ }
886
+ const screens = await this.getScreensByFile(file.id);
887
+ for (const screen of screens) {
888
+ await this.deleteScreen(screen.id);
889
+ }
890
+ return await this.deleteFile(file.id);
891
+ } catch (error) {
892
+ logger.error(`Failed to clear file cache: ${error instanceof Error ? error.message : String(error)}`);
893
+ return false;
894
+ }
895
+ }
896
+ /**
897
+ * Clear all expired cache entries
898
+ */
899
+ async clearExpiredCache(maxAgeHours = 24 * 7) {
900
+ try {
901
+ const cutoffDate = new Date(Date.now() - maxAgeHours * 60 * 60 * 1e3);
902
+ const expiredFiles = this.db.prepare(`
903
+ SELECT * FROM figma_files
904
+ WHERE last_analyzed < ? OR last_analyzed IS NULL
905
+ `).all(cutoffDate.toISOString());
906
+ let clearedCount = 0;
907
+ for (const file of expiredFiles) {
908
+ if (await this.clearFileCache(file.url)) {
909
+ clearedCount++;
910
+ }
911
+ }
912
+ return clearedCount;
913
+ } catch (error) {
914
+ logger.error(`Failed to clear expired cache: ${error instanceof Error ? error.message : String(error)}`);
915
+ return 0;
916
+ }
917
+ }
918
+ async close() {
919
+ try {
920
+ this.db.close();
921
+ logger.info("Database connection closed");
922
+ } catch (error) {
923
+ logger.error(`Failed to close database: ${error instanceof Error ? error.message : String(error)}`);
924
+ }
925
+ }
926
+ async vacuum() {
927
+ try {
928
+ this.db.exec("VACUUM");
929
+ logger.info("Database vacuumed");
930
+ } catch (error) {
931
+ logger.error(`Failed to vacuum database: ${error instanceof Error ? error.message : String(error)}`);
932
+ }
933
+ }
934
+ async getStats() {
935
+ try {
936
+ const files = this.db.prepare("SELECT COUNT(*) as count FROM figma_files").get();
937
+ const screens = this.db.prepare("SELECT COUNT(*) as count FROM figma_screens").get();
938
+ const nodes = this.db.prepare("SELECT COUNT(*) as count FROM figma_nodes").get();
939
+ const tokens = this.db.prepare("SELECT COUNT(*) as count FROM design_tokens").get();
940
+ const assets = this.db.prepare("SELECT COUNT(*) as count FROM figma_assets").get();
941
+ return {
942
+ files: files.count,
943
+ screens: screens.count,
944
+ nodes: nodes.count,
945
+ tokens: tokens.count,
946
+ assets: assets.count
947
+ };
948
+ } catch (error) {
949
+ logger.error(`Failed to get stats: ${error instanceof Error ? error.message : String(error)}`);
950
+ return { files: 0, screens: 0, nodes: 0, tokens: 0, assets: 0 };
951
+ }
952
+ }
953
+ };
954
+ }
955
+ });
956
+
179
957
  // src/core/config.ts
180
958
  var config_exports = {};
181
959
  __export(config_exports, {
@@ -233,15 +1011,41 @@ function deepMerge(base, override) {
233
1011
  }
234
1012
  return result;
235
1013
  }
236
- var ConfigSchema, Config;
1014
+ var PlatformConfigSchema, ConfigSchema, Config;
237
1015
  var init_config = __esm({
238
1016
  "src/core/config.ts"() {
239
1017
  "use strict";
240
1018
  init_esm_shims();
241
1019
  init_paths();
242
1020
  init_version();
1021
+ init_figma_db();
1022
+ PlatformConfigSchema = z.object({
1023
+ /**
1024
+ * Primary platform to use (affects default behavior)
1025
+ */
1026
+ primary: z.enum(["opencode", "claude", "cursor"]).default("opencode"),
1027
+ /**
1028
+ * Enable OpenCode platform support
1029
+ * Default: true (OpenCode is the primary focus)
1030
+ */
1031
+ opencode: z.boolean().default(true),
1032
+ /**
1033
+ * Enable Claude Code platform support
1034
+ * Default: false (archived, can be re-enabled later)
1035
+ */
1036
+ claude: z.boolean().default(false),
1037
+ /**
1038
+ * Enable Cursor platform support
1039
+ * Default: false (opt-in)
1040
+ */
1041
+ cursor: z.boolean().default(false)
1042
+ }).default({});
243
1043
  ConfigSchema = z.object({
244
1044
  version: z.string(),
1045
+ /**
1046
+ * Platform configuration - controls which platforms are active
1047
+ */
1048
+ platform: PlatformConfigSchema,
245
1049
  skills: z.object({
246
1050
  enabled: z.boolean().default(true),
247
1051
  directory: z.string().optional()
@@ -273,6 +1077,11 @@ var init_config = __esm({
273
1077
  specFile: z.string().default("spec.md"),
274
1078
  reviewFile: z.string().default("review.md")
275
1079
  }).default({}),
1080
+ database: z.object({
1081
+ enabled: z.boolean().default(true),
1082
+ path: z.string().optional()
1083
+ // If not provided, will use default path
1084
+ }).default({}),
276
1085
  mcp: z.object({
277
1086
  context7: z.boolean().default(false),
278
1087
  githubGrep: z.boolean().default(false),
@@ -312,12 +1121,46 @@ var init_config = __esm({
312
1121
  get antiHallucination() {
313
1122
  return this.config.antiHallucination;
314
1123
  }
1124
+ get database() {
1125
+ return this.config.database;
1126
+ }
315
1127
  get mode() {
316
1128
  return this.config.mode;
317
1129
  }
1130
+ get platform() {
1131
+ return this.config.platform;
1132
+ }
318
1133
  get configPath() {
319
1134
  return this.config.configPath;
320
1135
  }
1136
+ /**
1137
+ * Check if a specific platform is enabled
1138
+ */
1139
+ isPlatformEnabled(platform) {
1140
+ return this.config.platform[platform] ?? false;
1141
+ }
1142
+ /**
1143
+ * Get the primary platform
1144
+ */
1145
+ getPrimaryPlatform() {
1146
+ return this.config.platform.primary;
1147
+ }
1148
+ /**
1149
+ * Get list of all enabled platforms
1150
+ */
1151
+ getEnabledPlatforms() {
1152
+ const platforms = [];
1153
+ if (this.config.platform.opencode) {
1154
+ platforms.push("opencode");
1155
+ }
1156
+ if (this.config.platform.claude) {
1157
+ platforms.push("claude");
1158
+ }
1159
+ if (this.config.platform.cursor) {
1160
+ platforms.push("cursor");
1161
+ }
1162
+ return platforms;
1163
+ }
321
1164
  get projectPath() {
322
1165
  return this.config.projectPath;
323
1166
  }
@@ -325,49 +1168,20 @@ var init_config = __esm({
325
1168
  * Get path to a specific resource
326
1169
  */
327
1170
  getPath(resource) {
1171
+ if (resource === "database") {
1172
+ return this.config.database.path || join3(this.configPath, "figma.db");
1173
+ }
328
1174
  return paths[resource](this.configPath);
329
1175
  }
330
- };
331
- }
332
- });
333
-
334
- // src/utils/logger.ts
335
- import chalk from "chalk";
336
- var logger;
337
- var init_logger = __esm({
338
- "src/utils/logger.ts"() {
339
- "use strict";
340
- init_esm_shims();
341
- logger = {
342
- info(...args) {
343
- console.log(chalk.blue("\u2139"), ...args);
344
- },
345
- success(...args) {
346
- console.log(chalk.green("\u2713"), ...args);
347
- },
348
- warn(...args) {
349
- console.log(chalk.yellow("\u26A0"), ...args);
350
- },
351
- error(...args) {
352
- console.error(chalk.red("\u2716"), ...args);
353
- },
354
- debug(...args) {
355
- if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
356
- console.log(chalk.gray("\u22EF"), ...args);
357
- }
358
- },
359
- step(step, total, message) {
360
- console.log(chalk.cyan(`[${step}/${total}]`), message);
361
- },
362
- header(message) {
363
- console.log(chalk.bold.underline(`
364
- ${message}
365
- `));
366
- },
367
- list(items, prefix = "\u2022") {
368
- for (const item of items) {
369
- console.log(` ${prefix} ${item}`);
1176
+ /**
1177
+ * Get database instance
1178
+ */
1179
+ getDatabase() {
1180
+ if (this.config.database?.enabled === false) {
1181
+ throw new Error("Database is disabled in configuration");
370
1182
  }
1183
+ const dbPath = this.getPath("database");
1184
+ return new FigmaDatabase(dbPath);
371
1185
  }
372
1186
  };
373
1187
  }
@@ -605,8 +1419,10 @@ var init_figma_mcp = __esm({
605
1419
  init_logger();
606
1420
  FigmaMcpClient = class {
607
1421
  apiKey;
608
- constructor(apiKey, _configManager) {
1422
+ database;
1423
+ constructor(apiKey, _configManager, database) {
609
1424
  this.apiKey = apiKey;
1425
+ this.database = database;
610
1426
  }
611
1427
  /**
612
1428
  * Fetch helper with simple retry/backoff for 429/5xx
@@ -689,14 +1505,22 @@ ${text}`);
689
1505
  return data;
690
1506
  }
691
1507
  /**
692
- * Extract design tokens from Figma file
1508
+ * Extract design tokens from Figma file and optionally persist to database
693
1509
  */
694
1510
  async extractDesignTokens(url, downloadAssets = true, assetsDir) {
695
- const fileData = await this.getFileData(url);
696
1511
  const fileKey = this.extractFileKey(url);
697
1512
  if (!fileKey) {
698
1513
  throw new Error(`Invalid Figma URL: ${url}`);
699
1514
  }
1515
+ if (this.database) {
1516
+ const cachedData = await this.getFromDatabase(fileKey);
1517
+ if (cachedData) {
1518
+ logger.info(`\u26A1 Using cached data from database (analyzed ${this.getTimeAgo(cachedData.last_analyzed)})`);
1519
+ return cachedData.tokens;
1520
+ }
1521
+ }
1522
+ logger.info("\u23F3 Fetching fresh data from Figma API...");
1523
+ const fileData = await this.getFileData(url);
700
1524
  const tokens = {
701
1525
  colors: [],
702
1526
  typography: [],
@@ -741,8 +1565,227 @@ ${text}`);
741
1565
  logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
742
1566
  }
743
1567
  }
1568
+ if (this.database) {
1569
+ await this.persistToDatabase(url, fileKey, tokens);
1570
+ }
744
1571
  return tokens;
745
1572
  }
1573
+ /**
1574
+ * Persist extracted tokens to database
1575
+ */
1576
+ async persistToDatabase(url, fileKey, tokens) {
1577
+ if (!this.database) return;
1578
+ try {
1579
+ logger.info("Persisting Figma data to database...");
1580
+ const fileId = fileKey;
1581
+ await this.database.upsertFile({
1582
+ id: fileId,
1583
+ url,
1584
+ name: url.split("/").pop() || "Figma Design",
1585
+ file_key: fileKey,
1586
+ last_analyzed: /* @__PURE__ */ new Date()
1587
+ });
1588
+ for (const screen of tokens.screens) {
1589
+ await this.database.upsertScreen({
1590
+ id: screen.id,
1591
+ file_id: fileId,
1592
+ name: screen.name,
1593
+ width: screen.width,
1594
+ height: screen.height,
1595
+ type: screen.type,
1596
+ children_count: screen.childrenCount
1597
+ });
1598
+ }
1599
+ if (tokens.structure?.nodes) {
1600
+ for (const node of tokens.structure.nodes) {
1601
+ await this.database.upsertNode({
1602
+ id: node.id,
1603
+ file_id: fileId,
1604
+ screen_id: this.findScreenIdForNode(node.id, tokens.screens),
1605
+ parent_id: this.findParentId(node.id, tokens.structure.nodes),
1606
+ name: node.name,
1607
+ type: node.type,
1608
+ content: node.content,
1609
+ position_x: node.position?.x,
1610
+ position_y: node.position?.y,
1611
+ width: node.position?.width,
1612
+ height: node.position?.height,
1613
+ styles: node.styles ? JSON.stringify(node.styles) : void 0,
1614
+ children_ids: node.children ? JSON.stringify(node.children) : void 0
1615
+ });
1616
+ }
1617
+ }
1618
+ for (const color of tokens.colors) {
1619
+ await this.database.upsertDesignToken({
1620
+ file_id: fileId,
1621
+ type: "color",
1622
+ name: color.name,
1623
+ value: color.hex,
1624
+ category: "color"
1625
+ });
1626
+ }
1627
+ for (const typo of tokens.typography) {
1628
+ await this.database.upsertDesignToken({
1629
+ file_id: fileId,
1630
+ type: "typography",
1631
+ name: typo.name,
1632
+ value: JSON.stringify({
1633
+ fontFamily: typo.fontFamily,
1634
+ fontSize: typo.fontSize,
1635
+ fontWeight: typo.fontWeight,
1636
+ lineHeight: typo.lineHeight
1637
+ }),
1638
+ category: "typography"
1639
+ });
1640
+ }
1641
+ for (const component of tokens.components) {
1642
+ await this.database.upsertDesignToken({
1643
+ file_id: fileId,
1644
+ type: "component",
1645
+ name: component.name,
1646
+ value: component.type,
1647
+ category: component.description
1648
+ });
1649
+ }
1650
+ if (tokens.assets) {
1651
+ for (const asset of tokens.assets) {
1652
+ await this.database.upsertAsset({
1653
+ file_id: fileId,
1654
+ node_id: asset.nodeId,
1655
+ node_name: asset.nodeName,
1656
+ node_type: asset.nodeType,
1657
+ format: asset.format,
1658
+ file_path: asset.path,
1659
+ url: asset.url,
1660
+ width: asset.width,
1661
+ height: asset.height
1662
+ });
1663
+ }
1664
+ }
1665
+ logger.info("Successfully persisted Figma data to database");
1666
+ } catch (error) {
1667
+ logger.error(`Failed to persist to database: ${error instanceof Error ? error.message : String(error)}`);
1668
+ }
1669
+ }
1670
+ /**
1671
+ * Get cached data from database
1672
+ */
1673
+ async getFromDatabase(fileKey) {
1674
+ if (!this.database) return null;
1675
+ try {
1676
+ const file = await this.database.getFile(fileKey);
1677
+ if (!file || !file.last_analyzed) return null;
1678
+ const cacheAge = Date.now() - file.last_analyzed.getTime();
1679
+ const maxAge = 24 * 60 * 60 * 1e3;
1680
+ if (cacheAge > maxAge) {
1681
+ logger.info("Cache expired, fetching fresh data...");
1682
+ return null;
1683
+ }
1684
+ const screens = await this.database.getScreensByFile(fileKey);
1685
+ const nodes = await this.database.getNodesByFile(fileKey);
1686
+ const tokenRecords = await this.database.getDesignTokensByFile(fileKey);
1687
+ const assets = await this.database.getAssetsByFile(fileKey);
1688
+ const tokens = {
1689
+ colors: tokenRecords.filter((t) => t.type === "color").map((t) => ({ name: t.name, hex: t.value, rgba: t.value })),
1690
+ typography: tokenRecords.filter((t) => t.type === "typography").map((t) => {
1691
+ const parsed = JSON.parse(t.value);
1692
+ return {
1693
+ name: t.name,
1694
+ fontFamily: parsed.fontFamily,
1695
+ fontSize: parsed.fontSize,
1696
+ fontWeight: parsed.fontWeight,
1697
+ lineHeight: parsed.lineHeight,
1698
+ letterSpacing: parsed.letterSpacing
1699
+ };
1700
+ }),
1701
+ spacing: {
1702
+ unit: 8,
1703
+ scale: [4, 8, 12, 16, 24, 32, 48, 64]
1704
+ },
1705
+ components: tokenRecords.filter((t) => t.type === "component").map((t) => ({ name: t.name, type: t.value, description: t.category })),
1706
+ screens: screens.map((s) => ({
1707
+ id: s.id,
1708
+ name: s.name,
1709
+ width: s.width || 0,
1710
+ height: s.height || 0,
1711
+ type: s.type || "FRAME",
1712
+ description: s.description,
1713
+ childrenCount: s.children_count
1714
+ })),
1715
+ breakpoints: [375, 768, 1024, 1280, 1920],
1716
+ structure: {
1717
+ nodes: nodes.map((n) => ({
1718
+ id: n.id,
1719
+ name: n.name,
1720
+ type: n.type,
1721
+ content: n.content,
1722
+ position: n.position_x !== void 0 ? {
1723
+ x: n.position_x,
1724
+ y: n.position_y || 0,
1725
+ width: n.width || 0,
1726
+ height: n.height || 0
1727
+ } : void 0,
1728
+ styles: n.styles ? JSON.parse(n.styles) : void 0,
1729
+ children: n.children_ids ? JSON.parse(n.children_ids) : void 0
1730
+ })),
1731
+ hierarchy: this.buildHierarchy(nodes)
1732
+ },
1733
+ assets: assets.map((a) => ({
1734
+ nodeId: a.node_id,
1735
+ nodeName: a.node_name || "",
1736
+ nodeType: a.node_type || "",
1737
+ format: a.format || "png",
1738
+ path: a.file_path || "",
1739
+ url: a.url || "",
1740
+ width: a.width,
1741
+ height: a.height
1742
+ }))
1743
+ };
1744
+ return { tokens, last_analyzed: file.last_analyzed };
1745
+ } catch (error) {
1746
+ logger.warn(`Failed to retrieve from database: ${error instanceof Error ? error.message : String(error)}`);
1747
+ return null;
1748
+ }
1749
+ }
1750
+ /**
1751
+ * Build hierarchy string from nodes
1752
+ */
1753
+ buildHierarchy(nodes) {
1754
+ return nodes.map((n) => `${n.type} "${n.name}" [${n.id}]`).join("\n");
1755
+ }
1756
+ /**
1757
+ * Get human-readable time ago
1758
+ */
1759
+ getTimeAgo(date) {
1760
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
1761
+ if (seconds < 60) return `${seconds} seconds ago`;
1762
+ const minutes = Math.floor(seconds / 60);
1763
+ if (minutes < 60) return `${minutes} minutes ago`;
1764
+ const hours = Math.floor(minutes / 60);
1765
+ if (hours < 24) return `${hours} hours ago`;
1766
+ const days = Math.floor(hours / 24);
1767
+ return `${days} days ago`;
1768
+ }
1769
+ /**
1770
+ * Helper to find which screen a node belongs to
1771
+ */
1772
+ findScreenIdForNode(nodeId, screens) {
1773
+ if (screens.find((s) => s.id === nodeId)) {
1774
+ return nodeId;
1775
+ }
1776
+ return void 0;
1777
+ }
1778
+ /**
1779
+ * Helper to find parent node ID
1780
+ */
1781
+ findParentId(nodeId, nodes) {
1782
+ for (const node of nodes) {
1783
+ if (node.children?.includes(nodeId)) {
1784
+ return node.id;
1785
+ }
1786
+ }
1787
+ return void 0;
1788
+ }
746
1789
  /**
747
1790
  * Recursively extract colors from nodes
748
1791
  */
@@ -1013,11 +2056,66 @@ ${text}`);
1013
2056
  var figma_screen_developer_exports = {};
1014
2057
  __export(figma_screen_developer_exports, {
1015
2058
  checkCurrentCodeStatus: () => checkCurrentCodeStatus,
1016
- compareCodeWithFigma: () => compareCodeWithFigma
2059
+ compareCodeWithFigma: () => compareCodeWithFigma,
2060
+ getCachedFigmaData: () => getCachedFigmaData
1017
2061
  });
1018
2062
  import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
1019
2063
  import { join as join8 } from "path";
1020
2064
  import { existsSync as existsSync3 } from "fs";
2065
+ async function getCachedFigmaData(url, database) {
2066
+ if (!database) return null;
2067
+ try {
2068
+ const file = await database.getFileByUrl(url);
2069
+ if (!file) return null;
2070
+ const screens = await database.getScreensByFile(file.id);
2071
+ const nodes = await database.getNodesByFile(file.id);
2072
+ const tokens = await database.getDesignTokensByFile(file.id);
2073
+ const assets = await database.getAssetsByFile(file.id);
2074
+ return {
2075
+ screens: screens.map((s) => ({
2076
+ id: s.id,
2077
+ name: s.name,
2078
+ width: s.width,
2079
+ height: s.height,
2080
+ type: s.type,
2081
+ childrenCount: s.children_count
2082
+ })),
2083
+ nodes: nodes.map((n) => ({
2084
+ id: n.id,
2085
+ name: n.name,
2086
+ type: n.type,
2087
+ content: n.content,
2088
+ position: n.position_x ? {
2089
+ x: n.position_x,
2090
+ y: n.position_y,
2091
+ width: n.width,
2092
+ height: n.height
2093
+ } : void 0,
2094
+ styles: n.styles ? JSON.parse(n.styles) : void 0,
2095
+ children: n.children_ids ? JSON.parse(n.children_ids) : void 0
2096
+ })),
2097
+ tokens: tokens.map((t) => ({
2098
+ type: t.type,
2099
+ name: t.name,
2100
+ value: t.value,
2101
+ category: t.category
2102
+ })),
2103
+ assets: assets.map((a) => ({
2104
+ nodeId: a.node_id,
2105
+ nodeName: a.node_name,
2106
+ nodeType: a.node_type,
2107
+ format: a.format,
2108
+ path: a.file_path,
2109
+ url: a.url,
2110
+ width: a.width,
2111
+ height: a.height
2112
+ }))
2113
+ };
2114
+ } catch (error) {
2115
+ logger.warn(`Failed to get cached Figma data: ${error instanceof Error ? error.message : String(error)}`);
2116
+ return null;
2117
+ }
2118
+ }
1021
2119
  async function checkCurrentCodeStatus(projectPath = process.cwd()) {
1022
2120
  const status = {
1023
2121
  hasHTML: false,
@@ -1077,7 +2175,12 @@ async function checkCurrentCodeStatus(projectPath = process.cwd()) {
1077
2175
  }
1078
2176
  return status;
1079
2177
  }
1080
- async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd()) {
2178
+ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd(), database, figmaUrl) {
2179
+ let cachedData;
2180
+ if (database && figmaUrl) {
2181
+ cachedData = await getCachedFigmaData(figmaUrl, database);
2182
+ }
2183
+ const dataToUse = cachedData || figmaTokens;
1081
2184
  const codeStatus = await checkCurrentCodeStatus(projectPath);
1082
2185
  const result = {
1083
2186
  missingSections: [],
@@ -1085,17 +2188,18 @@ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath =
1085
2188
  needsUpdate: false,
1086
2189
  recommendations: []
1087
2190
  };
1088
- const selectedScreen = figmaTokens.screens?.find((s) => s.id === selectedScreenId);
2191
+ const selectedScreen = dataToUse.screens?.find((s) => s.id === selectedScreenId);
1089
2192
  if (!selectedScreen) {
1090
2193
  result.recommendations.push("Selected screen not found in Figma design");
1091
2194
  return result;
1092
2195
  }
1093
2196
  const figmaSections = [];
1094
- if (figmaTokens.structure?.nodes) {
1095
- const screenNode = figmaTokens.structure.nodes.find((n) => n.id === selectedScreenId);
2197
+ if (dataToUse.structure?.nodes || dataToUse.nodes) {
2198
+ const nodes = dataToUse.structure?.nodes || dataToUse.nodes;
2199
+ const screenNode = nodes.find((n) => n.id === selectedScreenId);
1096
2200
  if (screenNode?.children) {
1097
2201
  screenNode.children.forEach((childId) => {
1098
- const childNode = figmaTokens.structure.nodes.find((n) => n.id === childId);
2202
+ const childNode = nodes.find((n) => n.id === childId);
1099
2203
  if (childNode && (childNode.type === "FRAME" || childNode.type === "COMPONENT")) {
1100
2204
  const sectionName = childNode.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-");
1101
2205
  figmaSections.push(sectionName);
@@ -4794,7 +5898,15 @@ This will guide you through setting up your Figma Personal Access Token.`;
4794
5898
  }
4795
5899
  try {
4796
5900
  const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
4797
- const client = new FigmaMcpClient2(apiKey, configManager);
5901
+ let database;
5902
+ try {
5903
+ if (context?.config) {
5904
+ database = context.config.getDatabase();
5905
+ }
5906
+ } catch (error) {
5907
+ logger.info("Database not available for Figma persistence");
5908
+ }
5909
+ const client = new FigmaMcpClient2(apiKey, configManager, database);
4798
5910
  const assetsDir = "./assets/images";
4799
5911
  const tokens = await client.extractDesignTokens(url, false, assetsDir);
4800
5912
  let result = `# Figma Design Analysis
@@ -5025,25 +6137,6 @@ Please check:
5025
6137
  }
5026
6138
  }
5027
6139
  },
5028
- {
5029
- name: "analyze_figma",
5030
- description: "Analyze a Figma design URL and extract all design tokens automatically. The URL should be provided in the user input after the command.",
5031
- args: {
5032
- url: {
5033
- type: "string",
5034
- description: "Figma design URL to analyze",
5035
- required: true
5036
- }
5037
- },
5038
- async execute({ url }) {
5039
- return `Figma analysis tool called for: ${url}
5040
-
5041
- Next steps:
5042
- 1. Use @vision agent to analyze the design
5043
- 2. Extract all design tokens
5044
- 3. Save to memory/research/figma-analysis.md`;
5045
- }
5046
- },
5047
6140
  {
5048
6141
  name: "develop_figma_screen",
5049
6142
  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.",
@@ -5075,7 +6168,15 @@ Next steps:
5075
6168
  try {
5076
6169
  const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
5077
6170
  const { checkCurrentCodeStatus: checkCurrentCodeStatus2, compareCodeWithFigma: compareCodeWithFigma2 } = await Promise.resolve().then(() => (init_figma_screen_developer(), figma_screen_developer_exports));
5078
- const client = new FigmaMcpClient2(apiKey, configManager);
6171
+ let database;
6172
+ try {
6173
+ if (context?.config) {
6174
+ database = context.config.getDatabase();
6175
+ }
6176
+ } catch (error) {
6177
+ logger.info("Database not available for Figma persistence");
6178
+ }
6179
+ const client = new FigmaMcpClient2(apiKey, configManager, database);
5079
6180
  const tokens = await client.extractDesignTokens(figmaUrl, false, "./assets/images");
5080
6181
  const screenIdStr = String(screenId);
5081
6182
  const selectedScreen = tokens.screens?.find(
@@ -5636,12 +6737,226 @@ ${argsDesc}
5636
6737
  }
5637
6738
  };
5638
6739
 
6740
+ // src/core/tool-config.ts
6741
+ init_esm_shims();
6742
+ init_logger();
6743
+ import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir7, access as access4, constants as constants4 } from "fs/promises";
6744
+ import { join as join11 } from "path";
6745
+ import { homedir as homedir2 } from "os";
6746
+ import { z as z3 } from "zod";
6747
+ var ToolConfigSchema = z3.object({
6748
+ name: z3.string(),
6749
+ status: z3.enum(["ready", "needs_config", "error"]),
6750
+ description: z3.string(),
6751
+ configMethod: z3.enum(["oauth", "manual", "none"]),
6752
+ config: z3.record(z3.unknown()).optional(),
6753
+ errorMessage: z3.string().optional()
6754
+ });
6755
+ var REGISTERED_TOOLS = [
6756
+ {
6757
+ name: "figma-analysis",
6758
+ description: "Analyze Figma designs and extract design tokens using Figma API",
6759
+ configMethod: "oauth"
6760
+ }
6761
+ // Add more tools here as needed
6762
+ ];
6763
+ var ToolConfigManager = class {
6764
+ config;
6765
+ toolsConfigPath;
6766
+ constructor(config) {
6767
+ this.config = config;
6768
+ this.toolsConfigPath = join11(this.config.configPath, "config", "tools.json");
6769
+ }
6770
+ /**
6771
+ * Get all registered tools with their current status
6772
+ */
6773
+ async listTools() {
6774
+ const savedConfigs = await this.loadConfigs();
6775
+ const tools = [];
6776
+ for (const tool of REGISTERED_TOOLS) {
6777
+ const saved = savedConfigs[tool.name];
6778
+ const toolConfig = {
6779
+ ...tool,
6780
+ status: this.determineStatus(tool, saved),
6781
+ config: saved?.config,
6782
+ errorMessage: saved?.errorMessage
6783
+ };
6784
+ tools.push(toolConfig);
6785
+ }
6786
+ return tools;
6787
+ }
6788
+ /**
6789
+ * Get configuration for a specific tool
6790
+ */
6791
+ async getToolConfig(toolName) {
6792
+ const tools = await this.listTools();
6793
+ return tools.find((t) => t.name === toolName) || null;
6794
+ }
6795
+ /**
6796
+ * Update tool configuration
6797
+ */
6798
+ async updateToolConfig(toolName, updates) {
6799
+ const savedConfigs = await this.loadConfigs();
6800
+ const tool = REGISTERED_TOOLS.find((t) => t.name === toolName);
6801
+ if (!tool) {
6802
+ throw new Error(`Tool not found: ${toolName}`);
6803
+ }
6804
+ const existing = savedConfigs[toolName] || {};
6805
+ savedConfigs[toolName] = {
6806
+ ...existing,
6807
+ ...updates
6808
+ };
6809
+ await this.saveConfigs(savedConfigs);
6810
+ }
6811
+ /**
6812
+ * Check if a tool is ready to use
6813
+ */
6814
+ async isToolReady(toolName) {
6815
+ const toolConfig = await this.getToolConfig(toolName);
6816
+ const isReady = toolConfig?.status === "ready";
6817
+ logger.info(`\u{1F50D} Tool ready check for ${toolName}:`, {
6818
+ hasConfig: !!toolConfig,
6819
+ status: toolConfig?.status,
6820
+ isReady
6821
+ });
6822
+ return isReady;
6823
+ }
6824
+ /**
6825
+ * Get API key for a tool (if configured)
6826
+ */
6827
+ async getApiKey(toolName) {
6828
+ const toolConfig = await this.getToolConfig(toolName);
6829
+ logger.info(`\u{1F50D} Getting API key for ${toolName}:`, {
6830
+ hasConfig: !!toolConfig,
6831
+ hasApiKey: !!toolConfig?.config?.apiKey,
6832
+ status: toolConfig?.status,
6833
+ apiKeyLength: typeof toolConfig?.config?.apiKey === "string" ? toolConfig.config.apiKey.length : 0
6834
+ });
6835
+ if (toolConfig?.config?.apiKey && typeof toolConfig.config.apiKey === "string") {
6836
+ return toolConfig.config.apiKey;
6837
+ }
6838
+ return null;
6839
+ }
6840
+ /**
6841
+ * Determine tool status based on configuration
6842
+ */
6843
+ determineStatus(tool, saved) {
6844
+ if (tool.configMethod === "none") {
6845
+ logger.info(`\u{1F50D} Tool ${tool.name} uses 'none' config method - marking as ready`);
6846
+ return "ready";
6847
+ }
6848
+ if (saved?.errorMessage) {
6849
+ logger.warn(`\u{1F50D} Tool ${tool.name} has error: ${saved.errorMessage}`);
6850
+ return "error";
6851
+ }
6852
+ if (tool.configMethod === "oauth" || tool.configMethod === "manual") {
6853
+ const hasApiKey = saved?.config?.apiKey && typeof saved.config.apiKey === "string" && saved.config.apiKey.length > 0;
6854
+ logger.info(`\u{1F50D} Tool ${tool.name} status check:`, {
6855
+ configMethod: tool.configMethod,
6856
+ hasSaved: !!saved,
6857
+ hasConfig: !!saved?.config,
6858
+ hasApiKey,
6859
+ apiKeyLength: typeof saved?.config?.apiKey === "string" ? saved.config.apiKey.length : 0
6860
+ });
6861
+ if (hasApiKey) {
6862
+ return "ready";
6863
+ }
6864
+ return "needs_config";
6865
+ }
6866
+ logger.warn(`\u{1F50D} Tool ${tool.name} fell through to default 'needs_config'`);
6867
+ return "needs_config";
6868
+ }
6869
+ /**
6870
+ * Load saved configurations
6871
+ * Checks both global and project configs, project takes precedence
6872
+ */
6873
+ async loadConfigs() {
6874
+ const globalConfigPath = join11(homedir2(), ".config", "aikit", "config", "tools.json");
6875
+ let configs = {};
6876
+ try {
6877
+ await access4(globalConfigPath, constants4.R_OK);
6878
+ const content = await readFile7(globalConfigPath, "utf-8");
6879
+ configs = JSON.parse(content);
6880
+ logger.info("Loaded global tool configs");
6881
+ } catch {
6882
+ }
6883
+ try {
6884
+ await access4(this.toolsConfigPath, constants4.R_OK);
6885
+ const content = await readFile7(this.toolsConfigPath, "utf-8");
6886
+ const projectConfigs = JSON.parse(content);
6887
+ configs = { ...configs, ...projectConfigs };
6888
+ logger.info("Loaded project tool configs");
6889
+ } catch {
6890
+ }
6891
+ return configs;
6892
+ }
6893
+ /**
6894
+ * Save configurations
6895
+ */
6896
+ async saveConfigs(configs) {
6897
+ const configDir = join11(this.config.configPath, "config");
6898
+ await mkdir7(configDir, { recursive: true });
6899
+ await writeFile7(this.toolsConfigPath, JSON.stringify(configs, null, 2));
6900
+ }
6901
+ /**
6902
+ * Configure Claude Desktop MCP server for a tool
6903
+ * This adds the MCP server configuration to Claude Desktop's config file
6904
+ */
6905
+ async configureMcpServer(toolName, apiKey) {
6906
+ if (toolName !== "figma-analysis") {
6907
+ logger.info(`MCP server configuration not implemented for tool: ${toolName}`);
6908
+ return;
6909
+ }
6910
+ const isWindows = process.platform === "win32";
6911
+ const claudeConfigBase = isWindows ? process.env.APPDATA || join11(homedir2(), "AppData", "Roaming") : join11(homedir2(), ".config");
6912
+ const claudeConfigPath = join11(claudeConfigBase, "claude", "claude_desktop_config.json");
6913
+ try {
6914
+ let claudeConfig = {};
6915
+ try {
6916
+ const content = await readFile7(claudeConfigPath, "utf-8");
6917
+ claudeConfig = JSON.parse(content);
6918
+ } catch {
6919
+ claudeConfig = {};
6920
+ }
6921
+ if (!claudeConfig.mcpServers) {
6922
+ claudeConfig.mcpServers = {};
6923
+ }
6924
+ claudeConfig.mcpServers.figma = {
6925
+ command: "npx",
6926
+ args: ["-y", "figma-developer-mcp", `--figma-oauth-token=${apiKey}`, "--stdio"]
6927
+ };
6928
+ const claudeConfigDir = join11(claudeConfigBase, "claude");
6929
+ await mkdir7(claudeConfigDir, { recursive: true });
6930
+ await writeFile7(claudeConfigPath, JSON.stringify(claudeConfig, null, 2));
6931
+ logger.success("\u2705 Claude Desktop MCP server configured");
6932
+ logger.info(` Config file: ${claudeConfigPath}`);
6933
+ logger.info("");
6934
+ logger.info("\u26A0\uFE0F IMPORTANT: Restart Claude Desktop for changes to take effect");
6935
+ } catch (error) {
6936
+ logger.warn(`Could not configure MCP server automatically: ${error instanceof Error ? error.message : String(error)}`);
6937
+ logger.info("");
6938
+ logger.info("Please configure manually:");
6939
+ logger.info(`1. Edit: ${claudeConfigPath}`);
6940
+ logger.info('2. Add the following to "mcpServers":');
6941
+ logger.info(JSON.stringify({
6942
+ figma: {
6943
+ command: "npx",
6944
+ args: ["-y", "figma-developer-mcp"],
6945
+ env: {
6946
+ FIGMA_OAUTH_TOKEN: apiKey
6947
+ }
6948
+ }
6949
+ }, null, 2));
6950
+ }
6951
+ }
6952
+ };
6953
+
5639
6954
  // src/core/plugins.ts
5640
6955
  init_esm_shims();
5641
6956
  init_paths();
5642
6957
  init_logger();
5643
- import { readdir as readdir6, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
5644
- import { join as join11, basename as basename3, extname as extname4 } from "path";
6958
+ import { readdir as readdir6, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
6959
+ import { join as join12, basename as basename3, extname as extname4 } from "path";
5645
6960
  var PluginSystem = class {
5646
6961
  config;
5647
6962
  plugins = /* @__PURE__ */ new Map();
@@ -5739,9 +7054,9 @@ var PluginSystem = class {
5739
7054
  async createPlugin(name, options) {
5740
7055
  const configPath = options.global ? paths.globalConfig() : this.config.configPath;
5741
7056
  const pluginsDir = paths.plugins(configPath);
5742
- await mkdir7(pluginsDir, { recursive: true });
7057
+ await mkdir8(pluginsDir, { recursive: true });
5743
7058
  const fileName = `${name}.ts`;
5744
- const filePath = join11(pluginsDir, fileName);
7059
+ const filePath = join12(pluginsDir, fileName);
5745
7060
  const content = `import { Plugin } from 'aikit';
5746
7061
 
5747
7062
  /**
@@ -5757,7 +7072,7 @@ ${options.code}
5757
7072
 
5758
7073
  export default ${toPascalCase(name)}Plugin;
5759
7074
  `;
5760
- await writeFile7(filePath, content);
7075
+ await writeFile8(filePath, content);
5761
7076
  }
5762
7077
  /**
5763
7078
  * Process event queue
@@ -5831,7 +7146,7 @@ export default ${toPascalCase(name)}Plugin;
5831
7146
  }
5832
7147
  for (const file of files) {
5833
7148
  if (extname4(file) !== ".ts" && extname4(file) !== ".js") continue;
5834
- const filePath = join11(dir, file);
7149
+ const filePath = join12(dir, file);
5835
7150
  const name = basename3(file, extname4(file));
5836
7151
  this.plugins.set(name, {
5837
7152
  name,
@@ -5937,8 +7252,8 @@ init_beads();
5937
7252
  // src/core/anti-hallucination.ts
5938
7253
  init_esm_shims();
5939
7254
  init_logger();
5940
- import { readFile as readFile7, writeFile as writeFile8, readdir as readdir7, access as access4, constants as constants4 } from "fs/promises";
5941
- import { join as join12 } from "path";
7255
+ import { readFile as readFile8, writeFile as writeFile9, readdir as readdir7, access as access5, constants as constants5 } from "fs/promises";
7256
+ import { join as join13 } from "path";
5942
7257
  var AntiHallucination = class {
5943
7258
  config;
5944
7259
  constructor(config) {
@@ -5948,9 +7263,9 @@ var AntiHallucination = class {
5948
7263
  * Layer 1: Validate task exists before work begins
5949
7264
  */
5950
7265
  async validateTask(taskId) {
5951
- const beadsDir = join12(this.config.projectPath, ".beads");
7266
+ const beadsDir = join13(this.config.projectPath, ".beads");
5952
7267
  try {
5953
- await access4(beadsDir, constants4.R_OK);
7268
+ await access5(beadsDir, constants5.R_OK);
5954
7269
  } catch {
5955
7270
  return {
5956
7271
  valid: false,
@@ -5967,7 +7282,7 @@ var AntiHallucination = class {
5967
7282
  };
5968
7283
  }
5969
7284
  for (const file of beadFiles) {
5970
- const content = await readFile7(join12(beadsDir, file), "utf-8");
7285
+ const content = await readFile8(join13(beadsDir, file), "utf-8");
5971
7286
  if (content.includes("status: in-progress") || content.includes("Status: In Progress")) {
5972
7287
  const id = file.replace(".md", "");
5973
7288
  const descMatch = content.match(/description:\s*(.+)/i) || content.match(/# (.+)/);
@@ -5986,9 +7301,9 @@ var AntiHallucination = class {
5986
7301
  error: "No active task found. Start a task with /create or /implement."
5987
7302
  };
5988
7303
  }
5989
- const taskFile = join12(beadsDir, `${taskId}.md`);
7304
+ const taskFile = join13(beadsDir, `${taskId}.md`);
5990
7305
  try {
5991
- const content = await readFile7(taskFile, "utf-8");
7306
+ const content = await readFile8(taskFile, "utf-8");
5992
7307
  const statusMatch = content.match(/status:\s*(\w+)/i);
5993
7308
  const descMatch = content.match(/description:\s*(.+)/i) || content.match(/# (.+)/);
5994
7309
  return {
@@ -6011,9 +7326,9 @@ var AntiHallucination = class {
6011
7326
  */
6012
7327
  async checkSpec() {
6013
7328
  const specFile = this.config.antiHallucination.specFile;
6014
- const specPath = join12(this.config.projectPath, specFile);
7329
+ const specPath = join13(this.config.projectPath, specFile);
6015
7330
  try {
6016
- const content = await readFile7(specPath, "utf-8");
7331
+ const content = await readFile8(specPath, "utf-8");
6017
7332
  const constraints = {
6018
7333
  naming: this.extractConstraints(content, "Naming"),
6019
7334
  forbidden: this.extractConstraints(content, "Forbidden"),
@@ -6051,7 +7366,7 @@ var AntiHallucination = class {
6051
7366
  */
6052
7367
  async createReview(changes) {
6053
7368
  const reviewFile = this.config.antiHallucination.reviewFile;
6054
- const reviewPath = join12(this.config.projectPath, reviewFile);
7369
+ const reviewPath = join13(this.config.projectPath, reviewFile);
6055
7370
  const content = `# Code Review
6056
7371
 
6057
7372
  _Generated: ${(/* @__PURE__ */ new Date()).toISOString()}_
@@ -6080,7 +7395,7 @@ ${changes.inconsistencies?.map((i) => `- \u26A0\uFE0F ${i}`).join("\n") || "- No
6080
7395
  - [ ] Build succeeds
6081
7396
  - [ ] Manual testing completed
6082
7397
  `;
6083
- await writeFile8(reviewPath, content);
7398
+ await writeFile9(reviewPath, content);
6084
7399
  return reviewPath;
6085
7400
  }
6086
7401
  /**
@@ -6118,7 +7433,7 @@ ${changes.inconsistencies?.map((i) => `- \u26A0\uFE0F ${i}`).join("\n") || "- No
6118
7433
  * Recovery: Check for context loss
6119
7434
  */
6120
7435
  async checkContextLoss() {
6121
- const handoffsDir = join12(this.config.configPath, "memory", "handoffs");
7436
+ const handoffsDir = join13(this.config.configPath, "memory", "handoffs");
6122
7437
  try {
6123
7438
  const files = await readdir7(handoffsDir);
6124
7439
  const handoffs = files.filter((f) => f.endsWith(".md")).sort().reverse();
@@ -6136,9 +7451,9 @@ ${changes.inconsistencies?.map((i) => `- \u26A0\uFE0F ${i}`).join("\n") || "- No
6136
7451
  * Initialize spec.md template
6137
7452
  */
6138
7453
  async initSpec() {
6139
- const specPath = join12(this.config.projectPath, this.config.antiHallucination.specFile);
7454
+ const specPath = join13(this.config.projectPath, this.config.antiHallucination.specFile);
6140
7455
  try {
6141
- await access4(specPath, constants4.R_OK);
7456
+ await access5(specPath, constants5.R_OK);
6142
7457
  logger.info("spec.md already exists");
6143
7458
  return;
6144
7459
  } catch {
@@ -6174,7 +7489,7 @@ Describe your project architecture here.
6174
7489
 
6175
7490
  List approved dependencies here.
6176
7491
  `;
6177
- await writeFile8(specPath, template);
7492
+ await writeFile9(specPath, template);
6178
7493
  logger.success("Created spec.md template");
6179
7494
  }
6180
7495
  /**
@@ -6243,9 +7558,9 @@ async function getLatestVersion(packageName) {
6243
7558
  // src/utils/update-cache.ts
6244
7559
  init_esm_shims();
6245
7560
  init_paths();
6246
- import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir8 } from "fs/promises";
7561
+ import { readFile as readFile9, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
6247
7562
  import { existsSync as existsSync5 } from "fs";
6248
- import { join as join13 } from "path";
7563
+ import { join as join14 } from "path";
6249
7564
  var CACHE_FILE = ".update-cache.json";
6250
7565
  var DEFAULT_CACHE = {
6251
7566
  lastCheckTime: 0,
@@ -6254,7 +7569,7 @@ var DEFAULT_CACHE = {
6254
7569
  };
6255
7570
  function getCachePath() {
6256
7571
  const configDir = paths.globalConfig();
6257
- return join13(configDir, CACHE_FILE);
7572
+ return join14(configDir, CACHE_FILE);
6258
7573
  }
6259
7574
  async function readCache() {
6260
7575
  try {
@@ -6262,7 +7577,7 @@ async function readCache() {
6262
7577
  if (!existsSync5(cachePath)) {
6263
7578
  return DEFAULT_CACHE;
6264
7579
  }
6265
- const content = await readFile8(cachePath, "utf-8");
7580
+ const content = await readFile9(cachePath, "utf-8");
6266
7581
  const data = JSON.parse(content);
6267
7582
  return { ...DEFAULT_CACHE, ...data };
6268
7583
  } catch {
@@ -6274,9 +7589,9 @@ async function writeCache(data) {
6274
7589
  const cachePath = getCachePath();
6275
7590
  const configDir = paths.globalConfig();
6276
7591
  if (!existsSync5(configDir)) {
6277
- await mkdir8(configDir, { recursive: true });
7592
+ await mkdir9(configDir, { recursive: true });
6278
7593
  }
6279
- await writeFile9(cachePath, JSON.stringify(data, null, 2), "utf-8");
7594
+ await writeFile10(cachePath, JSON.stringify(data, null, 2), "utf-8");
6280
7595
  } catch {
6281
7596
  }
6282
7597
  }
@@ -6315,16 +7630,16 @@ async function incrementError(error) {
6315
7630
  init_esm_shims();
6316
7631
  init_paths();
6317
7632
  import { spawn } from "child_process";
6318
- import { writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
6319
- import { join as join14 } from "path";
7633
+ import { writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
7634
+ import { join as join15 } from "path";
6320
7635
  async function spawnUpdateProcess(targetVersion) {
6321
7636
  try {
6322
7637
  const configDir = paths.globalConfig();
6323
- const logsDir = join14(configDir, "logs");
6324
- await mkdir9(logsDir, { recursive: true });
7638
+ const logsDir = join15(configDir, "logs");
7639
+ await mkdir10(logsDir, { recursive: true });
6325
7640
  const scriptContent = generateUpdateScript(targetVersion, logsDir);
6326
- const scriptPath = join14(configDir, "update-script.js");
6327
- await writeFile10(scriptPath, scriptContent, "utf-8");
7641
+ const scriptPath = join15(configDir, "update-script.js");
7642
+ await writeFile11(scriptPath, scriptContent, "utf-8");
6328
7643
  const child = spawn(process.execPath, [scriptPath], {
6329
7644
  detached: true,
6330
7645
  stdio: "ignore",
@@ -6492,6 +7807,7 @@ export {
6492
7807
  MemoryManager,
6493
7808
  PluginSystem,
6494
7809
  SkillEngine,
7810
+ ToolConfigManager,
6495
7811
  ToolRegistry,
6496
7812
  getVersion as VERSION,
6497
7813
  checkForUpdatesAsync,