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