md4ai 0.11.0 → 0.13.0
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 +360 -104
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -73,7 +73,7 @@ var CURRENT_VERSION;
|
|
|
73
73
|
var init_check_update = __esm({
|
|
74
74
|
"dist/check-update.js"() {
|
|
75
75
|
"use strict";
|
|
76
|
-
CURRENT_VERSION = true ? "0.
|
|
76
|
+
CURRENT_VERSION = true ? "0.13.0" : "0.0.0-dev";
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
|
|
@@ -1656,21 +1656,40 @@ var init_marketplace_scanner = __esm({
|
|
|
1656
1656
|
|
|
1657
1657
|
// dist/doppler/auth.js
|
|
1658
1658
|
async function resolveDopplerToken() {
|
|
1659
|
+
const { join: join17 } = await import("node:path");
|
|
1660
|
+
const { homedir: homedir10 } = await import("node:os");
|
|
1661
|
+
const credPath = join17(homedir10(), ".md4ai", "credentials.json");
|
|
1659
1662
|
const creds = await loadCredentials();
|
|
1660
1663
|
if (creds?.dopplerToken) {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1664
|
+
return { token: creds.dopplerToken, sourcePath: credPath };
|
|
1665
|
+
}
|
|
1666
|
+
if (!creds?.accessToken || !creds?.userId)
|
|
1667
|
+
return null;
|
|
1668
|
+
try {
|
|
1669
|
+
let supabase = createSupabaseClient(getAnonKey(), creds.accessToken);
|
|
1670
|
+
let userId = creds.userId;
|
|
1671
|
+
if (Date.now() > creds.expiresAt) {
|
|
1672
|
+
const refreshed = await refreshSession();
|
|
1673
|
+
if (!refreshed)
|
|
1674
|
+
return null;
|
|
1675
|
+
supabase = refreshed.supabase;
|
|
1676
|
+
userId = refreshed.userId;
|
|
1677
|
+
}
|
|
1678
|
+
const { data } = await supabase.from("user_secrets").select("doppler_token").eq("user_id", userId).maybeSingle();
|
|
1679
|
+
if (!data?.doppler_token)
|
|
1680
|
+
return null;
|
|
1681
|
+
await mergeCredentials({ dopplerToken: data.doppler_token });
|
|
1682
|
+
return { token: data.doppler_token, sourcePath: "Supabase (cached locally)" };
|
|
1683
|
+
} catch {
|
|
1684
|
+
return null;
|
|
1667
1685
|
}
|
|
1668
|
-
return null;
|
|
1669
1686
|
}
|
|
1670
1687
|
var init_auth3 = __esm({
|
|
1671
1688
|
"dist/doppler/auth.js"() {
|
|
1672
1689
|
"use strict";
|
|
1673
1690
|
init_config();
|
|
1691
|
+
init_auth();
|
|
1692
|
+
init_dist();
|
|
1674
1693
|
}
|
|
1675
1694
|
});
|
|
1676
1695
|
|
|
@@ -1713,6 +1732,10 @@ async function validateDopplerToken(token) {
|
|
|
1713
1732
|
throw err;
|
|
1714
1733
|
}
|
|
1715
1734
|
}
|
|
1735
|
+
async function fetchDopplerProjects(token) {
|
|
1736
|
+
const data = await dopplerFetch("https://api.doppler.com/v3/projects", token);
|
|
1737
|
+
return data.projects.map((p) => ({ slug: p.slug, name: p.name }));
|
|
1738
|
+
}
|
|
1716
1739
|
var DopplerApiError, MAX_RETRIES;
|
|
1717
1740
|
var init_api = __esm({
|
|
1718
1741
|
"dist/doppler/api.js"() {
|
|
@@ -1733,9 +1756,10 @@ var init_api = __esm({
|
|
|
1733
1756
|
|
|
1734
1757
|
// dist/scanner/doppler-scanner.js
|
|
1735
1758
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
1736
|
-
import { join as join11 } from "node:path";
|
|
1759
|
+
import { join as join11, basename } from "node:path";
|
|
1737
1760
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1738
1761
|
import chalk10 from "chalk";
|
|
1762
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
1739
1763
|
async function parseDopplerYaml(projectRoot) {
|
|
1740
1764
|
const yamlPath = join11(projectRoot, "doppler.yaml");
|
|
1741
1765
|
if (!existsSync6(yamlPath))
|
|
@@ -1751,6 +1775,7 @@ async function parseDopplerYaml(projectRoot) {
|
|
|
1751
1775
|
async function scanDoppler(projectRoot, folderId) {
|
|
1752
1776
|
const tokenResult = await resolveDopplerToken();
|
|
1753
1777
|
if (!tokenResult) {
|
|
1778
|
+
console.log(chalk10.dim(' Doppler: no token configured. Run "md4ai doppler connect" to set up.'));
|
|
1754
1779
|
return null;
|
|
1755
1780
|
}
|
|
1756
1781
|
let projectSlug = await parseDopplerYaml(projectRoot);
|
|
@@ -1762,7 +1787,40 @@ async function scanDoppler(projectRoot, folderId) {
|
|
|
1762
1787
|
matchedVia = "manual";
|
|
1763
1788
|
}
|
|
1764
1789
|
if (!projectSlug) {
|
|
1765
|
-
|
|
1790
|
+
if (!folderId || !process.stdin.isTTY) {
|
|
1791
|
+
console.log(chalk10.dim(' Doppler: token configured but no project linked. Run "md4ai doppler set-project <slug>" to link one.'));
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const projects = await fetchDopplerProjects(tokenResult.token);
|
|
1795
|
+
if (projects.length === 0) {
|
|
1796
|
+
console.log(chalk10.yellow(" Doppler: no projects accessible with this token."));
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
const projectName = basename(projectRoot);
|
|
1800
|
+
if (projects.length === 1) {
|
|
1801
|
+
projectSlug = projects[0].slug;
|
|
1802
|
+
console.log(chalk10.green(` Doppler: using "${projectSlug}" (only accessible project).`));
|
|
1803
|
+
} else {
|
|
1804
|
+
const autoMatch = projects.find((p) => p.slug.toLowerCase() === projectName.toLowerCase());
|
|
1805
|
+
if (autoMatch) {
|
|
1806
|
+
projectSlug = autoMatch.slug;
|
|
1807
|
+
console.log(chalk10.green(` Doppler: auto-matched project "${projectSlug}" from project name.`));
|
|
1808
|
+
} else {
|
|
1809
|
+
projectSlug = await select3({
|
|
1810
|
+
message: `Which Doppler project holds secrets for "${projectName}"?`,
|
|
1811
|
+
choices: projects.map((p) => ({
|
|
1812
|
+
name: `${p.slug}${p.name !== p.slug ? ` (${p.name})` : ""}`,
|
|
1813
|
+
value: p.slug
|
|
1814
|
+
}))
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const state = await loadState();
|
|
1819
|
+
const dopplerProjects = state.dopplerProjects ?? {};
|
|
1820
|
+
dopplerProjects[folderId] = projectSlug;
|
|
1821
|
+
await saveState({ dopplerProjects });
|
|
1822
|
+
matchedVia = "manual";
|
|
1823
|
+
console.log(chalk10.dim(` Doppler: saved "${projectSlug}" for this project. To change: md4ai doppler set-project <slug>`));
|
|
1766
1824
|
}
|
|
1767
1825
|
console.log(chalk10.dim(` Doppler: checking project "${projectSlug}" (via ${matchedVia})...`));
|
|
1768
1826
|
try {
|
|
@@ -2136,7 +2194,7 @@ var init_push_toolings = __esm({
|
|
|
2136
2194
|
|
|
2137
2195
|
// dist/commands/push-health-results.js
|
|
2138
2196
|
import chalk11 from "chalk";
|
|
2139
|
-
async function pushHealthResults(supabase, folderId, manifest) {
|
|
2197
|
+
async function pushHealthResults(supabase, folderId, manifest, deviceId) {
|
|
2140
2198
|
const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
|
|
2141
2199
|
if (!checks?.length)
|
|
2142
2200
|
return;
|
|
@@ -2154,7 +2212,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
2154
2212
|
check_id: chk.id,
|
|
2155
2213
|
status: localStatus === "present" ? "pass" : "fail",
|
|
2156
2214
|
message: localStatus === "present" ? "Set" : "Missing",
|
|
2157
|
-
ran_at: ranAt
|
|
2215
|
+
ran_at: ranAt,
|
|
2216
|
+
device_id: deviceId ?? null
|
|
2158
2217
|
});
|
|
2159
2218
|
}
|
|
2160
2219
|
} else if (chk.check_type === "file_exists") {
|
|
@@ -2162,7 +2221,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
2162
2221
|
check_id: chk.id,
|
|
2163
2222
|
status: "pass",
|
|
2164
2223
|
message: "Found",
|
|
2165
|
-
ran_at: ranAt
|
|
2224
|
+
ran_at: ranAt,
|
|
2225
|
+
device_id: deviceId ?? null
|
|
2166
2226
|
});
|
|
2167
2227
|
} else if (chk.check_type === "gh_secret") {
|
|
2168
2228
|
const variable = config2.secret;
|
|
@@ -2172,7 +2232,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
2172
2232
|
check_id: chk.id,
|
|
2173
2233
|
status: ghStatus === "present" ? "pass" : ghStatus === "missing" ? "fail" : "warn",
|
|
2174
2234
|
message: ghStatus === "present" ? "Exists" : ghStatus === "missing" ? "Missing" : "Unknown",
|
|
2175
|
-
ran_at: ranAt
|
|
2235
|
+
ran_at: ranAt,
|
|
2236
|
+
device_id: deviceId ?? null
|
|
2176
2237
|
});
|
|
2177
2238
|
} else if (chk.check_type === "vercel_env") {
|
|
2178
2239
|
const variable = config2.variable;
|
|
@@ -2187,14 +2248,16 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
2187
2248
|
check_id: chk.id,
|
|
2188
2249
|
status: hasTarget ? "pass" : "warn",
|
|
2189
2250
|
message: hasTarget ? `Present (${target})` : `Present but not in ${target}`,
|
|
2190
|
-
ran_at: ranAt
|
|
2251
|
+
ran_at: ranAt,
|
|
2252
|
+
device_id: deviceId ?? null
|
|
2191
2253
|
});
|
|
2192
2254
|
} else {
|
|
2193
2255
|
results.push({
|
|
2194
2256
|
check_id: chk.id,
|
|
2195
2257
|
status: "fail",
|
|
2196
2258
|
message: `Not found in ${projectName}`,
|
|
2197
|
-
ran_at: ranAt
|
|
2259
|
+
ran_at: ranAt,
|
|
2260
|
+
device_id: deviceId ?? null
|
|
2198
2261
|
});
|
|
2199
2262
|
}
|
|
2200
2263
|
}
|
|
@@ -2274,11 +2337,11 @@ var map_exports = {};
|
|
|
2274
2337
|
__export(map_exports, {
|
|
2275
2338
|
mapCommand: () => mapCommand
|
|
2276
2339
|
});
|
|
2277
|
-
import { resolve as resolve4, basename } from "node:path";
|
|
2340
|
+
import { resolve as resolve4, basename as basename2 } from "node:path";
|
|
2278
2341
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
2279
2342
|
import { existsSync as existsSync8 } from "node:fs";
|
|
2280
2343
|
import chalk12 from "chalk";
|
|
2281
|
-
import { select as
|
|
2344
|
+
import { select as select4, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
2282
2345
|
async function mapCommand(path, options) {
|
|
2283
2346
|
await checkForUpdate();
|
|
2284
2347
|
const projectRoot = resolve4(path ?? process.cwd());
|
|
@@ -2288,7 +2351,16 @@ async function mapCommand(path, options) {
|
|
|
2288
2351
|
}
|
|
2289
2352
|
console.log(chalk12.blue(`Scanning: ${projectRoot}
|
|
2290
2353
|
`));
|
|
2291
|
-
|
|
2354
|
+
let earlyFolderId;
|
|
2355
|
+
if (!options.offline) {
|
|
2356
|
+
try {
|
|
2357
|
+
const { supabase: earlyDb } = await getAuthenticatedClient();
|
|
2358
|
+
const { data: dp } = await earlyDb.from("device_paths").select("folder_id").eq("path", projectRoot).maybeSingle();
|
|
2359
|
+
earlyFolderId = dp?.folder_id ?? void 0;
|
|
2360
|
+
} catch {
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
const result = await scanProject(projectRoot, earlyFolderId);
|
|
2292
2364
|
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
2293
2365
|
console.log(` References: ${result.graph.edges.length}`);
|
|
2294
2366
|
console.log(` Broken refs: ${result.brokenRefs.length}`);
|
|
@@ -2297,7 +2369,7 @@ async function mapCommand(path, options) {
|
|
|
2297
2369
|
console.log(` Skills: ${result.skills.length}`);
|
|
2298
2370
|
console.log(` Toolings: ${result.toolings.length}`);
|
|
2299
2371
|
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
2300
|
-
console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "
|
|
2372
|
+
console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
|
|
2301
2373
|
console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
|
|
2302
2374
|
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
2303
2375
|
if (result.brokenRefs.length > 0) {
|
|
@@ -2321,10 +2393,12 @@ async function mapCommand(path, options) {
|
|
|
2321
2393
|
Local preview: ${htmlPath}`));
|
|
2322
2394
|
if (!options.offline) {
|
|
2323
2395
|
try {
|
|
2324
|
-
const { supabase } = await getAuthenticatedClient();
|
|
2396
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
2325
2397
|
const { data: devicePaths } = await supabase.from("device_paths").select("folder_id, device_name").eq("path", projectRoot);
|
|
2326
2398
|
if (devicePaths?.length) {
|
|
2327
2399
|
const { folder_id, device_name } = devicePaths[0];
|
|
2400
|
+
const { data: deviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device_name).single();
|
|
2401
|
+
const deviceId = deviceRow?.id;
|
|
2328
2402
|
const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
|
|
2329
2403
|
if (proposedFiles?.length && process.stdin.isTTY) {
|
|
2330
2404
|
const { checkbox } = await import("@inquirer/prompts");
|
|
@@ -2388,39 +2462,60 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2388
2462
|
if (result.doppler) {
|
|
2389
2463
|
updatePayload.doppler_json = result.doppler;
|
|
2390
2464
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
await pushToolings(supabase, folder_id, result.toolings);
|
|
2396
|
-
const manifestForHealth = result.envManifest ?? storedManifest;
|
|
2397
|
-
if (manifestForHealth) {
|
|
2398
|
-
await pushHealthResults(supabase, folder_id, manifestForHealth);
|
|
2465
|
+
try {
|
|
2466
|
+
const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
|
|
2467
|
+
if (error) {
|
|
2468
|
+
console.log(chalk12.dim(`Debug: claude_folders update skipped (${error.message})`));
|
|
2399
2469
|
}
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2470
|
+
} catch (err) {
|
|
2471
|
+
console.log(chalk12.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
|
|
2472
|
+
}
|
|
2473
|
+
if (deviceId) {
|
|
2474
|
+
await supabase.from("device_scans").upsert({
|
|
2475
|
+
folder_id,
|
|
2476
|
+
device_id: deviceId,
|
|
2477
|
+
user_id: userId,
|
|
2478
|
+
graph_json: result.graph,
|
|
2479
|
+
orphans_json: result.orphans,
|
|
2480
|
+
skills_table_json: result.skills,
|
|
2481
|
+
stale_files_json: result.staleFiles,
|
|
2482
|
+
broken_refs_json: result.brokenRefs,
|
|
2483
|
+
env_manifest_json: result.envManifest,
|
|
2484
|
+
doppler_json: result.doppler,
|
|
2485
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
2486
|
+
data_hash: result.dataHash,
|
|
2487
|
+
scanned_at: result.scannedAt
|
|
2488
|
+
}, { onConflict: "folder_id,device_id" });
|
|
2489
|
+
}
|
|
2490
|
+
await pushToolings(supabase, folder_id, result.toolings);
|
|
2491
|
+
const manifestForHealth = result.envManifest ?? storedManifest;
|
|
2492
|
+
if (manifestForHealth) {
|
|
2493
|
+
await pushHealthResults(supabase, folder_id, manifestForHealth, deviceId);
|
|
2494
|
+
}
|
|
2495
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
2496
|
+
await saveState({
|
|
2497
|
+
lastFolderId: folder_id,
|
|
2498
|
+
lastDeviceName: device_name,
|
|
2499
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2500
|
+
});
|
|
2501
|
+
console.log(chalk12.green("Synced to Supabase."));
|
|
2502
|
+
console.log(chalk12.cyan(`
|
|
2408
2503
|
https://www.md4ai.com/project/${folder_id}
|
|
2409
2504
|
`));
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
}
|
|
2422
|
-
console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2505
|
+
const graphPaths = result.graph.nodes.map((n) => n.filePath);
|
|
2506
|
+
const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths);
|
|
2507
|
+
if (configFiles.length > 0) {
|
|
2508
|
+
for (const file of configFiles) {
|
|
2509
|
+
await supabase.from("folder_files").upsert({
|
|
2510
|
+
folder_id,
|
|
2511
|
+
file_path: file.filePath,
|
|
2512
|
+
content: file.content,
|
|
2513
|
+
size_bytes: file.sizeBytes,
|
|
2514
|
+
last_modified: file.lastModified,
|
|
2515
|
+
device_id: deviceId
|
|
2516
|
+
}, { onConflict: "folder_id,file_path,device_id" });
|
|
2423
2517
|
}
|
|
2518
|
+
console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2424
2519
|
}
|
|
2425
2520
|
} else {
|
|
2426
2521
|
console.log(chalk12.yellow("\nThis folder is not linked to a project on your dashboard."));
|
|
@@ -2432,13 +2527,13 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2432
2527
|
if (!shouldLink) {
|
|
2433
2528
|
console.log(chalk12.dim("Skipped \u2014 local preview still generated."));
|
|
2434
2529
|
} else {
|
|
2435
|
-
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
2530
|
+
const { supabase: sb, userId: userId2 } = await getAuthenticatedClient();
|
|
2436
2531
|
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
2437
2532
|
const choices = [
|
|
2438
2533
|
{ name: "+ Create a new project", value: "__new__" },
|
|
2439
2534
|
...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
|
|
2440
2535
|
];
|
|
2441
|
-
const chosen = await
|
|
2536
|
+
const chosen = await select4({
|
|
2442
2537
|
message: "Link to which project?",
|
|
2443
2538
|
choices
|
|
2444
2539
|
});
|
|
@@ -2446,9 +2541,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2446
2541
|
if (chosen === "__new__") {
|
|
2447
2542
|
const projectName = await input5({
|
|
2448
2543
|
message: "Project name:",
|
|
2449
|
-
default:
|
|
2544
|
+
default: basename2(projectRoot)
|
|
2450
2545
|
});
|
|
2451
|
-
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id:
|
|
2546
|
+
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId2, name: projectName }).select("id").single();
|
|
2452
2547
|
if (createErr || !newFolder) {
|
|
2453
2548
|
console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
|
|
2454
2549
|
return;
|
|
@@ -2461,33 +2556,59 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2461
2556
|
const deviceName = detectDeviceName();
|
|
2462
2557
|
const osType = detectOs2();
|
|
2463
2558
|
await sb.from("devices").upsert({
|
|
2464
|
-
user_id:
|
|
2559
|
+
user_id: userId2,
|
|
2465
2560
|
device_name: deviceName,
|
|
2466
2561
|
os_type: osType
|
|
2467
2562
|
}, { onConflict: "user_id,device_name" });
|
|
2563
|
+
const { data: inlineDeviceRow } = await sb.from("devices").select("id").eq("user_id", userId2).eq("device_name", deviceName).single();
|
|
2564
|
+
const inlineDeviceId = inlineDeviceRow?.id;
|
|
2468
2565
|
await sb.from("device_paths").upsert({
|
|
2469
|
-
user_id:
|
|
2566
|
+
user_id: userId2,
|
|
2470
2567
|
folder_id: folderId,
|
|
2471
2568
|
device_name: deviceName,
|
|
2472
2569
|
os_type: osType,
|
|
2473
2570
|
path: projectRoot,
|
|
2474
2571
|
last_synced: (/* @__PURE__ */ new Date()).toISOString()
|
|
2475
2572
|
}, { onConflict: "folder_id,device_name" });
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2573
|
+
try {
|
|
2574
|
+
const { error: inlineFolderErr } = await sb.from("claude_folders").update({
|
|
2575
|
+
graph_json: result.graph,
|
|
2576
|
+
orphans_json: result.orphans,
|
|
2577
|
+
broken_refs_json: result.brokenRefs,
|
|
2578
|
+
skills_table_json: result.skills,
|
|
2579
|
+
stale_files_json: result.staleFiles,
|
|
2580
|
+
env_manifest_json: result.envManifest,
|
|
2581
|
+
doppler_json: result.doppler,
|
|
2582
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
2583
|
+
last_scanned: result.scannedAt,
|
|
2584
|
+
data_hash: result.dataHash
|
|
2585
|
+
}).eq("id", folderId);
|
|
2586
|
+
if (inlineFolderErr) {
|
|
2587
|
+
console.log(chalk12.dim(`Debug: claude_folders update skipped (${inlineFolderErr.message})`));
|
|
2588
|
+
}
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
console.log(chalk12.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
|
|
2591
|
+
}
|
|
2592
|
+
if (inlineDeviceId) {
|
|
2593
|
+
await sb.from("device_scans").upsert({
|
|
2594
|
+
folder_id: folderId,
|
|
2595
|
+
device_id: inlineDeviceId,
|
|
2596
|
+
user_id: userId2,
|
|
2597
|
+
graph_json: result.graph,
|
|
2598
|
+
orphans_json: result.orphans,
|
|
2599
|
+
skills_table_json: result.skills,
|
|
2600
|
+
stale_files_json: result.staleFiles,
|
|
2601
|
+
broken_refs_json: result.brokenRefs,
|
|
2602
|
+
env_manifest_json: result.envManifest,
|
|
2603
|
+
doppler_json: result.doppler,
|
|
2604
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
2605
|
+
data_hash: result.dataHash,
|
|
2606
|
+
scanned_at: result.scannedAt
|
|
2607
|
+
}, { onConflict: "folder_id,device_id" });
|
|
2608
|
+
}
|
|
2488
2609
|
await pushToolings(sb, folderId, result.toolings);
|
|
2489
2610
|
if (result.envManifest) {
|
|
2490
|
-
await pushHealthResults(sb, folderId, result.envManifest);
|
|
2611
|
+
await pushHealthResults(sb, folderId, result.envManifest, inlineDeviceId);
|
|
2491
2612
|
}
|
|
2492
2613
|
const graphPaths2 = result.graph.nodes.map((n) => n.filePath);
|
|
2493
2614
|
const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths2);
|
|
@@ -2497,8 +2618,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2497
2618
|
file_path: file.filePath,
|
|
2498
2619
|
content: file.content,
|
|
2499
2620
|
size_bytes: file.sizeBytes,
|
|
2500
|
-
last_modified: file.lastModified
|
|
2501
|
-
|
|
2621
|
+
last_modified: file.lastModified,
|
|
2622
|
+
device_id: inlineDeviceId
|
|
2623
|
+
}, { onConflict: "folder_id,file_path,device_id" });
|
|
2502
2624
|
}
|
|
2503
2625
|
await saveState({
|
|
2504
2626
|
lastFolderId: folderId,
|
|
@@ -2543,7 +2665,7 @@ function isValidProjectPath(p) {
|
|
|
2543
2665
|
return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
|
|
2544
2666
|
}
|
|
2545
2667
|
async function syncCommand(options) {
|
|
2546
|
-
const { supabase } = await getAuthenticatedClient();
|
|
2668
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
2547
2669
|
if (options.all) {
|
|
2548
2670
|
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
|
|
2549
2671
|
if (error || !devices?.length) {
|
|
@@ -2562,16 +2684,40 @@ async function syncCommand(options) {
|
|
|
2562
2684
|
}
|
|
2563
2685
|
try {
|
|
2564
2686
|
const result = await scanProject(device.path);
|
|
2565
|
-
await supabase.from("
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2687
|
+
const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2688
|
+
const allDeviceId = allDeviceRow?.id;
|
|
2689
|
+
try {
|
|
2690
|
+
const { error: allFolderErr } = await supabase.from("claude_folders").update({
|
|
2691
|
+
graph_json: result.graph,
|
|
2692
|
+
orphans_json: result.orphans,
|
|
2693
|
+
broken_refs_json: result.brokenRefs,
|
|
2694
|
+
skills_table_json: result.skills,
|
|
2695
|
+
stale_files_json: result.staleFiles,
|
|
2696
|
+
env_manifest_json: result.envManifest,
|
|
2697
|
+
last_scanned: result.scannedAt,
|
|
2698
|
+
data_hash: result.dataHash
|
|
2699
|
+
}).eq("id", device.folder_id);
|
|
2700
|
+
if (allFolderErr) {
|
|
2701
|
+
console.log(chalk15.dim(` Debug: claude_folders update skipped (${allFolderErr.message})`));
|
|
2702
|
+
}
|
|
2703
|
+
} catch (folderErr) {
|
|
2704
|
+
console.log(chalk15.dim(` Debug: claude_folders update skipped (${folderErr instanceof Error ? folderErr.message : String(folderErr)})`));
|
|
2705
|
+
}
|
|
2706
|
+
if (allDeviceId) {
|
|
2707
|
+
await supabase.from("device_scans").upsert({
|
|
2708
|
+
folder_id: device.folder_id,
|
|
2709
|
+
device_id: allDeviceId,
|
|
2710
|
+
user_id: userId,
|
|
2711
|
+
graph_json: result.graph,
|
|
2712
|
+
orphans_json: result.orphans,
|
|
2713
|
+
skills_table_json: result.skills,
|
|
2714
|
+
stale_files_json: result.staleFiles,
|
|
2715
|
+
broken_refs_json: result.brokenRefs,
|
|
2716
|
+
env_manifest_json: result.envManifest,
|
|
2717
|
+
data_hash: result.dataHash,
|
|
2718
|
+
scanned_at: result.scannedAt
|
|
2719
|
+
}, { onConflict: "folder_id,device_id" });
|
|
2720
|
+
}
|
|
2575
2721
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
2576
2722
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2577
2723
|
console.log(chalk15.green(` Done: ${device.device_name}`));
|
|
@@ -2602,15 +2748,38 @@ async function syncCommand(options) {
|
|
|
2602
2748
|
console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
2603
2749
|
}
|
|
2604
2750
|
const result = await scanProject(device.path);
|
|
2605
|
-
await supabase.from("
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2751
|
+
const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2752
|
+
const singleDeviceId = singleDeviceRow?.id;
|
|
2753
|
+
try {
|
|
2754
|
+
const { error: singleFolderErr } = await supabase.from("claude_folders").update({
|
|
2755
|
+
graph_json: result.graph,
|
|
2756
|
+
orphans_json: result.orphans,
|
|
2757
|
+
broken_refs_json: result.brokenRefs,
|
|
2758
|
+
skills_table_json: result.skills,
|
|
2759
|
+
stale_files_json: result.staleFiles,
|
|
2760
|
+
last_scanned: result.scannedAt,
|
|
2761
|
+
data_hash: result.dataHash
|
|
2762
|
+
}).eq("id", device.folder_id);
|
|
2763
|
+
if (singleFolderErr) {
|
|
2764
|
+
console.log(chalk15.dim(`Debug: claude_folders update skipped (${singleFolderErr.message})`));
|
|
2765
|
+
}
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
console.log(chalk15.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
|
|
2768
|
+
}
|
|
2769
|
+
if (singleDeviceId) {
|
|
2770
|
+
await supabase.from("device_scans").upsert({
|
|
2771
|
+
folder_id: device.folder_id,
|
|
2772
|
+
device_id: singleDeviceId,
|
|
2773
|
+
user_id: userId,
|
|
2774
|
+
graph_json: result.graph,
|
|
2775
|
+
orphans_json: result.orphans,
|
|
2776
|
+
skills_table_json: result.skills,
|
|
2777
|
+
stale_files_json: result.staleFiles,
|
|
2778
|
+
broken_refs_json: result.brokenRefs,
|
|
2779
|
+
data_hash: result.dataHash,
|
|
2780
|
+
scanned_at: result.scannedAt
|
|
2781
|
+
}, { onConflict: "folder_id,device_id" });
|
|
2782
|
+
}
|
|
2614
2783
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
2615
2784
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2616
2785
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -3757,6 +3926,8 @@ Linking "${folder.name}" to this device...
|
|
|
3757
3926
|
device_name: deviceName,
|
|
3758
3927
|
os_type: osType
|
|
3759
3928
|
}, { onConflict: "user_id,device_name" });
|
|
3929
|
+
const { data: deviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", deviceName).single();
|
|
3930
|
+
const deviceId = deviceRow.id;
|
|
3760
3931
|
const { data: existing } = await supabase.from("device_paths").select("id").eq("folder_id", folder.id).eq("device_name", deviceName).maybeSingle();
|
|
3761
3932
|
if (existing) {
|
|
3762
3933
|
await supabase.from("device_paths").update({ path: cwd, os_type: osType, last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id);
|
|
@@ -3782,23 +3953,42 @@ Linking "${folder.name}" to this device...
|
|
|
3782
3953
|
console.log(` Skills: ${result.skills.length}`);
|
|
3783
3954
|
console.log(` Toolings: ${result.toolings.length}`);
|
|
3784
3955
|
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
3785
|
-
console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "
|
|
3956
|
+
console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
|
|
3786
3957
|
console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
|
|
3787
|
-
|
|
3958
|
+
try {
|
|
3959
|
+
const { error: scanErr } = await supabase.from("claude_folders").update({
|
|
3960
|
+
graph_json: result.graph,
|
|
3961
|
+
orphans_json: result.orphans,
|
|
3962
|
+
broken_refs_json: result.brokenRefs,
|
|
3963
|
+
skills_table_json: result.skills,
|
|
3964
|
+
stale_files_json: result.staleFiles,
|
|
3965
|
+
env_manifest_json: result.envManifest,
|
|
3966
|
+
doppler_json: result.doppler,
|
|
3967
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
3968
|
+
last_scanned: result.scannedAt,
|
|
3969
|
+
data_hash: result.dataHash
|
|
3970
|
+
}).eq("id", folder.id);
|
|
3971
|
+
if (scanErr) {
|
|
3972
|
+
console.log(chalk16.dim(`Debug: claude_folders update skipped (${scanErr.message})`));
|
|
3973
|
+
}
|
|
3974
|
+
} catch (err) {
|
|
3975
|
+
console.log(chalk16.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
|
|
3976
|
+
}
|
|
3977
|
+
await supabase.from("device_scans").upsert({
|
|
3978
|
+
folder_id: folder.id,
|
|
3979
|
+
device_id: deviceId,
|
|
3980
|
+
user_id: userId,
|
|
3788
3981
|
graph_json: result.graph,
|
|
3789
3982
|
orphans_json: result.orphans,
|
|
3790
|
-
broken_refs_json: result.brokenRefs,
|
|
3791
3983
|
skills_table_json: result.skills,
|
|
3792
3984
|
stale_files_json: result.staleFiles,
|
|
3985
|
+
broken_refs_json: result.brokenRefs,
|
|
3793
3986
|
env_manifest_json: result.envManifest,
|
|
3794
3987
|
doppler_json: result.doppler,
|
|
3795
3988
|
marketplace_plugins_json: result.marketplacePlugins,
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
}
|
|
3799
|
-
if (scanErr) {
|
|
3800
|
-
console.error(chalk16.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
3801
|
-
}
|
|
3989
|
+
data_hash: result.dataHash,
|
|
3990
|
+
scanned_at: result.scannedAt
|
|
3991
|
+
}, { onConflict: "folder_id,device_id" });
|
|
3802
3992
|
await pushToolings(supabase, folder.id, result.toolings);
|
|
3803
3993
|
const graphPaths = result.graph.nodes.map((n) => n.filePath);
|
|
3804
3994
|
const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
|
|
@@ -3809,8 +3999,9 @@ Linking "${folder.name}" to this device...
|
|
|
3809
3999
|
file_path: file.filePath,
|
|
3810
4000
|
content: file.content,
|
|
3811
4001
|
size_bytes: file.sizeBytes,
|
|
3812
|
-
last_modified: file.lastModified
|
|
3813
|
-
|
|
4002
|
+
last_modified: file.lastModified,
|
|
4003
|
+
device_id: deviceId
|
|
4004
|
+
}, { onConflict: "folder_id,file_path,device_id" });
|
|
3814
4005
|
}
|
|
3815
4006
|
console.log(chalk16.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
3816
4007
|
}
|
|
@@ -4581,6 +4772,8 @@ async function configSetCommand(key, value) {
|
|
|
4581
4772
|
// dist/commands/doppler.js
|
|
4582
4773
|
init_config();
|
|
4583
4774
|
init_api();
|
|
4775
|
+
init_auth();
|
|
4776
|
+
init_dist();
|
|
4584
4777
|
import chalk26 from "chalk";
|
|
4585
4778
|
import { input as input7 } from "@inquirer/prompts";
|
|
4586
4779
|
async function dopplerConnectCommand() {
|
|
@@ -4599,7 +4792,51 @@ async function dopplerConnectCommand() {
|
|
|
4599
4792
|
await mergeCredentials({ dopplerToken: token });
|
|
4600
4793
|
console.log(chalk26.green(`Token validated \u2014 ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.`));
|
|
4601
4794
|
console.log(chalk26.green("Saved to ~/.md4ai/credentials.json"));
|
|
4602
|
-
|
|
4795
|
+
const creds = await loadCredentials();
|
|
4796
|
+
if (!creds?.accessToken || !creds?.userId) {
|
|
4797
|
+
console.log("");
|
|
4798
|
+
console.log(chalk26.yellow("\u26A0 Not logged in to MD4AI \u2014 token saved locally only."));
|
|
4799
|
+
console.log(chalk26.yellow(" The web dashboard cannot show live Doppler status until the token is synced."));
|
|
4800
|
+
console.log(chalk26.yellow(' To fix: run "md4ai login", then "md4ai doppler connect" again.'));
|
|
4801
|
+
} else {
|
|
4802
|
+
let supabase = createSupabaseClient(getAnonKey(), creds.accessToken);
|
|
4803
|
+
let userId = creds.userId;
|
|
4804
|
+
if (Date.now() > creds.expiresAt) {
|
|
4805
|
+
const refreshed = await refreshSession();
|
|
4806
|
+
if (refreshed) {
|
|
4807
|
+
supabase = refreshed.supabase;
|
|
4808
|
+
userId = refreshed.userId;
|
|
4809
|
+
} else {
|
|
4810
|
+
console.log("");
|
|
4811
|
+
console.log(chalk26.yellow("\u26A0 Session expired \u2014 token saved locally only."));
|
|
4812
|
+
console.log(chalk26.yellow(' To sync to the web dashboard: run "md4ai login", then "md4ai doppler connect" again.'));
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
if (supabase) {
|
|
4816
|
+
try {
|
|
4817
|
+
const { error } = await supabase.from("user_secrets").upsert({ user_id: userId, doppler_token: token, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, { onConflict: "user_id" });
|
|
4818
|
+
if (error) {
|
|
4819
|
+
console.log(chalk26.yellow(`\u26A0 Could not sync token to web dashboard: ${error.message}`));
|
|
4820
|
+
} else {
|
|
4821
|
+
const { data: verify } = await supabase.from("user_secrets").select("id").eq("user_id", userId).maybeSingle();
|
|
4822
|
+
if (verify) {
|
|
4823
|
+
console.log(chalk26.green("Token synced to web dashboard."));
|
|
4824
|
+
} else {
|
|
4825
|
+
console.log(chalk26.yellow("\u26A0 Token sync could not be verified \u2014 check the web dashboard."));
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
} catch (err) {
|
|
4829
|
+
console.log(chalk26.yellow(`\u26A0 Could not sync token to web dashboard: ${err instanceof Error ? err.message : "unknown error"}`));
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
console.log("");
|
|
4834
|
+
console.log(chalk26.cyan("Next step: link a Doppler project to your Claude project."));
|
|
4835
|
+
console.log(chalk26.dim(" The scanner looks for a doppler.yaml in the project root, e.g.:"));
|
|
4836
|
+
console.log(chalk26.dim(" project: my-doppler-project"));
|
|
4837
|
+
console.log(chalk26.dim(" Or use a manual override:"));
|
|
4838
|
+
console.log(chalk26.dim(" md4ai doppler set-project <slug>"));
|
|
4839
|
+
console.log(chalk26.dim(' Then run "md4ai scan" to fetch secret names from Doppler.'));
|
|
4603
4840
|
}
|
|
4604
4841
|
async function dopplerDisconnectCommand() {
|
|
4605
4842
|
const creds = await loadCredentials();
|
|
@@ -4614,7 +4851,26 @@ async function dopplerDisconnectCommand() {
|
|
|
4614
4851
|
const credPath = join17(homedir10(), ".md4ai", "credentials.json");
|
|
4615
4852
|
await writeFile6(credPath, JSON.stringify(rest, null, 2), "utf-8");
|
|
4616
4853
|
await chmod2(credPath, 384);
|
|
4617
|
-
|
|
4854
|
+
try {
|
|
4855
|
+
const currentCreds = await loadCredentials();
|
|
4856
|
+
if (currentCreds?.accessToken && currentCreds?.userId) {
|
|
4857
|
+
let supabase = createSupabaseClient(getAnonKey(), currentCreds.accessToken);
|
|
4858
|
+
let userId = currentCreds.userId;
|
|
4859
|
+
if (Date.now() > currentCreds.expiresAt) {
|
|
4860
|
+
const refreshed = await refreshSession();
|
|
4861
|
+
if (refreshed) {
|
|
4862
|
+
supabase = refreshed.supabase;
|
|
4863
|
+
userId = refreshed.userId;
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
await supabase.from("user_secrets").delete().eq("user_id", userId);
|
|
4867
|
+
console.log(chalk26.green("Doppler token removed from local and web dashboard."));
|
|
4868
|
+
} else {
|
|
4869
|
+
console.log(chalk26.green("Doppler token removed locally."));
|
|
4870
|
+
}
|
|
4871
|
+
} catch {
|
|
4872
|
+
console.log(chalk26.green("Doppler token removed locally."));
|
|
4873
|
+
}
|
|
4618
4874
|
}
|
|
4619
4875
|
async function dopplerSetProjectCommand(slug) {
|
|
4620
4876
|
const state = await loadState();
|