@tdsoft-tech/aikit 0.1.31 → 0.1.32

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 CHANGED
@@ -182,6 +182,756 @@ var init_version = __esm({
182
182
  }
183
183
  });
184
184
 
185
+ // src/utils/logger.ts
186
+ import chalk from "chalk";
187
+ var logger;
188
+ var init_logger = __esm({
189
+ "src/utils/logger.ts"() {
190
+ "use strict";
191
+ init_esm_shims();
192
+ logger = {
193
+ info(...args) {
194
+ console.log(chalk.blue("\u2139"), ...args);
195
+ },
196
+ success(...args) {
197
+ console.log(chalk.green("\u2713"), ...args);
198
+ },
199
+ warn(...args) {
200
+ console.log(chalk.yellow("\u26A0"), ...args);
201
+ },
202
+ error(...args) {
203
+ console.error(chalk.red("\u2716"), ...args);
204
+ },
205
+ debug(...args) {
206
+ if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
207
+ console.log(chalk.gray("\u22EF"), ...args);
208
+ }
209
+ },
210
+ step(step, total, message) {
211
+ console.log(chalk.cyan(`[${step}/${total}]`), message);
212
+ },
213
+ header(message) {
214
+ console.log(chalk.bold.underline(`
215
+ ${message}
216
+ `));
217
+ },
218
+ list(items, prefix = "\u2022") {
219
+ for (const item of items) {
220
+ console.log(` ${prefix} ${item}`);
221
+ }
222
+ }
223
+ };
224
+ }
225
+ });
226
+
227
+ // src/core/database/schema.ts
228
+ function initializeSchema(db) {
229
+ try {
230
+ logger.info("Initializing Figma database schema...");
231
+ db.pragma("foreign_keys = ON");
232
+ db.pragma("journal_mode = WAL");
233
+ Object.entries(SCHEMA_SQL).forEach(([tableName, sql]) => {
234
+ logger.debug(`Creating table: ${tableName}`);
235
+ db.exec(sql);
236
+ });
237
+ Object.entries(INDEX_SQL).forEach(([indexName, sql]) => {
238
+ logger.debug(`Creating index: ${indexName}`);
239
+ db.exec(sql);
240
+ });
241
+ Object.entries(TRIGGER_SQL).forEach(([triggerName, sql]) => {
242
+ logger.debug(`Creating trigger: ${triggerName}`);
243
+ db.exec(sql);
244
+ });
245
+ const currentVersion = getCurrentSchemaVersion(db);
246
+ if (currentVersion < DATABASE_VERSION) {
247
+ db.prepare("INSERT OR REPLACE INTO schema_version (version) VALUES (?)").run(DATABASE_VERSION);
248
+ logger.info(`Schema updated to version ${DATABASE_VERSION}`);
249
+ }
250
+ logger.info("Database schema initialized successfully");
251
+ } catch (error) {
252
+ logger.error(`Failed to initialize database schema: ${error instanceof Error ? error.message : String(error)}`);
253
+ throw error;
254
+ }
255
+ }
256
+ function getCurrentSchemaVersion(db) {
257
+ try {
258
+ const result = db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
259
+ return result?.version || 0;
260
+ } catch {
261
+ return 0;
262
+ }
263
+ }
264
+ var DATABASE_VERSION, SCHEMA_SQL, INDEX_SQL, TRIGGER_SQL;
265
+ var init_schema = __esm({
266
+ "src/core/database/schema.ts"() {
267
+ "use strict";
268
+ init_esm_shims();
269
+ init_logger();
270
+ DATABASE_VERSION = 1;
271
+ SCHEMA_SQL = {
272
+ // Files table - stores Figma file metadata
273
+ figma_files: `
274
+ CREATE TABLE IF NOT EXISTS figma_files (
275
+ id TEXT PRIMARY KEY,
276
+ url TEXT NOT NULL UNIQUE,
277
+ name TEXT,
278
+ file_key TEXT NOT NULL,
279
+ last_analyzed DATETIME,
280
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
281
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
282
+ )
283
+ `,
284
+ // Screens table - stores screen/frame information
285
+ figma_screens: `
286
+ CREATE TABLE IF NOT EXISTS figma_screens (
287
+ id TEXT PRIMARY KEY,
288
+ file_id TEXT NOT NULL,
289
+ name TEXT NOT NULL,
290
+ width INTEGER,
291
+ height INTEGER,
292
+ type TEXT,
293
+ description TEXT,
294
+ children_count INTEGER,
295
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
296
+ )
297
+ `,
298
+ // Nodes table - stores hierarchical component structure
299
+ figma_nodes: `
300
+ CREATE TABLE IF NOT EXISTS figma_nodes (
301
+ id TEXT PRIMARY KEY,
302
+ file_id TEXT NOT NULL,
303
+ screen_id TEXT,
304
+ parent_id TEXT,
305
+ name TEXT NOT NULL,
306
+ type TEXT NOT NULL,
307
+ content TEXT,
308
+ position_x REAL,
309
+ position_y REAL,
310
+ width REAL,
311
+ height REAL,
312
+ styles TEXT, -- JSON string
313
+ children_ids TEXT, -- JSON string array
314
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE,
315
+ FOREIGN KEY (screen_id) REFERENCES figma_screens(id) ON DELETE CASCADE,
316
+ FOREIGN KEY (parent_id) REFERENCES figma_nodes(id) ON DELETE CASCADE
317
+ )
318
+ `,
319
+ // Design tokens table - stores extracted design tokens
320
+ design_tokens: `
321
+ CREATE TABLE IF NOT EXISTS design_tokens (
322
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
323
+ file_id TEXT NOT NULL,
324
+ type TEXT NOT NULL, -- 'color', 'typography', 'spacing', 'component'
325
+ name TEXT NOT NULL,
326
+ value TEXT NOT NULL,
327
+ category TEXT,
328
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
329
+ )
330
+ `,
331
+ // Assets table - stores downloadable assets
332
+ figma_assets: `
333
+ CREATE TABLE IF NOT EXISTS figma_assets (
334
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
335
+ file_id TEXT NOT NULL,
336
+ node_id TEXT NOT NULL,
337
+ node_name TEXT,
338
+ node_type TEXT,
339
+ format TEXT, -- 'png', 'svg', 'jpg'
340
+ file_path TEXT,
341
+ url TEXT,
342
+ width INTEGER,
343
+ height INTEGER,
344
+ FOREIGN KEY (file_id) REFERENCES figma_files(id) ON DELETE CASCADE
345
+ )
346
+ `,
347
+ // Schema version tracking
348
+ schema_version: `
349
+ CREATE TABLE IF NOT EXISTS schema_version (
350
+ version INTEGER PRIMARY KEY,
351
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
352
+ )
353
+ `
354
+ };
355
+ INDEX_SQL = {
356
+ // Files indexes
357
+ idx_files_url: "CREATE INDEX IF NOT EXISTS idx_files_url ON figma_files(url)",
358
+ idx_files_file_key: "CREATE INDEX IF NOT EXISTS idx_files_file_key ON figma_files(file_key)",
359
+ idx_files_last_analyzed: "CREATE INDEX IF NOT EXISTS idx_files_last_analyzed ON figma_files(last_analyzed)",
360
+ // Screens indexes
361
+ idx_screens_file_id: "CREATE INDEX IF NOT EXISTS idx_screens_file_id ON figma_screens(file_id)",
362
+ idx_screens_type: "CREATE INDEX IF NOT EXISTS idx_screens_type ON figma_screens(type)",
363
+ // Nodes indexes
364
+ idx_nodes_file_id: "CREATE INDEX IF NOT EXISTS idx_nodes_file_id ON figma_nodes(file_id)",
365
+ idx_nodes_screen_id: "CREATE INDEX IF NOT EXISTS idx_nodes_screen_id ON figma_nodes(screen_id)",
366
+ idx_nodes_parent_id: "CREATE INDEX IF NOT EXISTS idx_nodes_parent_id ON figma_nodes(parent_id)",
367
+ idx_nodes_type: "CREATE INDEX IF NOT EXISTS idx_nodes_type ON figma_nodes(type)",
368
+ // Design tokens indexes
369
+ idx_tokens_file_id: "CREATE INDEX IF NOT EXISTS idx_tokens_file_id ON design_tokens(file_id)",
370
+ idx_tokens_type: "CREATE INDEX IF NOT EXISTS idx_tokens_type ON design_tokens(type)",
371
+ idx_tokens_file_type: "CREATE INDEX IF NOT EXISTS idx_tokens_file_type ON design_tokens(file_id, type)",
372
+ // Assets indexes
373
+ idx_assets_file_id: "CREATE INDEX IF NOT EXISTS idx_assets_file_id ON figma_assets(file_id)",
374
+ idx_assets_node_id: "CREATE INDEX IF NOT EXISTS idx_assets_node_id ON figma_assets(node_id)",
375
+ idx_assets_format: "CREATE INDEX IF NOT EXISTS idx_assets_format ON figma_assets(format)"
376
+ };
377
+ TRIGGER_SQL = {
378
+ // Update timestamp trigger for files table
379
+ trigger_files_updated_at: `
380
+ CREATE TRIGGER IF NOT EXISTS trigger_files_updated_at
381
+ AFTER UPDATE ON figma_files
382
+ FOR EACH ROW
383
+ BEGIN
384
+ UPDATE figma_files SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
385
+ END
386
+ `
387
+ };
388
+ }
389
+ });
390
+
391
+ // src/core/database/figma-db.ts
392
+ import Database from "better-sqlite3";
393
+ var FigmaDatabase;
394
+ var init_figma_db = __esm({
395
+ "src/core/database/figma-db.ts"() {
396
+ "use strict";
397
+ init_esm_shims();
398
+ init_logger();
399
+ init_schema();
400
+ FigmaDatabase = class {
401
+ db;
402
+ constructor(dbPath = ":memory:") {
403
+ try {
404
+ this.db = new Database(dbPath);
405
+ initializeSchema(this.db);
406
+ logger.info(`Figma database initialized at: ${dbPath}`);
407
+ } catch (error) {
408
+ logger.error(`Failed to initialize database: ${error instanceof Error ? error.message : String(error)}`);
409
+ throw error;
410
+ }
411
+ }
412
+ /**
413
+ * Export database data to JSON
414
+ */
415
+ async exportData(fileId) {
416
+ try {
417
+ const files = fileId ? [await this.getFile(fileId)].filter(Boolean) : await this.listFiles();
418
+ const fileIds = files.map((f) => f.id);
419
+ let screens = [];
420
+ let nodes = [];
421
+ let tokens = [];
422
+ let assets = [];
423
+ for (const fId of fileIds) {
424
+ screens.push(...await this.getScreensByFile(fId));
425
+ nodes.push(...await this.getNodesByFile(fId));
426
+ tokens.push(...await this.getDesignTokensByFile(fId));
427
+ assets.push(...await this.getAssetsByFile(fId));
428
+ }
429
+ return {
430
+ files,
431
+ screens,
432
+ nodes,
433
+ tokens,
434
+ assets,
435
+ exported_at: (/* @__PURE__ */ new Date()).toISOString()
436
+ };
437
+ } catch (error) {
438
+ logger.error(`Failed to export data: ${error instanceof Error ? error.message : String(error)}`);
439
+ throw error;
440
+ }
441
+ }
442
+ /**
443
+ * Import database data from JSON
444
+ */
445
+ async importData(data) {
446
+ try {
447
+ const stats = { files: 0, screens: 0, nodes: 0, tokens: 0, assets: 0 };
448
+ for (const file of data.files) {
449
+ await this.upsertFile(file);
450
+ stats.files++;
451
+ }
452
+ for (const screen of data.screens) {
453
+ await this.upsertScreen(screen);
454
+ stats.screens++;
455
+ }
456
+ for (const node of data.nodes) {
457
+ await this.upsertNode(node);
458
+ stats.nodes++;
459
+ }
460
+ for (const token of data.tokens) {
461
+ await this.upsertDesignToken(token);
462
+ stats.tokens++;
463
+ }
464
+ for (const asset of data.assets) {
465
+ await this.upsertAsset(asset);
466
+ stats.assets++;
467
+ }
468
+ logger.info(`Imported ${stats.files} files, ${stats.screens} screens, ${stats.nodes} nodes, ${stats.tokens} tokens, ${stats.assets} assets`);
469
+ return stats;
470
+ } catch (error) {
471
+ logger.error(`Failed to import data: ${error instanceof Error ? error.message : String(error)}`);
472
+ throw error;
473
+ }
474
+ }
475
+ /**
476
+ * Utility Operations
477
+ */
478
+ async upsertFile(file) {
479
+ try {
480
+ const lastAnalyzed = file.last_analyzed instanceof Date ? file.last_analyzed.toISOString() : file.last_analyzed || null;
481
+ const existing = this.db.prepare("SELECT id FROM figma_files WHERE id = ?").get(file.id);
482
+ if (existing) {
483
+ this.db.prepare(`
484
+ UPDATE figma_files
485
+ SET url = ?, name = ?, file_key = ?, last_analyzed = ?, updated_at = CURRENT_TIMESTAMP
486
+ WHERE id = ?
487
+ `).run(file.url, file.name || null, file.file_key, lastAnalyzed, file.id);
488
+ return { id: file.id, created: false, updated: true };
489
+ } else {
490
+ this.db.prepare(`
491
+ INSERT INTO figma_files (id, url, name, file_key, last_analyzed)
492
+ VALUES (?, ?, ?, ?, ?)
493
+ `).run(file.id, file.url, file.name || null, file.file_key, lastAnalyzed);
494
+ return { id: file.id, created: true, updated: false };
495
+ }
496
+ } catch (error) {
497
+ logger.error(`Failed to upsert file: ${error instanceof Error ? error.message : String(error)}`);
498
+ throw error;
499
+ }
500
+ }
501
+ async getFile(id) {
502
+ try {
503
+ const row = this.db.prepare("SELECT * FROM figma_files WHERE id = ?").get(id);
504
+ if (!row) return null;
505
+ return {
506
+ ...row,
507
+ created_at: new Date(row.created_at),
508
+ updated_at: new Date(row.updated_at),
509
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
510
+ };
511
+ } catch (error) {
512
+ logger.error(`Failed to get file: ${error instanceof Error ? error.message : String(error)}`);
513
+ return null;
514
+ }
515
+ }
516
+ async getFileByUrl(url) {
517
+ try {
518
+ const row = this.db.prepare("SELECT * FROM figma_files WHERE url = ?").get(url);
519
+ if (!row) return null;
520
+ return {
521
+ ...row,
522
+ created_at: new Date(row.created_at),
523
+ updated_at: new Date(row.updated_at),
524
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
525
+ };
526
+ } catch (error) {
527
+ logger.error(`Failed to get file by URL: ${error instanceof Error ? error.message : String(error)}`);
528
+ return null;
529
+ }
530
+ }
531
+ async deleteFile(id) {
532
+ try {
533
+ const result = this.db.prepare("DELETE FROM figma_files WHERE id = ?").run(id);
534
+ return result.changes > 0;
535
+ } catch (error) {
536
+ logger.error(`Failed to delete file: ${error instanceof Error ? error.message : String(error)}`);
537
+ return false;
538
+ }
539
+ }
540
+ async listFiles(options) {
541
+ try {
542
+ const limit = options?.limit || 50;
543
+ const offset = options?.offset || 0;
544
+ const orderBy = options?.orderBy || "updated_at";
545
+ const orderDirection = options?.orderDirection || "DESC";
546
+ const query = `
547
+ SELECT * FROM figma_files
548
+ ORDER BY ${orderBy} ${orderDirection}
549
+ LIMIT ? OFFSET ?
550
+ `;
551
+ const rows = this.db.prepare(query).all(limit, offset);
552
+ return rows.map((row) => ({
553
+ ...row,
554
+ created_at: new Date(row.created_at),
555
+ updated_at: new Date(row.updated_at),
556
+ last_analyzed: row.last_analyzed ? new Date(row.last_analyzed) : void 0
557
+ }));
558
+ } catch (error) {
559
+ logger.error(`Failed to list files: ${error instanceof Error ? error.message : String(error)}`);
560
+ return [];
561
+ }
562
+ }
563
+ /**
564
+ * Screen Operations
565
+ */
566
+ async upsertScreen(screen) {
567
+ try {
568
+ const existing = this.db.prepare("SELECT id FROM figma_screens WHERE id = ?").get(screen.id);
569
+ if (existing) {
570
+ this.db.prepare(`
571
+ UPDATE figma_screens
572
+ SET file_id = ?, name = ?, width = ?, height = ?, type = ?, description = ?, children_count = ?
573
+ WHERE id = ?
574
+ `).run(
575
+ screen.file_id,
576
+ screen.name,
577
+ screen.width || null,
578
+ screen.height || null,
579
+ screen.type || null,
580
+ screen.description || null,
581
+ screen.children_count || null,
582
+ screen.id
583
+ );
584
+ return { id: screen.id, created: false, updated: true };
585
+ } else {
586
+ this.db.prepare(`
587
+ INSERT INTO figma_screens (id, file_id, name, width, height, type, description, children_count)
588
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
589
+ `).run(
590
+ screen.id,
591
+ screen.file_id,
592
+ screen.name,
593
+ screen.width || null,
594
+ screen.height || null,
595
+ screen.type || null,
596
+ screen.description || null,
597
+ screen.children_count || null
598
+ );
599
+ return { id: screen.id, created: true, updated: false };
600
+ }
601
+ } catch (error) {
602
+ logger.error(`Failed to upsert screen: ${error instanceof Error ? error.message : String(error)}`);
603
+ throw error;
604
+ }
605
+ }
606
+ async getScreen(id) {
607
+ try {
608
+ const row = this.db.prepare("SELECT * FROM figma_screens WHERE id = ?").get(id);
609
+ return row || null;
610
+ } catch (error) {
611
+ logger.error(`Failed to get screen: ${error instanceof Error ? error.message : String(error)}`);
612
+ return null;
613
+ }
614
+ }
615
+ async getScreensByFile(fileId) {
616
+ try {
617
+ const rows = this.db.prepare("SELECT * FROM figma_screens WHERE file_id = ? ORDER BY name").all(fileId);
618
+ return rows;
619
+ } catch (error) {
620
+ logger.error(`Failed to get screens by file: ${error instanceof Error ? error.message : String(error)}`);
621
+ return [];
622
+ }
623
+ }
624
+ async deleteScreen(id) {
625
+ try {
626
+ const result = this.db.prepare("DELETE FROM figma_screens WHERE id = ?").run(id);
627
+ return result.changes > 0;
628
+ } catch (error) {
629
+ logger.error(`Failed to delete screen: ${error instanceof Error ? error.message : String(error)}`);
630
+ return false;
631
+ }
632
+ }
633
+ /**
634
+ * Node Operations
635
+ */
636
+ async upsertNode(node) {
637
+ try {
638
+ const existing = this.db.prepare("SELECT id FROM figma_nodes WHERE id = ?").get(node.id);
639
+ if (existing) {
640
+ this.db.prepare(`
641
+ UPDATE figma_nodes
642
+ SET file_id = ?, screen_id = ?, parent_id = ?, name = ?, type = ?, content = ?,
643
+ position_x = ?, position_y = ?, width = ?, height = ?, styles = ?, children_ids = ?
644
+ WHERE id = ?
645
+ `).run(
646
+ node.file_id,
647
+ node.screen_id || null,
648
+ node.parent_id || null,
649
+ node.name,
650
+ node.type,
651
+ node.content || null,
652
+ node.position_x || null,
653
+ node.position_y || null,
654
+ node.width || null,
655
+ node.height || null,
656
+ node.styles || null,
657
+ node.children_ids || null,
658
+ node.id
659
+ );
660
+ return { id: node.id, created: false, updated: true };
661
+ } else {
662
+ this.db.prepare(`
663
+ INSERT INTO figma_nodes (id, file_id, screen_id, parent_id, name, type, content,
664
+ position_x, position_y, width, height, styles, children_ids)
665
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
666
+ `).run(
667
+ node.id,
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
+ );
681
+ return { id: node.id, created: true, updated: false };
682
+ }
683
+ } catch (error) {
684
+ logger.error(`Failed to upsert node: ${error instanceof Error ? error.message : String(error)}`);
685
+ throw error;
686
+ }
687
+ }
688
+ async getNode(id) {
689
+ try {
690
+ const row = this.db.prepare("SELECT * FROM figma_nodes WHERE id = ?").get(id);
691
+ return row || null;
692
+ } catch (error) {
693
+ logger.error(`Failed to get node: ${error instanceof Error ? error.message : String(error)}`);
694
+ return null;
695
+ }
696
+ }
697
+ async getNodesByScreen(screenId) {
698
+ try {
699
+ const rows = this.db.prepare("SELECT * FROM figma_nodes WHERE screen_id = ? ORDER BY name").all(screenId);
700
+ return rows;
701
+ } catch (error) {
702
+ logger.error(`Failed to get nodes by screen: ${error instanceof Error ? error.message : String(error)}`);
703
+ return [];
704
+ }
705
+ }
706
+ async getNodesByFile(fileId) {
707
+ try {
708
+ const rows = this.db.prepare("SELECT * FROM figma_nodes WHERE file_id = ? ORDER BY name").all(fileId);
709
+ return rows;
710
+ } catch (error) {
711
+ logger.error(`Failed to get nodes by file: ${error instanceof Error ? error.message : String(error)}`);
712
+ return [];
713
+ }
714
+ }
715
+ async deleteNode(id) {
716
+ try {
717
+ const result = this.db.prepare("DELETE FROM figma_nodes WHERE id = ?").run(id);
718
+ return result.changes > 0;
719
+ } catch (error) {
720
+ logger.error(`Failed to delete node: ${error instanceof Error ? error.message : String(error)}`);
721
+ return false;
722
+ }
723
+ }
724
+ /**
725
+ * Design Token Operations
726
+ */
727
+ async upsertDesignToken(token) {
728
+ try {
729
+ const existing = this.db.prepare(`
730
+ SELECT id FROM design_tokens
731
+ WHERE file_id = ? AND type = ? AND name = ?
732
+ `).get(token.file_id, token.type, token.name);
733
+ if (existing) {
734
+ this.db.prepare(`
735
+ UPDATE design_tokens
736
+ SET value = ?, category = ?
737
+ WHERE id = ?
738
+ `).run(token.value, token.category || null, existing.id);
739
+ return { id: existing.id.toString(), created: false, updated: true };
740
+ } else {
741
+ const result = this.db.prepare(`
742
+ INSERT INTO design_tokens (file_id, type, name, value, category)
743
+ VALUES (?, ?, ?, ?, ?)
744
+ `).run(token.file_id, token.type, token.name, token.value, token.category || null);
745
+ return { id: result.lastInsertRowid.toString(), created: true, updated: false };
746
+ }
747
+ } catch (error) {
748
+ logger.error(`Failed to upsert design token: ${error instanceof Error ? error.message : String(error)}`);
749
+ throw error;
750
+ }
751
+ }
752
+ async getDesignTokensByFile(fileId, type) {
753
+ try {
754
+ 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";
755
+ const rows = type ? this.db.prepare(query).all(fileId, type) : this.db.prepare(query).all(fileId);
756
+ return rows;
757
+ } catch (error) {
758
+ logger.error(`Failed to get design tokens: ${error instanceof Error ? error.message : String(error)}`);
759
+ return [];
760
+ }
761
+ }
762
+ async deleteDesignTokensByFile(fileId) {
763
+ try {
764
+ const result = this.db.prepare("DELETE FROM design_tokens WHERE file_id = ?").run(fileId);
765
+ return result.changes > 0;
766
+ } catch (error) {
767
+ logger.error(`Failed to delete design tokens: ${error instanceof Error ? error.message : String(error)}`);
768
+ return false;
769
+ }
770
+ }
771
+ /**
772
+ * Asset Operations
773
+ */
774
+ async upsertAsset(asset) {
775
+ try {
776
+ const existing = this.db.prepare(`
777
+ SELECT id FROM figma_assets
778
+ WHERE file_id = ? AND node_id = ?
779
+ `).get(asset.file_id, asset.node_id);
780
+ if (existing) {
781
+ this.db.prepare(`
782
+ UPDATE figma_assets
783
+ SET node_name = ?, node_type = ?, format = ?, file_path = ?, url = ?, width = ?, height = ?
784
+ WHERE id = ?
785
+ `).run(
786
+ asset.node_name || null,
787
+ asset.node_type || null,
788
+ asset.format || null,
789
+ asset.file_path || null,
790
+ asset.url || null,
791
+ asset.width || null,
792
+ asset.height || null,
793
+ existing.id
794
+ );
795
+ return { id: existing.id.toString(), created: false, updated: true };
796
+ } else {
797
+ const result = this.db.prepare(`
798
+ INSERT INTO figma_assets (file_id, node_id, node_name, node_type, format, file_path, url, width, height)
799
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
800
+ `).run(
801
+ asset.file_id,
802
+ asset.node_id,
803
+ asset.node_name || null,
804
+ asset.node_type || null,
805
+ asset.format || null,
806
+ asset.file_path || null,
807
+ asset.url || null,
808
+ asset.width || null,
809
+ asset.height || null
810
+ );
811
+ return { id: result.lastInsertRowid.toString(), created: true, updated: false };
812
+ }
813
+ } catch (error) {
814
+ logger.error(`Failed to upsert asset: ${error instanceof Error ? error.message : String(error)}`);
815
+ throw error;
816
+ }
817
+ }
818
+ async getAssetsByFile(fileId) {
819
+ try {
820
+ const rows = this.db.prepare("SELECT * FROM figma_assets WHERE file_id = ? ORDER BY node_name").all(fileId);
821
+ return rows;
822
+ } catch (error) {
823
+ logger.error(`Failed to get assets by file: ${error instanceof Error ? error.message : String(error)}`);
824
+ return [];
825
+ }
826
+ }
827
+ async deleteAssetsByFile(fileId) {
828
+ try {
829
+ const result = this.db.prepare("DELETE FROM figma_assets WHERE file_id = ?").run(fileId);
830
+ return result.changes > 0;
831
+ } catch (error) {
832
+ logger.error(`Failed to delete assets by file: ${error instanceof Error ? error.message : String(error)}`);
833
+ return false;
834
+ }
835
+ }
836
+ /**
837
+ * Cache invalidation logic
838
+ */
839
+ async isCacheValid(url, maxAgeHours = 24) {
840
+ try {
841
+ const file = await this.getFileByUrl(url);
842
+ if (!file || !file.last_analyzed) return false;
843
+ const now = /* @__PURE__ */ new Date();
844
+ const ageHours = (now.getTime() - file.last_analyzed.getTime()) / (1e3 * 60 * 60);
845
+ return ageHours < maxAgeHours;
846
+ } catch (error) {
847
+ logger.error(`Failed to check cache validity: ${error instanceof Error ? error.message : String(error)}`);
848
+ return false;
849
+ }
850
+ }
851
+ /**
852
+ * Clear cache for a specific file
853
+ */
854
+ async clearFileCache(url) {
855
+ try {
856
+ const file = await this.getFileByUrl(url);
857
+ if (!file) return false;
858
+ await this.deleteDesignTokensByFile(file.id);
859
+ await this.deleteAssetsByFile(file.id);
860
+ const nodes = await this.getNodesByFile(file.id);
861
+ for (const node of nodes) {
862
+ await this.deleteNode(node.id);
863
+ }
864
+ const screens = await this.getScreensByFile(file.id);
865
+ for (const screen of screens) {
866
+ await this.deleteScreen(screen.id);
867
+ }
868
+ return await this.deleteFile(file.id);
869
+ } catch (error) {
870
+ logger.error(`Failed to clear file cache: ${error instanceof Error ? error.message : String(error)}`);
871
+ return false;
872
+ }
873
+ }
874
+ /**
875
+ * Clear all expired cache entries
876
+ */
877
+ async clearExpiredCache(maxAgeHours = 24 * 7) {
878
+ try {
879
+ const cutoffDate = new Date(Date.now() - maxAgeHours * 60 * 60 * 1e3);
880
+ const expiredFiles = this.db.prepare(`
881
+ SELECT * FROM figma_files
882
+ WHERE last_analyzed < ? OR last_analyzed IS NULL
883
+ `).all(cutoffDate.toISOString());
884
+ let clearedCount = 0;
885
+ for (const file of expiredFiles) {
886
+ if (await this.clearFileCache(file.url)) {
887
+ clearedCount++;
888
+ }
889
+ }
890
+ return clearedCount;
891
+ } catch (error) {
892
+ logger.error(`Failed to clear expired cache: ${error instanceof Error ? error.message : String(error)}`);
893
+ return 0;
894
+ }
895
+ }
896
+ async close() {
897
+ try {
898
+ this.db.close();
899
+ logger.info("Database connection closed");
900
+ } catch (error) {
901
+ logger.error(`Failed to close database: ${error instanceof Error ? error.message : String(error)}`);
902
+ }
903
+ }
904
+ async vacuum() {
905
+ try {
906
+ this.db.exec("VACUUM");
907
+ logger.info("Database vacuumed");
908
+ } catch (error) {
909
+ logger.error(`Failed to vacuum database: ${error instanceof Error ? error.message : String(error)}`);
910
+ }
911
+ }
912
+ async getStats() {
913
+ try {
914
+ const files = this.db.prepare("SELECT COUNT(*) as count FROM figma_files").get();
915
+ const screens = this.db.prepare("SELECT COUNT(*) as count FROM figma_screens").get();
916
+ const nodes = this.db.prepare("SELECT COUNT(*) as count FROM figma_nodes").get();
917
+ const tokens = this.db.prepare("SELECT COUNT(*) as count FROM design_tokens").get();
918
+ const assets = this.db.prepare("SELECT COUNT(*) as count FROM figma_assets").get();
919
+ return {
920
+ files: files.count,
921
+ screens: screens.count,
922
+ nodes: nodes.count,
923
+ tokens: tokens.count,
924
+ assets: assets.count
925
+ };
926
+ } catch (error) {
927
+ logger.error(`Failed to get stats: ${error instanceof Error ? error.message : String(error)}`);
928
+ return { files: 0, screens: 0, nodes: 0, tokens: 0, assets: 0 };
929
+ }
930
+ }
931
+ };
932
+ }
933
+ });
934
+
185
935
  // src/core/config.ts
