md4ai 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.bundled.js +407 -384
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -1198,6 +1198,122 @@ var init_scanner = __esm({
|
|
|
1198
1198
|
}
|
|
1199
1199
|
});
|
|
1200
1200
|
|
|
1201
|
+
// dist/output/html-generator.js
|
|
1202
|
+
function generateOfflineHtml(result, projectRoot) {
|
|
1203
|
+
const title = projectRoot.split("/").pop() ?? "MD4AI";
|
|
1204
|
+
const dataJson = JSON.stringify(result);
|
|
1205
|
+
const orphanItems = result.orphans.map((o) => `<li data-path="${escapeHtml(o.path)}">${escapeHtml(o.path)} <span class="meta">(${o.sizeBytes} bytes)</span></li>`).join("\n");
|
|
1206
|
+
const staleItems = result.staleFiles.map((s) => `<li data-path="${escapeHtml(s.path)}">${escapeHtml(s.path)} <span class="stale-meta">(${s.daysSinceModified} days)</span></li>`).join("\n");
|
|
1207
|
+
const skillRows = result.skills.map((s) => `<tr><td>${escapeHtml(s.name)}</td><td>${s.machineWide ? '<span class="check">\u2713</span>' : ""}</td><td>${s.projectSpecific ? '<span class="check">\u2713</span>' : ""}</td><td class="meta">${escapeHtml(s.source)}</td></tr>`).join("\n");
|
|
1208
|
+
const fileItems = result.graph.nodes.map((n) => `<li data-path="${escapeHtml(n.filePath)}" data-type="${n.type}">${escapeHtml(n.filePath)} <span class="meta">(${n.type})</span></li>`).join("\n");
|
|
1209
|
+
return `<!DOCTYPE html>
|
|
1210
|
+
<html lang="en">
|
|
1211
|
+
<head>
|
|
1212
|
+
<meta charset="UTF-8">
|
|
1213
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1214
|
+
<title>MD4AI \u2014 ${escapeHtml(title)}</title>
|
|
1215
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
1216
|
+
<style>
|
|
1217
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1218
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
|
|
1219
|
+
h1 { color: #38bdf8; margin-bottom: 0.5rem; }
|
|
1220
|
+
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
|
|
1221
|
+
.section { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
1222
|
+
.section h2 { color: #38bdf8; margin-bottom: 1rem; font-size: 1.2rem; }
|
|
1223
|
+
.mermaid { background: #0f172a; border-radius: 8px; padding: 1rem; overflow-x: auto; }
|
|
1224
|
+
.badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
1225
|
+
.badge-orphan { background: #ef4444; color: white; }
|
|
1226
|
+
.badge-stale { background: #f59e0b; color: black; }
|
|
1227
|
+
table { width: 100%; border-collapse: collapse; }
|
|
1228
|
+
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
1229
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
1230
|
+
.check { color: #22c55e; }
|
|
1231
|
+
.meta { color: #94a3b8; }
|
|
1232
|
+
.stale-meta { color: #f59e0b; }
|
|
1233
|
+
.stats { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
|
1234
|
+
.stat { text-align: center; }
|
|
1235
|
+
.stat-value { font-size: 2rem; font-weight: bold; color: #38bdf8; }
|
|
1236
|
+
.stat-label { color: #94a3b8; font-size: 0.85rem; }
|
|
1237
|
+
.file-list { list-style: none; }
|
|
1238
|
+
.file-list li { padding: 0.3rem 0; color: #cbd5e1; font-family: monospace; font-size: 0.9rem; }
|
|
1239
|
+
#search { width: 100%; padding: 0.75rem 1rem; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 1rem; margin-bottom: 1rem; }
|
|
1240
|
+
#search:focus { outline: none; border-color: #38bdf8; }
|
|
1241
|
+
@media print { body { background: white; color: black; } .section { border: 1px solid #ccc; } }
|
|
1242
|
+
</style>
|
|
1243
|
+
</head>
|
|
1244
|
+
<body>
|
|
1245
|
+
<h1>MD4AI \u2014 ${escapeHtml(title)}</h1>
|
|
1246
|
+
<p class="subtitle">Scanned: ${new Date(result.scannedAt).toLocaleString()} | Hash: ${result.dataHash.slice(0, 12)}</p>
|
|
1247
|
+
|
|
1248
|
+
<div class="stats">
|
|
1249
|
+
<div class="stat"><div class="stat-value">${result.graph.nodes.length}</div><div class="stat-label">Files</div></div>
|
|
1250
|
+
<div class="stat"><div class="stat-value">${result.graph.edges.length}</div><div class="stat-label">References</div></div>
|
|
1251
|
+
<div class="stat"><div class="stat-value">${result.orphans.length}</div><div class="stat-label">Orphans</div></div>
|
|
1252
|
+
<div class="stat"><div class="stat-value">${result.staleFiles.length}</div><div class="stat-label">Stale</div></div>
|
|
1253
|
+
<div class="stat"><div class="stat-value">${result.skills.length}</div><div class="stat-label">Skills</div></div>
|
|
1254
|
+
</div>
|
|
1255
|
+
|
|
1256
|
+
<input type="text" id="search" placeholder="Search files..." oninput="filterFiles(this.value)">
|
|
1257
|
+
|
|
1258
|
+
<div class="section">
|
|
1259
|
+
<h2>Dependency Graph</h2>
|
|
1260
|
+
<pre class="mermaid">${escapeHtml(result.graph.mermaid)}</pre>
|
|
1261
|
+
</div>
|
|
1262
|
+
|
|
1263
|
+
${result.orphans.length > 0 ? `
|
|
1264
|
+
<div class="section">
|
|
1265
|
+
<h2>Orphan Files <span class="badge badge-orphan">${result.orphans.length}</span></h2>
|
|
1266
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not reachable from any root configuration file.</p>
|
|
1267
|
+
<ul class="file-list" id="orphan-list">${orphanItems}</ul>
|
|
1268
|
+
</div>
|
|
1269
|
+
` : ""}
|
|
1270
|
+
|
|
1271
|
+
${result.staleFiles.length > 0 ? `
|
|
1272
|
+
<div class="section">
|
|
1273
|
+
<h2>Stale Files <span class="badge badge-stale">${result.staleFiles.length}</span></h2>
|
|
1274
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not modified in over 90 days.</p>
|
|
1275
|
+
<ul class="file-list" id="stale-list">${staleItems}</ul>
|
|
1276
|
+
</div>
|
|
1277
|
+
` : ""}
|
|
1278
|
+
|
|
1279
|
+
<div class="section">
|
|
1280
|
+
<h2>Skills Comparison</h2>
|
|
1281
|
+
<table>
|
|
1282
|
+
<thead><tr><th>Skill/Plugin</th><th>Entire Machine</th><th>Project Specific</th><th>Source</th></tr></thead>
|
|
1283
|
+
<tbody>${skillRows}</tbody>
|
|
1284
|
+
</table>
|
|
1285
|
+
</div>
|
|
1286
|
+
|
|
1287
|
+
<div class="section">
|
|
1288
|
+
<h2>All Files</h2>
|
|
1289
|
+
<ul class="file-list" id="all-files">${fileItems}</ul>
|
|
1290
|
+
</div>
|
|
1291
|
+
|
|
1292
|
+
<script>
|
|
1293
|
+
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
1294
|
+
|
|
1295
|
+
function filterFiles(query) {
|
|
1296
|
+
var q = query.toLowerCase();
|
|
1297
|
+
document.querySelectorAll('.file-list li').forEach(function(li) {
|
|
1298
|
+
var path = li.getAttribute('data-path') || '';
|
|
1299
|
+
li.style.display = path.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
</script>
|
|
1303
|
+
|
|
1304
|
+
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
1305
|
+
</body>
|
|
1306
|
+
</html>`;
|
|
1307
|
+
}
|
|
1308
|
+
function escapeHtml(text) {
|
|
1309
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1310
|
+
}
|
|
1311
|
+
var init_html_generator = __esm({
|
|
1312
|
+
"dist/output/html-generator.js"() {
|
|
1313
|
+
"use strict";
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1201
1317
|
// dist/check-update.js
|
|
1202
1318
|
import chalk8 from "chalk";
|
|
1203
1319
|
async function fetchLatest() {
|
|
@@ -1262,7 +1378,7 @@ var CURRENT_VERSION;
|
|
|
1262
1378
|
var init_check_update = __esm({
|
|
1263
1379
|
"dist/check-update.js"() {
|
|
1264
1380
|
"use strict";
|
|
1265
|
-
CURRENT_VERSION = true ? "0.9.
|
|
1381
|
+
CURRENT_VERSION = true ? "0.9.3" : "0.0.0-dev";
|
|
1266
1382
|
}
|
|
1267
1383
|
});
|
|
1268
1384
|
|
|
@@ -1328,6 +1444,215 @@ var init_device_utils = __esm({
|
|
|
1328
1444
|
}
|
|
1329
1445
|
});
|
|
1330
1446
|
|
|
1447
|
+
// dist/commands/map.js
|
|
1448
|
+
var map_exports = {};
|
|
1449
|
+
__export(map_exports, {
|
|
1450
|
+
mapCommand: () => mapCommand
|
|
1451
|
+
});
|
|
1452
|
+
import { resolve as resolve3, basename } from "node:path";
|
|
1453
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1454
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1455
|
+
import chalk9 from "chalk";
|
|
1456
|
+
import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
1457
|
+
async function mapCommand(path, options) {
|
|
1458
|
+
await checkForUpdate();
|
|
1459
|
+
const projectRoot = resolve3(path ?? process.cwd());
|
|
1460
|
+
if (!existsSync7(projectRoot)) {
|
|
1461
|
+
console.error(chalk9.red(`Path not found: ${projectRoot}`));
|
|
1462
|
+
process.exit(1);
|
|
1463
|
+
}
|
|
1464
|
+
console.log(chalk9.blue(`Scanning: ${projectRoot}
|
|
1465
|
+
`));
|
|
1466
|
+
const result = await scanProject(projectRoot);
|
|
1467
|
+
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
1468
|
+
console.log(` References: ${result.graph.edges.length}`);
|
|
1469
|
+
console.log(` Orphans: ${result.orphans.length}`);
|
|
1470
|
+
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
1471
|
+
console.log(` Skills: ${result.skills.length}`);
|
|
1472
|
+
console.log(` Toolings: ${result.toolings.length}`);
|
|
1473
|
+
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
1474
|
+
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
1475
|
+
const outputDir = resolve3(projectRoot, "output");
|
|
1476
|
+
if (!existsSync7(outputDir)) {
|
|
1477
|
+
await mkdir2(outputDir, { recursive: true });
|
|
1478
|
+
}
|
|
1479
|
+
const htmlPath = resolve3(outputDir, "index.html");
|
|
1480
|
+
const html = generateOfflineHtml(result, projectRoot);
|
|
1481
|
+
await writeFile2(htmlPath, html, "utf-8");
|
|
1482
|
+
console.log(chalk9.green(`
|
|
1483
|
+
Local preview: ${htmlPath}`));
|
|
1484
|
+
if (!options.offline) {
|
|
1485
|
+
try {
|
|
1486
|
+
const { supabase } = await getAuthenticatedClient();
|
|
1487
|
+
const { data: devicePaths } = await supabase.from("device_paths").select("folder_id, device_name").eq("path", projectRoot);
|
|
1488
|
+
if (devicePaths?.length) {
|
|
1489
|
+
const { folder_id, device_name } = devicePaths[0];
|
|
1490
|
+
const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
|
|
1491
|
+
if (proposedFiles?.length) {
|
|
1492
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
1493
|
+
console.log(chalk9.yellow(`
|
|
1494
|
+
${proposedFiles.length} file(s) proposed for deletion:
|
|
1495
|
+
`));
|
|
1496
|
+
const toDelete = await checkbox({
|
|
1497
|
+
message: "Select files to delete (space to toggle, enter to confirm)",
|
|
1498
|
+
choices: proposedFiles.map((f) => ({
|
|
1499
|
+
name: `${f.file_path}${f.proposed_at ? ` (proposed ${new Date(f.proposed_at).toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", year: "numeric" })})` : ""}`,
|
|
1500
|
+
value: f
|
|
1501
|
+
}))
|
|
1502
|
+
});
|
|
1503
|
+
for (const file of toDelete) {
|
|
1504
|
+
const fullPath = resolve3(projectRoot, file.file_path);
|
|
1505
|
+
try {
|
|
1506
|
+
const { unlink } = await import("node:fs/promises");
|
|
1507
|
+
await unlink(fullPath);
|
|
1508
|
+
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
1509
|
+
console.log(chalk9.green(` Deleted: ${file.file_path}`));
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
1515
|
+
if (keptIds.length > 0) {
|
|
1516
|
+
for (const id of keptIds) {
|
|
1517
|
+
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
1518
|
+
}
|
|
1519
|
+
console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
1520
|
+
}
|
|
1521
|
+
console.log("");
|
|
1522
|
+
}
|
|
1523
|
+
const { error } = await supabase.from("claude_folders").update({
|
|
1524
|
+
graph_json: result.graph,
|
|
1525
|
+
orphans_json: result.orphans,
|
|
1526
|
+
skills_table_json: result.skills,
|
|
1527
|
+
stale_files_json: result.staleFiles,
|
|
1528
|
+
env_manifest_json: result.envManifest,
|
|
1529
|
+
last_scanned: result.scannedAt,
|
|
1530
|
+
data_hash: result.dataHash
|
|
1531
|
+
}).eq("id", folder_id);
|
|
1532
|
+
if (error) {
|
|
1533
|
+
console.error(chalk9.yellow(`Sync warning: ${error.message}`));
|
|
1534
|
+
} else {
|
|
1535
|
+
await pushToolings(supabase, folder_id, result.toolings);
|
|
1536
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
1537
|
+
await saveState({
|
|
1538
|
+
lastFolderId: folder_id,
|
|
1539
|
+
lastDeviceName: device_name,
|
|
1540
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1541
|
+
});
|
|
1542
|
+
console.log(chalk9.green("Synced to Supabase."));
|
|
1543
|
+
console.log(chalk9.cyan(`
|
|
1544
|
+
https://www.md4ai.com/project/${folder_id}
|
|
1545
|
+
`));
|
|
1546
|
+
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1547
|
+
if (configFiles.length > 0) {
|
|
1548
|
+
for (const file of configFiles) {
|
|
1549
|
+
await supabase.from("folder_files").upsert({
|
|
1550
|
+
folder_id,
|
|
1551
|
+
file_path: file.filePath,
|
|
1552
|
+
content: file.content,
|
|
1553
|
+
size_bytes: file.sizeBytes,
|
|
1554
|
+
last_modified: file.lastModified
|
|
1555
|
+
}, { onConflict: "folder_id,file_path" });
|
|
1556
|
+
}
|
|
1557
|
+
console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} else {
|
|
1561
|
+
console.log(chalk9.yellow("\nThis folder is not linked to a project on your dashboard."));
|
|
1562
|
+
const shouldLink = await confirm2({ message: "Would you like to link it now?" });
|
|
1563
|
+
if (!shouldLink) {
|
|
1564
|
+
console.log(chalk9.dim("Skipped \u2014 local preview still generated."));
|
|
1565
|
+
} else {
|
|
1566
|
+
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
1567
|
+
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
1568
|
+
const choices = [
|
|
1569
|
+
{ name: "+ Create a new project", value: "__new__" },
|
|
1570
|
+
...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
|
|
1571
|
+
];
|
|
1572
|
+
const chosen = await select3({
|
|
1573
|
+
message: "Link to which project?",
|
|
1574
|
+
choices
|
|
1575
|
+
});
|
|
1576
|
+
let folderId;
|
|
1577
|
+
if (chosen === "__new__") {
|
|
1578
|
+
const projectName = await input5({
|
|
1579
|
+
message: "Project name:",
|
|
1580
|
+
default: basename(projectRoot)
|
|
1581
|
+
});
|
|
1582
|
+
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
|
|
1583
|
+
if (createErr || !newFolder) {
|
|
1584
|
+
console.error(chalk9.red(`Failed to create project: ${createErr?.message}`));
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
folderId = newFolder.id;
|
|
1588
|
+
console.log(chalk9.green(`Created project "${projectName}".`));
|
|
1589
|
+
} else {
|
|
1590
|
+
folderId = chosen;
|
|
1591
|
+
}
|
|
1592
|
+
const deviceName = detectDeviceName();
|
|
1593
|
+
const osType = detectOs2();
|
|
1594
|
+
await sb.from("devices").upsert({
|
|
1595
|
+
user_id: userId,
|
|
1596
|
+
device_name: deviceName,
|
|
1597
|
+
os_type: osType
|
|
1598
|
+
}, { onConflict: "user_id,device_name" });
|
|
1599
|
+
await sb.from("device_paths").upsert({
|
|
1600
|
+
user_id: userId,
|
|
1601
|
+
folder_id: folderId,
|
|
1602
|
+
device_name: deviceName,
|
|
1603
|
+
os_type: osType,
|
|
1604
|
+
path: projectRoot,
|
|
1605
|
+
last_synced: (/* @__PURE__ */ new Date()).toISOString()
|
|
1606
|
+
}, { onConflict: "folder_id,device_name" });
|
|
1607
|
+
await sb.from("claude_folders").update({
|
|
1608
|
+
graph_json: result.graph,
|
|
1609
|
+
orphans_json: result.orphans,
|
|
1610
|
+
skills_table_json: result.skills,
|
|
1611
|
+
stale_files_json: result.staleFiles,
|
|
1612
|
+
env_manifest_json: result.envManifest,
|
|
1613
|
+
last_scanned: result.scannedAt,
|
|
1614
|
+
data_hash: result.dataHash
|
|
1615
|
+
}).eq("id", folderId);
|
|
1616
|
+
await pushToolings(sb, folderId, result.toolings);
|
|
1617
|
+
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1618
|
+
for (const file of configFiles) {
|
|
1619
|
+
await sb.from("folder_files").upsert({
|
|
1620
|
+
folder_id: folderId,
|
|
1621
|
+
file_path: file.filePath,
|
|
1622
|
+
content: file.content,
|
|
1623
|
+
size_bytes: file.sizeBytes,
|
|
1624
|
+
last_modified: file.lastModified
|
|
1625
|
+
}, { onConflict: "folder_id,file_path" });
|
|
1626
|
+
}
|
|
1627
|
+
await saveState({
|
|
1628
|
+
lastFolderId: folderId,
|
|
1629
|
+
lastDeviceName: deviceName,
|
|
1630
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1631
|
+
});
|
|
1632
|
+
console.log(chalk9.green("\nLinked and synced."));
|
|
1633
|
+
console.log(chalk9.cyan(`
|
|
1634
|
+
https://www.md4ai.com/project/${folderId}
|
|
1635
|
+
`));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
} catch {
|
|
1639
|
+
console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
var init_map = __esm({
|
|
1644
|
+
"dist/commands/map.js"() {
|
|
1645
|
+
"use strict";
|
|
1646
|
+
init_scanner();
|
|
1647
|
+
init_auth();
|
|
1648
|
+
init_config();
|
|
1649
|
+
init_html_generator();
|
|
1650
|
+
init_check_update();
|
|
1651
|
+
init_push_toolings();
|
|
1652
|
+
init_device_utils();
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1331
1656
|
// dist/commands/sync.js
|
|
1332
1657
|
var sync_exports = {};
|
|
1333
1658
|
__export(sync_exports, {
|
|
@@ -2176,385 +2501,76 @@ async function addDeviceCommand() {
|
|
|
2176
2501
|
});
|
|
2177
2502
|
const suggested = suggestDeviceName();
|
|
2178
2503
|
const deviceName = await input4({
|
|
2179
|
-
message: "Device name:",
|
|
2180
|
-
default: suggested
|
|
2181
|
-
});
|
|
2182
|
-
const osType = await select2({
|
|
2183
|
-
message: "OS type:",
|
|
2184
|
-
choices: [
|
|
2185
|
-
{ name: "Windows", value: "windows" },
|
|
2186
|
-
{ name: "macOS", value: "macos" },
|
|
2187
|
-
{ name: "Ubuntu", value: "ubuntu" },
|
|
2188
|
-
{ name: "Linux", value: "linux" },
|
|
2189
|
-
{ name: "Other", value: "other" }
|
|
2190
|
-
],
|
|
2191
|
-
default: detectOs()
|
|
2192
|
-
});
|
|
2193
|
-
const description = await input4({ message: "Description (optional):" });
|
|
2194
|
-
const { error } = await supabase.from("device_paths").insert({
|
|
2195
|
-
user_id: userId,
|
|
2196
|
-
folder_id: folderId,
|
|
2197
|
-
device_name: deviceName,
|
|
2198
|
-
os_type: osType,
|
|
2199
|
-
path: localPath,
|
|
2200
|
-
description: description || null
|
|
2201
|
-
});
|
|
2202
|
-
if (error) {
|
|
2203
|
-
console.error(chalk6.red(`Failed to add device: ${error.message}`));
|
|
2204
|
-
process.exit(1);
|
|
2205
|
-
}
|
|
2206
|
-
console.log(chalk6.green(`
|
|
2207
|
-
Device "${deviceName}" added to folder.`));
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
// dist/commands/list-devices.js
|
|
2211
|
-
init_auth();
|
|
2212
|
-
import chalk7 from "chalk";
|
|
2213
|
-
async function listDevicesCommand() {
|
|
2214
|
-
const { supabase } = await getAuthenticatedClient();
|
|
2215
|
-
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
2216
|
-
id, device_name, os_type, path, last_synced, description,
|
|
2217
|
-
claude_folders!inner ( name )
|
|
2218
|
-
`).order("device_name");
|
|
2219
|
-
if (error) {
|
|
2220
|
-
console.error(chalk7.red(`Failed to list devices: ${error.message}`));
|
|
2221
|
-
process.exit(1);
|
|
2222
|
-
}
|
|
2223
|
-
if (!devices?.length) {
|
|
2224
|
-
console.log(chalk7.yellow("No devices found. Run: md4ai add-device"));
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
2228
|
-
for (const d of devices) {
|
|
2229
|
-
const list = grouped.get(d.device_name) ?? [];
|
|
2230
|
-
list.push(d);
|
|
2231
|
-
grouped.set(d.device_name, list);
|
|
2232
|
-
}
|
|
2233
|
-
for (const [deviceName, entries] of grouped) {
|
|
2234
|
-
const first = entries[0];
|
|
2235
|
-
console.log(chalk7.bold(`
|
|
2236
|
-
${deviceName}`) + chalk7.dim(` (${first.os_type})`));
|
|
2237
|
-
for (const entry of entries) {
|
|
2238
|
-
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
2239
|
-
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
2240
|
-
console.log(` ${chalk7.cyan(folderName)} \u2192 ${entry.path}`);
|
|
2241
|
-
console.log(` Last synced: ${synced}`);
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
// dist/commands/map.js
|
|
2247
|
-
init_scanner();
|
|
2248
|
-
init_auth();
|
|
2249
|
-
init_config();
|
|
2250
|
-
import { resolve as resolve3, basename } from "node:path";
|
|
2251
|
-
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
2252
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
2253
|
-
import chalk9 from "chalk";
|
|
2254
|
-
|
|
2255
|
-
// dist/output/html-generator.js
|
|
2256
|
-
function generateOfflineHtml(result, projectRoot) {
|
|
2257
|
-
const title = projectRoot.split("/").pop() ?? "MD4AI";
|
|
2258
|
-
const dataJson = JSON.stringify(result);
|
|
2259
|
-
const orphanItems = result.orphans.map((o) => `<li data-path="${escapeHtml(o.path)}">${escapeHtml(o.path)} <span class="meta">(${o.sizeBytes} bytes)</span></li>`).join("\n");
|
|
2260
|
-
const staleItems = result.staleFiles.map((s) => `<li data-path="${escapeHtml(s.path)}">${escapeHtml(s.path)} <span class="stale-meta">(${s.daysSinceModified} days)</span></li>`).join("\n");
|
|
2261
|
-
const skillRows = result.skills.map((s) => `<tr><td>${escapeHtml(s.name)}</td><td>${s.machineWide ? '<span class="check">\u2713</span>' : ""}</td><td>${s.projectSpecific ? '<span class="check">\u2713</span>' : ""}</td><td class="meta">${escapeHtml(s.source)}</td></tr>`).join("\n");
|
|
2262
|
-
const fileItems = result.graph.nodes.map((n) => `<li data-path="${escapeHtml(n.filePath)}" data-type="${n.type}">${escapeHtml(n.filePath)} <span class="meta">(${n.type})</span></li>`).join("\n");
|
|
2263
|
-
return `<!DOCTYPE html>
|
|
2264
|
-
<html lang="en">
|
|
2265
|
-
<head>
|
|
2266
|
-
<meta charset="UTF-8">
|
|
2267
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2268
|
-
<title>MD4AI \u2014 ${escapeHtml(title)}</title>
|
|
2269
|
-
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
2270
|
-
<style>
|
|
2271
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2272
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
|
|
2273
|
-
h1 { color: #38bdf8; margin-bottom: 0.5rem; }
|
|
2274
|
-
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
|
|
2275
|
-
.section { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
2276
|
-
.section h2 { color: #38bdf8; margin-bottom: 1rem; font-size: 1.2rem; }
|
|
2277
|
-
.mermaid { background: #0f172a; border-radius: 8px; padding: 1rem; overflow-x: auto; }
|
|
2278
|
-
.badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
2279
|
-
.badge-orphan { background: #ef4444; color: white; }
|
|
2280
|
-
.badge-stale { background: #f59e0b; color: black; }
|
|
2281
|
-
table { width: 100%; border-collapse: collapse; }
|
|
2282
|
-
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
2283
|
-
th { color: #94a3b8; font-weight: 600; }
|
|
2284
|
-
.check { color: #22c55e; }
|
|
2285
|
-
.meta { color: #94a3b8; }
|
|
2286
|
-
.stale-meta { color: #f59e0b; }
|
|
2287
|
-
.stats { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
|
2288
|
-
.stat { text-align: center; }
|
|
2289
|
-
.stat-value { font-size: 2rem; font-weight: bold; color: #38bdf8; }
|
|
2290
|
-
.stat-label { color: #94a3b8; font-size: 0.85rem; }
|
|
2291
|
-
.file-list { list-style: none; }
|
|
2292
|
-
.file-list li { padding: 0.3rem 0; color: #cbd5e1; font-family: monospace; font-size: 0.9rem; }
|
|
2293
|
-
#search { width: 100%; padding: 0.75rem 1rem; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 1rem; margin-bottom: 1rem; }
|
|
2294
|
-
#search:focus { outline: none; border-color: #38bdf8; }
|
|
2295
|
-
@media print { body { background: white; color: black; } .section { border: 1px solid #ccc; } }
|
|
2296
|
-
</style>
|
|
2297
|
-
</head>
|
|
2298
|
-
<body>
|
|
2299
|
-
<h1>MD4AI \u2014 ${escapeHtml(title)}</h1>
|
|
2300
|
-
<p class="subtitle">Scanned: ${new Date(result.scannedAt).toLocaleString()} | Hash: ${result.dataHash.slice(0, 12)}</p>
|
|
2301
|
-
|
|
2302
|
-
<div class="stats">
|
|
2303
|
-
<div class="stat"><div class="stat-value">${result.graph.nodes.length}</div><div class="stat-label">Files</div></div>
|
|
2304
|
-
<div class="stat"><div class="stat-value">${result.graph.edges.length}</div><div class="stat-label">References</div></div>
|
|
2305
|
-
<div class="stat"><div class="stat-value">${result.orphans.length}</div><div class="stat-label">Orphans</div></div>
|
|
2306
|
-
<div class="stat"><div class="stat-value">${result.staleFiles.length}</div><div class="stat-label">Stale</div></div>
|
|
2307
|
-
<div class="stat"><div class="stat-value">${result.skills.length}</div><div class="stat-label">Skills</div></div>
|
|
2308
|
-
</div>
|
|
2309
|
-
|
|
2310
|
-
<input type="text" id="search" placeholder="Search files..." oninput="filterFiles(this.value)">
|
|
2311
|
-
|
|
2312
|
-
<div class="section">
|
|
2313
|
-
<h2>Dependency Graph</h2>
|
|
2314
|
-
<pre class="mermaid">${escapeHtml(result.graph.mermaid)}</pre>
|
|
2315
|
-
</div>
|
|
2316
|
-
|
|
2317
|
-
${result.orphans.length > 0 ? `
|
|
2318
|
-
<div class="section">
|
|
2319
|
-
<h2>Orphan Files <span class="badge badge-orphan">${result.orphans.length}</span></h2>
|
|
2320
|
-
<p class="meta" style="margin-bottom: 1rem;">Files not reachable from any root configuration file.</p>
|
|
2321
|
-
<ul class="file-list" id="orphan-list">${orphanItems}</ul>
|
|
2322
|
-
</div>
|
|
2323
|
-
` : ""}
|
|
2324
|
-
|
|
2325
|
-
${result.staleFiles.length > 0 ? `
|
|
2326
|
-
<div class="section">
|
|
2327
|
-
<h2>Stale Files <span class="badge badge-stale">${result.staleFiles.length}</span></h2>
|
|
2328
|
-
<p class="meta" style="margin-bottom: 1rem;">Files not modified in over 90 days.</p>
|
|
2329
|
-
<ul class="file-list" id="stale-list">${staleItems}</ul>
|
|
2330
|
-
</div>
|
|
2331
|
-
` : ""}
|
|
2332
|
-
|
|
2333
|
-
<div class="section">
|
|
2334
|
-
<h2>Skills Comparison</h2>
|
|
2335
|
-
<table>
|
|
2336
|
-
<thead><tr><th>Skill/Plugin</th><th>Entire Machine</th><th>Project Specific</th><th>Source</th></tr></thead>
|
|
2337
|
-
<tbody>${skillRows}</tbody>
|
|
2338
|
-
</table>
|
|
2339
|
-
</div>
|
|
2340
|
-
|
|
2341
|
-
<div class="section">
|
|
2342
|
-
<h2>All Files</h2>
|
|
2343
|
-
<ul class="file-list" id="all-files">${fileItems}</ul>
|
|
2344
|
-
</div>
|
|
2345
|
-
|
|
2346
|
-
<script>
|
|
2347
|
-
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
2348
|
-
|
|
2349
|
-
function filterFiles(query) {
|
|
2350
|
-
var q = query.toLowerCase();
|
|
2351
|
-
document.querySelectorAll('.file-list li').forEach(function(li) {
|
|
2352
|
-
var path = li.getAttribute('data-path') || '';
|
|
2353
|
-
li.style.display = path.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
|
2354
|
-
});
|
|
2355
|
-
}
|
|
2356
|
-
</script>
|
|
2357
|
-
|
|
2358
|
-
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
2359
|
-
</body>
|
|
2360
|
-
</html>`;
|
|
2361
|
-
}
|
|
2362
|
-
function escapeHtml(text) {
|
|
2363
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2504
|
+
message: "Device name:",
|
|
2505
|
+
default: suggested
|
|
2506
|
+
});
|
|
2507
|
+
const osType = await select2({
|
|
2508
|
+
message: "OS type:",
|
|
2509
|
+
choices: [
|
|
2510
|
+
{ name: "Windows", value: "windows" },
|
|
2511
|
+
{ name: "macOS", value: "macos" },
|
|
2512
|
+
{ name: "Ubuntu", value: "ubuntu" },
|
|
2513
|
+
{ name: "Linux", value: "linux" },
|
|
2514
|
+
{ name: "Other", value: "other" }
|
|
2515
|
+
],
|
|
2516
|
+
default: detectOs()
|
|
2517
|
+
});
|
|
2518
|
+
const description = await input4({ message: "Description (optional):" });
|
|
2519
|
+
const { error } = await supabase.from("device_paths").insert({
|
|
2520
|
+
user_id: userId,
|
|
2521
|
+
folder_id: folderId,
|
|
2522
|
+
device_name: deviceName,
|
|
2523
|
+
os_type: osType,
|
|
2524
|
+
path: localPath,
|
|
2525
|
+
description: description || null
|
|
2526
|
+
});
|
|
2527
|
+
if (error) {
|
|
2528
|
+
console.error(chalk6.red(`Failed to add device: ${error.message}`));
|
|
2529
|
+
process.exit(1);
|
|
2530
|
+
}
|
|
2531
|
+
console.log(chalk6.green(`
|
|
2532
|
+
Device "${deviceName}" added to folder.`));
|
|
2364
2533
|
}
|
|
2365
2534
|
|
|
2366
|
-
// dist/commands/
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2535
|
+
// dist/commands/list-devices.js
|
|
2536
|
+
init_auth();
|
|
2537
|
+
import chalk7 from "chalk";
|
|
2538
|
+
async function listDevicesCommand() {
|
|
2539
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2540
|
+
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
2541
|
+
id, device_name, os_type, path, last_synced, description,
|
|
2542
|
+
claude_folders!inner ( name )
|
|
2543
|
+
`).order("device_name");
|
|
2544
|
+
if (error) {
|
|
2545
|
+
console.error(chalk7.red(`Failed to list devices: ${error.message}`));
|
|
2376
2546
|
process.exit(1);
|
|
2377
2547
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
2382
|
-
console.log(` References: ${result.graph.edges.length}`);
|
|
2383
|
-
console.log(` Orphans: ${result.orphans.length}`);
|
|
2384
|
-
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
2385
|
-
console.log(` Skills: ${result.skills.length}`);
|
|
2386
|
-
console.log(` Toolings: ${result.toolings.length}`);
|
|
2387
|
-
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
2388
|
-
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
2389
|
-
const outputDir = resolve3(projectRoot, "output");
|
|
2390
|
-
if (!existsSync7(outputDir)) {
|
|
2391
|
-
await mkdir2(outputDir, { recursive: true });
|
|
2548
|
+
if (!devices?.length) {
|
|
2549
|
+
console.log(chalk7.yellow("No devices found. Run: md4ai add-device"));
|
|
2550
|
+
return;
|
|
2392
2551
|
}
|
|
2393
|
-
const
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
${proposedFiles.length} file(s) proposed for deletion:
|
|
2409
|
-
`));
|
|
2410
|
-
const toDelete = await checkbox({
|
|
2411
|
-
message: "Select files to delete (space to toggle, enter to confirm)",
|
|
2412
|
-
choices: proposedFiles.map((f) => ({
|
|
2413
|
-
name: `${f.file_path}${f.proposed_at ? ` (proposed ${new Date(f.proposed_at).toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", year: "numeric" })})` : ""}`,
|
|
2414
|
-
value: f
|
|
2415
|
-
}))
|
|
2416
|
-
});
|
|
2417
|
-
for (const file of toDelete) {
|
|
2418
|
-
const fullPath = resolve3(projectRoot, file.file_path);
|
|
2419
|
-
try {
|
|
2420
|
-
const { unlink } = await import("node:fs/promises");
|
|
2421
|
-
await unlink(fullPath);
|
|
2422
|
-
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
2423
|
-
console.log(chalk9.green(` Deleted: ${file.file_path}`));
|
|
2424
|
-
} catch (err) {
|
|
2425
|
-
console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
2429
|
-
if (keptIds.length > 0) {
|
|
2430
|
-
for (const id of keptIds) {
|
|
2431
|
-
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
2432
|
-
}
|
|
2433
|
-
console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
2434
|
-
}
|
|
2435
|
-
console.log("");
|
|
2436
|
-
}
|
|
2437
|
-
const { error } = await supabase.from("claude_folders").update({
|
|
2438
|
-
graph_json: result.graph,
|
|
2439
|
-
orphans_json: result.orphans,
|
|
2440
|
-
skills_table_json: result.skills,
|
|
2441
|
-
stale_files_json: result.staleFiles,
|
|
2442
|
-
env_manifest_json: result.envManifest,
|
|
2443
|
-
last_scanned: result.scannedAt,
|
|
2444
|
-
data_hash: result.dataHash
|
|
2445
|
-
}).eq("id", folder_id);
|
|
2446
|
-
if (error) {
|
|
2447
|
-
console.error(chalk9.yellow(`Sync warning: ${error.message}`));
|
|
2448
|
-
} else {
|
|
2449
|
-
await pushToolings(supabase, folder_id, result.toolings);
|
|
2450
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
2451
|
-
await saveState({
|
|
2452
|
-
lastFolderId: folder_id,
|
|
2453
|
-
lastDeviceName: device_name,
|
|
2454
|
-
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2455
|
-
});
|
|
2456
|
-
console.log(chalk9.green("Synced to Supabase."));
|
|
2457
|
-
console.log(chalk9.cyan(`
|
|
2458
|
-
https://www.md4ai.com/project/${folder_id}
|
|
2459
|
-
`));
|
|
2460
|
-
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
2461
|
-
if (configFiles.length > 0) {
|
|
2462
|
-
for (const file of configFiles) {
|
|
2463
|
-
await supabase.from("folder_files").upsert({
|
|
2464
|
-
folder_id,
|
|
2465
|
-
file_path: file.filePath,
|
|
2466
|
-
content: file.content,
|
|
2467
|
-
size_bytes: file.sizeBytes,
|
|
2468
|
-
last_modified: file.lastModified
|
|
2469
|
-
}, { onConflict: "folder_id,file_path" });
|
|
2470
|
-
}
|
|
2471
|
-
console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
} else {
|
|
2475
|
-
console.log(chalk9.yellow("\nThis folder is not linked to a project on your dashboard."));
|
|
2476
|
-
const shouldLink = await confirm2({ message: "Would you like to link it now?" });
|
|
2477
|
-
if (!shouldLink) {
|
|
2478
|
-
console.log(chalk9.dim("Skipped \u2014 local preview still generated."));
|
|
2479
|
-
} else {
|
|
2480
|
-
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
2481
|
-
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
2482
|
-
const choices = [
|
|
2483
|
-
{ name: "+ Create a new project", value: "__new__" },
|
|
2484
|
-
...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
|
|
2485
|
-
];
|
|
2486
|
-
const chosen = await select3({
|
|
2487
|
-
message: "Link to which project?",
|
|
2488
|
-
choices
|
|
2489
|
-
});
|
|
2490
|
-
let folderId;
|
|
2491
|
-
if (chosen === "__new__") {
|
|
2492
|
-
const projectName = await input5({
|
|
2493
|
-
message: "Project name:",
|
|
2494
|
-
default: basename(projectRoot)
|
|
2495
|
-
});
|
|
2496
|
-
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
|
|
2497
|
-
if (createErr || !newFolder) {
|
|
2498
|
-
console.error(chalk9.red(`Failed to create project: ${createErr?.message}`));
|
|
2499
|
-
return;
|
|
2500
|
-
}
|
|
2501
|
-
folderId = newFolder.id;
|
|
2502
|
-
console.log(chalk9.green(`Created project "${projectName}".`));
|
|
2503
|
-
} else {
|
|
2504
|
-
folderId = chosen;
|
|
2505
|
-
}
|
|
2506
|
-
const deviceName = detectDeviceName();
|
|
2507
|
-
const osType = detectOs2();
|
|
2508
|
-
await sb.from("devices").upsert({
|
|
2509
|
-
user_id: userId,
|
|
2510
|
-
device_name: deviceName,
|
|
2511
|
-
os_type: osType
|
|
2512
|
-
}, { onConflict: "user_id,device_name" });
|
|
2513
|
-
await sb.from("device_paths").upsert({
|
|
2514
|
-
user_id: userId,
|
|
2515
|
-
folder_id: folderId,
|
|
2516
|
-
device_name: deviceName,
|
|
2517
|
-
os_type: osType,
|
|
2518
|
-
path: projectRoot,
|
|
2519
|
-
last_synced: (/* @__PURE__ */ new Date()).toISOString()
|
|
2520
|
-
}, { onConflict: "folder_id,device_name" });
|
|
2521
|
-
await sb.from("claude_folders").update({
|
|
2522
|
-
graph_json: result.graph,
|
|
2523
|
-
orphans_json: result.orphans,
|
|
2524
|
-
skills_table_json: result.skills,
|
|
2525
|
-
stale_files_json: result.staleFiles,
|
|
2526
|
-
env_manifest_json: result.envManifest,
|
|
2527
|
-
last_scanned: result.scannedAt,
|
|
2528
|
-
data_hash: result.dataHash
|
|
2529
|
-
}).eq("id", folderId);
|
|
2530
|
-
await pushToolings(sb, folderId, result.toolings);
|
|
2531
|
-
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
2532
|
-
for (const file of configFiles) {
|
|
2533
|
-
await sb.from("folder_files").upsert({
|
|
2534
|
-
folder_id: folderId,
|
|
2535
|
-
file_path: file.filePath,
|
|
2536
|
-
content: file.content,
|
|
2537
|
-
size_bytes: file.sizeBytes,
|
|
2538
|
-
last_modified: file.lastModified
|
|
2539
|
-
}, { onConflict: "folder_id,file_path" });
|
|
2540
|
-
}
|
|
2541
|
-
await saveState({
|
|
2542
|
-
lastFolderId: folderId,
|
|
2543
|
-
lastDeviceName: deviceName,
|
|
2544
|
-
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2545
|
-
});
|
|
2546
|
-
console.log(chalk9.green("\nLinked and synced."));
|
|
2547
|
-
console.log(chalk9.cyan(`
|
|
2548
|
-
https://www.md4ai.com/project/${folderId}
|
|
2549
|
-
`));
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
} catch {
|
|
2553
|
-
console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
|
|
2552
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2553
|
+
for (const d of devices) {
|
|
2554
|
+
const list = grouped.get(d.device_name) ?? [];
|
|
2555
|
+
list.push(d);
|
|
2556
|
+
grouped.set(d.device_name, list);
|
|
2557
|
+
}
|
|
2558
|
+
for (const [deviceName, entries] of grouped) {
|
|
2559
|
+
const first = entries[0];
|
|
2560
|
+
console.log(chalk7.bold(`
|
|
2561
|
+
${deviceName}`) + chalk7.dim(` (${first.os_type})`));
|
|
2562
|
+
for (const entry of entries) {
|
|
2563
|
+
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
2564
|
+
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
2565
|
+
console.log(` ${chalk7.cyan(folderName)} \u2192 ${entry.path}`);
|
|
2566
|
+
console.log(` Last synced: ${synced}`);
|
|
2554
2567
|
}
|
|
2555
2568
|
}
|
|
2556
2569
|
}
|
|
2557
2570
|
|
|
2571
|
+
// dist/index.js
|
|
2572
|
+
init_map();
|
|
2573
|
+
|
|
2558
2574
|
// dist/commands/simulate.js
|
|
2559
2575
|
init_dist();
|
|
2560
2576
|
import { join as join9 } from "node:path";
|
|
@@ -3334,19 +3350,26 @@ async function postUpdateFlow() {
|
|
|
3334
3350
|
return;
|
|
3335
3351
|
}
|
|
3336
3352
|
}
|
|
3337
|
-
const
|
|
3338
|
-
const
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
}
|
|
3353
|
+
const { select: selectPrompt } = await import("@inquirer/prompts");
|
|
3354
|
+
const cwd = process.cwd();
|
|
3355
|
+
const scanChoice = await selectPrompt({
|
|
3356
|
+
message: "What would you like to scan?",
|
|
3357
|
+
choices: [
|
|
3358
|
+
{ name: `Current folder (${cwd})`, value: "cwd" },
|
|
3359
|
+
{ name: "All linked projects on this device", value: "all" },
|
|
3360
|
+
{ name: "Skip scanning", value: "skip" }
|
|
3361
|
+
]
|
|
3362
|
+
});
|
|
3363
|
+
if (scanChoice === "cwd") {
|
|
3364
|
+
console.log("");
|
|
3365
|
+
const { mapCommand: mapCommand2 } = await Promise.resolve().then(() => (init_map(), map_exports));
|
|
3366
|
+
await mapCommand2(cwd, {});
|
|
3367
|
+
console.log("");
|
|
3368
|
+
} else if (scanChoice === "all") {
|
|
3369
|
+
console.log("");
|
|
3370
|
+
const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
3371
|
+
await syncCommand2({ all: true });
|
|
3372
|
+
console.log("");
|
|
3350
3373
|
}
|
|
3351
3374
|
const wantWatch = await confirm4({
|
|
3352
3375
|
message: "Start monitoring MCP servers? (runs until you press Ctrl+C)",
|