md4ai 0.9.2 → 0.9.4
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 +442 -398
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -1149,22 +1149,43 @@ async function readClaudeConfigFiles(projectRoot) {
|
|
|
1149
1149
|
".claude/skills/*.md"
|
|
1150
1150
|
];
|
|
1151
1151
|
const files = [];
|
|
1152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1153
|
+
async function addFile(fullPath) {
|
|
1154
|
+
const relPath = relative4(projectRoot, fullPath);
|
|
1155
|
+
if (seen.has(relPath))
|
|
1156
|
+
return;
|
|
1157
|
+
seen.add(relPath);
|
|
1158
|
+
try {
|
|
1159
|
+
const content = await readFile10(fullPath, "utf-8");
|
|
1160
|
+
const fileStat = await stat(fullPath);
|
|
1161
|
+
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1162
|
+
files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1152
1166
|
for (const pattern of configPatterns) {
|
|
1153
1167
|
for await (const fullPath of glob2(join14(projectRoot, pattern))) {
|
|
1154
1168
|
if (!existsSync13(fullPath))
|
|
1155
1169
|
continue;
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1170
|
+
await addFile(fullPath);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
const pkgPath = join14(projectRoot, "package.json");
|
|
1174
|
+
if (existsSync13(pkgPath)) {
|
|
1175
|
+
try {
|
|
1176
|
+
const pkgContent = await readFile10(pkgPath, "utf-8");
|
|
1177
|
+
const pkg = JSON.parse(pkgContent);
|
|
1178
|
+
const preflightCmd = pkg.scripts?.preflight;
|
|
1179
|
+
if (preflightCmd) {
|
|
1180
|
+
const match = preflightCmd.match(/(?:bash\s+|sh\s+|\.\/)?(\S+\.(?:sh|bash|ts|js|mjs))/);
|
|
1181
|
+
if (match) {
|
|
1182
|
+
const scriptPath = join14(projectRoot, match[1]);
|
|
1183
|
+
if (existsSync13(scriptPath)) {
|
|
1184
|
+
await addFile(scriptPath);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1167
1187
|
}
|
|
1188
|
+
} catch {
|
|
1168
1189
|
}
|
|
1169
1190
|
}
|
|
1170
1191
|
return files;
|
|
@@ -1198,6 +1219,122 @@ var init_scanner = __esm({
|
|
|
1198
1219
|
}
|
|
1199
1220
|
});
|
|
1200
1221
|
|
|
1222
|
+
// dist/output/html-generator.js
|
|
1223
|
+
function generateOfflineHtml(result, projectRoot) {
|
|
1224
|
+
const title = projectRoot.split("/").pop() ?? "MD4AI";
|
|
1225
|
+
const dataJson = JSON.stringify(result);
|
|
1226
|
+
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");
|
|
1227
|
+
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");
|
|
1228
|
+
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");
|
|
1229
|
+
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");
|
|
1230
|
+
return `<!DOCTYPE html>
|
|
1231
|
+
<html lang="en">
|
|
1232
|
+
<head>
|
|
1233
|
+
<meta charset="UTF-8">
|
|
1234
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1235
|
+
<title>MD4AI \u2014 ${escapeHtml(title)}</title>
|
|
1236
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
1237
|
+
<style>
|
|
1238
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1239
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
|
|
1240
|
+
h1 { color: #38bdf8; margin-bottom: 0.5rem; }
|
|
1241
|
+
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
|
|
1242
|
+
.section { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
1243
|
+
.section h2 { color: #38bdf8; margin-bottom: 1rem; font-size: 1.2rem; }
|
|
1244
|
+
.mermaid { background: #0f172a; border-radius: 8px; padding: 1rem; overflow-x: auto; }
|
|
1245
|
+
.badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
1246
|
+
.badge-orphan { background: #ef4444; color: white; }
|
|
1247
|
+
.badge-stale { background: #f59e0b; color: black; }
|
|
1248
|
+
table { width: 100%; border-collapse: collapse; }
|
|
1249
|
+
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
1250
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
1251
|
+
.check { color: #22c55e; }
|
|
1252
|
+
.meta { color: #94a3b8; }
|
|
1253
|
+
.stale-meta { color: #f59e0b; }
|
|
1254
|
+
.stats { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
|
1255
|
+
.stat { text-align: center; }
|
|
1256
|
+
.stat-value { font-size: 2rem; font-weight: bold; color: #38bdf8; }
|
|
1257
|
+
.stat-label { color: #94a3b8; font-size: 0.85rem; }
|
|
1258
|
+
.file-list { list-style: none; }
|
|
1259
|
+
.file-list li { padding: 0.3rem 0; color: #cbd5e1; font-family: monospace; font-size: 0.9rem; }
|
|
1260
|
+
#search { width: 100%; padding: 0.75rem 1rem; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 1rem; margin-bottom: 1rem; }
|
|
1261
|
+
#search:focus { outline: none; border-color: #38bdf8; }
|
|
1262
|
+
@media print { body { background: white; color: black; } .section { border: 1px solid #ccc; } }
|
|
1263
|
+
</style>
|
|
1264
|
+
</head>
|
|
1265
|
+
<body>
|
|
1266
|
+
<h1>MD4AI \u2014 ${escapeHtml(title)}</h1>
|
|
1267
|
+
<p class="subtitle">Scanned: ${new Date(result.scannedAt).toLocaleString()} | Hash: ${result.dataHash.slice(0, 12)}</p>
|
|
1268
|
+
|
|
1269
|
+
<div class="stats">
|
|
1270
|
+
<div class="stat"><div class="stat-value">${result.graph.nodes.length}</div><div class="stat-label">Files</div></div>
|
|
1271
|
+
<div class="stat"><div class="stat-value">${result.graph.edges.length}</div><div class="stat-label">References</div></div>
|
|
1272
|
+
<div class="stat"><div class="stat-value">${result.orphans.length}</div><div class="stat-label">Orphans</div></div>
|
|
1273
|
+
<div class="stat"><div class="stat-value">${result.staleFiles.length}</div><div class="stat-label">Stale</div></div>
|
|
1274
|
+
<div class="stat"><div class="stat-value">${result.skills.length}</div><div class="stat-label">Skills</div></div>
|
|
1275
|
+
</div>
|
|
1276
|
+
|
|
1277
|
+
<input type="text" id="search" placeholder="Search files..." oninput="filterFiles(this.value)">
|
|
1278
|
+
|
|
1279
|
+
<div class="section">
|
|
1280
|
+
<h2>Dependency Graph</h2>
|
|
1281
|
+
<pre class="mermaid">${escapeHtml(result.graph.mermaid)}</pre>
|
|
1282
|
+
</div>
|
|
1283
|
+
|
|
1284
|
+
${result.orphans.length > 0 ? `
|
|
1285
|
+
<div class="section">
|
|
1286
|
+
<h2>Orphan Files <span class="badge badge-orphan">${result.orphans.length}</span></h2>
|
|
1287
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not reachable from any root configuration file.</p>
|
|
1288
|
+
<ul class="file-list" id="orphan-list">${orphanItems}</ul>
|
|
1289
|
+
</div>
|
|
1290
|
+
` : ""}
|
|
1291
|
+
|
|
1292
|
+
${result.staleFiles.length > 0 ? `
|
|
1293
|
+
<div class="section">
|
|
1294
|
+
<h2>Stale Files <span class="badge badge-stale">${result.staleFiles.length}</span></h2>
|
|
1295
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not modified in over 90 days.</p>
|
|
1296
|
+
<ul class="file-list" id="stale-list">${staleItems}</ul>
|
|
1297
|
+
</div>
|
|
1298
|
+
` : ""}
|
|
1299
|
+
|
|
1300
|
+
<div class="section">
|
|
1301
|
+
<h2>Skills Comparison</h2>
|
|
1302
|
+
<table>
|
|
1303
|
+
<thead><tr><th>Skill/Plugin</th><th>Entire Machine</th><th>Project Specific</th><th>Source</th></tr></thead>
|
|
1304
|
+
<tbody>${skillRows}</tbody>
|
|
1305
|
+
</table>
|
|
1306
|
+
</div>
|
|
1307
|
+
|
|
1308
|
+
<div class="section">
|
|
1309
|
+
<h2>All Files</h2>
|
|
1310
|
+
<ul class="file-list" id="all-files">${fileItems}</ul>
|
|
1311
|
+
</div>
|
|
1312
|
+
|
|
1313
|
+
<script>
|
|
1314
|
+
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
1315
|
+
|
|
1316
|
+
function filterFiles(query) {
|
|
1317
|
+
var q = query.toLowerCase();
|
|
1318
|
+
document.querySelectorAll('.file-list li').forEach(function(li) {
|
|
1319
|
+
var path = li.getAttribute('data-path') || '';
|
|
1320
|
+
li.style.display = path.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
</script>
|
|
1324
|
+
|
|
1325
|
+
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
1326
|
+
</body>
|
|
1327
|
+
</html>`;
|
|
1328
|
+
}
|
|
1329
|
+
function escapeHtml(text) {
|
|
1330
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1331
|
+
}
|
|
1332
|
+
var init_html_generator = __esm({
|
|
1333
|
+
"dist/output/html-generator.js"() {
|
|
1334
|
+
"use strict";
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1201
1338
|
// dist/check-update.js
|
|
1202
1339
|
import chalk8 from "chalk";
|
|
1203
1340
|
async function fetchLatest() {
|
|
@@ -1262,7 +1399,7 @@ var CURRENT_VERSION;
|
|
|
1262
1399
|
var init_check_update = __esm({
|
|
1263
1400
|
"dist/check-update.js"() {
|
|
1264
1401
|
"use strict";
|
|
1265
|
-
CURRENT_VERSION = true ? "0.9.
|
|
1402
|
+
CURRENT_VERSION = true ? "0.9.4" : "0.0.0-dev";
|
|
1266
1403
|
}
|
|
1267
1404
|
});
|
|
1268
1405
|
|
|
@@ -1320,11 +1457,220 @@ async function resolveDeviceId(supabase, userId) {
|
|
|
1320
1457
|
if (error || !data) {
|
|
1321
1458
|
throw new Error(`Failed to resolve device ID: ${error?.message ?? "not found"}`);
|
|
1322
1459
|
}
|
|
1323
|
-
return data.id;
|
|
1460
|
+
return data.id;
|
|
1461
|
+
}
|
|
1462
|
+
var init_device_utils = __esm({
|
|
1463
|
+
"dist/device-utils.js"() {
|
|
1464
|
+
"use strict";
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// dist/commands/map.js
|
|
1469
|
+
var map_exports = {};
|
|
1470
|
+
__export(map_exports, {
|
|
1471
|
+
mapCommand: () => mapCommand
|
|
1472
|
+
});
|
|
1473
|
+
import { resolve as resolve3, basename } from "node:path";
|
|
1474
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1475
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1476
|
+
import chalk9 from "chalk";
|
|
1477
|
+
import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
1478
|
+
async function mapCommand(path, options) {
|
|
1479
|
+
await checkForUpdate();
|
|
1480
|
+
const projectRoot = resolve3(path ?? process.cwd());
|
|
1481
|
+
if (!existsSync7(projectRoot)) {
|
|
1482
|
+
console.error(chalk9.red(`Path not found: ${projectRoot}`));
|
|
1483
|
+
process.exit(1);
|
|
1484
|
+
}
|
|
1485
|
+
console.log(chalk9.blue(`Scanning: ${projectRoot}
|
|
1486
|
+
`));
|
|
1487
|
+
const result = await scanProject(projectRoot);
|
|
1488
|
+
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
1489
|
+
console.log(` References: ${result.graph.edges.length}`);
|
|
1490
|
+
console.log(` Orphans: ${result.orphans.length}`);
|
|
1491
|
+
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
1492
|
+
console.log(` Skills: ${result.skills.length}`);
|
|
1493
|
+
console.log(` Toolings: ${result.toolings.length}`);
|
|
1494
|
+
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
1495
|
+
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
1496
|
+
const outputDir = resolve3(projectRoot, "output");
|
|
1497
|
+
if (!existsSync7(outputDir)) {
|
|
1498
|
+
await mkdir2(outputDir, { recursive: true });
|
|
1499
|
+
}
|
|
1500
|
+
const htmlPath = resolve3(outputDir, "index.html");
|
|
1501
|
+
const html = generateOfflineHtml(result, projectRoot);
|
|
1502
|
+
await writeFile2(htmlPath, html, "utf-8");
|
|
1503
|
+
console.log(chalk9.green(`
|
|
1504
|
+
Local preview: ${htmlPath}`));
|
|
1505
|
+
if (!options.offline) {
|
|
1506
|
+
try {
|
|
1507
|
+
const { supabase } = await getAuthenticatedClient();
|
|
1508
|
+
const { data: devicePaths } = await supabase.from("device_paths").select("folder_id, device_name").eq("path", projectRoot);
|
|
1509
|
+
if (devicePaths?.length) {
|
|
1510
|
+
const { folder_id, device_name } = devicePaths[0];
|
|
1511
|
+
const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
|
|
1512
|
+
if (proposedFiles?.length) {
|
|
1513
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
1514
|
+
console.log(chalk9.yellow(`
|
|
1515
|
+
${proposedFiles.length} file(s) proposed for deletion:
|
|
1516
|
+
`));
|
|
1517
|
+
const toDelete = await checkbox({
|
|
1518
|
+
message: "Select files to delete (space to toggle, enter to confirm)",
|
|
1519
|
+
choices: proposedFiles.map((f) => ({
|
|
1520
|
+
name: `${f.file_path}${f.proposed_at ? ` (proposed ${new Date(f.proposed_at).toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", year: "numeric" })})` : ""}`,
|
|
1521
|
+
value: f
|
|
1522
|
+
}))
|
|
1523
|
+
});
|
|
1524
|
+
for (const file of toDelete) {
|
|
1525
|
+
const fullPath = resolve3(projectRoot, file.file_path);
|
|
1526
|
+
try {
|
|
1527
|
+
const { unlink } = await import("node:fs/promises");
|
|
1528
|
+
await unlink(fullPath);
|
|
1529
|
+
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
1530
|
+
console.log(chalk9.green(` Deleted: ${file.file_path}`));
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
1536
|
+
if (keptIds.length > 0) {
|
|
1537
|
+
for (const id of keptIds) {
|
|
1538
|
+
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
1539
|
+
}
|
|
1540
|
+
console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
1541
|
+
}
|
|
1542
|
+
console.log("");
|
|
1543
|
+
}
|
|
1544
|
+
const { error } = await supabase.from("claude_folders").update({
|
|
1545
|
+
graph_json: result.graph,
|
|
1546
|
+
orphans_json: result.orphans,
|
|
1547
|
+
skills_table_json: result.skills,
|
|
1548
|
+
stale_files_json: result.staleFiles,
|
|
1549
|
+
env_manifest_json: result.envManifest,
|
|
1550
|
+
last_scanned: result.scannedAt,
|
|
1551
|
+
data_hash: result.dataHash
|
|
1552
|
+
}).eq("id", folder_id);
|
|
1553
|
+
if (error) {
|
|
1554
|
+
console.error(chalk9.yellow(`Sync warning: ${error.message}`));
|
|
1555
|
+
} else {
|
|
1556
|
+
await pushToolings(supabase, folder_id, result.toolings);
|
|
1557
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
1558
|
+
await saveState({
|
|
1559
|
+
lastFolderId: folder_id,
|
|
1560
|
+
lastDeviceName: device_name,
|
|
1561
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1562
|
+
});
|
|
1563
|
+
console.log(chalk9.green("Synced to Supabase."));
|
|
1564
|
+
console.log(chalk9.cyan(`
|
|
1565
|
+
https://www.md4ai.com/project/${folder_id}
|
|
1566
|
+
`));
|
|
1567
|
+
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1568
|
+
if (configFiles.length > 0) {
|
|
1569
|
+
for (const file of configFiles) {
|
|
1570
|
+
await supabase.from("folder_files").upsert({
|
|
1571
|
+
folder_id,
|
|
1572
|
+
file_path: file.filePath,
|
|
1573
|
+
content: file.content,
|
|
1574
|
+
size_bytes: file.sizeBytes,
|
|
1575
|
+
last_modified: file.lastModified
|
|
1576
|
+
}, { onConflict: "folder_id,file_path" });
|
|
1577
|
+
}
|
|
1578
|
+
console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
} else {
|
|
1582
|
+
console.log(chalk9.yellow("\nThis folder is not linked to a project on your dashboard."));
|
|
1583
|
+
const shouldLink = await confirm2({ message: "Would you like to link it now?" });
|
|
1584
|
+
if (!shouldLink) {
|
|
1585
|
+
console.log(chalk9.dim("Skipped \u2014 local preview still generated."));
|
|
1586
|
+
} else {
|
|
1587
|
+
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
1588
|
+
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
1589
|
+
const choices = [
|
|
1590
|
+
{ name: "+ Create a new project", value: "__new__" },
|
|
1591
|
+
...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
|
|
1592
|
+
];
|
|
1593
|
+
const chosen = await select3({
|
|
1594
|
+
message: "Link to which project?",
|
|
1595
|
+
choices
|
|
1596
|
+
});
|
|
1597
|
+
let folderId;
|
|
1598
|
+
if (chosen === "__new__") {
|
|
1599
|
+
const projectName = await input5({
|
|
1600
|
+
message: "Project name:",
|
|
1601
|
+
default: basename(projectRoot)
|
|
1602
|
+
});
|
|
1603
|
+
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
|
|
1604
|
+
if (createErr || !newFolder) {
|
|
1605
|
+
console.error(chalk9.red(`Failed to create project: ${createErr?.message}`));
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
folderId = newFolder.id;
|
|
1609
|
+
console.log(chalk9.green(`Created project "${projectName}".`));
|
|
1610
|
+
} else {
|
|
1611
|
+
folderId = chosen;
|
|
1612
|
+
}
|
|
1613
|
+
const deviceName = detectDeviceName();
|
|
1614
|
+
const osType = detectOs2();
|
|
1615
|
+
await sb.from("devices").upsert({
|
|
1616
|
+
user_id: userId,
|
|
1617
|
+
device_name: deviceName,
|
|
1618
|
+
os_type: osType
|
|
1619
|
+
}, { onConflict: "user_id,device_name" });
|
|
1620
|
+
await sb.from("device_paths").upsert({
|
|
1621
|
+
user_id: userId,
|
|
1622
|
+
folder_id: folderId,
|
|
1623
|
+
device_name: deviceName,
|
|
1624
|
+
os_type: osType,
|
|
1625
|
+
path: projectRoot,
|
|
1626
|
+
last_synced: (/* @__PURE__ */ new Date()).toISOString()
|
|
1627
|
+
}, { onConflict: "folder_id,device_name" });
|
|
1628
|
+
await sb.from("claude_folders").update({
|
|
1629
|
+
graph_json: result.graph,
|
|
1630
|
+
orphans_json: result.orphans,
|
|
1631
|
+
skills_table_json: result.skills,
|
|
1632
|
+
stale_files_json: result.staleFiles,
|
|
1633
|
+
env_manifest_json: result.envManifest,
|
|
1634
|
+
last_scanned: result.scannedAt,
|
|
1635
|
+
data_hash: result.dataHash
|
|
1636
|
+
}).eq("id", folderId);
|
|
1637
|
+
await pushToolings(sb, folderId, result.toolings);
|
|
1638
|
+
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1639
|
+
for (const file of configFiles) {
|
|
1640
|
+
await sb.from("folder_files").upsert({
|
|
1641
|
+
folder_id: folderId,
|
|
1642
|
+
file_path: file.filePath,
|
|
1643
|
+
content: file.content,
|
|
1644
|
+
size_bytes: file.sizeBytes,
|
|
1645
|
+
last_modified: file.lastModified
|
|
1646
|
+
}, { onConflict: "folder_id,file_path" });
|
|
1647
|
+
}
|
|
1648
|
+
await saveState({
|
|
1649
|
+
lastFolderId: folderId,
|
|
1650
|
+
lastDeviceName: deviceName,
|
|
1651
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1652
|
+
});
|
|
1653
|
+
console.log(chalk9.green("\nLinked and synced."));
|
|
1654
|
+
console.log(chalk9.cyan(`
|
|
1655
|
+
https://www.md4ai.com/project/${folderId}
|
|
1656
|
+
`));
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
} catch {
|
|
1660
|
+
console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1324
1663
|
}
|
|
1325
|
-
var
|
|
1326
|
-
"dist/
|
|
1664
|
+
var init_map = __esm({
|
|
1665
|
+
"dist/commands/map.js"() {
|
|
1327
1666
|
"use strict";
|
|
1667
|
+
init_scanner();
|
|
1668
|
+
init_auth();
|
|
1669
|
+
init_config();
|
|
1670
|
+
init_html_generator();
|
|
1671
|
+
init_check_update();
|
|
1672
|
+
init_push_toolings();
|
|
1673
|
+
init_device_utils();
|
|
1328
1674
|
}
|
|
1329
1675
|
});
|
|
1330
1676
|
|
|
@@ -2176,385 +2522,76 @@ async function addDeviceCommand() {
|
|
|
2176
2522
|
});
|
|
2177
2523
|
const suggested = suggestDeviceName();
|
|
2178
2524
|
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, """);
|
|
2525
|
+
message: "Device name:",
|
|
2526
|
+
default: suggested
|
|
2527
|
+
});
|
|
2528
|
+
const osType = await select2({
|
|
2529
|
+
message: "OS type:",
|
|
2530
|
+
choices: [
|
|
2531
|
+
{ name: "Windows", value: "windows" },
|
|
2532
|
+
{ name: "macOS", value: "macos" },
|
|
2533
|
+
{ name: "Ubuntu", value: "ubuntu" },
|
|
2534
|
+
{ name: "Linux", value: "linux" },
|
|
2535
|
+
{ name: "Other", value: "other" }
|
|
2536
|
+
],
|
|
2537
|
+
default: detectOs()
|
|
2538
|
+
});
|
|
2539
|
+
const description = await input4({ message: "Description (optional):" });
|
|
2540
|
+
const { error } = await supabase.from("device_paths").insert({
|
|
2541
|
+
user_id: userId,
|
|
2542
|
+
folder_id: folderId,
|
|
2543
|
+
device_name: deviceName,
|
|
2544
|
+
os_type: osType,
|
|
2545
|
+
path: localPath,
|
|
2546
|
+
description: description || null
|
|
2547
|
+
});
|
|
2548
|
+
if (error) {
|
|
2549
|
+
console.error(chalk6.red(`Failed to add device: ${error.message}`));
|
|
2550
|
+
process.exit(1);
|
|
2551
|
+
}
|
|
2552
|
+
console.log(chalk6.green(`
|
|
2553
|
+
Device "${deviceName}" added to folder.`));
|
|
2364
2554
|
}
|
|
2365
2555
|
|
|
2366
|
-
// dist/commands/
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2556
|
+
// dist/commands/list-devices.js
|
|
2557
|
+
init_auth();
|
|
2558
|
+
import chalk7 from "chalk";
|
|
2559
|
+
async function listDevicesCommand() {
|
|
2560
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2561
|
+
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
2562
|
+
id, device_name, os_type, path, last_synced, description,
|
|
2563
|
+
claude_folders!inner ( name )
|
|
2564
|
+
`).order("device_name");
|
|
2565
|
+
if (error) {
|
|
2566
|
+
console.error(chalk7.red(`Failed to list devices: ${error.message}`));
|
|
2376
2567
|
process.exit(1);
|
|
2377
2568
|
}
|
|
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 });
|
|
2569
|
+
if (!devices?.length) {
|
|
2570
|
+
console.log(chalk7.yellow("No devices found. Run: md4ai add-device"));
|
|
2571
|
+
return;
|
|
2392
2572
|
}
|
|
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."));
|
|
2573
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2574
|
+
for (const d of devices) {
|
|
2575
|
+
const list = grouped.get(d.device_name) ?? [];
|
|
2576
|
+
list.push(d);
|
|
2577
|
+
grouped.set(d.device_name, list);
|
|
2578
|
+
}
|
|
2579
|
+
for (const [deviceName, entries] of grouped) {
|
|
2580
|
+
const first = entries[0];
|
|
2581
|
+
console.log(chalk7.bold(`
|
|
2582
|
+
${deviceName}`) + chalk7.dim(` (${first.os_type})`));
|
|
2583
|
+
for (const entry of entries) {
|
|
2584
|
+
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
2585
|
+
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
2586
|
+
console.log(` ${chalk7.cyan(folderName)} \u2192 ${entry.path}`);
|
|
2587
|
+
console.log(` Last synced: ${synced}`);
|
|
2554
2588
|
}
|
|
2555
2589
|
}
|
|
2556
2590
|
}
|
|
2557
2591
|
|
|
2592
|
+
// dist/index.js
|
|
2593
|
+
init_map();
|
|
2594
|
+
|
|
2558
2595
|
// dist/commands/simulate.js
|
|
2559
2596
|
init_dist();
|
|
2560
2597
|
import { join as join9 } from "node:path";
|
|
@@ -3334,19 +3371,26 @@ async function postUpdateFlow() {
|
|
|
3334
3371
|
return;
|
|
3335
3372
|
}
|
|
3336
3373
|
}
|
|
3337
|
-
const
|
|
3338
|
-
const
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
}
|
|
3374
|
+
const { select: selectPrompt } = await import("@inquirer/prompts");
|
|
3375
|
+
const cwd = process.cwd();
|
|
3376
|
+
const scanChoice = await selectPrompt({
|
|
3377
|
+
message: "What would you like to scan?",
|
|
3378
|
+
choices: [
|
|
3379
|
+
{ name: `Current folder (${cwd})`, value: "cwd" },
|
|
3380
|
+
{ name: "All linked projects on this device", value: "all" },
|
|
3381
|
+
{ name: "Skip scanning", value: "skip" }
|
|
3382
|
+
]
|
|
3383
|
+
});
|
|
3384
|
+
if (scanChoice === "cwd") {
|
|
3385
|
+
console.log("");
|
|
3386
|
+
const { mapCommand: mapCommand2 } = await Promise.resolve().then(() => (init_map(), map_exports));
|
|
3387
|
+
await mapCommand2(cwd, {});
|
|
3388
|
+
console.log("");
|
|
3389
|
+
} else if (scanChoice === "all") {
|
|
3390
|
+
console.log("");
|
|
3391
|
+
const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
3392
|
+
await syncCommand2({ all: true });
|
|
3393
|
+
console.log("");
|
|
3350
3394
|
}
|
|
3351
3395
|
const wantWatch = await confirm4({
|
|
3352
3396
|
message: "Start monitoring MCP servers? (runs until you press Ctrl+C)",
|