186
936
  var config_exports = {};
187
937
  __export(config_exports, {
@@ -239,15 +989,36 @@ function deepMerge(base, override) {
239
989
  }
240
990
  return result;
241
991
  }
242
- var ConfigSchema, Config;
992
+ var PlatformConfigSchema, ConfigSchema, Config;
243
993
  var init_config = __esm({
244
994
  "src/core/config.ts"() {
245
995
  "use strict";
246
996
  init_esm_shims();
247
997
  init_paths();
248
998
  init_version();
999
+ init_figma_db();
1000
+ PlatformConfigSchema = z.object({
1001
+ /**
1002
+ * Primary platform to use (affects default behavior)
1003
+ */
1004
+ primary: z.enum(["opencode", "claude"]).default("opencode"),
1005
+ /**
1006
+ * Enable OpenCode platform support
1007
+ * Default: true (OpenCode is the primary focus)
1008
+ */
1009
+ opencode: z.boolean().default(true),
1010
+ /**
1011
+ * Enable Claude Code platform support
1012
+ * Default: false (archived, can be re-enabled later)
1013
+ */
1014
+ claude: z.boolean().default(false)
1015
+ }).default({});
249
1016
  ConfigSchema = z.object({
250
1017
  version: z.string(),
1018
+ /**
1019
+ * Platform configuration - controls which platforms are active
1020
+ */
1021
+ platform: PlatformConfigSchema,
251
1022
  skills: z.object({
252
1023
  enabled: z.boolean().default(true),
253
1024
  directory: z.string().optional()
@@ -279,6 +1050,11 @@ var init_config = __esm({
279
1050
  specFile: z.string().default("spec.md"),
280
1051
  reviewFile: z.string().default("review.md")
281
1052
  }).default({}),
1053
+ database: z.object({
1054
+ enabled: z.boolean().default(true),
1055
+ path: z.string().optional()
1056
+ // If not provided, will use default path
1057
+ }).default({}),
282
1058
  mcp: z.object({
283
1059
  context7: z.boolean().default(false),
284
1060
  githubGrep: z.boolean().default(false),
@@ -318,12 +1094,43 @@ var init_config = __esm({
318
1094
  get antiHallucination() {
319
1095
  return this.config.antiHallucination;
320
1096
  }
1097
+ get database() {
1098
+ return this.config.database;
1099
+ }
321
1100
  get mode() {
322
1101
  return this.config.mode;
323
1102
  }
1103
+ get platform() {
1104
+ return this.config.platform;
1105
+ }
324
1106
  get configPath() {
325
1107
  return this.config.configPath;
326
1108
  }
1109
+ /**
1110
+ * Check if a specific platform is enabled
1111
+ */
1112
+ isPlatformEnabled(platform) {
1113
+ return this.config.platform[platform] ?? false;
1114
+ }
1115
+ /**
1116
+ * Get the primary platform
1117
+ */
1118
+ getPrimaryPlatform() {
1119
+ return this.config.platform.primary;
1120
+ }
1121
+ /**
1122
+ * Get list of all enabled platforms
1123
+ */
1124
+ getEnabledPlatforms() {
1125
+ const platforms = [];
1126
+ if (this.config.platform.opencode) {
1127
+ platforms.push("opencode");
1128
+ }
1129
+ if (this.config.platform.claude) {
1130
+ platforms.push("claude");
1131
+ }
1132
+ return platforms;
1133
+ }
327
1134
  get projectPath() {
328
1135
  return this.config.projectPath;
329
1136
  }
@@ -331,49 +1138,20 @@ var init_config = __esm({
331
1138
  * Get path to a specific resource
332
1139
  */
333
1140
  getPath(resource) {
1141
+ if (resource === "database") {
1142
+ return this.config.database.path || join3(this.configPath, "figma.db");
1143
+ }
334
1144
  return paths[resource](this.configPath);
335
1145
  }
336
- };
337
- }
338
- });
339
-
340
- // src/utils/logger.ts
341
- import chalk from "chalk";
342
- var logger;
343
- var init_logger = __esm({
344
- "src/utils/logger.ts"() {
345
- "use strict";
346
- init_esm_shims();
347
- logger = {
348
- info(...args) {
349
- console.log(chalk.blue("\u2139"), ...args);
350
- },
351
- success(...args) {
352
- console.log(chalk.green("\u2713"), ...args);
353
- },
354
- warn(...args) {
355
- console.log(chalk.yellow("\u26A0"), ...args);
356
- },
357
- error(...args) {
358
- console.error(chalk.red("\u2716"), ...args);
359
- },
360
- debug(...args) {
361
- if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
362
- console.log(chalk.gray("\u22EF"), ...args);
363
- }
364
- },
365
- step(step, total, message) {
366
- console.log(chalk.cyan(`[${step}/${total}]`), message);
367
- },
368
- header(message) {
369
- console.log(chalk.bold.underline(`
370
- ${message}
371
- `));
372
- },
373
- list(items, prefix = "\u2022") {
374
- for (const item of items) {
375
- console.log(` ${prefix} ${item}`);
1146
+ /**
1147
+ * Get database instance
1148
+ */
1149
+ getDatabase() {
1150
+ if (this.config.database?.enabled === false) {
1151
+ throw new Error("Database is disabled in configuration");
376
1152
  }
1153
+ const dbPath = this.getPath("database");
1154
+ return new FigmaDatabase(dbPath);
377
1155
  }
378
1156
  };
379
1157
  }
@@ -407,8 +1185,8 @@ var init_memory = __esm({
407
1185
  for (const subDir of subDirs) {
408
1186
  const dirPath = join6(memoryPath, subDir);
409
1187
  try {
410
- const { readdir: readdir12 } = await import("fs/promises");
411
- const files = await readdir12(dirPath);
1188
+ const { readdir: readdir11 } = await import("fs/promises");
1189
+ const files = await readdir11(dirPath);
412
1190
  for (const file of files) {
413
1191
  if (!file.endsWith(".md")) continue;
414
1192
  const content = await readFile4(join6(dirPath, file), "utf-8");
@@ -611,8 +1389,10 @@ var init_figma_mcp = __esm({
611
1389
  init_logger();
612
1390
  FigmaMcpClient = class {
613
1391
  apiKey;
614
- constructor(apiKey, _configManager) {
1392
+ database;
1393
+ constructor(apiKey, _configManager, database) {
615
1394
  this.apiKey = apiKey;
1395
+ this.database = database;
616
1396
  }
617
1397
  /**
618
1398
  * Fetch helper with simple retry/backoff for 429/5xx
@@ -695,14 +1475,22 @@ ${text}`);
695
1475
  return data;
696
1476
  }
697
1477
  /**
698
- * Extract design tokens from Figma file
1478
+ * Extract design tokens from Figma file and optionally persist to database
699
1479
  */
700
1480
  async extractDesignTokens(url, downloadAssets = true, assetsDir) {
701
- const fileData = await this.getFileData(url);
702
1481
  const fileKey = this.extractFileKey(url);
703
1482
  if (!fileKey) {
704
1483
  throw new Error(`Invalid Figma URL: ${url}`);
705
1484
  }
1485
+ if (this.database) {
1486
+ const cachedData = await this.getFromDatabase(fileKey);
1487
+ if (cachedData) {
1488
+ logger.info(`\u26A1 Using cached data from database (analyzed ${this.getTimeAgo(cachedData.last_analyzed)})`);
1489
+ return cachedData.tokens;
1490
+ }
1491
+ }
1492
+ logger.info("\u23F3 Fetching fresh data from Figma API...");
1493
+ const fileData = await this.getFileData(url);
706
1494
  const tokens = {
707
1495
  colors: [],
708
1496
  typography: [],
@@ -747,7 +1535,226 @@ ${text}`);
747
1535
  logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
748
1536
  }
749
1537
  }
750
- return tokens;
1538
+ if (this.database) {
1539
+ await this.persistToDatabase(url, fileKey, tokens);
1540
+ }
1541
+ return tokens;
1542
+ }
1543
+ /**
1544
+ * Persist extracted tokens to database
1545
+ */
1546
+ async persistToDatabase(url, fileKey, tokens) {
1547
+ if (!this.database) return;
1548
+ try {
1549
+ logger.info("Persisting Figma data to database...");
1550
+ const fileId = fileKey;
1551
+ await this.database.upsertFile({
1552
+ id: fileId,
1553
+ url,
1554
+ name: url.split("/").pop() || "Figma Design",
1555
+ file_key: fileKey,
1556
+ last_analyzed: /* @__PURE__ */ new Date()
1557
+ });
1558
+ for (const screen of tokens.screens) {
1559
+ await this.database.upsertScreen({
1560
+ id: screen.id,
1561
+ file_id: fileId,
1562
+ name: screen.name,
1563
+ width: screen.width,
1564
+ height: screen.height,
1565
+ type: screen.type,
1566
+ children_count: screen.childrenCount
1567
+ });
1568
+ }
1569
+ if (tokens.structure?.nodes) {
1570
+ for (const node of tokens.structure.nodes) {
1571
+ await this.database.upsertNode({
1572
+ id: node.id,
1573
+ file_id: fileId,
1574
+ screen_id: this.findScreenIdForNode(node.id, tokens.screens),
1575
+ parent_id: this.findParentId(node.id, tokens.structure.nodes),
1576
+ name: node.name,
1577
+ type: node.type,
1578
+ content: node.content,
1579
+ position_x: node.position?.x,
1580
+ position_y: node.position?.y,
1581
+ width: node.position?.width,
1582
+ height: node.position?.height,
1583
+ styles: node.styles ? JSON.stringify(node.styles) : void 0,
1584
+ children_ids: node.children ? JSON.stringify(node.children) : void 0
1585
+ });
1586
+ }
1587
+ }
1588
+ for (const color of tokens.colors) {
1589
+ await this.database.upsertDesignToken({
1590
+ file_id: fileId,
1591
+ type: "color",
1592
+ name: color.name,
1593
+ value: color.hex,
1594
+ category: "color"
1595
+ });
1596
+ }
1597
+ for (const typo of tokens.typography) {
1598
+ await this.database.upsertDesignToken({
1599
+ file_id: fileId,
1600
+ type: "typography",
1601
+ name: typo.name,
1602
+ value: JSON.stringify({
1603
+ fontFamily: typo.fontFamily,
1604
+ fontSize: typo.fontSize,
1605
+ fontWeight: typo.fontWeight,
1606
+ lineHeight: typo.lineHeight
1607
+ }),
1608
+ category: "typography"
1609
+ });
1610
+ }
1611
+ for (const component of tokens.components) {
1612
+ await this.database.upsertDesignToken({
1613
+ file_id: fileId,
1614
+ type: "component",
1615
+ name: component.name,
1616
+ value: component.type,
1617
+ category: component.description
1618
+ });
1619
+ }
1620
+ if (tokens.assets) {
1621
+ for (const asset of tokens.assets) {
1622
+ await this.database.upsertAsset({
1623
+ file_id: fileId,
1624
+ node_id: asset.nodeId,
1625
+ node_name: asset.nodeName,
1626
+ node_type: asset.nodeType,
1627
+ format: asset.format,
1628
+ file_path: asset.path,
1629
+ url: asset.url,
1630
+ width: asset.width,
1631
+ height: asset.height
1632
+ });
1633
+ }
1634
+ }
1635
+ logger.info("Successfully persisted Figma data to database");
1636
+ } catch (error) {
1637
+ logger.error(`Failed to persist to database: ${error instanceof Error ? error.message : String(error)}`);
1638
+ }
1639
+ }
1640
+ /**
1641
+ * Get cached data from database
1642
+ */
1643
+ async getFromDatabase(fileKey) {
1644
+ if (!this.database) return null;
1645
+ try {
1646
+ const file = await this.database.getFile(fileKey);
1647
+ if (!file || !file.last_analyzed) return null;
1648
+ const cacheAge = Date.now() - file.last_analyzed.getTime();
1649
+ const maxAge = 24 * 60 * 60 * 1e3;
1650
+ if (cacheAge > maxAge) {
1651
+ logger.info("Cache expired, fetching fresh data...");
1652
+ return null;
1653
+ }
1654
+ const screens = await this.database.getScreensByFile(fileKey);
1655
+ const nodes = await this.database.getNodesByFile(fileKey);
1656
+ const tokenRecords = await this.database.getDesignTokensByFile(fileKey);
1657
+ const assets = await this.database.getAssetsByFile(fileKey);
1658
+ const tokens = {
1659
+ colors: tokenRecords.filter((t) => t.type === "color").map((t) => ({ name: t.name, hex: t.value, rgba: t.value })),
1660
+ typography: tokenRecords.filter((t) => t.type === "typography").map((t) => {
1661
+ const parsed = JSON.parse(t.value);
1662
+ return {
1663
+ name: t.name,
1664
+ fontFamily: parsed.fontFamily,
1665
+ fontSize: parsed.fontSize,
1666
+ fontWeight: parsed.fontWeight,
1667
+ lineHeight: parsed.lineHeight,
1668
+ letterSpacing: parsed.letterSpacing
1669
+ };
1670
+ }),
1671
+ spacing: {
1672
+ unit: 8,
1673
+ scale: [4, 8, 12, 16, 24, 32, 48, 64]
1674
+ },
1675
+ components: tokenRecords.filter((t) => t.type === "component").map((t) => ({ name: t.name, type: t.value, description: t.category })),
1676
+ screens: screens.map((s) => ({
1677
+ id: s.id,
1678
+ name: s.name,
1679
+ width: s.width || 0,
1680
+ height: s.height || 0,
1681
+ type: s.type || "FRAME",
1682
+ description: s.description,
1683
+ childrenCount: s.children_count
1684
+ })),
1685
+ breakpoints: [375, 768, 1024, 1280, 1920],
1686
+ structure: {
1687
+ nodes: nodes.map((n) => ({
1688
+ id: n.id,
1689
+ name: n.name,
1690
+ type: n.type,
1691
+ content: n.content,
1692
+ position: n.position_x !== void 0 ? {
1693
+ x: n.position_x,
1694
+ y: n.position_y || 0,
1695
+ width: n.width || 0,
1696
+ height: n.height || 0
1697
+ } : void 0,
1698
+ styles: n.styles ? JSON.parse(n.styles) : void 0,
1699
+ children: n.children_ids ? JSON.parse(n.children_ids) : void 0
1700
+ })),
1701
+ hierarchy: this.buildHierarchy(nodes)
1702
+ },
1703
+ assets: assets.map((a) => ({
1704
+ nodeId: a.node_id,
1705
+ nodeName: a.node_name || "",
1706
+ nodeType: a.node_type || "",
1707
+ format: a.format || "png",
1708
+ path: a.file_path || "",
1709
+ url: a.url || "",
1710
+ width: a.width,
1711
+ height: a.height
1712
+ }))
1713
+ };
1714
+ return { tokens, last_analyzed: file.last_analyzed };
1715
+ } catch (error) {
1716
+ logger.warn(`Failed to retrieve from database: ${error instanceof Error ? error.message : String(error)}`);
1717
+ return null;
1718
+ }
1719
+ }
1720
+ /**
1721
+ * Build hierarchy string from nodes
1722
+ */
1723
+ buildHierarchy(nodes) {
1724
+ return nodes.map((n) => `${n.type} "${n.name}" [${n.id}]`).join("\n");
1725
+ }
1726
+ /**
1727
+ * Get human-readable time ago
1728
+ */
1729
+ getTimeAgo(date) {
1730
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
1731
+ if (seconds < 60) return `${seconds} seconds ago`;
1732
+ const minutes = Math.floor(seconds / 60);
1733
+ if (minutes < 60) return `${minutes} minutes ago`;
1734
+ const hours = Math.floor(minutes / 60);
1735
+ if (hours < 24) return `${hours} hours ago`;
1736
+ const days = Math.floor(hours / 24);
1737
+ return `${days} days ago`;
1738
+ }
1739
+ /**
1740
+ * Helper to find which screen a node belongs to
1741
+ */
1742
+ findScreenIdForNode(nodeId, screens) {
1743
+ if (screens.find((s) => s.id === nodeId)) {
1744
+ return nodeId;
1745
+ }
1746
+ return void 0;
1747
+ }
1748
+ /**
1749
+ * Helper to find parent node ID
1750
+ */
1751
+ findParentId(nodeId, nodes) {
1752
+ for (const node of nodes) {
1753
+ if (node.children?.includes(nodeId)) {
1754
+ return node.id;
1755
+ }
1756
+ }
1757
+ return void 0;
751
1758
  }
752
1759
  /**
753
1760
  * Recursively extract colors from nodes
@@ -1019,11 +2026,66 @@ ${text}`);
1019
2026
  var figma_screen_developer_exports = {};
1020
2027
  __export(figma_screen_developer_exports, {
1021
2028
  checkCurrentCodeStatus: () => checkCurrentCodeStatus,
1022
- compareCodeWithFigma: () => compareCodeWithFigma
2029
+ compareCodeWithFigma: () => compareCodeWithFigma,
2030
+ getCachedFigmaData: () => getCachedFigmaData
1023
2031
  });
1024
2032
  import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
1025
2033
  import { join as join8 } from "path";
1026
2034
  import { existsSync as existsSync3 } from "fs";
2035
+ async function getCachedFigmaData(url, database) {
2036
+ if (!database) return null;
2037
+ try {
2038
+ const file = await database.getFileByUrl(url);
2039
+ if (!file) return null;
2040
+ const screens = await database.getScreensByFile(file.id);
2041
+ const nodes = await database.getNodesByFile(file.id);
2042
+ const tokens = await database.getDesignTokensByFile(file.id);
2043
+ const assets = await database.getAssetsByFile(file.id);
2044
+ return {
2045
+ screens: screens.map((s) => ({
2046
+ id: s.id,
2047
+ name: s.name,
2048
+ width: s.width,
2049
+ height: s.height,
2050
+ type: s.type,
2051
+ childrenCount: s.children_count
2052
+ })),
2053
+ nodes: nodes.map((n) => ({
2054
+ id: n.id,
2055
+ name: n.name,
2056
+ type: n.type,
2057
+ content: n.content,
2058
+ position: n.position_x ? {
2059
+ x: n.position_x,
2060
+ y: n.position_y,
2061
+ width: n.width,
2062
+ height: n.height
2063
+ } : void 0,
2064
+ styles: n.styles ? JSON.parse(n.styles) : void 0,
2065
+ children: n.children_ids ? JSON.parse(n.children_ids) : void 0
2066
+ })),
2067
+ tokens: tokens.map((t) => ({
2068
+ type: t.type,
2069
+ name: t.name,
2070
+ value: t.value,
2071
+ category: t.category
2072
+ })),
2073
+ assets: assets.map((a) => ({
2074
+ nodeId: a.node_id,
2075
+ nodeName: a.node_name,
2076
+ nodeType: a.node_type,
2077
+ format: a.format,
2078
+ path: a.file_path,
2079
+ url: a.url,
2080
+ width: a.width,
2081
+ height: a.height
2082
+ }))
2083
+ };
2084
+ } catch (error) {
2085
+ logger.warn(`Failed to get cached Figma data: ${error instanceof Error ? error.message : String(error)}`);
2086
+ return null;
2087
+ }
2088
+ }
1027
2089
  async function checkCurrentCodeStatus(projectPath = process.cwd()) {
1028
2090
  const status = {
1029
2091
  hasHTML: false,
@@ -1083,7 +2145,12 @@ async function checkCurrentCodeStatus(projectPath = process.cwd()) {
1083
2145
  }
1084
2146
  return status;
1085
2147
  }
1086
- async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd()) {
2148
+ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd(), database, figmaUrl) {
2149
+ let cachedData;
2150
+ if (database && figmaUrl) {
2151
+ cachedData = await getCachedFigmaData(figmaUrl, database);
2152
+ }
2153
+ const dataToUse = cachedData || figmaTokens;
1087
2154
  const codeStatus = await checkCurrentCodeStatus(projectPath);
1088
2155
  const result = {
1089
2156
  missingSections: [],
@@ -1091,17 +2158,18 @@ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath =
1091
2158
  needsUpdate: false,
1092
2159
  recommendations: []
1093
2160
  };
1094
- const selectedScreen = figmaTokens.screens?.find((s) => s.id === selectedScreenId);
2161
+ const selectedScreen = dataToUse.screens?.find((s) => s.id === selectedScreenId);
1095
2162
  if (!selectedScreen) {
1096
2163
  result.recommendations.push("Selected screen not found in Figma design");
1097
2164
  return result;
1098
2165
  }
1099
2166
  const figmaSections = [];
1100
- if (figmaTokens.structure?.nodes) {
1101
- const screenNode = figmaTokens.structure.nodes.find((n) => n.id === selectedScreenId);
2167
+ if (dataToUse.structure?.nodes || dataToUse.nodes) {
2168
+ const nodes = dataToUse.structure?.nodes || dataToUse.nodes;
2169
+ const screenNode = nodes.find((n) => n.id === selectedScreenId);
1102
2170
  if (screenNode?.children) {
1103
2171
  screenNode.children.forEach((childId) => {
1104
- const childNode = figmaTokens.structure.nodes.find((n) => n.id === childId);
2172
+ const childNode = nodes.find((n) => n.id === childId);
1105
2173
  if (childNode && (childNode.type === "FRAME" || childNode.type === "COMPONENT")) {
1106
2174
  const sectionName = childNode.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-");
1107
2175
  figmaSections.push(sectionName);
@@ -1578,104 +2646,328 @@ type: ${type}
1578
2646
  `);
1579
2647
  });
1580
2648
  }
1581
- content = content.replace(
1582
- /updated:\s*.+/,
1583
- `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
1584
- );
1585
- await writeFile5(filePath, content);
1586
- return true;
1587
- } catch {
1588
- return false;
2649
+ content = content.replace(
2650
+ /updated:\s*.+/,
2651
+ `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
2652
+ );
2653
+ await writeFile5(filePath, content);
2654
+ return true;
2655
+ } catch {
2656
+ return false;
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Complete a bead with quality gates
2661
+ */
2662
+ async completeBead(id) {
2663
+ const gates = [
2664
+ { name: "Type Check", command: "npm run typecheck" },
2665
+ { name: "Tests", command: "npm run test" },
2666
+ { name: "Lint", command: "npm run lint" },
2667
+ { name: "Build", command: "npm run build" }
2668
+ ];
2669
+ const results = [];
2670
+ for (const gate of gates) {
2671
+ try {
2672
+ await execAsync(gate.command, { cwd: this.projectPath });
2673
+ results.push({ name: gate.name, passed: true });
2674
+ logger.success(`${gate.name}: passed`);
2675
+ } catch (error) {
2676
+ results.push({
2677
+ name: gate.name,
2678
+ passed: false,
2679
+ error: error instanceof Error ? error.message.slice(0, 200) : "Failed"
2680
+ });
2681
+ logger.error(`${gate.name}: failed`);
2682
+ }
2683
+ }
2684
+ const allPassed = results.every((r) => r.passed);
2685
+ if (allPassed) {
2686
+ await this.updateBeadStatus(id, "completed");
2687
+ await this.addNote(id, "Task completed - all quality gates passed");
2688
+ const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
2689
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2690
+ const config = await loadConfig2(this.projectPath);
2691
+ const memory = new MemoryManager2(config);
2692
+ await memory.createHandoff({
2693
+ completed: [id],
2694
+ inProgress: [],
2695
+ remaining: [],
2696
+ context: `Auto-generated handoff for completed task: ${id}`,
2697
+ nextSteps: ["Review completed work", "Start new task if needed"]
2698
+ });
2699
+ } else {
2700
+ await this.addNote(id, "Completion attempted but quality gates failed");
2701
+ }
2702
+ return {
2703
+ success: allPassed,
2704
+ gates: results,
2705
+ handoffCreated: allPassed
2706
+ };
2707
+ }
2708
+ /**
2709
+ * Get current active bead
2710
+ */
2711
+ async getCurrentBead() {
2712
+ const beads = await this.listBeads();
2713
+ return beads.find((b) => b.status === "in-progress") || null;
2714
+ }
2715
+ /**
2716
+ * Parse a bead file
2717
+ */
2718
+ parseBeadFile(fileName, content) {
2719
+ try {
2720
+ const id = fileName.replace(".md", "");
2721
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
2722
+ const frontmatter = frontmatterMatch?.[1] || "";
2723
+ const getValue = (key) => {
2724
+ const match = frontmatter.match(new RegExp(`${key}:\\s*(.+)`));
2725
+ return match?.[1]?.trim() || "";
2726
+ };
2727
+ const titleMatch = content.match(/^# (.+)/m);
2728
+ const title = getValue("title") || titleMatch?.[1] || id;
2729
+ const descMatch = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
2730
+ const description = descMatch?.[1]?.trim() || "";
2731
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
2732
+ const notesContent = notesMatch?.[1] || "";
2733
+ const notes = notesContent.split("\n").filter((line) => line.trim().startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
2734
+ const type = getValue("type");
2735
+ return {
2736
+ id,
2737
+ title,
2738
+ description,
2739
+ status: getValue("status") || "todo",
2740
+ type,
2741
+ createdAt: new Date(getValue("created") || Date.now()),
2742
+ updatedAt: new Date(getValue("updated") || Date.now()),
2743
+ notes
2744
+ };
2745
+ } catch {
2746
+ return null;
2747
+ }
2748
+ }
2749
+ };
2750
+ }
2751
+ });
2752
+
2753
+ // src/core/tool-config.ts
2754
+ var tool_config_exports = {};
2755
+ __export(tool_config_exports, {
2756
+ ToolConfigManager: () => ToolConfigManager
2757
+ });
2758
+ import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir7, access as access4, constants as constants4 } from "fs/promises";
2759
+ import { join as join11 } from "path";
2760
+ import { homedir as homedir2 } from "os";
2761
+ import { z as z3 } from "zod";
2762
+ var ToolConfigSchema, REGISTERED_TOOLS, ToolConfigManager;
2763
+ var init_tool_config = __esm({
2764
+ "src/core/tool-config.ts"() {
2765
+ "use strict";
2766
+ init_esm_shims();
2767
+ init_logger();
2768
+ ToolConfigSchema = z3.object({
2769
+ name: z3.string(),
2770
+ status: z3.enum(["ready", "needs_config", "error"]),
2771
+ description: z3.string(),
2772
+ configMethod: z3.enum(["oauth", "manual", "none"]),
2773
+ config: z3.record(z3.unknown()).optional(),
2774
+ errorMessage: z3.string().optional()
2775
+ });
2776
+ REGISTERED_TOOLS = [
2777
+ {
2778
+ name: "figma-analysis",
2779
+ description: "Analyze Figma designs and extract design tokens using Figma API",
2780
+ configMethod: "oauth"
2781
+ }
2782
+ // Add more tools here as needed
2783
+ ];
2784
+ ToolConfigManager = class {
2785
+ config;
2786
+ toolsConfigPath;
2787
+ constructor(config) {
2788
+ this.config = config;
2789
+ this.toolsConfigPath = join11(this.config.configPath, "config", "tools.json");
2790
+ }
2791
+ /**
2792
+ * Get all registered tools with their current status
2793
+ */
2794
+ async listTools() {
2795
+ const savedConfigs = await this.loadConfigs();
2796
+ const tools = [];
2797
+ for (const tool of REGISTERED_TOOLS) {
2798
+ const saved = savedConfigs[tool.name];
2799
+ const toolConfig = {
2800
+ ...tool,
2801
+ status: this.determineStatus(tool, saved),
2802
+ config: saved?.config,
2803
+ errorMessage: saved?.errorMessage
2804
+ };
2805
+ tools.push(toolConfig);
2806
+ }
2807
+ return tools;
2808
+ }
2809
+ /**
2810
+ * Get configuration for a specific tool
2811
+ */
2812
+ async getToolConfig(toolName) {
2813
+ const tools = await this.listTools();
2814
+ return tools.find((t) => t.name === toolName) || null;
2815
+ }
2816
+ /**
2817
+ * Update tool configuration
2818
+ */
2819
+ async updateToolConfig(toolName, updates) {
2820
+ const savedConfigs = await this.loadConfigs();
2821
+ const tool = REGISTERED_TOOLS.find((t) => t.name === toolName);
2822
+ if (!tool) {
2823
+ throw new Error(`Tool not found: ${toolName}`);
2824
+ }
2825
+ const existing = savedConfigs[toolName] || {};
2826
+ savedConfigs[toolName] = {
2827
+ ...existing,
2828
+ ...updates
2829
+ };
2830
+ await this.saveConfigs(savedConfigs);
2831
+ }
2832
+ /**
2833
+ * Check if a tool is ready to use
2834
+ */
2835
+ async isToolReady(toolName) {
2836
+ const toolConfig = await this.getToolConfig(toolName);
2837
+ const isReady = toolConfig?.status === "ready";
2838
+ logger.info(`\u{1F50D} Tool ready check for ${toolName}:`, {
2839
+ hasConfig: !!toolConfig,
2840
+ status: toolConfig?.status,
2841
+ isReady
2842
+ });
2843
+ return isReady;
2844
+ }
2845
+ /**
2846
+ * Get API key for a tool (if configured)
2847
+ */
2848
+ async getApiKey(toolName) {
2849
+ const toolConfig = await this.getToolConfig(toolName);
2850
+ logger.info(`\u{1F50D} Getting API key for ${toolName}:`, {
2851
+ hasConfig: !!toolConfig,
2852
+ hasApiKey: !!toolConfig?.config?.apiKey,
2853
+ status: toolConfig?.status,
2854
+ apiKeyLength: typeof toolConfig?.config?.apiKey === "string" ? toolConfig.config.apiKey.length : 0
2855
+ });
2856
+ if (toolConfig?.config?.apiKey && typeof toolConfig.config.apiKey === "string") {
2857
+ return toolConfig.config.apiKey;
2858
+ }
2859
+ return null;
2860
+ }
2861
+ /**
2862
+ * Determine tool status based on configuration
2863
+ */
2864
+ determineStatus(tool, saved) {
2865
+ if (tool.configMethod === "none") {
2866
+ logger.info(`\u{1F50D} Tool ${tool.name} uses 'none' config method - marking as ready`);
2867
+ return "ready";
2868
+ }
2869
+ if (saved?.errorMessage) {
2870
+ logger.warn(`\u{1F50D} Tool ${tool.name} has error: ${saved.errorMessage}`);
2871
+ return "error";
2872
+ }
2873
+ if (tool.configMethod === "oauth" || tool.configMethod === "manual") {
2874
+ const hasApiKey = saved?.config?.apiKey && typeof saved.config.apiKey === "string" && saved.config.apiKey.length > 0;
2875
+ logger.info(`\u{1F50D} Tool ${tool.name} status check:`, {
2876
+ configMethod: tool.configMethod,
2877
+ hasSaved: !!saved,
2878
+ hasConfig: !!saved?.config,
2879
+ hasApiKey,
2880
+ apiKeyLength: typeof saved?.config?.apiKey === "string" ? saved.config.apiKey.length : 0
2881
+ });
2882
+ if (hasApiKey) {
2883
+ return "ready";
2884
+ }
2885
+ return "needs_config";
1589
2886
  }
2887
+ logger.warn(`\u{1F50D} Tool ${tool.name} fell through to default 'needs_config'`);
2888
+ return "needs_config";
1590
2889
  }
1591
2890
  /**
1592
- * Complete a bead with quality gates
2891
+ * Load saved configurations
2892
+ * Checks both global and project configs, project takes precedence
1593
2893
  */
1594
- async completeBead(id) {
1595
- const gates = [
1596
- { name: "Type Check", command: "npm run typecheck" },
1597
- { name: "Tests", command: "npm run test" },
1598
- { name: "Lint", command: "npm run lint" },
1599
- { name: "Build", command: "npm run build" }
1600
- ];
1601
- const results = [];
1602
- for (const gate of gates) {
1603
- try {
1604
- await execAsync(gate.command, { cwd: this.projectPath });
1605
- results.push({ name: gate.name, passed: true });
1606
- logger.success(`${gate.name}: passed`);
1607
- } catch (error) {
1608
- results.push({
1609
- name: gate.name,
1610
- passed: false,
1611
- error: error instanceof Error ? error.message.slice(0, 200) : "Failed"
1612
- });
1613
- logger.error(`${gate.name}: failed`);
1614
- }
2894
+ async loadConfigs() {
2895
+ const globalConfigPath = join11(homedir2(), ".config", "aikit", "config", "tools.json");
2896
+ let configs = {};
2897
+ try {
2898
+ await access4(globalConfigPath, constants4.R_OK);
2899
+ const content = await readFile7(globalConfigPath, "utf-8");
2900
+ configs = JSON.parse(content);
2901
+ logger.info("Loaded global tool configs");
2902
+ } catch {
1615
2903
  }
1616
- const allPassed = results.every((r) => r.passed);
1617
- if (allPassed) {
1618
- await this.updateBeadStatus(id, "completed");
1619
- await this.addNote(id, "Task completed - all quality gates passed");
1620
- const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
1621
- const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1622
- const config = await loadConfig2(this.projectPath);
1623
- const memory = new MemoryManager2(config);
1624
- await memory.createHandoff({
1625
- completed: [id],
1626
- inProgress: [],
1627
- remaining: [],
1628
- context: `Auto-generated handoff for completed task: ${id}`,
1629
- nextSteps: ["Review completed work", "Start new task if needed"]
1630
- });
1631
- } else {
1632
- await this.addNote(id, "Completion attempted but quality gates failed");
2904
+ try {
2905
+ await access4(this.toolsConfigPath, constants4.R_OK);
2906
+ const content = await readFile7(this.toolsConfigPath, "utf-8");
2907
+ const projectConfigs = JSON.parse(content);
2908
+ configs = { ...configs, ...projectConfigs };
2909
+ logger.info("Loaded project tool configs");
2910
+ } catch {
1633
2911
  }
1634
- return {
1635
- success: allPassed,
1636
- gates: results,
1637
- handoffCreated: allPassed
1638
- };
2912
+ return configs;
1639
2913
  }
1640
2914
  /**
1641
- * Get current active bead
2915
+ * Save configurations
1642
2916
  */
1643
- async getCurrentBead() {
1644
- const beads = await this.listBeads();
1645
- return beads.find((b) => b.status === "in-progress") || null;
2917
+ async saveConfigs(configs) {
2918
+ const configDir = join11(this.config.configPath, "config");
2919
+ await mkdir7(configDir, { recursive: true });
2920
+ await writeFile7(this.toolsConfigPath, JSON.stringify(configs, null, 2));
1646
2921
  }
1647
2922
  /**
1648
- * Parse a bead file
2923
+ * Configure Claude Desktop MCP server for a tool
2924
+ * This adds the MCP server configuration to Claude Desktop's config file
1649
2925
  */
1650
- parseBeadFile(fileName, content) {
2926
+ async configureMcpServer(toolName, apiKey) {
2927
+ if (toolName !== "figma-analysis") {
2928
+ logger.info(`MCP server configuration not implemented for tool: ${toolName}`);
2929
+ return;
2930
+ }
2931
+ const isWindows = process.platform === "win32";
2932
+ const claudeConfigBase = isWindows ? process.env.APPDATA || join11(homedir2(), "AppData", "Roaming") : join11(homedir2(), ".config");
2933
+ const claudeConfigPath = join11(claudeConfigBase, "claude", "claude_desktop_config.json");
1651
2934
  try {
1652
- const id = fileName.replace(".md", "");
1653
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1654
- const frontmatter = frontmatterMatch?.[1] || "";
1655
- const getValue = (key) => {
1656
- const match = frontmatter.match(new RegExp(`${key}:\\s*(.+)`));
1657
- return match?.[1]?.trim() || "";
1658
- };
1659
- const titleMatch = content.match(/^# (.+)/m);
1660
- const title = getValue("title") || titleMatch?.[1] || id;
1661
- const descMatch = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
1662
- const description = descMatch?.[1]?.trim() || "";
1663
- const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
1664
- const notesContent = notesMatch?.[1] || "";
1665
- const notes = notesContent.split("\n").filter((line) => line.trim().startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
1666
- const type = getValue("type");
1667
- return {
1668
- id,
1669
- title,
1670
- description,
1671
- status: getValue("status") || "todo",
1672
- type,
1673
- createdAt: new Date(getValue("created") || Date.now()),
1674
- updatedAt: new Date(getValue("updated") || Date.now()),
1675
- notes
2935
+ let claudeConfig = {};
2936
+ try {
2937
+ const content = await readFile7(claudeConfigPath, "utf-8");
2938
+ claudeConfig = JSON.parse(content);
2939
+ } catch {
2940
+ claudeConfig = {};
2941
+ }
2942
+ if (!claudeConfig.mcpServers) {
2943
+ claudeConfig.mcpServers = {};
2944
+ }
2945
+ claudeConfig.mcpServers.figma = {
2946
+ command: "npx",
2947
+ args: ["-y", "figma-developer-mcp", `--figma-oauth-token=${apiKey}`, "--stdio"]
1676
2948
  };
1677
- } catch {
1678
- return null;
2949
+ const claudeConfigDir = join11(claudeConfigBase, "claude");
2950
+ await mkdir7(claudeConfigDir, { recursive: true });
2951
+ await writeFile7(claudeConfigPath, JSON.stringify(claudeConfig, null, 2));
2952
+ logger.success("\u2705 Claude Desktop MCP server configured");
2953
+ logger.info(` Config file: ${claudeConfigPath}`);
2954
+ logger.info("");
2955
+ logger.info("\u26A0\uFE0F IMPORTANT: Restart Claude Desktop for changes to take effect");
2956
+ } catch (error) {
2957
+ logger.warn(`Could not configure MCP server automatically: ${error instanceof Error ? error.message : String(error)}`);
2958
+ logger.info("");
2959
+ logger.info("Please configure manually:");
2960
+ logger.info(`1. Edit: ${claudeConfigPath}`);
2961
+ logger.info('2. Add the following to "mcpServers":');
2962
+ logger.info(JSON.stringify({
2963
+ figma: {
2964
+ command: "npx",
2965
+ args: ["-y", "figma-developer-mcp"],
2966
+ env: {
2967
+ FIGMA_OAUTH_TOKEN: apiKey
2968
+ }
2969
+ }
2970
+ }, null, 2));
1679
2971
  }
1680
2972
  }
1681
2973
  };
@@ -1688,8 +2980,8 @@ __export(sessions_exports, {
1688
2980
  SessionManager: () => SessionManager,
1689
2981
  formatSession: () => formatSession
1690
2982
  });
1691
- import { readFile as readFile8, writeFile as writeFile10, readdir as readdir7, mkdir as mkdir10, access as access4, constants as constants4 } from "fs/promises";
1692
- import { join as join15 } from "path";
2983
+ import { readFile as readFile9, writeFile as writeFile11, readdir as readdir7, mkdir as mkdir11, access as access5, constants as constants5 } from "fs/promises";
2984
+ import { join as join16 } from "path";
1693
2985
  import matter3 from "gray-matter";
1694
2986
  import { exec as exec2 } from "child_process";
1695
2987
  import { promisify as promisify2 } from "util";
@@ -1721,8 +3013,8 @@ var init_sessions = __esm({
1721
3013
  aikitDir;
1722
3014
  constructor(projectPath) {
1723
3015
  this.projectPath = projectPath || process.cwd();
1724
- this.aikitDir = join15(this.projectPath, ".aikit");
1725
- this.sessionsDir = join15(this.aikitDir, "sessions");
3016
+ this.aikitDir = join16(this.projectPath, ".aikit");
3017
+ this.sessionsDir = join16(this.aikitDir, "sessions");
1726
3018
  }
1727
3019
  /**
1728
3020
  * Get current terminal's TTY path
@@ -1758,7 +3050,7 @@ var init_sessions = __esm({
1758
3050
  */
1759
3051
  async getSessionTrackerPath() {
1760
3052
  const identifier = await this.getTerminalIdentifier();
1761
- return join15(this.sessionsDir, `.current-${identifier}-session`);
3053
+ return join16(this.sessionsDir, `.current-${identifier}-session`);
1762
3054
  }
1763
3055
  /**
1764
3056
  * Switch to a different session in the current terminal
@@ -1769,7 +3061,7 @@ var init_sessions = __esm({
1769
3061
  throw new Error(`Session not found: ${sessionId}`);
1770
3062
  }
1771
3063
  const sessionFile = await this.getSessionTrackerPath();
1772
- await writeFile10(sessionFile, sessionId);
3064
+ await writeFile11(sessionFile, sessionId);
1773
3065
  }
1774
3066
  /**
1775
3067
  * Initialize the current terminal's session file
@@ -1778,9 +3070,9 @@ var init_sessions = __esm({
1778
3070
  async initTerminalSession() {
1779
3071
  const sessionFile = await this.getSessionTrackerPath();
1780
3072
  try {
1781
- await access4(sessionFile);
3073
+ await access5(sessionFile);
1782
3074
  } catch {
1783
- await writeFile10(sessionFile, "");
3075
+ await writeFile11(sessionFile, "");
1784
3076
  }
1785
3077
  return { tracker: sessionFile };
1786
3078
  }
@@ -1797,7 +3089,7 @@ var init_sessions = __esm({
1797
3089
  */
1798
3090
  async ensureAikitExists() {
1799
3091
  try {
1800
- await access4(this.aikitDir, constants4.R_OK);
3092
+ await access5(this.aikitDir, constants5.R_OK);
1801
3093
  } catch {
1802
3094
  throw new Error(
1803
3095
  `AIKit not initialized in current directory (${this.projectPath}). Run 'aikit init' first to initialize AIKit in this directory.`
@@ -1809,7 +3101,7 @@ var init_sessions = __esm({
1809
3101
  */
1810
3102
  async init() {
1811
3103
  await this.ensureAikitExists();
1812
- await mkdir10(this.sessionsDir, { recursive: true });
3104
+ await mkdir11(this.sessionsDir, { recursive: true });
1813
3105
  }
1814
3106
  /**
1815
3107
  * Start a new session
@@ -1939,9 +3231,9 @@ var init_sessions = __esm({
1939
3231
  */
1940
3232
  async getSession(id) {
1941
3233
  await this.ensureAikitExists();
1942
- const filePath = join15(this.sessionsDir, `${id}.md`);
3234
+ const filePath = join16(this.sessionsDir, `${id}.md`);
1943
3235
  try {
1944
- const content = await readFile8(filePath, "utf-8");
3236
+ const content = await readFile9(filePath, "utf-8");
1945
3237
  const { data, content: body } = matter3(content);
1946
3238
  const updates = this.parseUpdates(body);
1947
3239
  return {
@@ -2025,7 +3317,7 @@ var init_sessions = __esm({
2025
3317
  async getActiveSessionId() {
2026
3318
  const sessionFile = await this.getSessionTrackerPath();
2027
3319
  try {
2028
- const content = await readFile8(sessionFile, "utf-8");
3320
+ const content = await readFile9(sessionFile, "utf-8");
2029
3321
  return content.trim() || null;
2030
3322
  } catch {
2031
3323
  return null;
@@ -2037,7 +3329,7 @@ var init_sessions = __esm({
2037
3329
  */
2038
3330
  async setActiveSession(id) {
2039
3331
  const sessionFile = await this.getSessionTrackerPath();
2040
- await writeFile10(sessionFile, id);
3332
+ await writeFile11(sessionFile, id);
2041
3333
  }
2042
3334
  /**
2043
3335
  * Clear active session for current terminal
@@ -2045,7 +3337,7 @@ var init_sessions = __esm({
2045
3337
  async clearActiveSession() {
2046
3338
  const sessionFile = await this.getSessionTrackerPath();
2047
3339
  try {
2048
- await writeFile10(sessionFile, "");
3340
+ await writeFile11(sessionFile, "");
2049
3341
  } catch {
2050
3342
  }
2051
3343
  }
@@ -2053,7 +3345,7 @@ var init_sessions = __esm({
2053
3345
  * Save session to file
2054
3346
  */
2055
3347
  async saveSession(session) {
2056
- const filePath = join15(this.sessionsDir, `${session.id}.md`);
3348
+ const filePath = join16(this.sessionsDir, `${session.id}.md`);
2057
3349
  const updatesMarkdown = session.updates.map((update) => {
2058
3350
  const date = new Date(update.timestamp);
2059
3351
  const dateStr = date.toLocaleString();
@@ -2102,7 +3394,7 @@ ${updatesMarkdown}
2102
3394
  ${this.generateSummary(session)}
2103
3395
  `;
2104
3396
  const fileContent = matter3.stringify(content, frontmatter);
2105
- await writeFile10(filePath, fileContent);
3397
+ await writeFile11(filePath, fileContent);
2106
3398
  }
2107
3399
  /**
2108
3400
  * Generate session summary
@@ -2193,200 +3485,64 @@ ${this.generateSummary(session)}
2193
3485
  notesLines.push(line);
2194
3486
  }
2195
3487
  }
2196
- if (currentUpdate) {
2197
- currentUpdate.notes = notesLines.join("\n").trim();
2198
- updates.push(currentUpdate);
2199
- }
2200
- return updates;
2201
- }
2202
- /**
2203
- * Get git state
2204
- */
2205
- async getGitState() {
2206
- try {
2207
- const { stdout: branch } = await execAsync2("git rev-parse --abbrev-ref HEAD");
2208
- const { stdout: log } = await execAsync2("git log --oneline | wc -l");
2209
- return {
2210
- branch: branch.trim(),
2211
- commits: parseInt(log.trim(), 10)
2212
- };
2213
- } catch {
2214
- return { branch: "main" };
2215
- }
2216
- }
2217
- /**
2218
- * Get modified files
2219
- */
2220
- async getModifiedFiles() {
2221
- try {
2222
- const { stdout } = await execAsync2("git status --porcelain");
2223
- const lines = stdout.trim().split("\n");
2224
- return lines.filter((line) => line.trim()).map((line) => line.substring(3));
2225
- } catch {
2226
- return [];
2227
- }
2228
- }
2229
- /**
2230
- * Get current Beads task
2231
- * NOTE: This only reads Beads metadata for session updates.
2232
- * Sessions are NEVER stored in .beads - they are always in .aikit/sessions
2233
- */
2234
- async getCurrentBeadsTask() {
2235
- const beadsDir = join15(this.projectPath, ".beads");
2236
- try {
2237
- const files = await readdir7(beadsDir);
2238
- const beadFiles = files.filter((f) => f.startsWith("bead-") && f.endsWith(".md"));
2239
- for (const file of beadFiles) {
2240
- const content = await readFile8(join15(beadsDir, file), "utf-8");
2241
- const statusMatch = content.match(/^status:\s*(\w+)/m);
2242
- const id = file.replace(".md", "");
2243
- if (statusMatch && (statusMatch[1] === "in-progress" || statusMatch[1] === "todo")) {
2244
- return {
2245
- id,
2246
- status: statusMatch[1]
2247
- };
2248
- }
2249
- }
2250
- return null;
2251
- } catch {
2252
- return null;
2253
- }
2254
- }
2255
- };
2256
- }
2257
- });
2258
-
2259
- // src/core/tool-config.ts
2260
- var tool_config_exports = {};
2261
- __export(tool_config_exports, {
2262
- ToolConfigManager: () => ToolConfigManager
2263
- });
2264
- import { readFile as readFile15, writeFile as writeFile18, mkdir as mkdir16, access as access7, constants as constants5 } from "fs/promises";
2265
- import { join as join23 } from "path";
2266
- import { z as z3 } from "zod";
2267
- var ToolConfigSchema, REGISTERED_TOOLS, ToolConfigManager;
2268
- var init_tool_config = __esm({
2269
- "src/core/tool-config.ts"() {
2270
- "use strict";
2271
- init_esm_shims();
2272
- ToolConfigSchema = z3.object({
2273
- name: z3.string(),
2274
- status: z3.enum(["ready", "needs_config", "error"]),
2275
- description: z3.string(),
2276
- configMethod: z3.enum(["oauth", "manual", "none"]),
2277
- config: z3.record(z3.unknown()).optional(),
2278
- errorMessage: z3.string().optional()
2279
- });
2280
- REGISTERED_TOOLS = [
2281
- {
2282
- name: "figma-analysis",
2283
- description: "Analyze Figma designs and extract design tokens using Figma API",
2284
- configMethod: "oauth"
2285
- }
2286
- // Add more tools here as needed
2287
- ];
2288
- ToolConfigManager = class {
2289
- config;
2290
- toolsConfigPath;
2291
- constructor(config) {
2292
- this.config = config;
2293
- this.toolsConfigPath = join23(this.config.configPath, "config", "tools.json");
2294
- }
2295
- /**
2296
- * Get all registered tools with their current status
2297
- */
2298
- async listTools() {
2299
- const savedConfigs = await this.loadConfigs();
2300
- const tools = [];
2301
- for (const tool of REGISTERED_TOOLS) {
2302
- const saved = savedConfigs[tool.name];
2303
- const toolConfig = {
2304
- ...tool,
2305
- status: this.determineStatus(tool, saved),
2306
- config: saved?.config,
2307
- errorMessage: saved?.errorMessage
2308
- };
2309
- tools.push(toolConfig);
2310
- }
2311
- return tools;
2312
- }
2313
- /**
2314
- * Get configuration for a specific tool
2315
- */
2316
- async getToolConfig(toolName) {
2317
- const tools = await this.listTools();
2318
- return tools.find((t) => t.name === toolName) || null;
2319
- }
2320
- /**
2321
- * Update tool configuration
2322
- */
2323
- async updateToolConfig(toolName, updates) {
2324
- const savedConfigs = await this.loadConfigs();
2325
- const tool = REGISTERED_TOOLS.find((t) => t.name === toolName);
2326
- if (!tool) {
2327
- throw new Error(`Tool not found: ${toolName}`);
2328
- }
2329
- const existing = savedConfigs[toolName] || {};
2330
- savedConfigs[toolName] = {
2331
- ...existing,
2332
- ...updates
2333
- };
2334
- await this.saveConfigs(savedConfigs);
2335
- }
2336
- /**
2337
- * Check if a tool is ready to use
2338
- */
2339
- async isToolReady(toolName) {
2340
- const toolConfig = await this.getToolConfig(toolName);
2341
- return toolConfig?.status === "ready";
2342
- }
2343
- /**
2344
- * Get API key for a tool (if configured)
2345
- */
2346
- async getApiKey(toolName) {
2347
- const toolConfig = await this.getToolConfig(toolName);
2348
- if (toolConfig?.config?.apiKey && typeof toolConfig.config.apiKey === "string") {
2349
- return toolConfig.config.apiKey;
3488
+ if (currentUpdate) {
3489
+ currentUpdate.notes = notesLines.join("\n").trim();
3490
+ updates.push(currentUpdate);
2350
3491
  }
2351
- return null;
3492
+ return updates;
2352
3493
  }
2353
3494
  /**
2354
- * Determine tool status based on configuration
3495
+ * Get git state
2355
3496
  */
2356
- determineStatus(tool, saved) {
2357
- if (tool.configMethod === "none") {
2358
- return "ready";
2359
- }
2360
- if (saved?.errorMessage) {
2361
- return "error";
2362
- }
2363
- if (tool.configMethod === "oauth" || tool.configMethod === "manual") {
2364
- if (saved?.config?.apiKey && typeof saved.config.apiKey === "string" && saved.config.apiKey.length > 0) {
2365
- return "ready";
2366
- }
2367
- return "needs_config";
3497
+ async getGitState() {
3498
+ try {
3499
+ const { stdout: branch } = await execAsync2("git rev-parse --abbrev-ref HEAD");
3500
+ const { stdout: log } = await execAsync2("git log --oneline | wc -l");
3501
+ return {
3502
+ branch: branch.trim(),
3503
+ commits: parseInt(log.trim(), 10)
3504
+ };
3505
+ } catch {
3506
+ return { branch: "main" };
2368
3507
  }
2369
- return "needs_config";
2370
3508
  }
2371
3509
  /**
2372
- * Load saved configurations
3510
+ * Get modified files
2373
3511
  */
2374
- async loadConfigs() {
3512
+ async getModifiedFiles() {
2375
3513
  try {
2376
- await access7(this.toolsConfigPath, constants5.R_OK);
2377
- const content = await readFile15(this.toolsConfigPath, "utf-8");
2378
- return JSON.parse(content);
3514
+ const { stdout } = await execAsync2("git status --porcelain");
3515
+ const lines = stdout.trim().split("\n");
3516
+ return lines.filter((line) => line.trim()).map((line) => line.substring(3));
2379
3517
  } catch {
2380
- return {};
3518
+ return [];
2381
3519
  }
2382
3520
  }
2383
3521
  /**
2384
- * Save configurations
3522
+ * Get current Beads task
3523
+ * NOTE: This only reads Beads metadata for session updates.
3524
+ * Sessions are NEVER stored in .beads - they are always in .aikit/sessions
2385
3525
  */
2386
- async saveConfigs(configs) {
2387
- const configDir = join23(this.config.configPath, "config");
2388
- await mkdir16(configDir, { recursive: true });
2389
- await writeFile18(this.toolsConfigPath, JSON.stringify(configs, null, 2));
3526
+ async getCurrentBeadsTask() {
3527
+ const beadsDir = join16(this.projectPath, ".beads");
3528
+ try {
3529
+ const files = await readdir7(beadsDir);
3530
+ const beadFiles = files.filter((f) => f.startsWith("bead-") && f.endsWith(".md"));
3531
+ for (const file of beadFiles) {
3532
+ const content = await readFile9(join16(beadsDir, file), "utf-8");
3533
+ const statusMatch = content.match(/^status:\s*(\w+)/m);
3534
+ const id = file.replace(".md", "");
3535
+ if (statusMatch && (statusMatch[1] === "in-progress" || statusMatch[1] === "todo")) {
3536
+ return {
3537
+ id,
3538
+ status: statusMatch[1]
3539
+ };
3540
+ }
3541
+ }
3542
+ return null;
3543
+ } catch {
3544
+ return null;
3545
+ }
2390
3546
  }
2391
3547
  };
2392
3548
  }
@@ -5643,7 +6799,15 @@ This will guide you through setting up your Figma Personal Access Token.`;
5643
6799
  }
5644
6800
  try {
5645
6801
  const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
5646
- const client = new FigmaMcpClient2(apiKey, configManager);
6802
+ let database;
6803
+ try {
6804
+ if (context?.config) {
6805
+ database = context.config.getDatabase();
6806
+ }
6807
+ } catch (error) {
6808
+ logger.info("Database not available for Figma persistence");
6809
+ }
6810
+ const client = new FigmaMcpClient2(apiKey, configManager, database);
5647
6811
  const assetsDir = "./assets/images";
5648
6812
  const tokens = await client.extractDesignTokens(url, false, assetsDir);
5649
6813
  let result = `# Figma Design Analysis
@@ -5874,25 +7038,6 @@ Please check:
5874
7038
  }
5875
7039
  }
5876
7040
  },
5877
- {
5878
- name: "analyze_figma",
5879
- description: "Analyze a Figma design URL and extract all design tokens automatically. The URL should be provided in the user input after the command.",
5880
- args: {
5881
- url: {
5882
- type: "string",
5883
- description: "Figma design URL to analyze",
5884
- required: true
5885
- }
5886
- },
5887
- async execute({ url }) {
5888
- return `Figma analysis tool called for: ${url}
5889
-
5890
- Next steps:
5891
- 1. Use @vision agent to analyze the design
5892
- 2. Extract all design tokens
5893
- 3. Save to memory/research/figma-analysis.md`;
5894
- }
5895
- },
5896
7041
  {
5897
7042
  name: "develop_figma_screen",
5898
7043
  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.",
@@ -5924,7 +7069,15 @@ Next steps:
5924
7069
  try {
5925
7070
  const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
5926
7071
  const { checkCurrentCodeStatus: checkCurrentCodeStatus2, compareCodeWithFigma: compareCodeWithFigma2 } = await Promise.resolve().then(() => (init_figma_screen_developer(), figma_screen_developer_exports));
5927
- const client = new FigmaMcpClient2(apiKey, configManager);
7072
+ let database;
7073
+ try {
7074
+ if (context?.config) {
7075
+ database = context.config.getDatabase();
7076
+ }
7077
+ } catch (error) {
7078
+ logger.info("Database not available for Figma persistence");
7079
+ }
7080
+ const client = new FigmaMcpClient2(apiKey, configManager, database);
5928
7081
  const tokens = await client.extractDesignTokens(figmaUrl, false, "./assets/images");
5929
7082
  const screenIdStr = String(screenId);
5930
7083
  const selectedScreen = tokens.screens?.find(
@@ -6485,12 +7638,15 @@ ${argsDesc}
6485
7638
  }
6486
7639
  };
6487
7640
 
7641
+ // src/index.ts
7642
+ init_tool_config();
7643
+
6488
7644
  // src/core/plugins.ts
6489
7645
  init_esm_shims();
6490
7646
  init_paths();
6491
7647
  init_logger();
6492
- import { readdir as readdir6, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
6493
- import { join as join11, basename as basename3, extname as extname4 } from "path";
7648
+ import { readdir as readdir6, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
7649
+ import { join as join12, basename as basename3, extname as extname4 } from "path";
6494
7650
  var PluginSystem = class {
6495
7651
  config;
6496
7652
  plugins = /* @__PURE__ */ new Map();
@@ -6588,9 +7744,9 @@ var PluginSystem = class {
6588
7744
  async createPlugin(name, options) {
6589
7745
  const configPath = options.global ? paths.globalConfig() : this.config.configPath;
6590
7746
  const pluginsDir = paths.plugins(configPath);
6591
- await mkdir7(pluginsDir, { recursive: true });
7747
+ await mkdir8(pluginsDir, { recursive: true });
6592
7748
  const fileName = `${name}.ts`;
6593
- const filePath = join11(pluginsDir, fileName);
7749
+ const filePath = join12(pluginsDir, fileName);
6594
7750
  const content = `import { Plugin } from 'aikit';
6595
7751
 
6596
7752
  /**
@@ -6606,7 +7762,7 @@ ${options.code}
6606
7762
 
6607
7763
  export default ${toPascalCase(name)}Plugin;
6608
7764
  `;
6609
- await writeFile7(filePath, content);
7765
+ await writeFile8(filePath, content);
6610
7766
  }
6611
7767
  /**
6612
7768
  * Process event queue
@@ -6680,7 +7836,7 @@ export default ${toPascalCase(name)}Plugin;
6680
7836
  }
6681
7837
  for (const file of files) {
6682
7838
  if (extname4(file) !== ".ts" && extname4(file) !== ".js") continue;
6683
- const filePath = join11(dir, file);
7839
+ const filePath = join12(dir, file);
6684
7840
  const name = basename3(file, extname4(file));
6685
7841
  this.plugins.set(name, {
6686
7842
  name,
@@ -6827,9 +7983,9 @@ async function getLatestVersion(packageName) {
6827
7983
  // src/utils/update-cache.ts
6828
7984
  init_esm_shims();
6829
7985
  init_paths();
6830
- import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
7986
+ import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
6831
7987
  import { existsSync as existsSync5 } from "fs";
6832
- import { join as join12 } from "path";
7988
+ import { join as join13 } from "path";
6833
7989
  var CACHE_FILE = ".update-cache.json";
6834
7990
  var DEFAULT_CACHE = {
6835
7991
  lastCheckTime: 0,
@@ -6838,7 +7994,7 @@ var DEFAULT_CACHE = {
6838
7994
  };
6839
7995
  function getCachePath() {
6840
7996
  const configDir = paths.globalConfig();
6841
- return join12(configDir, CACHE_FILE);
7997
+ return join13(configDir, CACHE_FILE);
6842
7998
  }
6843
7999
  async function readCache() {
6844
8000
  try {
@@ -6846,7 +8002,7 @@ async function readCache() {
6846
8002
  if (!existsSync5(cachePath)) {
6847
8003
  return DEFAULT_CACHE;
6848
8004
  }
6849
- const content = await readFile7(cachePath, "utf-8");
8005
+ const content = await readFile8(cachePath, "utf-8");
6850
8006
  const data = JSON.parse(content);
6851
8007
  return { ...DEFAULT_CACHE, ...data };
6852
8008
  } catch {
@@ -6858,9 +8014,9 @@ async function writeCache(data) {
6858
8014
  const cachePath = getCachePath();
6859
8015
  const configDir = paths.globalConfig();
6860
8016
  if (!existsSync5(configDir)) {
6861
- await mkdir8(configDir, { recursive: true });
8017
+ await mkdir9(configDir, { recursive: true });
6862
8018
  }
6863
- await writeFile8(cachePath, JSON.stringify(data, null, 2), "utf-8");
8019
+ await writeFile9(cachePath, JSON.stringify(data, null, 2), "utf-8");
6864
8020
  } catch {
6865
8021
  }
6866
8022
  }
@@ -6899,16 +8055,16 @@ async function incrementError(error) {
6899
8055
  init_esm_shims();
6900
8056
  init_paths();
6901
8057
  import { spawn } from "child_process";
6902
- import { writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
6903
- import { join as join13 } from "path";
8058
+ import { writeFile as writeFile10, mkdir as mkdir10 } from "fs/promises";
8059
+ import { join as join14 } from "path";
6904
8060
  async function spawnUpdateProcess(targetVersion) {
6905
8061
  try {
6906
8062
  const configDir = paths.globalConfig();
6907
- const logsDir = join13(configDir, "logs");
6908
- await mkdir9(logsDir, { recursive: true });
8063
+ const logsDir = join14(configDir, "logs");
8064
+ await mkdir10(logsDir, { recursive: true });
6909
8065
  const scriptContent = generateUpdateScript(targetVersion, logsDir);
6910
- const scriptPath = join13(configDir, "update-script.js");
6911
- await writeFile9(scriptPath, scriptContent, "utf-8");
8066
+ const scriptPath = join14(configDir, "update-script.js");
8067
+ await writeFile10(scriptPath, scriptContent, "utf-8");
6912
8068
  const child = spawn(process.execPath, [scriptPath], {
6913
8069
  detached: true,
6914
8070
  stdio: "ignore",
@@ -7082,8 +8238,8 @@ init_beads();
7082
8238
  init_esm_shims();
7083
8239
  import { execSync } from "child_process";
7084
8240
  import { existsSync as existsSync6 } from "fs";
7085
- import { join as join14 } from "path";
7086
- import { homedir as homedir2 } from "os";
8241
+ import { join as join15 } from "path";
8242
+ import { homedir as homedir3 } from "os";
7087
8243
  var CliPlatform = /* @__PURE__ */ ((CliPlatform2) => {
7088
8244
  CliPlatform2["OPENCODE"] = "opencode";
7089
8245
  CliPlatform2["CLAUDE"] = "claude";
@@ -7096,8 +8252,8 @@ var CliDetector = class {
7096
8252
  */
7097
8253
  static async checkOpenCode() {
7098
8254
  try {
7099
- const opencodePath = process.platform === "win32" ? process.env.APPDATA || join14(homedir2(), "AppData", "Roaming") : join14(homedir2(), ".config");
7100
- const opencodeConfig = join14(opencodePath, "opencode", "opencode.json");
8255
+ const opencodePath = process.platform === "win32" ? process.env.APPDATA || join15(homedir3(), "AppData", "Roaming") : join15(homedir3(), ".config");
8256
+ const opencodeConfig = join15(opencodePath, "opencode", "opencode.json");
7101
8257
  let installed = existsSync6(opencodeConfig);
7102
8258
  let version;
7103
8259
  if (installed) {
@@ -7202,14 +8358,14 @@ var CliDetector = class {
7202
8358
  */
7203
8359
  static async detectPlatforms() {
7204
8360
  const platforms = [];
7205
- const opencodePath = process.platform === "win32" ? process.env.APPDATA || join14(homedir2(), "AppData", "Roaming") : join14(homedir2(), ".config");
8361
+ const opencodePath = process.platform === "win32" ? process.env.APPDATA || join15(homedir3(), "AppData", "Roaming") : join15(homedir3(), ".config");
7206
8362
  platforms.push({
7207
8363
  platform: "opencode" /* OPENCODE */,
7208
8364
  displayName: "OpenCode",
7209
- installed: existsSync6(join14(opencodePath, "opencode", "opencode.json")),
8365
+ installed: existsSync6(join15(opencodePath, "opencode", "opencode.json")),
7210
8366
  configPath: opencodePath
7211
8367
  });
7212
- const claudePath = process.platform === "win32" ? process.env.APPDATA || join14(homedir2(), "AppData", "Roaming") : join14(homedir2(), ".claude");
8368
+ const claudePath = process.platform === "win32" ? process.env.APPDATA || join15(homedir3(), "AppData", "Roaming") : join15(homedir3(), ".claude");
7213
8369
  platforms.push({
7214
8370
  platform: "claude" /* CLAUDE */,
7215
8371
  displayName: "Claude Code CLI",
@@ -7247,15 +8403,15 @@ init_paths();
7247
8403
  // src/cli/helpers.ts
7248
8404
  init_esm_shims();
7249
8405
  import { existsSync as existsSync7 } from "fs";
7250
- import { mkdir as mkdir11, writeFile as writeFile11, readFile as readFile9, access as access5 } from "fs/promises";
7251
- import { join as join16, dirname as dirname2 } from "path";
7252
- import { homedir as homedir3 } from "os";
8406
+ import { mkdir as mkdir12, writeFile as writeFile12, readFile as readFile10, access as access6 } from "fs/promises";
8407
+ import { join as join17, dirname as dirname2 } from "path";
8408
+ import { homedir as homedir4 } from "os";
7253
8409
  import { fileURLToPath as fileURLToPath3 } from "url";
7254
8410
  import { execSync as execSync2 } from "child_process";
7255
8411
  init_config();
7256
8412
  init_logger();
7257
8413
  init_paths();
7258
- async function initializeConfig(configDir, _isGlobal) {
8414
+ async function initializeConfig(configDir, _isGlobal, platformConfig) {
7259
8415
  const dirs = [
7260
8416
  "",
7261
8417
  "skills",
@@ -7274,10 +8430,17 @@ async function initializeConfig(configDir, _isGlobal) {
7274
8430
  "memory/research"
7275
8431
  ];
7276
8432
  for (const dir of dirs) {
7277
- await mkdir11(join16(configDir, dir), { recursive: true });
8433
+ await mkdir12(join17(configDir, dir), { recursive: true });
7278
8434
  }
8435
+ const platform = platformConfig || {
8436
+ primary: "opencode",
8437
+ opencode: true,
8438
+ claude: false
8439
+ // Archived - can be re-enabled later
8440
+ };
7279
8441
  const defaultConfig = {
7280
8442
  version: getVersion(),
8443
+ platform,
7281
8444
  skills: { enabled: true },
7282
8445
  agents: { enabled: true, default: "build" },
7283
8446
  commands: { enabled: true },
@@ -7285,10 +8448,11 @@ async function initializeConfig(configDir, _isGlobal) {
7285
8448
  plugins: { enabled: true },
7286
8449
  memory: { enabled: true },
7287
8450
  beads: { enabled: true },
7288
- antiHallucination: { enabled: true }
8451
+ antiHallucination: { enabled: true },
8452
+ database: { enabled: true, path: ".aikit/figma.db" }
7289
8453
  };
7290
- await writeFile11(
7291
- join16(configDir, "aikit.json"),
8454
+ await writeFile12(
8455
+ join17(configDir, "aikit.json"),
7292
8456
  JSON.stringify(defaultConfig, null, 2)
7293
8457
  );
7294
8458
  const agentsMd = `# AIKit Agent Rules
@@ -7311,20 +8475,20 @@ async function initializeConfig(configDir, _isGlobal) {
7311
8475
  ## Project-Specific Rules
7312
8476
  Add your project-specific rules here.
7313
8477
  `;
7314
- await writeFile11(join16(configDir, "AGENTS.md"), agentsMd);
8478
+ await writeFile12(join17(configDir, "AGENTS.md"), agentsMd);
7315
8479
  }
7316
8480
  async function configureMcpServer(projectPath) {
7317
8481
  const currentFile = fileURLToPath3(import.meta.url);
7318
8482
  const currentDir = dirname2(currentFile);
7319
- const aikitPath = join16(currentDir, "..", "..");
7320
- const mcpServerPath = join16(aikitPath, "dist", "mcp-server.js");
8483
+ const aikitPath = join17(currentDir, "..", "..");
8484
+ const mcpServerPath = join17(aikitPath, "dist", "mcp-server.js");
7321
8485
  const configLocations = [
7322
8486
  // Global config (most common)
7323
- join16(homedir3(), ".config", "opencode", "opencode.json"),
8487
+ join17(homedir4(), ".config", "opencode", "opencode.json"),
7324
8488
  // Project-level config
7325
- join16(projectPath, ".opencode", "opencode.json"),
8489
+ join17(projectPath, ".opencode", "opencode.json"),
7326
8490
  // Alternative global location
7327
- join16(homedir3(), ".opencode", "opencode.json")
8491
+ join17(homedir4(), ".opencode", "opencode.json")
7328
8492
  ];
7329
8493
  const mcpServerConfig = {
7330
8494
  type: "local",
@@ -7333,12 +8497,12 @@ async function configureMcpServer(projectPath) {
7333
8497
  };
7334
8498
  for (const configPath of configLocations) {
7335
8499
  try {
7336
- const configDir = join16(configPath, "..");
7337
- await mkdir11(configDir, { recursive: true });
8500
+ const configDir = join17(configPath, "..");
8501
+ await mkdir12(configDir, { recursive: true });
7338
8502
  let config = {};
7339
8503
  if (existsSync7(configPath)) {
7340
8504
  try {
7341
- const existing = await readFile9(configPath, "utf-8");
8505
+ const existing = await readFile10(configPath, "utf-8");
7342
8506
  config = JSON.parse(existing);
7343
8507
  } catch {
7344
8508
  config = {};
@@ -7348,7 +8512,7 @@ async function configureMcpServer(projectPath) {
7348
8512
  config.mcp = {};
7349
8513
  }
7350
8514
  config.mcp.aikit = mcpServerConfig;
7351
- await writeFile11(configPath, JSON.stringify(config, null, 2));
8515
+ await writeFile12(configPath, JSON.stringify(config, null, 2));
7352
8516
  logger.success(`
7353
8517
  \u2705 MCP server configured: ${configPath}`);
7354
8518
  logger.info(` Server: node ${mcpServerPath}`);
@@ -7357,9 +8521,9 @@ async function configureMcpServer(projectPath) {
7357
8521
  continue;
7358
8522
  }
7359
8523
  }
7360
- const instructionsPath = join16(projectPath, ".opencode", "MCP_SETUP.md");
7361
- await mkdir11(join16(projectPath, ".opencode"), { recursive: true });
7362
- await writeFile11(instructionsPath, `# AIKit MCP Server Configuration
8524
+ const instructionsPath = join17(projectPath, ".opencode", "MCP_SETUP.md");
8525
+ await mkdir12(join17(projectPath, ".opencode"), { recursive: true });
8526
+ await writeFile12(instructionsPath, `# AIKit MCP Server Configuration
7363
8527
 
7364
8528
  ## Automatic Setup Failed
7365
8529
 
@@ -7531,50 +8695,18 @@ Analyze a Figma design and extract design tokens
7531
8695
  ## Examples
7532
8696
  - \`/analyze-figma https://www.figma.com/design/...\`
7533
8697
 
7534
- ## \u26A0\uFE0F CRITICAL: Extract URL FIRST!
7535
-
7536
- **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!
7537
-
7538
- **The URL pattern**: Look for text containing \`figma.com/design/\` anywhere in the user's message.
7539
-
7540
- **Example of what user input looks like**:
7541
- \`\`\`
7542
- /analyze-figma https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/
7543
- Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0
7544
- \`\`\`
7545
-
7546
- **Extract the complete URL** (combine if split):
7547
- \`https://www.figma.com/design/lC34qpTSy2MYalTIOsj8S2/Online-Education-Website-Free-Template--Community-?t=7G5yzTiEtJlIZBtY-0\`
7548
-
7549
8698
  ## Workflow
7550
8699
 
7551
- **IMPORTANT**: When user provides a Figma URL, you MUST immediately:
8700
+ Analyze a Figma design and extract all design tokens automatically using Figma API.
7552
8701
 
7553
- **Step 1: Extract URL from User Input**
8702
+ **Step 1: Extract Figma URL**
8703
+
8704
+ The Figma URL is provided as: \`$ARGUMENTS\`
7554
8705
 
7555
- **CRITICAL**: The URL is ALWAYS in the user's input message! DO NOT ask for it - just extract it!
7556
-
7557
- **MANDATORY**: You MUST extract the URL before proceeding. This is not optional!
7558
-
7559
- **How to Extract**:
7560
- 1. **Read the ENTIRE user input message** - look at ALL lines, not just the first line
7561
- 2. **Search for ANY text containing** \`figma.com/design/\` - this is the URL
7562
- 3. **URL may appear in different formats**:
7563
- - On same line: \`/analyze-figma https://www.figma.com/design/...\`
7564
- - Split across lines
7565
- 4. **Extract the COMPLETE URL**:
7566
- - Start from \`https://\` or \`http://\`
7567
- - Include everything until the end of the line or next whitespace
7568
- - If URL is split, combine ALL parts into one complete URL
7569
- 5. **Include ALL query parameters**: \`?node-id=...\`, \`&t=...\`, etc.
7570
-
7571
- **CRITICAL RULES**:
7572
- - \u2705 DO: Read the ENTIRE user message (all lines)
7573
- - \u2705 DO: Look for \`figma.com/design/\` anywhere in the message
7574
- - \u2705 DO: Combine split lines into one URL
7575
- - \u274C DO NOT: Ask user for URL - it's ALWAYS in the input
7576
- - \u274C DO NOT: Skip this step - URL extraction is MANDATORY
7577
- - \u274C DO NOT: Proceed without extracting URL first
8706
+ Extract the URL from \`$ARGUMENTS\`:
8707
+ - The URL pattern: \`https://www.figma.com/design/...\` or \`http://www.figma.com/design/...\`
8708
+ - Extract the ENTIRE URL including all query parameters
8709
+ - If URL contains spaces or line breaks, clean and combine them
7578
8710
 
7579
8711
  **Step 2: Check Tool Configuration**
7580
8712
 
@@ -7582,56 +8714,50 @@ Before calling the tool, verify that Figma tool is configured:
7582
8714
  - If not configured, inform user to run: \`aikit skills figma-analysis config\`
7583
8715
  - The tool requires a Figma Personal Access Token
7584
8716
 
7585
- **Step 3: Call MCP Tool read_figma_design**
8717
+ **Step 3: Call MCP Tool**
7586
8718
 
7587
- Use the MCP tool \`read_figma_design\` with the extracted URL:
8719
+ Use the MCP Figma tool to analyze the design:
7588
8720
  \`\`\`
7589
- Use tool: read_figma_design
7590
- Arguments: { "url": "[extracted URL]" }
8721
+ Use MCP tool: mcp__figma__get_figma_data
8722
+ Arguments: {
8723
+ "fileKey": "[extracted file key from URL]"
8724
+ }
7591
8725
  \`\`\`
7592
8726
 
7593
- **Step 4: Format and Save**
7594
-
7595
- Format extracted tokens as structured markdown and save using memory-update tool.
7596
-
7597
- **Step 5: Report Results**
7598
-
7599
- Report what was extracted:
7600
- - Number of screens found
7601
- - Number of colors in palette
7602
- - Typography styles found
7603
- - Components identified
8727
+ **Step 4: Format and Save Results**
7604
8728
 
7605
- ## Critical Instructions
8729
+ Format extracted tokens as structured markdown with:
8730
+ - Colors (from fills and strokes)
8731
+ - Typography (font families, sizes, weights, line heights)
8732
+ - Spacing system
8733
+ - Components
8734
+ - Screens/Frames
8735
+ - Layout information
7606
8736
 
7607
- - **DO NOT** ask user to "share the Figma URL" - they already provided it in the command
7608
- - **DO NOT** wait for confirmation - just start analyzing immediately
7609
- - **DO** extract URL from full user input message
7610
- - **DO** call MCP tool \`read_figma_design\` immediately
7611
- - **DO** save to memory automatically`;
8737
+ **Category**: design`;
7612
8738
  }
7613
8739
  async function installToOpenCode(_opencodePath) {
7614
8740
  const projectPath = process.cwd();
7615
- const opencodeCommandDir = join16(projectPath, ".opencode", "command");
7616
- const aikitDir = join16(projectPath, ".aikit");
7617
- const opencodeAgentDir = join16(paths.opencodeConfig(), "agent");
7618
- await mkdir11(opencodeCommandDir, { recursive: true });
7619
- await mkdir11(join16(aikitDir, "skills"), { recursive: true });
7620
- await mkdir11(opencodeAgentDir, { recursive: true });
8741
+ const opencodeCommandDir = join17(projectPath, ".opencode", "command");
8742
+ const aikitDir = join17(projectPath, ".aikit");
8743
+ const opencodeAgentDir = join17(paths.opencodeConfig(), "agent");
8744
+ await mkdir12(opencodeCommandDir, { recursive: true });
8745
+ await mkdir12(join17(aikitDir, "skills"), { recursive: true });
8746
+ await mkdir12(opencodeAgentDir, { recursive: true });
7621
8747
  for (const [name, content] of Object.entries(AGENT_FILES)) {
7622
- const filePath = join16(opencodeAgentDir, `${name}.md`);
8748
+ const filePath = join17(opencodeAgentDir, `${name}.md`);
7623
8749
  try {
7624
- await access5(filePath);
7625
- const existingContent = await readFile9(filePath, "utf8");
8750
+ await access6(filePath);
8751
+ const existingContent = await readFile10(filePath, "utf8");
7626
8752
  if (!existingContent.includes("mode: subagent")) {
7627
8753
  const matter5 = await import("gray-matter");
7628
8754
  const { data: frontmatter, content: body } = matter5.default(existingContent);
7629
8755
  frontmatter.mode = "subagent";
7630
8756
  const updatedContent = matter5.default.stringify(body, frontmatter);
7631
- await writeFile11(filePath, updatedContent, "utf8");
8757
+ await writeFile12(filePath, updatedContent, "utf8");
7632
8758
  }
7633
8759
  } catch {
7634
- await writeFile11(filePath, content, "utf8");
8760
+ await writeFile12(filePath, content, "utf8");
7635
8761
  }
7636
8762
  }
7637
8763
  const config = await loadConfig();
@@ -7693,27 +8819,6 @@ ${cmd.description}
7693
8819
  ## Examples
7694
8820
  ${examples}
7695
8821
 
7696
- ## \u26A0\uFE0F CRITICAL: The User Has Already Provided Arguments!
7697
-
7698
- **The user has provided arguments with this command!**
7699
-
7700
- The arguments are available in this command response - look at the command workflow below, which now includes explicit instructions to use the provided arguments.
7701
-
7702
- **YOUR JOB**:
7703
- 1. Follow the command workflow steps
7704
- 2. The workflow will tell you to look at "Arguments Provided" section
7705
- 3. Use those arguments - do NOT ask the user for this information!
7706
- 4. They have already provided it - extract and use it!
7707
-
7708
- **Example Scenario**:
7709
- - User runs: \`/${cmd.name} snake game with html & css\`
7710
- - Command: \`/${cmd.name}\`
7711
- - Arguments to use: \`snake game with html & css\`
7712
- - You must use "snake game with html & css" as provided in the workflow!
7713
-
7714
- **DO NOT**: Ask "Please provide a task description"
7715
- **DO**: Follow the workflow and use the arguments provided in it!
7716
-
7717
8822
  ## Workflow
7718
8823
  ${cmd.content}
7719
8824
 
@@ -7722,8 +8827,8 @@ ${cmd.content}
7722
8827
  }
7723
8828
  let count = 0;
7724
8829
  for (const [name, content] of Object.entries(opencodeCommands)) {
7725
- const filePath = join16(opencodeCommandDir, `${name}.md`);
7726
- await writeFile11(filePath, content.trim());
8830
+ const filePath = join17(opencodeCommandDir, `${name}.md`);
8831
+ await writeFile12(filePath, content.trim());
7727
8832
  logger.info(` \u2713 Created /${name} command`);
7728
8833
  count++;
7729
8834
  }
@@ -8133,19 +9238,19 @@ init_esm_shims();
8133
9238
  // src/platform/opencode-adapter.ts
8134
9239
  init_esm_shims();
8135
9240
  init_paths();
8136
- import { readFile as readFile10, writeFile as writeFile12, mkdir as mkdir12, access as access6 } from "fs/promises";
8137
- import { join as join17 } from "path";
9241
+ import { readFile as readFile11, writeFile as writeFile13, mkdir as mkdir13, access as access7 } from "fs/promises";
9242
+ import { join as join18 } from "path";
8138
9243
  var OpenCodeAdapter = class {
8139
9244
  platform = "opencode" /* OPENCODE */;
8140
9245
  displayName = "OpenCode";
8141
9246
  getCommandsDir() {
8142
- return join17(process.cwd(), ".opencode", "command");
9247
+ return join18(process.cwd(), ".opencode", "command");
8143
9248
  }
8144
9249
  getSkillsDir() {
8145
- return join17(process.cwd(), ".opencode", "skill");
9250
+ return join18(process.cwd(), ".opencode", "skill");
8146
9251
  }
8147
9252
  getAgentsDir() {
8148
- return join17(paths.opencodeConfig(), "agent");
9253
+ return join18(paths.opencodeConfig(), "agent");
8149
9254
  }
8150
9255
  async transformCommand(command) {
8151
9256
  const name = command.name.replace(/:/g, "-");
@@ -8168,39 +9273,41 @@ var OpenCodeAdapter = class {
8168
9273
  }
8169
9274
  async installCommand(name, content) {
8170
9275
  const dir = this.getCommandsDir();
8171
- await mkdir12(dir, { recursive: true });
8172
- await writeFile12(join17(dir, `${name}.md`), content);
9276
+ await mkdir13(dir, { recursive: true });
9277
+ await writeFile13(join18(dir, `${name}.md`), content);
8173
9278
  }
8174
9279
  async installSkill(_name, directory, files) {
8175
9280
  const baseDir = this.getSkillsDir();
8176
- const targetDir = directory ? join17(baseDir, directory) : baseDir;
8177
- await mkdir12(targetDir, { recursive: true });
9281
+ const targetDir = directory ? join18(baseDir, directory) : baseDir;
9282
+ await mkdir13(targetDir, { recursive: true });
8178
9283
  for (const [filename, content] of Object.entries(files)) {
8179
- await writeFile12(join17(targetDir, filename), content);
9284
+ await writeFile13(join18(targetDir, filename), content);
8180
9285
  }
8181
9286
  }
8182
9287
  async installAgent(name, content) {
8183
9288
  const dir = this.getAgentsDir();
8184
- await mkdir12(dir, { recursive: true });
8185
- const filePath = join17(dir, `${name}.md`);
9289
+ await mkdir13(dir, { recursive: true });
9290
+ const filePath = join18(dir, `${name}.md`);
8186
9291
  try {
8187
- await access6(filePath);
8188
- const existingContent = await readFile10(filePath, "utf-8");
9292
+ await access7(filePath);
9293
+ const existingContent = await readFile11(filePath, "utf-8");
8189
9294
  if (!existingContent.includes("mode: subagent")) {
8190
9295
  const matter5 = await import("gray-matter");
8191
9296
  const { data: frontmatter, content: body } = matter5.default(existingContent);
8192
9297
  frontmatter.mode = "subagent";
8193
9298
  const updatedContent = matter5.default.stringify(body, frontmatter);
8194
- await writeFile12(filePath, updatedContent, "utf-8");
9299
+ await writeFile13(filePath, updatedContent, "utf-8");
8195
9300
  }
8196
9301
  } catch {
8197
- await writeFile12(filePath, content, "utf-8");
9302
+ await writeFile13(filePath, content, "utf-8");
8198
9303
  }
8199
9304
  }
8200
9305
  generateCommandContent(command) {
8201
9306
  const examples = command.examples.map((e) => {
8202
9307
  return `- \`${e}\``;
8203
9308
  }).join("\n");
9309
+ let workflow = command.content;
9310
+ workflow = workflow.replace(/\$ARGUMENTS/g, "$ARGUMENTS").replace(/\$1/g, "$1").replace(/\$2/g, "$2").replace(/\$3/g, "$3").replace(/\$4/g, "$4").replace(/\$5/g, "$5");
8204
9311
  return `# Command: /${command.name}
8205
9312
 
8206
9313
  ## Description
@@ -8212,29 +9319,8 @@ ${command.description}
8212
9319
  ## Examples
8213
9320
  ${examples}
8214
9321
 
8215
- ## \u26A0\uFE0F CRITICAL: The User Has Already Provided Arguments!
8216
-
8217
- **The user has provided arguments with this command!**
8218
-
8219
- The arguments are available in this command response - look at the command workflow below, which now includes explicit instructions to use the provided arguments.
8220
-
8221
- **YOUR JOB**:
8222
- 1. Follow the command workflow steps
8223
- 2. The workflow will tell you to look at "Arguments Provided" section
8224
- 3. Use those arguments - do NOT ask the user for this information!
8225
- 4. They have already provided it - extract and use it!
8226
-
8227
- **Example Scenario**:
8228
- - User runs: \`/${command.name} snake game with html & css\`
8229
- - Command: \`/${command.name}\`
8230
- - Arguments to use: \`snake game with html & css\`
8231
- - You must use "snake game with html & css" as provided in the workflow!
8232
-
8233
- **DO NOT**: Ask "Please provide a task description"
8234
- **DO**: Follow the workflow and use the arguments provided in it!
8235
-
8236
9322
  ## Workflow
8237
- ${command.content}
9323
+ ${workflow}
8238
9324
 
8239
9325
  **Category**: ${command.category}`;
8240
9326
  }
@@ -8269,8 +9355,8 @@ ${agent.systemPrompt}`;
8269
9355
  // src/platform/claude-adapter.ts
8270
9356
  init_esm_shims();
8271
9357
  init_paths();
8272
- import { writeFile as writeFile13, mkdir as mkdir13 } from "fs/promises";
8273
- import { join as join18 } from "path";
9358
+ import { writeFile as writeFile14, mkdir as mkdir14 } from "fs/promises";
9359
+ import { join as join19 } from "path";
8274
9360
  import matter4 from "gray-matter";
8275
9361
  var ClaudeAdapter = class {
8276
9362
  platform = "claude" /* CLAUDE */;
@@ -8307,28 +9393,27 @@ var ClaudeAdapter = class {
8307
9393
  }
8308
9394
  async installCommand(name, content) {
8309
9395
  const dir = this.getCommandsDir();
8310
- await mkdir13(dir, { recursive: true });
8311
- await writeFile13(join18(dir, `${name}.md`), content);
9396
+ await mkdir14(dir, { recursive: true });
9397
+ await writeFile14(join19(dir, `${name}.md`), content);
8312
9398
  this.installedCommands.push(name);
8313
9399
  }
8314
9400
  async installSkill(_name, directory, files) {
8315
9401
  const baseDir = this.getSkillsDir();
8316
- const targetDir = join18(baseDir, directory);
8317
- await mkdir13(targetDir, { recursive: true });
9402
+ const targetDir = join19(baseDir, directory);
9403
+ await mkdir14(targetDir, { recursive: true });
8318
9404
  for (const [filename, content] of Object.entries(files)) {
8319
- await writeFile13(join18(targetDir, filename), content);
9405
+ await writeFile14(join19(targetDir, filename), content);
8320
9406
  }
8321
9407
  }
8322
9408
  async installAgent(name, content) {
8323
9409
  const dir = this.getAgentsDir();
8324
- await mkdir13(dir, { recursive: true });
8325
- await writeFile13(join18(dir, `${name}.md`), content);
9410
+ await mkdir14(dir, { recursive: true });
9411
+ await writeFile14(join19(dir, `${name}.md`), content);
8326
9412
  }
8327
9413
  generateCommandContent(command) {
8328
9414
  let workflow = command.content;
8329
- workflow = workflow.replace(/## ⚠️ CRITICAL: The User Has Already Provided Arguments!.*?(?![\n])?.*/gs, "").replace(/\*\*The user has provided arguments with this command!\*\*/g, "").replace(/\*\*The arguments are available in this command response.*?\*\*/g, "").replace(/\*\*YOUR JOB\*\*:.*/gs, "").replace(/1\. Follow command workflow steps.*?\n2\. The workflow will tell you.*?\n3\. Use those arguments.*?\n4\. They have already provided it.*?\n5\. DO NOT ask.*?\n6\. DO: Follow workflow.*?\n+\**Example Scenario\*\*:.*/gs, "").replace(/\*\*User runs:.*?\*\*:\n.*?\n+- Command:.*?\n+- Arguments to use:.*?\n+- You must use.*?\n+- DO NOT:.*?\n+- DO:.*?\n+\*\*\*/g, "").replace(/\*\*\*/g, "");
8330
9415
  workflow = workflow.replace(/^# Command: \/[a-z_]*[\s-]+\n+/g, "");
8331
- workflow = workflow.replace(/\$ARGUMENTS/g, "$ARGUMENTS").replace(/\$1/g, "$1").replace(/\$2/g, "$2");
9416
+ workflow = workflow.replace(/\$ARGUMENTS/g, "$ARGUMENTS").replace(/\$1/g, "$1").replace(/\$2/g, "$2").replace(/\$3/g, "$3").replace(/\$4/g, "$4").replace(/\$5/g, "$5");
8332
9417
  const frontmatter = {
8333
9418
  description: command.description,
8334
9419
  argumentHint: command.usage.replace(/^\//, "").replace(/<[^>]+>/g, "[args]")
@@ -8375,7 +9460,7 @@ ${skill.content}
8375
9460
  */
8376
9461
  async generateCommandsManifest() {
8377
9462
  const commandsDir = this.getCommandsDir();
8378
- const manifestPath = join18(commandsDir, "commands.json");
9463
+ const manifestPath = join19(commandsDir, "commands.json");
8379
9464
  const sortedCommands = [...this.installedCommands].sort();
8380
9465
  const manifest = {
8381
9466
  commands: sortedCommands,
@@ -8384,7 +9469,7 @@ ${skill.content}
8384
9469
  generatedBy: "aikit",
8385
9470
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
8386
9471
  };
8387
- await writeFile13(manifestPath, JSON.stringify(manifest, null, 2));
9472
+ await writeFile14(manifestPath, JSON.stringify(manifest, null, 2));
8388
9473
  }
8389
9474
  };
8390
9475
 
@@ -8399,54 +9484,98 @@ function createAdapter(platform) {
8399
9484
  throw new Error(`Unsupported platform: ${platform}`);
8400
9485
  }
8401
9486
  }
9487
+ function platformTypeToCliPlatform(type) {
9488
+ switch (type) {
9489
+ case "opencode":
9490
+ return "opencode" /* OPENCODE */;
9491
+ case "claude":
9492
+ return "claude" /* CLAUDE */;
9493
+ }
9494
+ }
9495
+ function getEnabledAdapters(config) {
9496
+ const enabledPlatforms = config.getEnabledPlatforms();
9497
+ return enabledPlatforms.map(
9498
+ (platform) => createAdapter(platformTypeToCliPlatform(platform))
9499
+ );
9500
+ }
8402
9501
  var SUPPORTED_PLATFORMS = [
8403
- { platform: "opencode" /* OPENCODE */, name: "OpenCode" },
8404
- { platform: "claude" /* CLAUDE */, name: "Claude Code CLI" }
9502
+ { platform: "opencode" /* OPENCODE */, name: "OpenCode", configKey: "opencode" },
9503
+ { platform: "claude" /* CLAUDE */, name: "Claude Code CLI", configKey: "claude" }
8405
9504
  ];
8406
9505
 
8407
9506
  // src/cli/commands/init.ts
9507
+ function getPlatformConfig(choice) {
9508
+ switch (choice) {
9509
+ case "opencode":
9510
+ return { opencode: true, claude: false, primary: "opencode" };
9511
+ case "claude":
9512
+ return { opencode: false, claude: true, primary: "claude" };
9513
+ case "both":
9514
+ return { opencode: true, claude: true, primary: "opencode" };
9515
+ }
9516
+ }
8408
9517
  function registerInitCommand(program2) {
8409
- program2.command("init [platform]").description("Initialize AIKit configuration for a specific platform").option("-g, --global", "Initialize global configuration").option("-p, --project", "Initialize project-level configuration").action(async (platformArg, options) => {
9518
+ program2.command("init [platform]").description("Initialize AIKit configuration for a specific platform").option("-g, --global", "Initialize global configuration").option("-p, --project", "Initialize project-level configuration").option("--opencode", "Use OpenCode only").option("--claude", "Use Claude Code only").option("--both", "Use both OpenCode and Claude Code").action(async (platformArg, options) => {
8410
9519
  const configDir = options.global ? paths.globalConfig() : paths.projectConfig();
8411
9520
  console.log(chalk3.bold("\n\u{1F680} AIKit Setup\n"));
8412
9521
  logger.info(`Initializing AIKit in ${configDir}...`);
8413
9522
  try {
8414
- await initializeConfig(configDir, options.global);
8415
- logger.success("\u2713 Configuration created");
8416
- if (!options.global) {
8417
- let selectedPlatform;
8418
- if (platformArg) {
8419
- selectedPlatform = CliDetector.matchPlatform(platformArg);
8420
- } else {
8421
- const platforms = await CliDetector.detectPlatforms();
8422
- const installed = CliDetector.filterInstalledPlatforms(platforms);
8423
- console.log(chalk3.bold("\n\u{1F50D} Available CLI Tools\n"));
8424
- for (const p of platforms) {
8425
- const status = p.installed ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
8426
- console.log(` ${status} ${p.displayName}`);
9523
+ let platformChoice;
9524
+ if (options.opencode) {
9525
+ platformChoice = "opencode";
9526
+ } else if (options.claude) {
9527
+ platformChoice = "claude";
9528
+ } else if (options.both) {
9529
+ platformChoice = "both";
9530
+ } else if (platformArg) {
9531
+ const mapped = CliDetector.matchPlatform(platformArg);
9532
+ platformChoice = mapped === "claude" /* CLAUDE */ ? "claude" : "opencode";
9533
+ } else {
9534
+ console.log(chalk3.bold("\n\u{1F4E6} Select Your AI Coding Platform\n"));
9535
+ const { choice } = await inquirer.prompt([
9536
+ {
9537
+ type: "list",
9538
+ name: "choice",
9539
+ message: "Which platform(s) do you want to use?",
9540
+ choices: [
9541
+ {
9542
+ name: `${chalk3.green("\u25CF")} OpenCode ${chalk3.gray("(recommended)")}`,
9543
+ value: "opencode"
9544
+ },
9545
+ {
9546
+ name: `${chalk3.yellow("\u25CF")} Claude Code ${chalk3.yellow("(Beta)")}`,
9547
+ value: "claude"
9548
+ },
9549
+ {
9550
+ name: `${chalk3.cyan("\u25CF")} Both Platforms ${chalk3.gray("(OpenCode + Claude Code)")}`,
9551
+ value: "both"
9552
+ }
9553
+ ],
9554
+ default: "opencode"
8427
9555
  }
8428
- const { platform } = await inquirer.prompt([
8429
- {
8430
- type: "list",
8431
- name: "platform",
8432
- message: "Which CLI tool do you want to configure AIKit for?",
8433
- choices: platforms.map((p) => ({
8434
- name: p.displayName,
8435
- value: p.platform
8436
- })),
8437
- default: installed[0]?.platform || "opencode" /* OPENCODE */
8438
- }
8439
- ]);
8440
- selectedPlatform = platform;
9556
+ ]);
9557
+ platformChoice = choice;
9558
+ if (platformChoice === "claude" || platformChoice === "both") {
9559
+ console.log(chalk3.yellow("\n\u26A0\uFE0F Claude Code support is in Beta"));
9560
+ console.log(chalk3.gray(" Some features may be limited or experimental.\n"));
8441
9561
  }
8442
- logger.info(`Selected platform: ${selectedPlatform}`);
9562
+ }
9563
+ const platformConfig = getPlatformConfig(platformChoice);
9564
+ await initializeConfig(configDir, options.global, platformConfig);
9565
+ logger.success("\u2713 Configuration created");
9566
+ console.log(chalk3.bold("\n\u{1F4CB} Platform Configuration:"));
9567
+ console.log(` OpenCode: ${platformConfig.opencode ? chalk3.green("enabled") : chalk3.gray("disabled")}`);
9568
+ console.log(` Claude Code: ${platformConfig.claude ? chalk3.yellow("enabled (Beta)") : chalk3.gray("disabled")}`);
9569
+ console.log(` Primary: ${chalk3.cyan(platformConfig.primary)}
9570
+ `);
9571
+ if (!options.global) {
8443
9572
  const config = await loadConfig();
8444
9573
  const engine = new SkillEngine(config);
8445
9574
  const result = await engine.syncSkillsToProject();
8446
9575
  if (result.count > 0) {
8447
9576
  logger.success(`\u2713 Synced ${result.count} skills`);
8448
9577
  }
8449
- if (selectedPlatform === "claude" /* CLAUDE */) {
9578
+ if (platformConfig.claude) {
8450
9579
  const cliTools = await CliDetector.checkAll();
8451
9580
  const claudeTool = cliTools.find((t) => t.name === "claude" /* CLAUDE */);
8452
9581
  if (claudeTool && !claudeTool.installed) {
@@ -8483,15 +9612,22 @@ function registerInitCommand(program2) {
8483
9612
  logger.success("\u2713 Sessions folder initialized");
8484
9613
  const { tracker } = await sessionManager.initTerminalSession();
8485
9614
  logger.success(`\u2713 Session tracker initialized (${tracker.split("/").pop()})`);
8486
- const adapter = createAdapter(selectedPlatform);
8487
- logger.info(`Installing AIKit for ${adapter.displayName}...`);
8488
- await installToPlatform(adapter, config);
9615
+ const enabledAdapters = getEnabledAdapters(config);
9616
+ for (const adapter of enabledAdapters) {
9617
+ logger.info(`Installing AIKit for ${adapter.displayName}...`);
9618
+ await installToPlatform(adapter, config);
9619
+ }
8489
9620
  console.log(chalk3.bold("\n\u2728 AIKit is ready!\n"));
8490
- if (selectedPlatform === "opencode" /* OPENCODE */) {
9621
+ if (platformConfig.primary === "opencode") {
8491
9622
  showOpenCodeUsage();
8492
- } else if (selectedPlatform === "claude" /* CLAUDE */) {
9623
+ } else {
8493
9624
  showClaudeUsage();
8494
9625
  }
9626
+ console.log(chalk3.gray("\u2501".repeat(50)));
9627
+ console.log(chalk3.gray("\nSwitch platforms anytime:"));
9628
+ console.log(chalk3.gray(" aikit platform enable claude"));
9629
+ console.log(chalk3.gray(" aikit platform disable opencode"));
9630
+ console.log(chalk3.gray(" aikit platform status\n"));
8495
9631
  }
8496
9632
  } catch (error) {
8497
9633
  logger.error("Failed to initialize AIKit:", error);
@@ -8524,6 +9660,10 @@ async function installToPlatform(adapter, config) {
8524
9660
  await adapter.installAgent(name, content);
8525
9661
  logger.info(` \u2713 Created ${name} agent`);
8526
9662
  }
9663
+ if (adapter.platform === "claude" /* CLAUDE */ && adapter.generateCommandsManifest) {
9664
+ await adapter.generateCommandsManifest();
9665
+ logger.success("\u2713 Generated commands manifest");
9666
+ }
8527
9667
  }
8528
9668
  function showOpenCodeUsage() {
8529
9669
  console.log("Usage in OpenCode:");
@@ -8537,7 +9677,7 @@ function showOpenCodeUsage() {
8537
9677
  console.log("\nPress " + chalk3.bold("Ctrl+K") + " in OpenCode to see all commands.\n");
8538
9678
  }
8539
9679
  function showClaudeUsage() {
8540
- console.log("Usage in Claude Code CLI:");
9680
+ console.log(chalk3.yellow("Claude Code (Beta) Usage:"));
8541
9681
  console.log(chalk3.cyan(" /help") + " - List all available commands");
8542
9682
  console.log(chalk3.cyan(" /plan") + " - Create implementation plan");
8543
9683
  console.log(chalk3.cyan(" /implement") + " - Implement a task");
@@ -8551,9 +9691,42 @@ init_logger();
8551
9691
  import inquirer2 from "inquirer";
8552
9692
  init_config();
8553
9693
  init_sessions();
9694
+ function cliPlatformToType(platform) {
9695
+ switch (platform) {
9696
+ case "opencode" /* OPENCODE */:
9697
+ return "opencode";
9698
+ case "claude" /* CLAUDE */:
9699
+ return "claude";
9700
+ default:
9701
+ return "opencode";
9702
+ }
9703
+ }
8554
9704
  function registerInstallCommand(program2) {
8555
- program2.command("install [platform]").description("Install AIKit to specific CLI tool configuration").action(async (platformArg) => {
9705
+ program2.command("install [platform]").description("Install AIKit to specific CLI tool configuration").option("--all", "Install to all enabled platforms").option("--force", "Force install even if platform is disabled in config").action(async (platformArg, options) => {
8556
9706
  try {
9707
+ const config = await loadConfig();
9708
+ const enabledPlatforms = config.getEnabledPlatforms();
9709
+ logger.info("Platform Configuration:");
9710
+ logger.info(` Primary: ${config.getPrimaryPlatform()}`);
9711
+ logger.info(` OpenCode: ${config.isPlatformEnabled("opencode") ? "enabled" : "disabled"}`);
9712
+ logger.info(` Claude Code: ${config.isPlatformEnabled("claude") ? "enabled (archived)" : "disabled (archived)"}`);
9713
+ logger.info("");
9714
+ if (enabledPlatforms.length === 0) {
9715
+ logger.error("No platforms enabled in config!");
9716
+ logger.info("Enable at least one platform in .aikit/aikit.json:");
9717
+ logger.info(' { "platform": { "opencode": true } }');
9718
+ process.exit(1);
9719
+ }
9720
+ if (options.all) {
9721
+ logger.info(`Installing to all enabled platforms: ${enabledPlatforms.join(", ")}`);
9722
+ const adapters = getEnabledAdapters(config);
9723
+ for (const adapter2 of adapters) {
9724
+ await installToAdapter(adapter2, config);
9725
+ }
9726
+ logger.success(`
9727
+ \u2713 AIKit installed to ${enabledPlatforms.length} platform(s)!`);
9728
+ return;
9729
+ }
8557
9730
  let selectedPlatform;
8558
9731
  if (platformArg) {
8559
9732
  selectedPlatform = CliDetector.matchPlatform(platformArg);
@@ -8562,23 +9735,48 @@ function registerInstallCommand(program2) {
8562
9735
  logger.info(`Supported platforms: ${Object.values(CliPlatform).join(", ")}`);
8563
9736
  process.exit(1);
8564
9737
  }
9738
+ const platformType = cliPlatformToType(selectedPlatform);
9739
+ if (!config.isPlatformEnabled(platformType) && !options.force) {
9740
+ logger.warn(`Platform '${platformType}' is disabled in config.`);
9741
+ logger.info("To enable it, update .aikit/aikit.json:");
9742
+ logger.info(` { "platform": { "${platformType}": true } }`);
9743
+ logger.info("Or use --force to install anyway.");
9744
+ process.exit(1);
9745
+ }
8565
9746
  } else {
8566
9747
  const platforms = await CliDetector.detectPlatforms();
8567
- const installedPlatforms = CliDetector.filterInstalledPlatforms(platforms);
8568
- const defaultPlatform = installedPlatforms.length > 0 ? installedPlatforms[0].platform : platforms[0]?.platform;
8569
- const { platform } = await inquirer2.prompt([
8570
- {
8571
- type: "list",
8572
- name: "platform",
8573
- message: "Which CLI tool do you want to install AIKit for?",
8574
- choices: platforms.map((p) => ({
8575
- name: p.displayName,
8576
- value: p.platform
8577
- })),
8578
- default: defaultPlatform
8579
- }
8580
- ]);
8581
- selectedPlatform = platform;
9748
+ const availablePlatforms = platforms.filter((p) => {
9749
+ const platformInfo = SUPPORTED_PLATFORMS.find((sp) => sp.platform === p.platform);
9750
+ return platformInfo && config.isPlatformEnabled(platformInfo.configKey);
9751
+ });
9752
+ if (availablePlatforms.length === 0) {
9753
+ logger.error("No enabled platforms detected!");
9754
+ logger.info("Enable platforms in .aikit/aikit.json");
9755
+ process.exit(1);
9756
+ }
9757
+ if (availablePlatforms.length === 1) {
9758
+ selectedPlatform = availablePlatforms[0].platform;
9759
+ logger.info(`Using only enabled platform: ${availablePlatforms[0].displayName}`);
9760
+ } else {
9761
+ const primaryPlatform = config.getPrimaryPlatform();
9762
+ const defaultChoice = availablePlatforms.find((p) => {
9763
+ const platformInfo = SUPPORTED_PLATFORMS.find((sp) => sp.platform === p.platform);
9764
+ return platformInfo?.configKey === primaryPlatform;
9765
+ })?.platform || availablePlatforms[0].platform;
9766
+ const { platform } = await inquirer2.prompt([
9767
+ {
9768
+ type: "list",
9769
+ name: "platform",
9770
+ message: "Which CLI tool do you want to install AIKit for?",
9771
+ choices: availablePlatforms.map((p) => ({
9772
+ name: `${p.displayName}${p.installed ? " (installed)" : ""}`,
9773
+ value: p.platform
9774
+ })),
9775
+ default: defaultChoice
9776
+ }
9777
+ ]);
9778
+ selectedPlatform = platform;
9779
+ }
8582
9780
  }
8583
9781
  logger.info(`Installing AIKit for ${selectedPlatform}...`);
8584
9782
  const sessionManager = new SessionManager();
@@ -8586,50 +9784,62 @@ function registerInstallCommand(program2) {
8586
9784
  logger.success("\u2713 Sessions folder initialized");
8587
9785
  const { tracker } = await sessionManager.initTerminalSession();
8588
9786
  logger.success(`\u2713 Session tracker initialized (${tracker.split("/").pop()})`);
8589
- const config = await loadConfig();
8590
9787
  const adapter = createAdapter(selectedPlatform);
8591
- const skillEngine = config.skills.enabled ? new SkillEngine(config) : null;
8592
- const commandRunner = config.commands.enabled ? new CommandRunner(config) : null;
8593
- const agentManager = config.agents.enabled ? new AgentManager(config) : null;
8594
- if (commandRunner) {
8595
- const commands = await commandRunner.listCommands();
8596
- logger.info(`Installing ${commands.length} commands...`);
8597
- for (const command of commands) {
8598
- const { name, content } = await adapter.transformCommand(command);
8599
- await adapter.installCommand(name, content);
8600
- logger.info(` \u2713 Created ${name} command`);
8601
- }
8602
- }
8603
- if (skillEngine) {
8604
- const skills = await skillEngine.listSkills();
8605
- logger.info(`Installing ${skills.length} skills...`);
8606
- for (const skill of skills) {
8607
- const { name, directory, files } = await adapter.transformSkill(skill);
8608
- await adapter.installSkill(name, directory, files);
8609
- logger.info(` \u2713 Created ${name} skill`);
8610
- }
8611
- }
8612
- if (agentManager) {
8613
- const agents = await agentManager.listAgents();
8614
- logger.info(`Installing ${agents.length} agents...`);
8615
- for (const agent of agents) {
8616
- const { name, content } = await adapter.transformAgent(agent);
8617
- await adapter.installAgent(name, content);
8618
- logger.info(` \u2713 Created ${name} agent`);
8619
- }
8620
- }
8621
- if (selectedPlatform === "claude" && adapter.generateCommandsManifest) {
8622
- await adapter.generateCommandsManifest();
8623
- logger.success("\u2713 Generated commands manifest");
8624
- }
9788
+ await installToAdapter(adapter, config);
8625
9789
  logger.success(`
8626
9790
  \u2713 AIKit installed to ${adapter.displayName}!`);
9791
+ const disabledPlatforms = SUPPORTED_PLATFORMS.filter((p) => !config.isPlatformEnabled(p.configKey));
9792
+ if (disabledPlatforms.length > 0) {
9793
+ logger.info("");
9794
+ logger.info("Note: Some platforms are disabled:");
9795
+ disabledPlatforms.forEach((p) => {
9796
+ logger.info(` - ${p.name}: Enable with { "platform": { "${p.configKey}": true } }`);
9797
+ });
9798
+ }
8627
9799
  } catch (error) {
8628
9800
  logger.error("Failed to install:", error);
8629
9801
  process.exit(1);
8630
9802
  }
8631
9803
  });
8632
9804
  }
9805
+ async function installToAdapter(adapter, config) {
9806
+ const skillEngine = config.skills.enabled ? new SkillEngine(config) : null;
9807
+ const commandRunner = config.commands.enabled ? new CommandRunner(config) : null;
9808
+ const agentManager = config.agents.enabled ? new AgentManager(config) : null;
9809
+ logger.info(`
9810
+ \u{1F4E6} Installing to ${adapter.displayName}...`);
9811
+ if (commandRunner) {
9812
+ const commands = await commandRunner.listCommands();
9813
+ logger.info(`Installing ${commands.length} commands...`);
9814
+ for (const command of commands) {
9815
+ const { name, content } = await adapter.transformCommand(command);
9816
+ await adapter.installCommand(name, content);
9817
+ logger.info(` \u2713 Created ${name} command`);
9818
+ }
9819
+ }
9820
+ if (skillEngine) {
9821
+ const skills = await skillEngine.listSkills();
9822
+ logger.info(`Installing ${skills.length} skills...`);
9823
+ for (const skill of skills) {
9824
+ const { name, directory, files } = await adapter.transformSkill(skill);
9825
+ await adapter.installSkill(name, directory, files);
9826
+ logger.info(` \u2713 Created ${name} skill`);
9827
+ }
9828
+ }
9829
+ if (agentManager) {
9830
+ const agents = await agentManager.listAgents();
9831
+ logger.info(`Installing ${agents.length} agents...`);
9832
+ for (const agent of agents) {
9833
+ const { name, content } = await adapter.transformAgent(agent);
9834
+ await adapter.installAgent(name, content);
9835
+ logger.info(` \u2713 Created ${name} agent`);
9836
+ }
9837
+ }
9838
+ if (adapter.platform === "claude" && adapter.generateCommandsManifest) {
9839
+ await adapter.generateCommandsManifest();
9840
+ logger.success("\u2713 Generated commands manifest");
9841
+ }
9842
+ }
8633
9843
 
8634
9844
  // src/cli/commands/sync.ts
8635
9845
  init_esm_shims();
@@ -8638,8 +9848,8 @@ import chalk5 from "chalk";
8638
9848
 
8639
9849
  // src/core/sync-engine.ts
8640
9850
  init_esm_shims();
8641
- import { readFile as readFile14, writeFile as writeFile17, copyFile, mkdir as mkdir15 } from "fs/promises";
8642
- import { join as join22, dirname as dirname4 } from "path";
9851
+ import { readFile as readFile15, writeFile as writeFile18, copyFile, mkdir as mkdir16 } from "fs/promises";
9852
+ import { join as join23, dirname as dirname4 } from "path";
8643
9853
  import inquirer3 from "inquirer";
8644
9854
  import chalk4 from "chalk";
8645
9855
 
@@ -8647,8 +9857,8 @@ import chalk4 from "chalk";
8647
9857
  init_esm_shims();
8648
9858
  init_paths();
8649
9859
  init_logger();
8650
- import { readFile as readFile11, readdir as readdir9, writeFile as writeFile14, stat } from "fs/promises";
8651
- import { join as join19 } from "path";
9860
+ import { readFile as readFile12, readdir as readdir8, writeFile as writeFile15, stat } from "fs/promises";
9861
+ import { join as join20 } from "path";
8652
9862
  import { createHash } from "crypto";
8653
9863
  var VersionManager = class {
8654
9864
  config;
@@ -8659,9 +9869,9 @@ var VersionManager = class {
8659
9869
  * Get current installed version
8660
9870
  */
8661
9871
  async getCurrentVersion() {
8662
- const versionPath = join19(paths.globalConfig(), ".version.json");
9872
+ const versionPath = join20(paths.globalConfig(), ".version.json");
8663
9873
  try {
8664
- const content = await readFile11(versionPath, "utf-8");
9874
+ const content = await readFile12(versionPath, "utf-8");
8665
9875
  return JSON.parse(content);
8666
9876
  } catch {
8667
9877
  return null;
@@ -8672,7 +9882,7 @@ var VersionManager = class {
8672
9882
  */
8673
9883
  getPackageVersion() {
8674
9884
  try {
8675
- const packageJson = __require(join19(process.cwd(), "package.json"));
9885
+ const packageJson = __require(join20(process.cwd(), "package.json"));
8676
9886
  return packageJson.version || "0.0.0";
8677
9887
  } catch {
8678
9888
  return "0.0.0";
@@ -8730,9 +9940,9 @@ var VersionManager = class {
8730
9940
  const removedSkills = [];
8731
9941
  const conflicts = [];
8732
9942
  const installedSkills = /* @__PURE__ */ new Map();
8733
- const installedPath = join19(paths.globalConfig(), ".installed-skills.json");
9943
+ const installedPath = join20(paths.globalConfig(), ".installed-skills.json");
8734
9944
  try {
8735
- const installedData = await readFile11(installedPath, "utf-8");
9945
+ const installedData = await readFile12(installedPath, "utf-8");
8736
9946
  const installedList = JSON.parse(installedData);
8737
9947
  installedList.forEach((skill) => {
8738
9948
  installedSkills.set(skill.name, skill);
@@ -8780,9 +9990,9 @@ var VersionManager = class {
8780
9990
  const hashes = [];
8781
9991
  try {
8782
9992
  const loadFromDir = async (dir) => {
8783
- const files = await readdir9(dir);
9993
+ const files = await readdir8(dir);
8784
9994
  for (const file of files) {
8785
- const filePath = join19(dir, file);
9995
+ const filePath = join20(dir, file);
8786
9996
  const stats = await stat(filePath);
8787
9997
  if (stats.isDirectory()) {
8788
9998
  await loadFromDir(filePath);
@@ -8808,7 +10018,7 @@ var VersionManager = class {
8808
10018
  */
8809
10019
  async calculateSkillHash(filePath) {
8810
10020
  try {
8811
- const content = await readFile11(filePath, "utf-8");
10021
+ const content = await readFile12(filePath, "utf-8");
8812
10022
  return createHash("sha256").update(content).digest("hex");
8813
10023
  } catch {
8814
10024
  return "";
@@ -8829,9 +10039,9 @@ var VersionManager = class {
8829
10039
  * Save installed skills info
8830
10040
  */
8831
10041
  async saveInstalledSkills(skills) {
8832
- const installedPath = join19(paths.globalConfig(), ".installed-skills.json");
10042
+ const installedPath = join20(paths.globalConfig(), ".installed-skills.json");
8833
10043
  try {
8834
- await writeFile14(installedPath, JSON.stringify(skills, null, 2));
10044
+ await writeFile15(installedPath, JSON.stringify(skills, null, 2));
8835
10045
  } catch (error) {
8836
10046
  logger.error("Failed to save installed skills info:", error);
8837
10047
  }
@@ -8852,8 +10062,8 @@ var VersionManager = class {
8852
10062
  packageVersion: this.getPackageVersion(),
8853
10063
  migrationHistory: migration ? [...current.migrationHistory, migration] : current.migrationHistory
8854
10064
  };
8855
- const versionPath = join19(paths.globalConfig(), ".version.json");
8856
- await writeFile14(versionPath, JSON.stringify(updated, null, 2));
10065
+ const versionPath = join20(paths.globalConfig(), ".version.json");
10066
+ await writeFile15(versionPath, JSON.stringify(updated, null, 2));
8857
10067
  }
8858
10068
  /**
8859
10069
  * Check if migration is needed
@@ -8868,8 +10078,8 @@ var VersionManager = class {
8868
10078
  // src/core/backup-manager.ts
8869
10079
  init_esm_shims();
8870
10080
  init_logger();
8871
- import { readFile as readFile12, writeFile as writeFile15, readdir as readdir10, stat as stat2, unlink as unlink2, mkdir as mkdir14 } from "fs/promises";
8872
- import { join as join20, dirname as dirname3 } from "path";
10081
+ import { readFile as readFile13, writeFile as writeFile16, readdir as readdir9, stat as stat2, unlink as unlink2, mkdir as mkdir15 } from "fs/promises";
10082
+ import { join as join21, dirname as dirname3 } from "path";
8873
10083
  import { createHash as createHash2 } from "crypto";
8874
10084
  var BackupManager = class {
8875
10085
  configPath;
@@ -8877,7 +10087,7 @@ var BackupManager = class {
8877
10087
  maxBackups;
8878
10088
  constructor(configPath, maxBackups = 5) {
8879
10089
  this.configPath = configPath;
8880
- this.backupsDir = join20(configPath, ".backups");
10090
+ this.backupsDir = join21(configPath, ".backups");
8881
10091
  this.maxBackups = maxBackups;
8882
10092
  }
8883
10093
  /**
@@ -8885,10 +10095,10 @@ var BackupManager = class {
8885
10095
  */
8886
10096
  async createBackup(fromVersion, toVersion) {
8887
10097
  try {
8888
- await mkdir14(this.backupsDir, { recursive: true });
10098
+ await mkdir15(this.backupsDir, { recursive: true });
8889
10099
  const backupId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
8890
- const backupPath = join20(this.backupsDir, `${backupId}-v${toVersion}`);
8891
- await mkdir14(backupPath, { recursive: true });
10100
+ const backupPath = join21(this.backupsDir, `${backupId}-v${toVersion}`);
10101
+ await mkdir15(backupPath, { recursive: true });
8892
10102
  logger.info(`Creating backup: ${backupPath}`);
8893
10103
  const files = [];
8894
10104
  const backupItems = [
@@ -8909,8 +10119,8 @@ var BackupManager = class {
8909
10119
  files,
8910
10120
  success: true
8911
10121
  };
8912
- const manifestPath = join20(backupPath, "backup-manifest.json");
8913
- await writeFile15(manifestPath, JSON.stringify(manifest, null, 2));
10122
+ const manifestPath = join21(backupPath, "backup-manifest.json");
10123
+ await writeFile16(manifestPath, JSON.stringify(manifest, null, 2));
8914
10124
  await this.cleanupOldBackups();
8915
10125
  logger.success(`\u2713 Backup created: ${backupId}`);
8916
10126
  return backupId;
@@ -8923,20 +10133,20 @@ var BackupManager = class {
8923
10133
  * Backup a file or directory
8924
10134
  */
8925
10135
  async backupItem(sourceDir, item, targetDir) {
8926
- const sourcePath = join20(sourceDir, item);
8927
- const targetPath = join20(targetDir, item);
10136
+ const sourcePath = join21(sourceDir, item);
10137
+ const targetPath = join21(targetDir, item);
8928
10138
  const files = [];
8929
10139
  try {
8930
10140
  const stats = await stat2(sourcePath);
8931
10141
  if (stats.isDirectory()) {
8932
- await mkdir14(targetPath, { recursive: true });
8933
- const entries = await readdir10(sourcePath);
10142
+ await mkdir15(targetPath, { recursive: true });
10143
+ const entries = await readdir9(sourcePath);
8934
10144
  for (const entry of entries) {
8935
10145
  const entryFiles = await this.backupItem(sourcePath, entry, targetPath);
8936
10146
  files.push(...entryFiles);
8937
10147
  }
8938
10148
  } else if (stats.isFile()) {
8939
- await mkdir14(dirname3(targetPath), { recursive: true });
10149
+ await mkdir15(dirname3(targetPath), { recursive: true });
8940
10150
  await this.copyFile(sourcePath, targetPath);
8941
10151
  const hash = await this.calculateHash(targetPath);
8942
10152
  files.push({
@@ -8954,15 +10164,15 @@ var BackupManager = class {
8954
10164
  * Copy file with hash calculation
8955
10165
  */
8956
10166
  async copyFile(source, target) {
8957
- const content = await readFile12(source);
8958
- await writeFile15(target, content);
10167
+ const content = await readFile13(source);
10168
+ await writeFile16(target, content);
8959
10169
  }
8960
10170
  /**
8961
10171
  * Calculate file hash
8962
10172
  */
8963
10173
  async calculateHash(filePath) {
8964
10174
  try {
8965
- const content = await readFile12(filePath);
10175
+ const content = await readFile13(filePath);
8966
10176
  return createHash2("sha256").update(content).digest("hex");
8967
10177
  } catch {
8968
10178
  return "";
@@ -8973,13 +10183,13 @@ var BackupManager = class {
8973
10183
  */
8974
10184
  async listBackups() {
8975
10185
  try {
8976
- const entries = await readdir10(this.backupsDir);
10186
+ const entries = await readdir9(this.backupsDir);
8977
10187
  const backups = [];
8978
10188
  for (const entry of entries) {
8979
- const backupPath = join20(this.backupsDir, entry);
8980
- const manifestPath = join20(backupPath, "backup-manifest.json");
10189
+ const backupPath = join21(this.backupsDir, entry);
10190
+ const manifestPath = join21(backupPath, "backup-manifest.json");
8981
10191
  try {
8982
- const manifestContent = await readFile12(manifestPath, "utf-8");
10192
+ const manifestContent = await readFile13(manifestPath, "utf-8");
8983
10193
  const manifest = JSON.parse(manifestContent);
8984
10194
  const size = await this.calculateBackupSize(backupPath);
8985
10195
  backups.push({
@@ -9005,9 +10215,9 @@ var BackupManager = class {
9005
10215
  let totalSize = 0;
9006
10216
  try {
9007
10217
  const calculate = async (dir) => {
9008
- const entries = await readdir10(dir);
10218
+ const entries = await readdir9(dir);
9009
10219
  for (const entry of entries) {
9010
- const entryPath = join20(dir, entry);
10220
+ const entryPath = join21(dir, entry);
9011
10221
  const stats = await stat2(entryPath);
9012
10222
  if (stats.isDirectory()) {
9013
10223
  await calculate(entryPath);
@@ -9039,9 +10249,9 @@ var BackupManager = class {
9039
10249
  return false;
9040
10250
  }
9041
10251
  for (const file of backup.manifest.files) {
9042
- const sourcePath = join20(backup.path, file.path);
9043
- const targetPath = join20(this.configPath, file.path);
9044
- await mkdir14(dirname3(targetPath), { recursive: true });
10252
+ const sourcePath = join21(backup.path, file.path);
10253
+ const targetPath = join21(this.configPath, file.path);
10254
+ await mkdir15(dirname3(targetPath), { recursive: true });
9045
10255
  await this.copyFile(sourcePath, targetPath);
9046
10256
  }
9047
10257
  logger.success(`\u2713 Backup restored: ${backupId}`);
@@ -9056,10 +10266,10 @@ var BackupManager = class {
9056
10266
  */
9057
10267
  async validateBackup(backup) {
9058
10268
  try {
9059
- const manifestPath = join20(backup.path, "backup-manifest.json");
9060
- await readFile12(manifestPath, "utf-8");
10269
+ const manifestPath = join21(backup.path, "backup-manifest.json");
10270
+ await readFile13(manifestPath, "utf-8");
9061
10271
  for (const file of backup.manifest.files) {
9062
- const filePath = join20(backup.path, file.path);
10272
+ const filePath = join21(backup.path, file.path);
9063
10273
  await stat2(filePath);
9064
10274
  const currentHash = await this.calculateHash(filePath);
9065
10275
  if (currentHash !== file.hash) {
@@ -9083,9 +10293,9 @@ var BackupManager = class {
9083
10293
  if (!backup) {
9084
10294
  return false;
9085
10295
  }
9086
- const entries = await readdir10(backup.path);
10296
+ const entries = await readdir9(backup.path);
9087
10297
  for (const entry of entries) {
9088
- const entryPath = join20(backup.path, entry);
10298
+ const entryPath = join21(backup.path, entry);
9089
10299
  const stats = await stat2(entryPath);
9090
10300
  if (stats.isDirectory()) {
9091
10301
  await this.removeDirectory(entryPath);
@@ -9104,9 +10314,9 @@ var BackupManager = class {
9104
10314
  * Remove directory recursively
9105
10315
  */
9106
10316
  async removeDirectory(dirPath) {
9107
- const entries = await readdir10(dirPath);
10317
+ const entries = await readdir9(dirPath);
9108
10318
  for (const entry of entries) {
9109
- const entryPath = join20(dirPath, entry);
10319
+ const entryPath = join21(dirPath, entry);
9110
10320
  const stats = await stat2(entryPath);
9111
10321
  if (stats.isDirectory()) {
9112
10322
  await this.removeDirectory(entryPath);
@@ -9152,14 +10362,14 @@ var BackupManager = class {
9152
10362
  // src/core/migration-manager.ts
9153
10363
  init_esm_shims();
9154
10364
  init_logger();
9155
- import { readFile as readFile13, writeFile as writeFile16, readdir as readdir11 } from "fs/promises";
9156
- import { join as join21 } from "path";
10365
+ import { readFile as readFile14, writeFile as writeFile17, readdir as readdir10 } from "fs/promises";
10366
+ import { join as join22 } from "path";
9157
10367
  var MigrationManager = class {
9158
10368
  configPath;
9159
10369
  migrationsDir;
9160
10370
  constructor(configPath) {
9161
10371
  this.configPath = configPath;
9162
- this.migrationsDir = join21(process.cwd(), "src/core/migrations");
10372
+ this.migrationsDir = join22(process.cwd(), "src/core/migrations");
9163
10373
  }
9164
10374
  /**
9165
10375
  * Load all available migrations
@@ -9167,11 +10377,11 @@ var MigrationManager = class {
9167
10377
  async loadMigrations() {
9168
10378
  const migrations = [];
9169
10379
  try {
9170
- const files = await readdir11(this.migrationsDir);
10380
+ const files = await readdir10(this.migrationsDir);
9171
10381
  for (const file of files) {
9172
10382
  if (file.endsWith(".js") && file.startsWith("migrate-")) {
9173
10383
  try {
9174
- const module = await import(join21(this.migrationsDir, file));
10384
+ const module = await import(join22(this.migrationsDir, file));
9175
10385
  const migration = module.default || module.migration;
9176
10386
  if (migration) {
9177
10387
  migrations.push(migration);
@@ -9190,9 +10400,9 @@ var MigrationManager = class {
9190
10400
  * Get applied migrations
9191
10401
  */
9192
10402
  async getAppliedMigrations() {
9193
- const migrationHistoryPath = join21(this.configPath, ".migration-history.json");
10403
+ const migrationHistoryPath = join22(this.configPath, ".migration-history.json");
9194
10404
  try {
9195
- const content = await readFile13(migrationHistoryPath, "utf-8");
10405
+ const content = await readFile14(migrationHistoryPath, "utf-8");
9196
10406
  const history = JSON.parse(content);
9197
10407
  return history.filter((m) => m.status === "completed").map((m) => m.to);
9198
10408
  } catch {
@@ -9278,16 +10488,16 @@ var MigrationManager = class {
9278
10488
  * Update migration history
9279
10489
  */
9280
10490
  async updateMigrationHistory(entries) {
9281
- const historyPath = join21(this.configPath, ".migration-history.json");
10491
+ const historyPath = join22(this.configPath, ".migration-history.json");
9282
10492
  try {
9283
10493
  let history = [];
9284
10494
  try {
9285
- const content = await readFile13(historyPath, "utf-8");
10495
+ const content = await readFile14(historyPath, "utf-8");
9286
10496
  history = JSON.parse(content);
9287
10497
  } catch {
9288
10498
  }
9289
10499
  history.push(...entries);
9290
- await writeFile16(historyPath, JSON.stringify(history, null, 2));
10500
+ await writeFile17(historyPath, JSON.stringify(history, null, 2));
9291
10501
  } catch (error) {
9292
10502
  logger.error("Failed to update migration history:", error);
9293
10503
  }
@@ -9296,14 +10506,14 @@ var MigrationManager = class {
9296
10506
  * Update migration history status
9297
10507
  */
9298
10508
  async updateMigrationHistoryStatus(version, status) {
9299
- const historyPath = join21(this.configPath, ".migration-history.json");
10509
+ const historyPath = join22(this.configPath, ".migration-history.json");
9300
10510
  try {
9301
- const content = await readFile13(historyPath, "utf-8");
10511
+ const content = await readFile14(historyPath, "utf-8");
9302
10512
  const history = JSON.parse(content);
9303
10513
  const updated = history.map(
9304
10514
  (m) => m.to === version ? { ...m, status } : m
9305
10515
  );
9306
- await writeFile16(historyPath, JSON.stringify(updated, null, 2));
10516
+ await writeFile17(historyPath, JSON.stringify(updated, null, 2));
9307
10517
  } catch (error) {
9308
10518
  logger.error("Failed to update migration history status:", error);
9309
10519
  }
@@ -9312,9 +10522,9 @@ var MigrationManager = class {
9312
10522
  * Get migration history
9313
10523
  */
9314
10524
  async getMigrationHistory() {
9315
- const historyPath = join21(this.configPath, ".migration-history.json");
10525
+ const historyPath = join22(this.configPath, ".migration-history.json");
9316
10526
  try {
9317
- const content = await readFile13(historyPath, "utf-8");
10527
+ const content = await readFile14(historyPath, "utf-8");
9318
10528
  return JSON.parse(content);
9319
10529
  } catch {
9320
10530
  return [];
@@ -9623,19 +10833,19 @@ var SyncEngine = class {
9623
10833
  * Install a skill
9624
10834
  */
9625
10835
  async installSkill(sourceDir, skill, targetDir) {
9626
- const sourcePath = join22(sourceDir, skill.category, `${skill.name}.md`);
9627
- const targetPath = join22(targetDir, skill.category, `${skill.name}.md`);
9628
- await mkdir15(dirname4(targetPath), { recursive: true });
10836
+ const sourcePath = join23(sourceDir, skill.category, `${skill.name}.md`);
10837
+ const targetPath = join23(targetDir, skill.category, `${skill.name}.md`);
10838
+ await mkdir16(dirname4(targetPath), { recursive: true });
9629
10839
  await copyFile(sourcePath, targetPath);
9630
10840
  }
9631
10841
  /**
9632
10842
  * Archive a removed skill
9633
10843
  */
9634
10844
  async archiveSkill(targetDir, skill) {
9635
- const sourcePath = join22(targetDir, skill.category, `${skill.name}.md`);
9636
- const targetPath = join22(targetDir, skill.category, `${skill.name}-deprecated.md`);
10845
+ const sourcePath = join23(targetDir, skill.category, `${skill.name}.md`);
10846
+ const targetPath = join23(targetDir, skill.category, `${skill.name}-deprecated.md`);
9637
10847
  try {
9638
- const content = await readFile14(sourcePath, "utf-8");
10848
+ const content = await readFile15(sourcePath, "utf-8");
9639
10849
  const deprecatedNotice = `---
9640
10850
  \u26A0\uFE0F DEPRECATED: This skill has been removed
9641
10851
 
@@ -9644,8 +10854,8 @@ Reason: Check release notes for replacement
9644
10854
  ---
9645
10855
 
9646
10856
  ${content}`;
9647
- await mkdir15(dirname4(targetPath), { recursive: true });
9648
- await writeFile17(targetPath, deprecatedNotice);
10857
+ await mkdir16(dirname4(targetPath), { recursive: true });
10858
+ await writeFile18(targetPath, deprecatedNotice);
9649
10859
  } catch (error) {
9650
10860
  if (error.code === "ENOENT") {
9651
10861
  console.log(chalk4.yellow(` - ${skill.name} (not found, skipping)`));
@@ -9844,6 +11054,8 @@ async function configureToolAction(toolName) {
9844
11054
  if (isValid) {
9845
11055
  logger.success(`
9846
11056
  \u2705 ${tool.name} configured successfully!`);
11057
+ console.log(chalk6.gray("\nConfiguring Claude Desktop MCP server..."));
11058
+ await toolConfigManager.configureMcpServer(toolName, token);
9847
11059
  console.log(chalk6.gray("\nYou can now use the /analyze-figma command in OpenCode.\n"));
9848
11060
  } else {
9849
11061
  await toolConfigManager.updateToolConfig(toolName, {
@@ -9876,7 +11088,7 @@ async function configureToolAction(toolName) {
9876
11088
  // src/cli/commands/misc.ts
9877
11089
  init_esm_shims();
9878
11090
  import chalk7 from "chalk";
9879
- import { readFile as readFile16, writeFile as writeFile19 } from "fs/promises";
11091
+ import { readFile as readFile16, writeFile as writeFile19, mkdir as mkdir17 } from "fs/promises";
9880
11092
  import { join as join24 } from "path";
9881
11093
  init_config();
9882
11094
  init_memory();
@@ -10018,6 +11230,107 @@ function registerBeadsCommand(program2) {
10018
11230
  });
10019
11231
  return beadsCmd;
10020
11232
  }
11233
+ function registerPlatformCommand(program2) {
11234
+ const platformCmd = program2.command("platform").description("Manage platform toggles (OpenCode/Claude Code)");
11235
+ platformCmd.command("status").description("Show platform configuration status").action(async () => {
11236
+ const config = await loadConfig();
11237
+ const { platform } = config;
11238
+ const enabledPlatforms = config.getEnabledPlatforms();
11239
+ console.log(chalk7.bold("\n\u{1F5A5}\uFE0F Platform Configuration:\n"));
11240
+ console.log(` Primary: ${chalk7.cyan(platform.primary)}`);
11241
+ console.log();
11242
+ console.log(" Platforms:");
11243
+ console.log(` ${platform.opencode ? chalk7.green("\u2713") : chalk7.gray("\u25CB")} OpenCode ${platform.opencode ? chalk7.green("(enabled)") : chalk7.gray("(disabled)")}`);
11244
+ console.log(` ${platform.claude ? chalk7.green("\u2713") : chalk7.gray("\u25CB")} Claude Code ${platform.claude ? chalk7.green("(enabled)") : chalk7.yellow("(archived)")}`);
11245
+ console.log();
11246
+ if (enabledPlatforms.length === 0) {
11247
+ console.log(chalk7.yellow('\u26A0 No platforms enabled! Run "aikit platform enable opencode" to enable.'));
11248
+ } else {
11249
+ console.log(chalk7.gray(`Enabled: ${enabledPlatforms.join(", ")}`));
11250
+ }
11251
+ console.log();
11252
+ console.log(chalk7.gray("Commands:"));
11253
+ console.log(chalk7.gray(" aikit platform enable <platform> Enable a platform"));
11254
+ console.log(chalk7.gray(" aikit platform disable <platform> Disable a platform"));
11255
+ console.log(chalk7.gray(" aikit platform primary <platform> Set primary platform"));
11256
+ console.log();
11257
+ });
11258
+ platformCmd.command("enable <platform>").description("Enable a platform (opencode, claude)").action(async (platform) => {
11259
+ await togglePlatform(platform, true);
11260
+ });
11261
+ platformCmd.command("disable <platform>").description("Disable a platform (opencode, claude)").action(async (platform) => {
11262
+ await togglePlatform(platform, false);
11263
+ });
11264
+ platformCmd.command("primary <platform>").description("Set primary platform (opencode, claude)").action(async (platform) => {
11265
+ const validPlatforms = ["opencode", "claude"];
11266
+ if (!validPlatforms.includes(platform)) {
11267
+ console.log(chalk7.red(`Invalid platform. Available: ${validPlatforms.join(", ")}`));
11268
+ return;
11269
+ }
11270
+ const config = await loadConfig();
11271
+ const configPath = config.configPath;
11272
+ try {
11273
+ let configData = {};
11274
+ try {
11275
+ configData = JSON.parse(await readFile16(join24(configPath, "aikit.json"), "utf-8"));
11276
+ } catch {
11277
+ }
11278
+ if (!configData.platform) {
11279
+ configData.platform = {};
11280
+ }
11281
+ configData.platform.primary = platform;
11282
+ configData.platform[platform] = true;
11283
+ await mkdir17(configPath, { recursive: true });
11284
+ await writeFile19(join24(configPath, "aikit.json"), JSON.stringify(configData, null, 2));
11285
+ console.log(chalk7.green(`\u2713 Primary platform set to: ${platform}`));
11286
+ console.log(chalk7.gray(`Configuration updated at: ${configPath}/aikit.json`));
11287
+ console.log();
11288
+ console.log(chalk7.gray('Run "aikit install" to apply changes.'));
11289
+ } catch (error) {
11290
+ console.log(chalk7.red(`Failed to set primary platform: ${error instanceof Error ? error.message : String(error)}`));
11291
+ }
11292
+ });
11293
+ return platformCmd;
11294
+ }
11295
+ async function togglePlatform(platform, enable) {
11296
+ const validPlatforms = ["opencode", "claude"];
11297
+ if (!validPlatforms.includes(platform)) {
11298
+ console.log(chalk7.red(`Invalid platform. Available: ${validPlatforms.join(", ")}`));
11299
+ return;
11300
+ }
11301
+ const config = await loadConfig();
11302
+ const configPath = config.configPath;
11303
+ try {
11304
+ let configData = {};
11305
+ try {
11306
+ configData = JSON.parse(await readFile16(join24(configPath, "aikit.json"), "utf-8"));
11307
+ } catch {
11308
+ }
11309
+ if (!configData.platform) {
11310
+ configData.platform = {
11311
+ primary: "opencode",
11312
+ opencode: true,
11313
+ claude: false
11314
+ };
11315
+ }
11316
+ configData.platform[platform] = enable;
11317
+ await mkdir17(configPath, { recursive: true });
11318
+ await writeFile19(join24(configPath, "aikit.json"), JSON.stringify(configData, null, 2));
11319
+ const action = enable ? "enabled" : "disabled";
11320
+ const emoji = enable ? "\u2713" : "\u25CB";
11321
+ console.log(chalk7.green(`${emoji} Platform ${platform} ${action}`));
11322
+ console.log(chalk7.gray(`Configuration updated at: ${configPath}/aikit.json`));
11323
+ console.log();
11324
+ if (enable) {
11325
+ console.log(chalk7.gray('Run "aikit install" to install AIKit for this platform.'));
11326
+ } else {
11327
+ console.log(chalk7.gray(`Note: Existing ${platform} files will remain but won't be updated.`));
11328
+ console.log(chalk7.gray(`Use "aikit platform enable ${platform}" to re-enable later.`));
11329
+ }
11330
+ } catch (error) {
11331
+ console.log(chalk7.red(`Failed to ${enable ? "enable" : "disable"} platform: ${error instanceof Error ? error.message : String(error)}`));
11332
+ }
11333
+ }
10021
11334
  function registerStatusCommand(program2) {
10022
11335
  program2.command("status").description("Show AIKit status").action(async () => {
10023
11336
  console.log(chalk7.bold(`
@@ -10026,6 +11339,9 @@ function registerStatusCommand(program2) {
10026
11339
  try {
10027
11340
  const config = await loadConfig();
10028
11341
  console.log(chalk7.green("\u2713 Configuration loaded"));
11342
+ const enabledPlatforms = config.getEnabledPlatforms();
11343
+ console.log(` Primary Platform: ${chalk7.cyan(config.getPrimaryPlatform())}`);
11344
+ console.log(` Enabled Platforms: ${enabledPlatforms.length > 0 ? enabledPlatforms.join(", ") : chalk7.yellow("none")}`);
10029
11345
  const skillEngine = new SkillEngine(config);
10030
11346
  const skills = await skillEngine.listSkills();
10031
11347
  console.log(` Skills: ${skills.length}`);
@@ -10092,6 +11408,7 @@ registerSkillsCommand(program);
10092
11408
  registerAgentsCommand(program);
10093
11409
  registerCommandsCommand(program);
10094
11410
  registerModeCommand(program);
11411
+ registerPlatformCommand(program);
10095
11412
  registerToolsCommand(program);
10096
11413
  registerPluginsCommand(program);
10097
11414
  registerMemoryCommand(program);