@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 +2024 -707
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +309 -4
- package/dist/index.js +1384 -104
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +1235 -76
- package/dist/mcp-server.js.map +1 -1
- package/package.json +3 -1
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
|
-
|
|
341
|
-
|
|
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:
|
|
411
|
-
const files = await
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
1101
|
-
const
|
|
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 =
|
|
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
|
-
*
|
|
2891
|
+
* Load saved configurations
|
|
2892
|
+
* Checks both global and project configs, project takes precedence
|
|
1593
2893
|
*/
|
|
1594
|
-
async
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
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
|
-
|
|
1617
|
-
|
|
1618
|
-
await this.
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
-
*
|
|
2915
|
+
* Save configurations
|
|
1642
2916
|
*/
|
|
1643
|
-
async
|
|
1644
|
-
const
|
|
1645
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
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
|
-
|
|
1678
|
-
|
|
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
|
|
1692
|
-
import { join as
|
|
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 =
|
|
1725
|
-
this.sessionsDir =
|
|
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
|
|
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
|
|
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
|
|
3073
|
+
await access5(sessionFile);
|
|
1782
3074
|
} catch {
|
|
1783
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
3234
|
+
const filePath = join16(this.sessionsDir, `${id}.md`);
|
|
1943
3235
|
try {
|
|
1944
|
-
const content = await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
3492
|
+
return updates;
|
|
2352
3493
|
}
|
|
2353
3494
|
/**
|
|
2354
|
-
*
|
|
3495
|
+
* Get git state
|
|
2355
3496
|
*/
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
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
|
-
*
|
|
3510
|
+
* Get modified files
|
|
2373
3511
|
*/
|
|
2374
|
-
async
|
|
3512
|
+
async getModifiedFiles() {
|
|
2375
3513
|
try {
|
|
2376
|
-
await
|
|
2377
|
-
const
|
|
2378
|
-
return
|
|
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
|
-
*
|
|
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
|
|
2387
|
-
const
|
|
2388
|
-
|
|
2389
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6493
|
-
import { join as
|
|
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
|
|
7747
|
+
await mkdir8(pluginsDir, { recursive: true });
|
|
6592
7748
|
const fileName = `${name}.ts`;
|
|
6593
|
-
const filePath =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8017
|
+
await mkdir9(configDir, { recursive: true });
|
|
6862
8018
|
}
|
|
6863
|
-
await
|
|
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
|
|
6903
|
-
import { join as
|
|
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 =
|
|
6908
|
-
await
|
|
8063
|
+
const logsDir = join14(configDir, "logs");
|
|
8064
|
+
await mkdir10(logsDir, { recursive: true });
|
|
6909
8065
|
const scriptContent = generateUpdateScript(targetVersion, logsDir);
|
|
6910
|
-
const scriptPath =
|
|
6911
|
-
await
|
|
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
|
|
7086
|
-
import { homedir as
|
|
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 ||
|
|
7100
|
-
const opencodeConfig =
|
|
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 ||
|
|
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(
|
|
8365
|
+
installed: existsSync6(join15(opencodePath, "opencode", "opencode.json")),
|
|
7210
8366
|
configPath: opencodePath
|
|
7211
8367
|
});
|
|
7212
|
-
const claudePath = process.platform === "win32" ? process.env.APPDATA ||
|
|
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
|
|
7251
|
-
import { join as
|
|
7252
|
-
import { homedir as
|
|
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
|
|
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
|
|
7291
|
-
|
|
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
|
|
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 =
|
|
7320
|
-
const mcpServerPath =
|
|
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
|
-
|
|
8487
|
+
join17(homedir4(), ".config", "opencode", "opencode.json"),
|
|
7324
8488
|
// Project-level config
|
|
7325
|
-
|
|
8489
|
+
join17(projectPath, ".opencode", "opencode.json"),
|
|
7326
8490
|
// Alternative global location
|
|
7327
|
-
|
|
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 =
|
|
7337
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
7361
|
-
await
|
|
7362
|
-
await
|
|
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
|
-
|
|
8700
|
+
Analyze a Figma design and extract all design tokens automatically using Figma API.
|
|
7552
8701
|
|
|
7553
|
-
**Step 1: Extract URL
|
|
8702
|
+
**Step 1: Extract Figma URL**
|
|
8703
|
+
|
|
8704
|
+
The Figma URL is provided as: \`$ARGUMENTS\`
|
|
7554
8705
|
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
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
|
|
8717
|
+
**Step 3: Call MCP Tool**
|
|
7586
8718
|
|
|
7587
|
-
Use the MCP tool
|
|
8719
|
+
Use the MCP Figma tool to analyze the design:
|
|
7588
8720
|
\`\`\`
|
|
7589
|
-
Use tool:
|
|
7590
|
-
Arguments: {
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
7616
|
-
const aikitDir =
|
|
7617
|
-
const opencodeAgentDir =
|
|
7618
|
-
await
|
|
7619
|
-
await
|
|
7620
|
-
await
|
|
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 =
|
|
8748
|
+
const filePath = join17(opencodeAgentDir, `${name}.md`);
|
|
7623
8749
|
try {
|
|
7624
|
-
await
|
|
7625
|
-
const existingContent = await
|
|
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
|
|
8757
|
+
await writeFile12(filePath, updatedContent, "utf8");
|
|
7632
8758
|
}
|
|
7633
8759
|
} catch {
|
|
7634
|
-
await
|
|
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 =
|
|
7726
|
-
await
|
|
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
|
|
8137
|
-
import { join as
|
|
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
|
|
9247
|
+
return join18(process.cwd(), ".opencode", "command");
|
|
8143
9248
|
}
|
|
8144
9249
|
getSkillsDir() {
|
|
8145
|
-
return
|
|
9250
|
+
return join18(process.cwd(), ".opencode", "skill");
|
|
8146
9251
|
}
|
|
8147
9252
|
getAgentsDir() {
|
|
8148
|
-
return
|
|
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
|
|
8172
|
-
await
|
|
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 ?
|
|
8177
|
-
await
|
|
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
|
|
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
|
|
8185
|
-
const filePath =
|
|
9289
|
+
await mkdir13(dir, { recursive: true });
|
|
9290
|
+
const filePath = join18(dir, `${name}.md`);
|
|
8186
9291
|
try {
|
|
8187
|
-
await
|
|
8188
|
-
const existingContent = await
|
|
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
|
|
9299
|
+
await writeFile13(filePath, updatedContent, "utf-8");
|
|
8195
9300
|
}
|
|
8196
9301
|
} catch {
|
|
8197
|
-
await
|
|
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
|
-
${
|
|
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
|
|
8273
|
-
import { join as
|
|
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
|
|
8311
|
-
await
|
|
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 =
|
|
8317
|
-
await
|
|
9402
|
+
const targetDir = join19(baseDir, directory);
|
|
9403
|
+
await mkdir14(targetDir, { recursive: true });
|
|
8318
9404
|
for (const [filename, content] of Object.entries(files)) {
|
|
8319
|
-
await
|
|
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
|
|
8325
|
-
await
|
|
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 =
|
|
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
|
|
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
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
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
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
8487
|
-
|
|
8488
|
-
|
|
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 (
|
|
9621
|
+
if (platformConfig.primary === "opencode") {
|
|
8491
9622
|
showOpenCodeUsage();
|
|
8492
|
-
} else
|
|
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("
|
|
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
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
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
|
-
|
|
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
|
|
8642
|
-
import { join as
|
|
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
|
|
8651
|
-
import { join as
|
|
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 =
|
|
9872
|
+
const versionPath = join20(paths.globalConfig(), ".version.json");
|
|
8663
9873
|
try {
|
|
8664
|
-
const content = await
|
|
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(
|
|
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 =
|
|
9943
|
+
const installedPath = join20(paths.globalConfig(), ".installed-skills.json");
|
|
8734
9944
|
try {
|
|
8735
|
-
const installedData = await
|
|
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
|
|
9993
|
+
const files = await readdir8(dir);
|
|
8784
9994
|
for (const file of files) {
|
|
8785
|
-
const filePath =
|
|
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
|
|
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 =
|
|
10042
|
+
const installedPath = join20(paths.globalConfig(), ".installed-skills.json");
|
|
8833
10043
|
try {
|
|
8834
|
-
await
|
|
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 =
|
|
8856
|
-
await
|
|
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
|
|
8872
|
-
import { join as
|
|
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 =
|
|
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
|
|
10098
|
+
await mkdir15(this.backupsDir, { recursive: true });
|
|
8889
10099
|
const backupId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
8890
|
-
const backupPath =
|
|
8891
|
-
await
|
|
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 =
|
|
8913
|
-
await
|
|
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 =
|
|
8927
|
-
const targetPath =
|
|
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
|
|
8933
|
-
const entries = await
|
|
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
|
|
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
|
|
8958
|
-
await
|
|
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
|
|
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
|
|
10186
|
+
const entries = await readdir9(this.backupsDir);
|
|
8977
10187
|
const backups = [];
|
|
8978
10188
|
for (const entry of entries) {
|
|
8979
|
-
const backupPath =
|
|
8980
|
-
const manifestPath =
|
|
10189
|
+
const backupPath = join21(this.backupsDir, entry);
|
|
10190
|
+
const manifestPath = join21(backupPath, "backup-manifest.json");
|
|
8981
10191
|
try {
|
|
8982
|
-
const manifestContent = await
|
|
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
|
|
10218
|
+
const entries = await readdir9(dir);
|
|
9009
10219
|
for (const entry of entries) {
|
|
9010
|
-
const entryPath =
|
|
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 =
|
|
9043
|
-
const targetPath =
|
|
9044
|
-
await
|
|
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 =
|
|
9060
|
-
await
|
|
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 =
|
|
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
|
|
10296
|
+
const entries = await readdir9(backup.path);
|
|
9087
10297
|
for (const entry of entries) {
|
|
9088
|
-
const entryPath =
|
|
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
|
|
10317
|
+
const entries = await readdir9(dirPath);
|
|
9108
10318
|
for (const entry of entries) {
|
|
9109
|
-
const entryPath =
|
|
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
|
|
9156
|
-
import { join as
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
10403
|
+
const migrationHistoryPath = join22(this.configPath, ".migration-history.json");
|
|
9194
10404
|
try {
|
|
9195
|
-
const content = await
|
|
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 =
|
|
10491
|
+
const historyPath = join22(this.configPath, ".migration-history.json");
|
|
9282
10492
|
try {
|
|
9283
10493
|
let history = [];
|
|
9284
10494
|
try {
|
|
9285
|
-
const content = await
|
|
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
|
|
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 =
|
|
10509
|
+
const historyPath = join22(this.configPath, ".migration-history.json");
|
|
9300
10510
|
try {
|
|
9301
|
-
const content = await
|
|
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
|
|
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 =
|
|
10525
|
+
const historyPath = join22(this.configPath, ".migration-history.json");
|
|
9316
10526
|
try {
|
|
9317
|
-
const content = await
|
|
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 =
|
|
9627
|
-
const targetPath =
|
|
9628
|
-
await
|
|
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 =
|
|
9636
|
-
const targetPath =
|
|
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
|
|
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
|
|
9648
|
-
await
|
|
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);
|