md4ai 0.7.1 → 0.7.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 +1777 -1451
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
2
11
|
|
|
3
|
-
// dist/
|
|
4
|
-
|
|
12
|
+
// ../packages/shared/dist/types.js
|
|
13
|
+
var init_types = __esm({
|
|
14
|
+
"../packages/shared/dist/types.js"() {
|
|
15
|
+
"use strict";
|
|
16
|
+
}
|
|
17
|
+
});
|
|
5
18
|
|
|
6
19
|
// ../packages/shared/dist/constants.js
|
|
7
|
-
var SUPABASE_URL
|
|
8
|
-
var
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
var SUPABASE_URL, ROOT_FILES, GLOBAL_ROOT_FILES, STALE_THRESHOLD_DAYS, CONFIG_DIR, CREDENTIALS_FILE, STATE_FILE;
|
|
21
|
+
var init_constants = __esm({
|
|
22
|
+
"../packages/shared/dist/constants.js"() {
|
|
23
|
+
"use strict";
|
|
24
|
+
SUPABASE_URL = "https://gkrfwmwlfnixeffhwwju.supabase.co";
|
|
25
|
+
ROOT_FILES = [
|
|
26
|
+
"CLAUDE.md",
|
|
27
|
+
".claude/settings.json",
|
|
28
|
+
".claude/settings.local.json"
|
|
29
|
+
];
|
|
30
|
+
GLOBAL_ROOT_FILES = [
|
|
31
|
+
"~/.claude/CLAUDE.md",
|
|
32
|
+
"~/.claude/settings.json",
|
|
33
|
+
"~/.claude/settings.local.json"
|
|
34
|
+
];
|
|
35
|
+
STALE_THRESHOLD_DAYS = 90;
|
|
36
|
+
CONFIG_DIR = ".md4ai";
|
|
37
|
+
CREDENTIALS_FILE = "credentials.json";
|
|
38
|
+
STATE_FILE = "state.json";
|
|
39
|
+
}
|
|
40
|
+
});
|
|
22
41
|
|
|
23
42
|
// ../packages/shared/dist/supabase.js
|
|
24
43
|
import { createClient } from "@supabase/supabase-js";
|
|
@@ -26,15 +45,28 @@ function createSupabaseClient(anonKey, accessToken) {
|
|
|
26
45
|
const options = accessToken ? { global: { headers: { Authorization: `Bearer ${accessToken}` } } } : {};
|
|
27
46
|
return createClient(SUPABASE_URL, anonKey, options);
|
|
28
47
|
}
|
|
48
|
+
var init_supabase = __esm({
|
|
49
|
+
"../packages/shared/dist/supabase.js"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
init_constants();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ../packages/shared/dist/index.js
|
|
56
|
+
var init_dist = __esm({
|
|
57
|
+
"../packages/shared/dist/index.js"() {
|
|
58
|
+
"use strict";
|
|
59
|
+
init_types();
|
|
60
|
+
init_constants();
|
|
61
|
+
init_supabase();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
29
64
|
|
|
30
65
|
// dist/config.js
|
|
31
66
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
32
67
|
import { join } from "node:path";
|
|
33
68
|
import { homedir } from "node:os";
|
|
34
69
|
import { existsSync } from "node:fs";
|
|
35
|
-
var configPath = join(homedir(), CONFIG_DIR);
|
|
36
|
-
var credentialsPath = join(configPath, CREDENTIALS_FILE);
|
|
37
|
-
var statePath = join(configPath, STATE_FILE);
|
|
38
70
|
async function ensureConfigDir() {
|
|
39
71
|
if (!existsSync(configPath)) {
|
|
40
72
|
await mkdir(configPath, { recursive: true });
|
|
@@ -84,66 +116,16 @@ function getAnonKey() {
|
|
|
84
116
|
}
|
|
85
117
|
return key;
|
|
86
118
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const anonKey = getAnonKey();
|
|
96
|
-
const supabase = createSupabaseClient(anonKey);
|
|
97
|
-
const { data, error } = await supabase.auth.signInWithPassword({
|
|
98
|
-
email,
|
|
99
|
-
password: pwd
|
|
100
|
-
});
|
|
101
|
-
if (error) {
|
|
102
|
-
console.error(chalk.red(`Login failed: ${error.message}`));
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
await saveCredentials({
|
|
106
|
-
accessToken: data.session.access_token,
|
|
107
|
-
refreshToken: data.session.refresh_token,
|
|
108
|
-
expiresAt: Date.now() + data.session.expires_in * 1e3,
|
|
109
|
-
userId: data.user.id,
|
|
110
|
-
email: data.user.email ?? email
|
|
111
|
-
});
|
|
112
|
-
console.log(chalk.green(`
|
|
113
|
-
Logged in as ${data.user.email}`));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// dist/commands/logout.js
|
|
117
|
-
import chalk2 from "chalk";
|
|
118
|
-
async function logoutCommand() {
|
|
119
|
-
await clearCredentials();
|
|
120
|
-
console.log(chalk2.green("Logged out successfully."));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// dist/commands/status.js
|
|
124
|
-
import chalk3 from "chalk";
|
|
125
|
-
async function statusCommand() {
|
|
126
|
-
const creds = await loadCredentials();
|
|
127
|
-
if (!creds?.accessToken) {
|
|
128
|
-
console.log(chalk3.yellow("Not logged in. Run: md4ai login"));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
console.log(chalk3.blue("MD4AI Status\n"));
|
|
132
|
-
console.log(` User: ${creds.email}`);
|
|
133
|
-
console.log(` Expires: ${new Date(creds.expiresAt).toLocaleString()}`);
|
|
134
|
-
try {
|
|
135
|
-
const anonKey = getAnonKey();
|
|
136
|
-
const supabase = createSupabaseClient(anonKey, creds.accessToken);
|
|
137
|
-
const { count: folderCount } = await supabase.from("claude_folders").select("*", { count: "exact", head: true });
|
|
138
|
-
const { count: deviceCount } = await supabase.from("device_paths").select("*", { count: "exact", head: true });
|
|
139
|
-
console.log(` Folders: ${folderCount ?? 0}`);
|
|
140
|
-
console.log(` Devices: ${deviceCount ?? 0}`);
|
|
141
|
-
const state = await loadState();
|
|
142
|
-
console.log(` Last sync: ${state.lastSyncAt ?? "never"}`);
|
|
143
|
-
} catch {
|
|
144
|
-
console.log(chalk3.yellow(" (Could not fetch remote data)"));
|
|
119
|
+
var configPath, credentialsPath, statePath;
|
|
120
|
+
var init_config = __esm({
|
|
121
|
+
"dist/config.js"() {
|
|
122
|
+
"use strict";
|
|
123
|
+
init_dist();
|
|
124
|
+
configPath = join(homedir(), CONFIG_DIR);
|
|
125
|
+
credentialsPath = join(configPath, CREDENTIALS_FILE);
|
|
126
|
+
statePath = join(configPath, STATE_FILE);
|
|
145
127
|
}
|
|
146
|
-
}
|
|
128
|
+
});
|
|
147
129
|
|
|
148
130
|
// dist/auth.js
|
|
149
131
|
import chalk4 from "chalk";
|
|
@@ -161,180 +143,19 @@ async function getAuthenticatedClient() {
|
|
|
161
143
|
const supabase = createSupabaseClient(anonKey, creds.accessToken);
|
|
162
144
|
return { supabase, userId: creds.userId };
|
|
163
145
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
170
|
-
const name = await input2({ message: "Folder name:" });
|
|
171
|
-
const description = await input2({ message: "Description (optional):" });
|
|
172
|
-
const { data: teams } = await supabase.from("team_members").select("team_id, teams(id, name)").eq("user_id", userId);
|
|
173
|
-
const { data: ownedTeams } = await supabase.from("teams").select("id, name").eq("created_by", userId);
|
|
174
|
-
const allTeams = /* @__PURE__ */ new Map();
|
|
175
|
-
for (const t of ownedTeams ?? []) {
|
|
176
|
-
allTeams.set(t.id, t.name);
|
|
177
|
-
}
|
|
178
|
-
for (const t of teams ?? []) {
|
|
179
|
-
const team = t.teams;
|
|
180
|
-
if (team)
|
|
181
|
-
allTeams.set(team.id, team.name);
|
|
182
|
-
}
|
|
183
|
-
let teamId = null;
|
|
184
|
-
if (allTeams.size > 0) {
|
|
185
|
-
const choices = [
|
|
186
|
-
{ name: "Personal (only you)", value: "__personal__" },
|
|
187
|
-
...Array.from(allTeams.entries()).map(([id, teamName]) => ({
|
|
188
|
-
name: teamName,
|
|
189
|
-
value: id
|
|
190
|
-
}))
|
|
191
|
-
];
|
|
192
|
-
const selected = await select({
|
|
193
|
-
message: "Assign to a team?",
|
|
194
|
-
choices
|
|
195
|
-
});
|
|
196
|
-
teamId = selected === "__personal__" ? null : selected;
|
|
197
|
-
}
|
|
198
|
-
const { data, error } = await supabase.from("claude_folders").insert({
|
|
199
|
-
user_id: userId,
|
|
200
|
-
name,
|
|
201
|
-
description: description || null,
|
|
202
|
-
team_id: teamId
|
|
203
|
-
}).select().single();
|
|
204
|
-
if (error) {
|
|
205
|
-
console.error(chalk5.red(`Failed to create folder: ${error.message}`));
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
const teamLabel = teamId ? `(team: ${allTeams.get(teamId)})` : "(personal)";
|
|
209
|
-
console.log(chalk5.green(`
|
|
210
|
-
Folder "${name}" created ${teamLabel} (${data.id})`));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// dist/commands/add-device.js
|
|
214
|
-
import { resolve } from "node:path";
|
|
215
|
-
import { hostname, platform } from "node:os";
|
|
216
|
-
import chalk6 from "chalk";
|
|
217
|
-
import { input as input3, select as select2 } from "@inquirer/prompts";
|
|
218
|
-
function detectOs() {
|
|
219
|
-
const p = platform();
|
|
220
|
-
if (p === "win32")
|
|
221
|
-
return "windows";
|
|
222
|
-
if (p === "darwin")
|
|
223
|
-
return "macos";
|
|
224
|
-
if (p === "linux")
|
|
225
|
-
return "linux";
|
|
226
|
-
return "other";
|
|
227
|
-
}
|
|
228
|
-
function suggestDeviceName() {
|
|
229
|
-
const os = detectOs();
|
|
230
|
-
const host = hostname().split(".")[0];
|
|
231
|
-
const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
|
|
232
|
-
return `${host}-${osLabel}`;
|
|
233
|
-
}
|
|
234
|
-
async function addDeviceCommand() {
|
|
235
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
236
|
-
const cwd = resolve(process.cwd());
|
|
237
|
-
console.log(chalk6.blue.bold("\n\u{1F4C2} Where is this Claude project?\n"));
|
|
238
|
-
const localPath = await input3({
|
|
239
|
-
message: "Local project path:",
|
|
240
|
-
default: cwd
|
|
241
|
-
});
|
|
242
|
-
const { data: folders, error: foldersErr } = await supabase.from("claude_folders").select("id, name").order("name");
|
|
243
|
-
if (foldersErr || !folders?.length) {
|
|
244
|
-
console.error(chalk6.red("No folders found. Run: md4ai add-folder"));
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
const folderId = await select2({
|
|
248
|
-
message: "Select folder:",
|
|
249
|
-
choices: folders.map((f) => ({ name: f.name, value: f.id }))
|
|
250
|
-
});
|
|
251
|
-
const suggested = suggestDeviceName();
|
|
252
|
-
const deviceName = await input3({
|
|
253
|
-
message: "Device name:",
|
|
254
|
-
default: suggested
|
|
255
|
-
});
|
|
256
|
-
const osType = await select2({
|
|
257
|
-
message: "OS type:",
|
|
258
|
-
choices: [
|
|
259
|
-
{ name: "Windows", value: "windows" },
|
|
260
|
-
{ name: "macOS", value: "macos" },
|
|
261
|
-
{ name: "Ubuntu", value: "ubuntu" },
|
|
262
|
-
{ name: "Linux", value: "linux" },
|
|
263
|
-
{ name: "Other", value: "other" }
|
|
264
|
-
],
|
|
265
|
-
default: detectOs()
|
|
266
|
-
});
|
|
267
|
-
const description = await input3({ message: "Description (optional):" });
|
|
268
|
-
const { error } = await supabase.from("device_paths").insert({
|
|
269
|
-
user_id: userId,
|
|
270
|
-
folder_id: folderId,
|
|
271
|
-
device_name: deviceName,
|
|
272
|
-
os_type: osType,
|
|
273
|
-
path: localPath,
|
|
274
|
-
description: description || null
|
|
275
|
-
});
|
|
276
|
-
if (error) {
|
|
277
|
-
console.error(chalk6.red(`Failed to add device: ${error.message}`));
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
console.log(chalk6.green(`
|
|
281
|
-
Device "${deviceName}" added to folder.`));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// dist/commands/list-devices.js
|
|
285
|
-
import chalk7 from "chalk";
|
|
286
|
-
async function listDevicesCommand() {
|
|
287
|
-
const { supabase } = await getAuthenticatedClient();
|
|
288
|
-
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
289
|
-
id, device_name, os_type, path, last_synced, description,
|
|
290
|
-
claude_folders!inner ( name )
|
|
291
|
-
`).order("device_name");
|
|
292
|
-
if (error) {
|
|
293
|
-
console.error(chalk7.red(`Failed to list devices: ${error.message}`));
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
if (!devices?.length) {
|
|
297
|
-
console.log(chalk7.yellow("No devices found. Run: md4ai add-device"));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
301
|
-
for (const d of devices) {
|
|
302
|
-
const list = grouped.get(d.device_name) ?? [];
|
|
303
|
-
list.push(d);
|
|
304
|
-
grouped.set(d.device_name, list);
|
|
305
|
-
}
|
|
306
|
-
for (const [deviceName, entries] of grouped) {
|
|
307
|
-
const first = entries[0];
|
|
308
|
-
console.log(chalk7.bold(`
|
|
309
|
-
${deviceName}`) + chalk7.dim(` (${first.os_type})`));
|
|
310
|
-
for (const entry of entries) {
|
|
311
|
-
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
312
|
-
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
313
|
-
console.log(` ${chalk7.cyan(folderName)} \u2192 ${entry.path}`);
|
|
314
|
-
console.log(` Last synced: ${synced}`);
|
|
315
|
-
}
|
|
146
|
+
var init_auth = __esm({
|
|
147
|
+
"dist/auth.js"() {
|
|
148
|
+
"use strict";
|
|
149
|
+
init_dist();
|
|
150
|
+
init_config();
|
|
316
151
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// dist/commands/map.js
|
|
320
|
-
import { resolve as resolve3 } from "node:path";
|
|
321
|
-
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
322
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
323
|
-
import chalk9 from "chalk";
|
|
324
|
-
|
|
325
|
-
// dist/scanner/index.js
|
|
326
|
-
import { readdir as readdir2 } from "node:fs/promises";
|
|
327
|
-
import { join as join8, relative as relative2 } from "node:path";
|
|
328
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
329
|
-
import { homedir as homedir5 } from "node:os";
|
|
330
|
-
import { createHash } from "node:crypto";
|
|
152
|
+
});
|
|
331
153
|
|
|
332
154
|
// dist/scanner/file-parser.js
|
|
333
155
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
334
156
|
import { existsSync as existsSync2 } from "node:fs";
|
|
335
157
|
import { resolve as resolve2, dirname, join as join2 } from "node:path";
|
|
336
158
|
import { homedir as homedir2 } from "node:os";
|
|
337
|
-
var FILE_EXT_PATTERN = "md|json|ya?ml|ts|js|sh|py|bash";
|
|
338
159
|
async function parseFileReferences(filePath, projectRoot) {
|
|
339
160
|
const refs = [];
|
|
340
161
|
const content = await readFile2(filePath, "utf-8");
|
|
@@ -451,6 +272,13 @@ function classifyFileType(filePath) {
|
|
|
451
272
|
return "plan";
|
|
452
273
|
return "other";
|
|
453
274
|
}
|
|
275
|
+
var FILE_EXT_PATTERN;
|
|
276
|
+
var init_file_parser = __esm({
|
|
277
|
+
"dist/scanner/file-parser.js"() {
|
|
278
|
+
"use strict";
|
|
279
|
+
FILE_EXT_PATTERN = "md|json|ya?ml|ts|js|sh|py|bash";
|
|
280
|
+
}
|
|
281
|
+
});
|
|
454
282
|
|
|
455
283
|
// dist/scanner/git-dates.js
|
|
456
284
|
import { execFileSync } from "node:child_process";
|
|
@@ -479,6 +307,11 @@ function getGitCreationDate(filePath, cwd) {
|
|
|
479
307
|
}
|
|
480
308
|
}
|
|
481
309
|
}
|
|
310
|
+
var init_git_dates = __esm({
|
|
311
|
+
"dist/scanner/git-dates.js"() {
|
|
312
|
+
"use strict";
|
|
313
|
+
}
|
|
314
|
+
});
|
|
482
315
|
|
|
483
316
|
// dist/scanner/graph-builder.js
|
|
484
317
|
import { statSync as statSync2 } from "node:fs";
|
|
@@ -552,6 +385,13 @@ function buildGraph(files, references, projectRoot) {
|
|
|
552
385
|
function sanitiseId(path) {
|
|
553
386
|
return path.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 64);
|
|
554
387
|
}
|
|
388
|
+
var init_graph_builder = __esm({
|
|
389
|
+
"dist/scanner/graph-builder.js"() {
|
|
390
|
+
"use strict";
|
|
391
|
+
init_file_parser();
|
|
392
|
+
init_git_dates();
|
|
393
|
+
}
|
|
394
|
+
});
|
|
555
395
|
|
|
556
396
|
// dist/scanner/orphan-detector.js
|
|
557
397
|
import { statSync as statSync3 } from "node:fs";
|
|
@@ -595,6 +435,12 @@ function detectOrphans(allFiles, references, rootFiles, projectRoot) {
|
|
|
595
435
|
};
|
|
596
436
|
});
|
|
597
437
|
}
|
|
438
|
+
var init_orphan_detector = __esm({
|
|
439
|
+
"dist/scanner/orphan-detector.js"() {
|
|
440
|
+
"use strict";
|
|
441
|
+
init_git_dates();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
598
444
|
|
|
599
445
|
// dist/scanner/skills-parser.js
|
|
600
446
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
@@ -680,6 +526,11 @@ async function parseSettingsForPlugins(settingsPath, skills, isMachineWide) {
|
|
|
680
526
|
} catch {
|
|
681
527
|
}
|
|
682
528
|
}
|
|
529
|
+
var init_skills_parser = __esm({
|
|
530
|
+
"dist/scanner/skills-parser.js"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
}
|
|
533
|
+
});
|
|
683
534
|
|
|
684
535
|
// dist/scanner/tooling-detector.js
|
|
685
536
|
import { readFile as readFile4, readdir } from "node:fs/promises";
|
|
@@ -687,13 +538,6 @@ import { existsSync as existsSync4 } from "node:fs";
|
|
|
687
538
|
import { join as join6 } from "node:path";
|
|
688
539
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
689
540
|
import { homedir as homedir4 } from "node:os";
|
|
690
|
-
var CLI_VERSION_COMMANDS = [
|
|
691
|
-
{ name: "node", command: "node", args: ["--version"] },
|
|
692
|
-
{ name: "npm", command: "npm", args: ["--version"] },
|
|
693
|
-
{ name: "pnpm", command: "pnpm", args: ["--version"], conditionFile: "pnpm-lock.yaml" },
|
|
694
|
-
{ name: "supabase-cli", command: "npx", args: ["supabase", "--version"] },
|
|
695
|
-
{ name: "claude-code", command: "claude", args: ["--version"] }
|
|
696
|
-
];
|
|
697
541
|
async function detectToolings(projectRoot) {
|
|
698
542
|
const toolings = [];
|
|
699
543
|
const pkgToolings = await detectFromPackageJson(projectRoot);
|
|
@@ -922,12 +766,24 @@ async function detectFromMcpSettings(projectRoot) {
|
|
|
922
766
|
}
|
|
923
767
|
return toolings;
|
|
924
768
|
}
|
|
769
|
+
var CLI_VERSION_COMMANDS;
|
|
770
|
+
var init_tooling_detector = __esm({
|
|
771
|
+
"dist/scanner/tooling-detector.js"() {
|
|
772
|
+
"use strict";
|
|
773
|
+
CLI_VERSION_COMMANDS = [
|
|
774
|
+
{ name: "node", command: "node", args: ["--version"] },
|
|
775
|
+
{ name: "npm", command: "npm", args: ["--version"] },
|
|
776
|
+
{ name: "pnpm", command: "pnpm", args: ["--version"], conditionFile: "pnpm-lock.yaml" },
|
|
777
|
+
{ name: "supabase-cli", command: "npx", args: ["supabase", "--version"] },
|
|
778
|
+
{ name: "claude-code", command: "claude", args: ["--version"] }
|
|
779
|
+
];
|
|
780
|
+
}
|
|
781
|
+
});
|
|
925
782
|
|
|
926
783
|
// dist/scanner/env-manifest-scanner.js
|
|
927
784
|
import { readFile as readFile5, glob } from "node:fs/promises";
|
|
928
785
|
import { join as join7, relative } from "node:path";
|
|
929
786
|
import { existsSync as existsSync5 } from "node:fs";
|
|
930
|
-
var MANIFEST_PATH = "docs/reference/env-manifest.md";
|
|
931
787
|
async function scanEnvManifest(projectRoot) {
|
|
932
788
|
const manifestFullPath = join7(projectRoot, MANIFEST_PATH);
|
|
933
789
|
if (!existsSync5(manifestFullPath)) {
|
|
@@ -1120,8 +976,20 @@ function computeDrift(variables, apps, localPresence, workflowRefs) {
|
|
|
1120
976
|
workflowBrokenRefs: workflowBrokenRefs.sort()
|
|
1121
977
|
};
|
|
1122
978
|
}
|
|
979
|
+
var MANIFEST_PATH;
|
|
980
|
+
var init_env_manifest_scanner = __esm({
|
|
981
|
+
"dist/scanner/env-manifest-scanner.js"() {
|
|
982
|
+
"use strict";
|
|
983
|
+
MANIFEST_PATH = "docs/reference/env-manifest.md";
|
|
984
|
+
}
|
|
985
|
+
});
|
|
1123
986
|
|
|
1124
987
|
// dist/scanner/index.js
|
|
988
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
989
|
+
import { join as join8, relative as relative2 } from "node:path";
|
|
990
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
991
|
+
import { homedir as homedir5 } from "node:os";
|
|
992
|
+
import { createHash } from "node:crypto";
|
|
1125
993
|
async function scanProject(projectRoot) {
|
|
1126
994
|
const allFiles = await discoverFiles(projectRoot);
|
|
1127
995
|
const rootFiles = identifyRoots(allFiles, projectRoot);
|
|
@@ -1247,122 +1115,23 @@ function detectStaleFiles(allFiles, projectRoot) {
|
|
|
1247
1115
|
}
|
|
1248
1116
|
return stale;
|
|
1249
1117
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1264
|
-
<title>MD4AI \u2014 ${escapeHtml(title)}</title>
|
|
1265
|
-
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
1266
|
-
<style>
|
|
1267
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1268
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
|
|
1269
|
-
h1 { color: #38bdf8; margin-bottom: 0.5rem; }
|
|
1270
|
-
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
|
|
1271
|
-
.section { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
1272
|
-
.section h2 { color: #38bdf8; margin-bottom: 1rem; font-size: 1.2rem; }
|
|
1273
|
-
.mermaid { background: #0f172a; border-radius: 8px; padding: 1rem; overflow-x: auto; }
|
|
1274
|
-
.badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
1275
|
-
.badge-orphan { background: #ef4444; color: white; }
|
|
1276
|
-
.badge-stale { background: #f59e0b; color: black; }
|
|
1277
|
-
table { width: 100%; border-collapse: collapse; }
|
|
1278
|
-
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
1279
|
-
th { color: #94a3b8; font-weight: 600; }
|
|
1280
|
-
.check { color: #22c55e; }
|
|
1281
|
-
.meta { color: #94a3b8; }
|
|
1282
|
-
.stale-meta { color: #f59e0b; }
|
|
1283
|
-
.stats { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
|
1284
|
-
.stat { text-align: center; }
|
|
1285
|
-
.stat-value { font-size: 2rem; font-weight: bold; color: #38bdf8; }
|
|
1286
|
-
.stat-label { color: #94a3b8; font-size: 0.85rem; }
|
|
1287
|
-
.file-list { list-style: none; }
|
|
1288
|
-
.file-list li { padding: 0.3rem 0; color: #cbd5e1; font-family: monospace; font-size: 0.9rem; }
|
|
1289
|
-
#search { width: 100%; padding: 0.75rem 1rem; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 1rem; margin-bottom: 1rem; }
|
|
1290
|
-
#search:focus { outline: none; border-color: #38bdf8; }
|
|
1291
|
-
@media print { body { background: white; color: black; } .section { border: 1px solid #ccc; } }
|
|
1292
|
-
</style>
|
|
1293
|
-
</head>
|
|
1294
|
-
<body>
|
|
1295
|
-
<h1>MD4AI \u2014 ${escapeHtml(title)}</h1>
|
|
1296
|
-
<p class="subtitle">Scanned: ${new Date(result.scannedAt).toLocaleString()} | Hash: ${result.dataHash.slice(0, 12)}</p>
|
|
1297
|
-
|
|
1298
|
-
<div class="stats">
|
|
1299
|
-
<div class="stat"><div class="stat-value">${result.graph.nodes.length}</div><div class="stat-label">Files</div></div>
|
|
1300
|
-
<div class="stat"><div class="stat-value">${result.graph.edges.length}</div><div class="stat-label">References</div></div>
|
|
1301
|
-
<div class="stat"><div class="stat-value">${result.orphans.length}</div><div class="stat-label">Orphans</div></div>
|
|
1302
|
-
<div class="stat"><div class="stat-value">${result.staleFiles.length}</div><div class="stat-label">Stale</div></div>
|
|
1303
|
-
<div class="stat"><div class="stat-value">${result.skills.length}</div><div class="stat-label">Skills</div></div>
|
|
1304
|
-
</div>
|
|
1305
|
-
|
|
1306
|
-
<input type="text" id="search" placeholder="Search files..." oninput="filterFiles(this.value)">
|
|
1307
|
-
|
|
1308
|
-
<div class="section">
|
|
1309
|
-
<h2>Dependency Graph</h2>
|
|
1310
|
-
<pre class="mermaid">${escapeHtml(result.graph.mermaid)}</pre>
|
|
1311
|
-
</div>
|
|
1312
|
-
|
|
1313
|
-
${result.orphans.length > 0 ? `
|
|
1314
|
-
<div class="section">
|
|
1315
|
-
<h2>Orphan Files <span class="badge badge-orphan">${result.orphans.length}</span></h2>
|
|
1316
|
-
<p class="meta" style="margin-bottom: 1rem;">Files not reachable from any root configuration file.</p>
|
|
1317
|
-
<ul class="file-list" id="orphan-list">${orphanItems}</ul>
|
|
1318
|
-
</div>
|
|
1319
|
-
` : ""}
|
|
1320
|
-
|
|
1321
|
-
${result.staleFiles.length > 0 ? `
|
|
1322
|
-
<div class="section">
|
|
1323
|
-
<h2>Stale Files <span class="badge badge-stale">${result.staleFiles.length}</span></h2>
|
|
1324
|
-
<p class="meta" style="margin-bottom: 1rem;">Files not modified in over 90 days.</p>
|
|
1325
|
-
<ul class="file-list" id="stale-list">${staleItems}</ul>
|
|
1326
|
-
</div>
|
|
1327
|
-
` : ""}
|
|
1328
|
-
|
|
1329
|
-
<div class="section">
|
|
1330
|
-
<h2>Skills Comparison</h2>
|
|
1331
|
-
<table>
|
|
1332
|
-
<thead><tr><th>Skill/Plugin</th><th>Entire Machine</th><th>Project Specific</th><th>Source</th></tr></thead>
|
|
1333
|
-
<tbody>${skillRows}</tbody>
|
|
1334
|
-
</table>
|
|
1335
|
-
</div>
|
|
1336
|
-
|
|
1337
|
-
<div class="section">
|
|
1338
|
-
<h2>All Files</h2>
|
|
1339
|
-
<ul class="file-list" id="all-files">${fileItems}</ul>
|
|
1340
|
-
</div>
|
|
1341
|
-
|
|
1342
|
-
<script>
|
|
1343
|
-
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
1344
|
-
|
|
1345
|
-
function filterFiles(query) {
|
|
1346
|
-
var q = query.toLowerCase();
|
|
1347
|
-
document.querySelectorAll('.file-list li').forEach(function(li) {
|
|
1348
|
-
var path = li.getAttribute('data-path') || '';
|
|
1349
|
-
li.style.display = path.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
</script>
|
|
1353
|
-
|
|
1354
|
-
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
1355
|
-
</body>
|
|
1356
|
-
</html>`;
|
|
1357
|
-
}
|
|
1358
|
-
function escapeHtml(text) {
|
|
1359
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1360
|
-
}
|
|
1118
|
+
var init_scanner = __esm({
|
|
1119
|
+
"dist/scanner/index.js"() {
|
|
1120
|
+
"use strict";
|
|
1121
|
+
init_dist();
|
|
1122
|
+
init_file_parser();
|
|
1123
|
+
init_graph_builder();
|
|
1124
|
+
init_orphan_detector();
|
|
1125
|
+
init_skills_parser();
|
|
1126
|
+
init_git_dates();
|
|
1127
|
+
init_tooling_detector();
|
|
1128
|
+
init_env_manifest_scanner();
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1361
1131
|
|
|
1362
1132
|
// dist/check-update.js
|
|
1363
1133
|
import chalk8 from "chalk";
|
|
1364
|
-
|
|
1365
|
-
async function checkForUpdate() {
|
|
1134
|
+
async function fetchLatest() {
|
|
1366
1135
|
try {
|
|
1367
1136
|
const controller = new AbortController();
|
|
1368
1137
|
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
@@ -1371,23 +1140,40 @@ async function checkForUpdate() {
|
|
|
1371
1140
|
});
|
|
1372
1141
|
clearTimeout(timeout);
|
|
1373
1142
|
if (!res.ok)
|
|
1374
|
-
return;
|
|
1143
|
+
return null;
|
|
1375
1144
|
const data = await res.json();
|
|
1376
|
-
|
|
1145
|
+
return data.version ?? null;
|
|
1146
|
+
} catch {
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function printUpdateBanner(latest) {
|
|
1151
|
+
console.log("");
|
|
1152
|
+
console.log(chalk8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1153
|
+
console.log(chalk8.yellow("\u2502") + chalk8.bold(" Update available! ") + chalk8.dim(`${CURRENT_VERSION}`) + chalk8.white(" \u2192 ") + chalk8.green.bold(`${latest}`) + " " + chalk8.yellow("\u2502"));
|
|
1154
|
+
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
1155
|
+
console.log(chalk8.yellow("\u2502") + " Run: " + chalk8.cyan("md4ai update") + " " + chalk8.yellow("\u2502"));
|
|
1156
|
+
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
1157
|
+
console.log(chalk8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1158
|
+
console.log("");
|
|
1159
|
+
}
|
|
1160
|
+
async function checkForUpdate() {
|
|
1161
|
+
const latest = await fetchLatest();
|
|
1162
|
+
if (!latest)
|
|
1163
|
+
return;
|
|
1164
|
+
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1165
|
+
printUpdateBanner(latest);
|
|
1166
|
+
} else {
|
|
1167
|
+
console.log(chalk8.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
async function autoCheckForUpdate() {
|
|
1171
|
+
try {
|
|
1172
|
+
const latest = await fetchLatest();
|
|
1377
1173
|
if (!latest)
|
|
1378
1174
|
return;
|
|
1379
1175
|
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1380
|
-
|
|
1381
|
-
console.log(chalk8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1382
|
-
console.log(chalk8.yellow("\u2502") + chalk8.bold(" Update available! ") + chalk8.dim(`${CURRENT_VERSION}`) + chalk8.white(" \u2192 ") + chalk8.green.bold(`${latest}`) + " " + chalk8.yellow("\u2502"));
|
|
1383
|
-
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
1384
|
-
console.log(chalk8.yellow("\u2502") + " Run: " + chalk8.cyan("npm install -g md4ai") + " " + chalk8.yellow("\u2502"));
|
|
1385
|
-
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
1386
|
-
console.log(chalk8.yellow("\u2502") + chalk8.dim(" Changelog: https://www.md4ai.com/how-it-works") + " " + chalk8.yellow("\u2502"));
|
|
1387
|
-
console.log(chalk8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1388
|
-
console.log("");
|
|
1389
|
-
} else {
|
|
1390
|
-
console.log(chalk8.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
1176
|
+
printUpdateBanner(latest);
|
|
1391
1177
|
}
|
|
1392
1178
|
} catch {
|
|
1393
1179
|
}
|
|
@@ -1403,6 +1189,13 @@ function isNewer(a, b) {
|
|
|
1403
1189
|
}
|
|
1404
1190
|
return false;
|
|
1405
1191
|
}
|
|
1192
|
+
var CURRENT_VERSION;
|
|
1193
|
+
var init_check_update = __esm({
|
|
1194
|
+
"dist/check-update.js"() {
|
|
1195
|
+
"use strict";
|
|
1196
|
+
CURRENT_VERSION = true ? "0.7.3" : "0.0.0-dev";
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1406
1199
|
|
|
1407
1200
|
// dist/commands/push-toolings.js
|
|
1408
1201
|
async function pushToolings(supabase, folderId, toolings) {
|
|
@@ -1422,75 +1215,35 @@ async function pushToolings(supabase, folderId, toolings) {
|
|
|
1422
1215
|
}, { onConflict: "folder_id,tool_name,detection_source" });
|
|
1423
1216
|
}
|
|
1424
1217
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
await checkForUpdate();
|
|
1429
|
-
const projectRoot = resolve3(path ?? process.cwd());
|
|
1430
|
-
if (!existsSync7(projectRoot)) {
|
|
1431
|
-
console.error(chalk9.red(`Path not found: ${projectRoot}`));
|
|
1432
|
-
process.exit(1);
|
|
1433
|
-
}
|
|
1434
|
-
console.log(chalk9.blue(`Scanning: ${projectRoot}
|
|
1435
|
-
`));
|
|
1436
|
-
const result = await scanProject(projectRoot);
|
|
1437
|
-
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
1438
|
-
console.log(` References: ${result.graph.edges.length}`);
|
|
1439
|
-
console.log(` Orphans: ${result.orphans.length}`);
|
|
1440
|
-
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
1441
|
-
console.log(` Skills: ${result.skills.length}`);
|
|
1442
|
-
console.log(` Toolings: ${result.toolings.length}`);
|
|
1443
|
-
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
1444
|
-
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
1445
|
-
const outputDir = resolve3(projectRoot, "output");
|
|
1446
|
-
if (!existsSync7(outputDir)) {
|
|
1447
|
-
await mkdir2(outputDir, { recursive: true });
|
|
1218
|
+
var init_push_toolings = __esm({
|
|
1219
|
+
"dist/commands/push-toolings.js"() {
|
|
1220
|
+
"use strict";
|
|
1448
1221
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const fullPath = resolve3(projectRoot, file.file_path);
|
|
1475
|
-
try {
|
|
1476
|
-
const { unlink } = await import("node:fs/promises");
|
|
1477
|
-
await unlink(fullPath);
|
|
1478
|
-
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
1479
|
-
console.log(chalk9.green(` Deleted: ${file.file_path}`));
|
|
1480
|
-
} catch (err) {
|
|
1481
|
-
console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
1485
|
-
if (keptIds.length > 0) {
|
|
1486
|
-
for (const id of keptIds) {
|
|
1487
|
-
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
1488
|
-
}
|
|
1489
|
-
console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
1490
|
-
}
|
|
1491
|
-
console.log("");
|
|
1492
|
-
}
|
|
1493
|
-
const { error } = await supabase.from("claude_folders").update({
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// dist/commands/sync.js
|
|
1225
|
+
var sync_exports = {};
|
|
1226
|
+
__export(sync_exports, {
|
|
1227
|
+
syncCommand: () => syncCommand
|
|
1228
|
+
});
|
|
1229
|
+
import chalk12 from "chalk";
|
|
1230
|
+
async function syncCommand(options) {
|
|
1231
|
+
const { supabase } = await getAuthenticatedClient();
|
|
1232
|
+
if (options.all) {
|
|
1233
|
+
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
|
|
1234
|
+
if (error || !devices?.length) {
|
|
1235
|
+
console.error(chalk12.red("No devices found."));
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
for (const device of devices) {
|
|
1239
|
+
console.log(chalk12.blue(`Syncing: ${device.path}`));
|
|
1240
|
+
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1241
|
+
if (proposedAll?.length) {
|
|
1242
|
+
console.log(chalk12.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1243
|
+
}
|
|
1244
|
+
try {
|
|
1245
|
+
const result = await scanProject(device.path);
|
|
1246
|
+
await supabase.from("claude_folders").update({
|
|
1494
1247
|
graph_json: result.graph,
|
|
1495
1248
|
orphans_json: result.orphans,
|
|
1496
1249
|
skills_table_json: result.skills,
|
|
@@ -1498,197 +1251,12 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1498
1251
|
env_manifest_json: result.envManifest,
|
|
1499
1252
|
last_scanned: result.scannedAt,
|
|
1500
1253
|
data_hash: result.dataHash
|
|
1501
|
-
}).eq("id", folder_id);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
await saveState({
|
|
1508
|
-
lastFolderId: folder_id,
|
|
1509
|
-
lastDeviceName: device_name,
|
|
1510
|
-
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1511
|
-
});
|
|
1512
|
-
console.log(chalk9.green("Synced to Supabase."));
|
|
1513
|
-
console.log(chalk9.cyan(`
|
|
1514
|
-
https://www.md4ai.com/project/${folder_id}
|
|
1515
|
-
`));
|
|
1516
|
-
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1517
|
-
if (configFiles.length > 0) {
|
|
1518
|
-
for (const file of configFiles) {
|
|
1519
|
-
await supabase.from("folder_files").upsert({
|
|
1520
|
-
folder_id,
|
|
1521
|
-
file_path: file.filePath,
|
|
1522
|
-
content: file.content,
|
|
1523
|
-
size_bytes: file.sizeBytes,
|
|
1524
|
-
last_modified: file.lastModified
|
|
1525
|
-
}, { onConflict: "folder_id,file_path" });
|
|
1526
|
-
}
|
|
1527
|
-
console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
} else {
|
|
1531
|
-
console.log(chalk9.yellow("No device path matches this folder. Run: md4ai add-device\n(Local preview still generated.)"));
|
|
1532
|
-
}
|
|
1533
|
-
} catch {
|
|
1534
|
-
console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
// dist/commands/simulate.js
|
|
1540
|
-
import { join as join9 } from "node:path";
|
|
1541
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
1542
|
-
import { homedir as homedir6 } from "node:os";
|
|
1543
|
-
import chalk10 from "chalk";
|
|
1544
|
-
async function simulateCommand(prompt) {
|
|
1545
|
-
const projectRoot = process.cwd();
|
|
1546
|
-
console.log(chalk10.blue(`Simulating prompt: "${prompt}"
|
|
1547
|
-
`));
|
|
1548
|
-
console.log(chalk10.dim("Files Claude would load:\n"));
|
|
1549
|
-
const globalClaude = join9(homedir6(), ".claude", "CLAUDE.md");
|
|
1550
|
-
if (existsSync8(globalClaude)) {
|
|
1551
|
-
console.log(chalk10.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
1552
|
-
}
|
|
1553
|
-
for (const rootFile of ROOT_FILES) {
|
|
1554
|
-
const fullPath = join9(projectRoot, rootFile);
|
|
1555
|
-
if (existsSync8(fullPath)) {
|
|
1556
|
-
console.log(chalk10.green(` \u2713 ${rootFile} (project root)`));
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
const settingsFiles = [
|
|
1560
|
-
join9(homedir6(), ".claude", "settings.json"),
|
|
1561
|
-
join9(homedir6(), ".claude", "settings.local.json"),
|
|
1562
|
-
join9(projectRoot, ".claude", "settings.json"),
|
|
1563
|
-
join9(projectRoot, ".claude", "settings.local.json")
|
|
1564
|
-
];
|
|
1565
|
-
for (const sf of settingsFiles) {
|
|
1566
|
-
if (existsSync8(sf)) {
|
|
1567
|
-
const display = sf.startsWith(homedir6()) ? sf.replace(homedir6(), "~") : sf.replace(projectRoot + "/", "");
|
|
1568
|
-
console.log(chalk10.green(` \u2713 ${display} (settings)`));
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
const words = prompt.split(/\s+/);
|
|
1572
|
-
for (const word of words) {
|
|
1573
|
-
const cleaned = word.replace(/['"]/g, "");
|
|
1574
|
-
const candidatePath = join9(projectRoot, cleaned);
|
|
1575
|
-
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
1576
|
-
console.log(chalk10.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
console.log(chalk10.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// dist/commands/print.js
|
|
1583
|
-
import { join as join10 } from "node:path";
|
|
1584
|
-
import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
1585
|
-
import { existsSync as existsSync9 } from "node:fs";
|
|
1586
|
-
import chalk11 from "chalk";
|
|
1587
|
-
async function printCommand(title) {
|
|
1588
|
-
const projectRoot = process.cwd();
|
|
1589
|
-
const scanDataPath = join10(projectRoot, "output", "index.html");
|
|
1590
|
-
if (!existsSync9(scanDataPath)) {
|
|
1591
|
-
console.error(chalk11.red("No scan data found. Run: md4ai scan"));
|
|
1592
|
-
process.exit(1);
|
|
1593
|
-
}
|
|
1594
|
-
const html = await readFile6(scanDataPath, "utf-8");
|
|
1595
|
-
const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
|
|
1596
|
-
if (!match) {
|
|
1597
|
-
console.error(chalk11.red("Could not extract scan data from output/index.html"));
|
|
1598
|
-
process.exit(1);
|
|
1599
|
-
}
|
|
1600
|
-
const result = JSON.parse(match[1]);
|
|
1601
|
-
const printHtml = generatePrintHtml(result, title);
|
|
1602
|
-
const outputPath = join10(projectRoot, "output", `print-${Date.now()}.html`);
|
|
1603
|
-
await writeFile3(outputPath, printHtml, "utf-8");
|
|
1604
|
-
console.log(chalk11.green(`Print-ready wall sheet: ${outputPath}`));
|
|
1605
|
-
}
|
|
1606
|
-
function escapeHtml2(text) {
|
|
1607
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1608
|
-
}
|
|
1609
|
-
function generatePrintHtml(result, title) {
|
|
1610
|
-
const skillRows = result.skills.map((s) => `<tr><td>${escapeHtml2(s.name)}</td><td>${s.machineWide ? '<span class="check">\u2713</span>' : ""}</td><td>${s.projectSpecific ? '<span class="check">\u2713</span>' : ""}</td></tr>`).join("\n");
|
|
1611
|
-
const orphanItems = result.orphans.map((o) => `<li class="orphan">${escapeHtml2(o.path)}</li>`).join("\n");
|
|
1612
|
-
const staleItems = result.staleFiles.map((s) => `<li class="stale">${escapeHtml2(s.path)} (${s.daysSinceModified}d)</li>`).join("\n");
|
|
1613
|
-
return `<!DOCTYPE html>
|
|
1614
|
-
<html lang="en">
|
|
1615
|
-
<head>
|
|
1616
|
-
<meta charset="UTF-8">
|
|
1617
|
-
<title>${escapeHtml2(title)} \u2014 MD4AI Wall Sheet</title>
|
|
1618
|
-
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
1619
|
-
<style>
|
|
1620
|
-
@page { size: A3 landscape; margin: 1cm; }
|
|
1621
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 1rem; }
|
|
1622
|
-
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
1623
|
-
.subtitle { color: #666; margin-bottom: 2rem; }
|
|
1624
|
-
.mermaid { margin: 2rem 0; }
|
|
1625
|
-
.columns { display: flex; gap: 2rem; }
|
|
1626
|
-
.column { flex: 1; }
|
|
1627
|
-
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
|
1628
|
-
th, td { padding: 0.3rem 0.5rem; text-align: left; border-bottom: 1px solid #ddd; }
|
|
1629
|
-
th { background: #f5f5f5; }
|
|
1630
|
-
.orphan { color: #dc2626; }
|
|
1631
|
-
.stale { color: #d97706; }
|
|
1632
|
-
.check { color: #16a34a; font-weight: bold; }
|
|
1633
|
-
</style>
|
|
1634
|
-
</head>
|
|
1635
|
-
<body>
|
|
1636
|
-
<h1>${escapeHtml2(title)}</h1>
|
|
1637
|
-
<p class="subtitle">Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()} | Files: ${result.graph.nodes.length} | Orphans: ${result.orphans.length}</p>
|
|
1638
|
-
|
|
1639
|
-
<pre class="mermaid">${escapeHtml2(result.graph.mermaid)}</pre>
|
|
1640
|
-
|
|
1641
|
-
<div class="columns">
|
|
1642
|
-
<div class="column">
|
|
1643
|
-
<h2>Skills Comparison</h2>
|
|
1644
|
-
<table>
|
|
1645
|
-
<tr><th>Skill</th><th>Machine</th><th>Project</th></tr>
|
|
1646
|
-
${skillRows}
|
|
1647
|
-
</table>
|
|
1648
|
-
</div>
|
|
1649
|
-
<div class="column">
|
|
1650
|
-
${result.orphans.length ? `<h2>Orphans</h2><ul>${orphanItems}</ul>` : ""}
|
|
1651
|
-
${result.staleFiles.length ? `<h2>Stale Files</h2><ul>${staleItems}</ul>` : ""}
|
|
1652
|
-
</div>
|
|
1653
|
-
</div>
|
|
1654
|
-
|
|
1655
|
-
<script>mermaid.initialize({ startOnLoad: true, theme: 'default' });</script>
|
|
1656
|
-
</body>
|
|
1657
|
-
</html>`;
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// dist/commands/sync.js
|
|
1661
|
-
import chalk12 from "chalk";
|
|
1662
|
-
async function syncCommand(options) {
|
|
1663
|
-
const { supabase } = await getAuthenticatedClient();
|
|
1664
|
-
if (options.all) {
|
|
1665
|
-
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
|
|
1666
|
-
if (error || !devices?.length) {
|
|
1667
|
-
console.error(chalk12.red("No devices found."));
|
|
1668
|
-
process.exit(1);
|
|
1669
|
-
}
|
|
1670
|
-
for (const device of devices) {
|
|
1671
|
-
console.log(chalk12.blue(`Syncing: ${device.path}`));
|
|
1672
|
-
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1673
|
-
if (proposedAll?.length) {
|
|
1674
|
-
console.log(chalk12.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1675
|
-
}
|
|
1676
|
-
try {
|
|
1677
|
-
const result = await scanProject(device.path);
|
|
1678
|
-
await supabase.from("claude_folders").update({
|
|
1679
|
-
graph_json: result.graph,
|
|
1680
|
-
orphans_json: result.orphans,
|
|
1681
|
-
skills_table_json: result.skills,
|
|
1682
|
-
stale_files_json: result.staleFiles,
|
|
1683
|
-
env_manifest_json: result.envManifest,
|
|
1684
|
-
last_scanned: result.scannedAt,
|
|
1685
|
-
data_hash: result.dataHash
|
|
1686
|
-
}).eq("id", device.folder_id);
|
|
1687
|
-
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1688
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
1689
|
-
console.log(chalk12.green(` Done: ${device.device_name}`));
|
|
1690
|
-
} catch (err) {
|
|
1691
|
-
console.error(chalk12.red(` Failed: ${device.path}: ${err}`));
|
|
1254
|
+
}).eq("id", device.folder_id);
|
|
1255
|
+
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1256
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
1257
|
+
console.log(chalk12.green(` Done: ${device.device_name}`));
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
console.error(chalk12.red(` Failed: ${device.path}: ${err}`));
|
|
1692
1260
|
}
|
|
1693
1261
|
}
|
|
1694
1262
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -1724,10 +1292,15 @@ async function syncCommand(options) {
|
|
|
1724
1292
|
console.log(chalk12.green("Synced."));
|
|
1725
1293
|
}
|
|
1726
1294
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1295
|
+
var init_sync = __esm({
|
|
1296
|
+
"dist/commands/sync.js"() {
|
|
1297
|
+
"use strict";
|
|
1298
|
+
init_auth();
|
|
1299
|
+
init_config();
|
|
1300
|
+
init_scanner();
|
|
1301
|
+
init_push_toolings();
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1731
1304
|
|
|
1732
1305
|
// dist/device-utils.js
|
|
1733
1306
|
import { hostname as hostname2, platform as platform2 } from "node:os";
|
|
@@ -1761,890 +1334,1519 @@ async function resolveDeviceId(supabase, userId) {
|
|
|
1761
1334
|
}
|
|
1762
1335
|
return data.id;
|
|
1763
1336
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
1768
|
-
const cwd = resolve4(process.cwd());
|
|
1769
|
-
const deviceName = detectDeviceName();
|
|
1770
|
-
const osType = detectOs2();
|
|
1771
|
-
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
1772
|
-
if (folderErr || !folder) {
|
|
1773
|
-
console.error(chalk13.red("Project not found, or you do not have access."));
|
|
1774
|
-
console.error(chalk13.yellow("Check the project ID in the MD4AI web dashboard."));
|
|
1775
|
-
process.exit(1);
|
|
1337
|
+
var init_device_utils = __esm({
|
|
1338
|
+
"dist/device-utils.js"() {
|
|
1339
|
+
"use strict";
|
|
1776
1340
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const { error: pathErr } = await supabase.from("device_paths").insert({
|
|
1794
|
-
user_id: userId,
|
|
1795
|
-
folder_id: folder.id,
|
|
1796
|
-
device_name: deviceName,
|
|
1797
|
-
os_type: osType,
|
|
1798
|
-
path: cwd
|
|
1799
|
-
});
|
|
1800
|
-
if (pathErr) {
|
|
1801
|
-
console.error(chalk13.red(`Failed to link: ${pathErr.message}`));
|
|
1802
|
-
process.exit(1);
|
|
1803
|
-
}
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
// dist/mcp/read-configs.js
|
|
1344
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
1345
|
+
import { join as join12 } from "node:path";
|
|
1346
|
+
import { homedir as homedir7 } from "node:os";
|
|
1347
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
1348
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
1349
|
+
async function readJsonSafe(path) {
|
|
1350
|
+
try {
|
|
1351
|
+
if (!existsSync11(path))
|
|
1352
|
+
return null;
|
|
1353
|
+
const raw = await readFile8(path, "utf-8");
|
|
1354
|
+
return JSON.parse(raw);
|
|
1355
|
+
} catch {
|
|
1356
|
+
return null;
|
|
1804
1357
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
console.log(` Toolings: ${result.toolings.length}`);
|
|
1813
|
-
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
1814
|
-
const { error: scanErr } = await supabase.from("claude_folders").update({
|
|
1815
|
-
graph_json: result.graph,
|
|
1816
|
-
orphans_json: result.orphans,
|
|
1817
|
-
skills_table_json: result.skills,
|
|
1818
|
-
stale_files_json: result.staleFiles,
|
|
1819
|
-
env_manifest_json: result.envManifest,
|
|
1820
|
-
last_scanned: result.scannedAt,
|
|
1821
|
-
data_hash: result.dataHash
|
|
1822
|
-
}).eq("id", folder.id);
|
|
1823
|
-
if (scanErr) {
|
|
1824
|
-
console.error(chalk13.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
1358
|
+
}
|
|
1359
|
+
function extractPackageName(args) {
|
|
1360
|
+
for (const arg of args) {
|
|
1361
|
+
if (arg.startsWith("-"))
|
|
1362
|
+
continue;
|
|
1363
|
+
if (arg.includes("mcp") || arg.startsWith("@"))
|
|
1364
|
+
return arg;
|
|
1825
1365
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
for (const file of configFiles) {
|
|
1830
|
-
await supabase.from("folder_files").upsert({
|
|
1831
|
-
folder_id: folder.id,
|
|
1832
|
-
file_path: file.filePath,
|
|
1833
|
-
content: file.content,
|
|
1834
|
-
size_bytes: file.sizeBytes,
|
|
1835
|
-
last_modified: file.lastModified
|
|
1836
|
-
}, { onConflict: "folder_id,file_path" });
|
|
1837
|
-
}
|
|
1838
|
-
console.log(chalk13.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1366
|
+
for (const arg of args) {
|
|
1367
|
+
if (!arg.startsWith("-"))
|
|
1368
|
+
return arg;
|
|
1839
1369
|
}
|
|
1840
|
-
|
|
1841
|
-
await saveState({
|
|
1842
|
-
lastFolderId: folder.id,
|
|
1843
|
-
lastDeviceName: deviceName,
|
|
1844
|
-
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1845
|
-
});
|
|
1846
|
-
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
1847
|
-
console.log(chalk13.green("\nDone! Project linked and scanned."));
|
|
1848
|
-
console.log(chalk13.cyan(`
|
|
1849
|
-
${projectUrl}
|
|
1850
|
-
`));
|
|
1851
|
-
console.log(chalk13.grey('Run "md4ai scan" to rescan at any time.'));
|
|
1370
|
+
return null;
|
|
1852
1371
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1372
|
+
function parseServers(data, source) {
|
|
1373
|
+
if (!data?.mcpServers)
|
|
1374
|
+
return [];
|
|
1375
|
+
const entries = [];
|
|
1376
|
+
for (const [name, server] of Object.entries(data.mcpServers)) {
|
|
1377
|
+
const serverType = server.type === "http" ? "http" : "stdio";
|
|
1378
|
+
entries.push({
|
|
1379
|
+
name,
|
|
1380
|
+
source,
|
|
1381
|
+
type: serverType,
|
|
1382
|
+
command: server.command,
|
|
1383
|
+
args: server.args,
|
|
1384
|
+
env: server.env,
|
|
1385
|
+
url: server.url
|
|
1386
|
+
});
|
|
1864
1387
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1388
|
+
return entries;
|
|
1389
|
+
}
|
|
1390
|
+
function parseFlatConfig(data, source) {
|
|
1391
|
+
if (!data)
|
|
1392
|
+
return [];
|
|
1393
|
+
const entries = [];
|
|
1394
|
+
for (const [name, server] of Object.entries(data)) {
|
|
1395
|
+
if (typeof server !== "object" || server === null)
|
|
1396
|
+
continue;
|
|
1397
|
+
if (!server.command && !server.url)
|
|
1398
|
+
continue;
|
|
1399
|
+
const serverType = server.type === "http" ? "http" : "stdio";
|
|
1400
|
+
entries.push({
|
|
1401
|
+
name,
|
|
1402
|
+
source,
|
|
1403
|
+
type: serverType,
|
|
1404
|
+
command: server.command,
|
|
1405
|
+
args: server.args,
|
|
1406
|
+
env: server.env,
|
|
1407
|
+
url: server.url
|
|
1408
|
+
});
|
|
1872
1409
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1410
|
+
return entries;
|
|
1411
|
+
}
|
|
1412
|
+
async function readAllMcpConfigs() {
|
|
1413
|
+
const home = homedir7();
|
|
1414
|
+
const entries = [];
|
|
1415
|
+
const userConfig = await readJsonSafe(join12(home, ".claude.json"));
|
|
1416
|
+
entries.push(...parseServers(userConfig, "global"));
|
|
1417
|
+
const globalMcp = await readJsonSafe(join12(home, ".claude", "mcp.json"));
|
|
1418
|
+
entries.push(...parseServers(globalMcp, "global"));
|
|
1419
|
+
const cwdMcp = await readJsonSafe(join12(process.cwd(), ".mcp.json"));
|
|
1420
|
+
entries.push(...parseServers(cwdMcp, "project"));
|
|
1421
|
+
const pluginsBase = join12(home, ".claude", "plugins", "marketplaces");
|
|
1422
|
+
if (existsSync11(pluginsBase)) {
|
|
1423
|
+
try {
|
|
1424
|
+
const marketplaces = await readdir3(pluginsBase, { withFileTypes: true });
|
|
1425
|
+
for (const mp of marketplaces) {
|
|
1426
|
+
if (!mp.isDirectory())
|
|
1427
|
+
continue;
|
|
1428
|
+
const extDir = join12(pluginsBase, mp.name, "external_plugins");
|
|
1429
|
+
if (!existsSync11(extDir))
|
|
1430
|
+
continue;
|
|
1431
|
+
const plugins = await readdir3(extDir, { withFileTypes: true });
|
|
1432
|
+
for (const plugin of plugins) {
|
|
1433
|
+
if (!plugin.isDirectory())
|
|
1434
|
+
continue;
|
|
1435
|
+
const pluginMcp = await readJsonSafe(join12(extDir, plugin.name, ".mcp.json"));
|
|
1436
|
+
entries.push(...parseServers(pluginMcp, "plugin"));
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} catch {
|
|
1885
1440
|
}
|
|
1886
1441
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1442
|
+
const cacheBase = join12(home, ".claude", "plugins", "cache");
|
|
1443
|
+
if (existsSync11(cacheBase)) {
|
|
1444
|
+
try {
|
|
1445
|
+
const registries = await readdir3(cacheBase, { withFileTypes: true });
|
|
1446
|
+
for (const reg of registries) {
|
|
1447
|
+
if (!reg.isDirectory())
|
|
1448
|
+
continue;
|
|
1449
|
+
const regDir = join12(cacheBase, reg.name);
|
|
1450
|
+
const plugins = await readdir3(regDir, { withFileTypes: true });
|
|
1451
|
+
for (const plugin of plugins) {
|
|
1452
|
+
if (!plugin.isDirectory())
|
|
1453
|
+
continue;
|
|
1454
|
+
const versionDirs = await readdir3(join12(regDir, plugin.name), { withFileTypes: true });
|
|
1455
|
+
for (const ver of versionDirs) {
|
|
1456
|
+
if (!ver.isDirectory())
|
|
1457
|
+
continue;
|
|
1458
|
+
const mcpPath = join12(regDir, plugin.name, ver.name, ".mcp.json");
|
|
1459
|
+
const flatData = await readJsonSafe(mcpPath);
|
|
1460
|
+
if (flatData) {
|
|
1461
|
+
entries.push(...parseFlatConfig(flatData, "plugin"));
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
} catch {
|
|
1468
|
+
}
|
|
1890
1469
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
for (const
|
|
1894
|
-
|
|
1470
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1471
|
+
const deduped = [];
|
|
1472
|
+
for (const entry of entries) {
|
|
1473
|
+
if (seen.has(entry.name))
|
|
1474
|
+
continue;
|
|
1475
|
+
seen.add(entry.name);
|
|
1476
|
+
deduped.push(entry);
|
|
1895
1477
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
1902
|
-
});
|
|
1903
|
-
if (!proceed) {
|
|
1904
|
-
console.log(chalk14.yellow("Cancelled."));
|
|
1905
|
-
return;
|
|
1478
|
+
return deduped;
|
|
1479
|
+
}
|
|
1480
|
+
var init_read_configs = __esm({
|
|
1481
|
+
"dist/mcp/read-configs.js"() {
|
|
1482
|
+
"use strict";
|
|
1906
1483
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
// dist/mcp/scan-processes.js
|
|
1487
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1488
|
+
function parsePsOutput(output) {
|
|
1489
|
+
const lines = output.trim().split("\n").slice(1);
|
|
1490
|
+
const entries = [];
|
|
1491
|
+
for (const line of lines) {
|
|
1492
|
+
const trimmed = line.trim();
|
|
1493
|
+
const match = trimmed.match(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(.+)$/);
|
|
1494
|
+
if (!match)
|
|
1495
|
+
continue;
|
|
1496
|
+
entries.push({
|
|
1497
|
+
pid: parseInt(match[1], 10),
|
|
1498
|
+
tty: match[2],
|
|
1499
|
+
etimeRaw: match[3],
|
|
1500
|
+
rss: parseInt(match[4], 10),
|
|
1501
|
+
args: match[5]
|
|
1502
|
+
});
|
|
1915
1503
|
}
|
|
1916
|
-
|
|
1917
|
-
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
1504
|
+
return entries;
|
|
1918
1505
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
console.error(chalk15.red("--name is required."));
|
|
1927
|
-
process.exit(1);
|
|
1506
|
+
function parseEtime(etime) {
|
|
1507
|
+
let days = 0;
|
|
1508
|
+
let rest = etime;
|
|
1509
|
+
if (rest.includes("-")) {
|
|
1510
|
+
const [d, r] = rest.split("-");
|
|
1511
|
+
days = parseInt(d, 10);
|
|
1512
|
+
rest = r;
|
|
1928
1513
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1514
|
+
const parts = rest.split(":").map(Number);
|
|
1515
|
+
if (parts.length === 3) {
|
|
1516
|
+
return days * 86400 + parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
1517
|
+
} else if (parts.length === 2) {
|
|
1518
|
+
return days * 86400 + parts[0] * 60 + parts[1];
|
|
1932
1519
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
if (
|
|
1943
|
-
|
|
1944
|
-
if (
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if (options.notes)
|
|
1949
|
-
updates.notes = options.notes;
|
|
1950
|
-
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
1951
|
-
if (error) {
|
|
1952
|
-
console.error(chalk15.red(`Failed to update: ${error.message}`));
|
|
1953
|
-
process.exit(1);
|
|
1954
|
-
}
|
|
1955
|
-
console.log(chalk15.green(`Updated "${existing.display_name}" in the registry.`));
|
|
1956
|
-
} else {
|
|
1957
|
-
if (!options.display || !options.category) {
|
|
1958
|
-
console.error(chalk15.red("New tools require --display and --category."));
|
|
1959
|
-
process.exit(1);
|
|
1520
|
+
return days * 86400 + (parts[0] ?? 0);
|
|
1521
|
+
}
|
|
1522
|
+
function findProcessesForConfig(config, processes) {
|
|
1523
|
+
if (config.type === "http")
|
|
1524
|
+
return [];
|
|
1525
|
+
const packageName = config.args ? extractPackageName(config.args) : null;
|
|
1526
|
+
const matches = [];
|
|
1527
|
+
for (const proc of processes) {
|
|
1528
|
+
let matched = false;
|
|
1529
|
+
if (packageName && proc.args.includes(packageName)) {
|
|
1530
|
+
matched = true;
|
|
1531
|
+
} else if (config.command === "node" && config.args?.[0]) {
|
|
1532
|
+
if (proc.args.includes(config.args[0])) {
|
|
1533
|
+
matched = true;
|
|
1534
|
+
}
|
|
1960
1535
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
notes: options.notes ?? null
|
|
1970
|
-
});
|
|
1971
|
-
if (error) {
|
|
1972
|
-
console.error(chalk15.red(`Failed to create: ${error.message}`));
|
|
1973
|
-
process.exit(1);
|
|
1536
|
+
if (matched) {
|
|
1537
|
+
matches.push({
|
|
1538
|
+
pid: proc.pid,
|
|
1539
|
+
tty: proc.tty === "?" ? "" : proc.tty,
|
|
1540
|
+
uptimeSeconds: parseEtime(proc.etimeRaw),
|
|
1541
|
+
memoryMb: Math.round(proc.rss / 1024),
|
|
1542
|
+
commandLine: proc.args
|
|
1543
|
+
});
|
|
1974
1544
|
}
|
|
1975
|
-
console.log(chalk15.green(`Added "${options.display}" to the registry.`));
|
|
1976
1545
|
}
|
|
1546
|
+
return matches;
|
|
1977
1547
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1548
|
+
function getProcessTable() {
|
|
1549
|
+
try {
|
|
1550
|
+
const output = execFileSync4("ps", ["-eo", "pid,tty,etime,rss,args"], {
|
|
1551
|
+
encoding: "utf-8",
|
|
1552
|
+
timeout: 5e3
|
|
1553
|
+
});
|
|
1554
|
+
return parsePsOutput(output);
|
|
1555
|
+
} catch {
|
|
1556
|
+
return [];
|
|
1987
1557
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1558
|
+
}
|
|
1559
|
+
var init_scan_processes = __esm({
|
|
1560
|
+
"dist/mcp/scan-processes.js"() {
|
|
1561
|
+
"use strict";
|
|
1562
|
+
init_read_configs();
|
|
1991
1563
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
(tool.latest_beta ?? "\u2014").padEnd(betaW),
|
|
2012
|
-
lastChecked
|
|
2013
|
-
].join(" ");
|
|
2014
|
-
console.log(row);
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
// dist/commands/mcp-watch.js
|
|
1567
|
+
var mcp_watch_exports = {};
|
|
1568
|
+
__export(mcp_watch_exports, {
|
|
1569
|
+
mcpWatchCommand: () => mcpWatchCommand
|
|
1570
|
+
});
|
|
1571
|
+
import chalk18 from "chalk";
|
|
1572
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1573
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1574
|
+
function detectTty() {
|
|
1575
|
+
try {
|
|
1576
|
+
const result = execFileSync5("ps", ["-o", "tty=", "-p", String(process.pid)], {
|
|
1577
|
+
encoding: "utf-8",
|
|
1578
|
+
timeout: 3e3
|
|
1579
|
+
}).trim();
|
|
1580
|
+
return result && result !== "?" ? result : null;
|
|
1581
|
+
} catch {
|
|
1582
|
+
return null;
|
|
2015
1583
|
}
|
|
2016
|
-
console.log(chalk16.grey(`
|
|
2017
|
-
${tools.length} tool(s) in registry.`));
|
|
2018
1584
|
}
|
|
2019
|
-
function
|
|
2020
|
-
const
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
return "just now";
|
|
2024
|
-
if (hours < 24)
|
|
2025
|
-
return `${hours}h ago`;
|
|
2026
|
-
const days = Math.floor(hours / 24);
|
|
2027
|
-
if (days < 7)
|
|
2028
|
-
return `${days}d ago`;
|
|
2029
|
-
return date.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
|
|
1585
|
+
function checkEnvVars(config) {
|
|
1586
|
+
const required = config.env ? Object.keys(config.env) : [];
|
|
1587
|
+
const missing = required.filter((key) => !process.env[key]);
|
|
1588
|
+
return { required, missing };
|
|
2030
1589
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
};
|
|
2056
|
-
|
|
2057
|
-
// dist/commands/admin-fetch-versions.js
|
|
2058
|
-
async function adminFetchVersionsCommand() {
|
|
2059
|
-
const { supabase } = await getAuthenticatedClient();
|
|
2060
|
-
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
2061
|
-
if (error) {
|
|
2062
|
-
console.error(chalk17.red(`Failed to fetch tools: ${error.message}`));
|
|
2063
|
-
process.exit(1);
|
|
2064
|
-
}
|
|
2065
|
-
if (!tools?.length) {
|
|
2066
|
-
console.log(chalk17.yellow("No tools in the registry."));
|
|
2067
|
-
}
|
|
2068
|
-
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
2069
|
-
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
2070
|
-
const unregisteredNames = /* @__PURE__ */ new Set();
|
|
2071
|
-
for (const pt of allProjectToolings ?? []) {
|
|
2072
|
-
if (!registeredNames.has(pt.tool_name) && pt.detection_source === "package.json") {
|
|
2073
|
-
unregisteredNames.add(pt.tool_name);
|
|
1590
|
+
function buildRows(configs) {
|
|
1591
|
+
const processes = getProcessTable();
|
|
1592
|
+
const rows = [];
|
|
1593
|
+
for (const config of configs) {
|
|
1594
|
+
const { required, missing } = checkEnvVars(config);
|
|
1595
|
+
const packageName = config.args ? extractPackageName(config.args) : null;
|
|
1596
|
+
if (config.type === "http") {
|
|
1597
|
+
rows.push({
|
|
1598
|
+
server_name: config.name,
|
|
1599
|
+
config_source: config.source,
|
|
1600
|
+
server_type: "http",
|
|
1601
|
+
command: null,
|
|
1602
|
+
package_name: null,
|
|
1603
|
+
http_url: config.url ?? null,
|
|
1604
|
+
status: "stopped",
|
|
1605
|
+
pid: null,
|
|
1606
|
+
session_tty: null,
|
|
1607
|
+
uptime_seconds: null,
|
|
1608
|
+
memory_mb: null,
|
|
1609
|
+
env_vars_required: required.length ? required : null,
|
|
1610
|
+
env_vars_missing: missing.length ? missing : null,
|
|
1611
|
+
error_detail: "HTTP server \u2014 cannot verify from CLI"
|
|
1612
|
+
});
|
|
1613
|
+
continue;
|
|
2074
1614
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
1615
|
+
if (missing.length > 0) {
|
|
1616
|
+
rows.push({
|
|
1617
|
+
server_name: config.name,
|
|
1618
|
+
config_source: config.source,
|
|
1619
|
+
server_type: "stdio",
|
|
1620
|
+
command: config.command ?? null,
|
|
1621
|
+
package_name: packageName,
|
|
1622
|
+
http_url: null,
|
|
1623
|
+
status: "error",
|
|
1624
|
+
pid: null,
|
|
1625
|
+
session_tty: null,
|
|
1626
|
+
uptime_seconds: null,
|
|
1627
|
+
memory_mb: null,
|
|
1628
|
+
env_vars_required: required,
|
|
1629
|
+
env_vars_missing: missing,
|
|
1630
|
+
error_detail: `Missing env: ${missing.join(", ")}`
|
|
1631
|
+
});
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
const matches = findProcessesForConfig(config, processes);
|
|
1635
|
+
if (matches.length === 0) {
|
|
1636
|
+
rows.push({
|
|
1637
|
+
server_name: config.name,
|
|
1638
|
+
config_source: config.source,
|
|
1639
|
+
server_type: "stdio",
|
|
1640
|
+
command: config.command ?? null,
|
|
1641
|
+
package_name: packageName,
|
|
1642
|
+
http_url: null,
|
|
1643
|
+
status: "stopped",
|
|
1644
|
+
pid: null,
|
|
1645
|
+
session_tty: null,
|
|
1646
|
+
uptime_seconds: null,
|
|
1647
|
+
memory_mb: null,
|
|
1648
|
+
env_vars_required: required.length ? required : null,
|
|
1649
|
+
env_vars_missing: null,
|
|
1650
|
+
error_detail: null
|
|
1651
|
+
});
|
|
1652
|
+
} else {
|
|
1653
|
+
for (const match of matches) {
|
|
1654
|
+
rows.push({
|
|
1655
|
+
server_name: config.name,
|
|
1656
|
+
config_source: config.source,
|
|
1657
|
+
server_type: "stdio",
|
|
1658
|
+
command: config.command ?? null,
|
|
1659
|
+
package_name: packageName,
|
|
1660
|
+
http_url: null,
|
|
1661
|
+
status: "running",
|
|
1662
|
+
pid: match.pid,
|
|
1663
|
+
session_tty: match.tty || null,
|
|
1664
|
+
uptime_seconds: match.uptimeSeconds,
|
|
1665
|
+
memory_mb: match.memoryMb,
|
|
1666
|
+
env_vars_required: required.length ? required : null,
|
|
1667
|
+
env_vars_missing: null,
|
|
1668
|
+
error_detail: null
|
|
1669
|
+
});
|
|
2091
1670
|
}
|
|
2092
1671
|
}
|
|
2093
1672
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
console.log(
|
|
1673
|
+
return rows;
|
|
1674
|
+
}
|
|
1675
|
+
function printTable(rows, deviceName) {
|
|
1676
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
1677
|
+
console.log(chalk18.bold.cyan(`
|
|
1678
|
+
MCP Monitor \u2014 ${deviceName}`));
|
|
1679
|
+
console.log(chalk18.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
|
|
2099
1680
|
`));
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
1681
|
+
const running = rows.filter((r) => r.status === "running");
|
|
1682
|
+
const stopped = rows.filter((r) => r.status === "stopped");
|
|
1683
|
+
const errored = rows.filter((r) => r.status === "error");
|
|
1684
|
+
const byTty = /* @__PURE__ */ new Map();
|
|
1685
|
+
for (const r of running) {
|
|
1686
|
+
const key = r.session_tty ?? "unknown";
|
|
1687
|
+
const list = byTty.get(key) ?? [];
|
|
1688
|
+
list.push(r);
|
|
1689
|
+
byTty.set(key, list);
|
|
1690
|
+
}
|
|
1691
|
+
if (byTty.size > 0) {
|
|
1692
|
+
for (const [tty, servers] of byTty) {
|
|
1693
|
+
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
1694
|
+
console.log(chalk18.green(` Session ${tty}`) + chalk18.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB`));
|
|
1695
|
+
for (const s of servers) {
|
|
1696
|
+
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
1697
|
+
console.log(` ${chalk18.green("\u25CF")} ${s.server_name.padEnd(20)} ${chalk18.dim((s.package_name ?? "").padEnd(30))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
|
|
2116
1698
|
}
|
|
2117
|
-
|
|
2118
|
-
displayName: tool.display_name,
|
|
2119
|
-
stable,
|
|
2120
|
-
beta,
|
|
2121
|
-
status: stableChanged || betaChanged ? "updated" : "unchanged"
|
|
2122
|
-
};
|
|
2123
|
-
} catch {
|
|
2124
|
-
return { displayName: tool.display_name, stable: null, beta: null, status: "failed" };
|
|
1699
|
+
console.log("");
|
|
2125
1700
|
}
|
|
2126
|
-
}));
|
|
2127
|
-
const nameW = Math.max(16, ...results.map((r) => r.displayName.length));
|
|
2128
|
-
const stableW = Math.max(8, ...results.map((r) => (r.stable ?? "\u2014").length));
|
|
2129
|
-
const betaW = Math.max(8, ...results.map((r) => (r.beta ?? "\u2014").length));
|
|
2130
|
-
const statusW = 10;
|
|
2131
|
-
const header = [
|
|
2132
|
-
"Tool".padEnd(nameW),
|
|
2133
|
-
"Stable".padEnd(stableW),
|
|
2134
|
-
"Beta".padEnd(betaW),
|
|
2135
|
-
"Status".padEnd(statusW)
|
|
2136
|
-
].join(" ");
|
|
2137
|
-
console.log(chalk17.bold(header));
|
|
2138
|
-
console.log("\u2500".repeat(header.length));
|
|
2139
|
-
for (const result of results) {
|
|
2140
|
-
const statusColour = result.status === "updated" ? chalk17.green : result.status === "unchanged" ? chalk17.grey : result.status === "failed" ? chalk17.red : chalk17.yellow;
|
|
2141
|
-
const row = [
|
|
2142
|
-
result.displayName.padEnd(nameW),
|
|
2143
|
-
(result.stable ?? "\u2014").padEnd(stableW),
|
|
2144
|
-
(result.beta ?? "\u2014").padEnd(betaW),
|
|
2145
|
-
statusColour(result.status.padEnd(statusW))
|
|
2146
|
-
].join(" ");
|
|
2147
|
-
console.log(row);
|
|
2148
1701
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
}
|
|
2156
|
-
async function fetchVersions(source) {
|
|
2157
|
-
const controller = new AbortController();
|
|
2158
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
2159
|
-
try {
|
|
2160
|
-
if (source.type === "npm") {
|
|
2161
|
-
return await fetchNpmVersions(source.package, controller.signal);
|
|
2162
|
-
} else {
|
|
2163
|
-
return await fetchGitHubVersions(source.repo, controller.signal);
|
|
1702
|
+
if (stopped.length > 0 || errored.length > 0) {
|
|
1703
|
+
const notRunning = [...stopped, ...errored];
|
|
1704
|
+
console.log(chalk18.yellow(` Not Running (${notRunning.length})`));
|
|
1705
|
+
for (const s of notRunning) {
|
|
1706
|
+
const icon = s.status === "error" ? chalk18.red("\u2717") : chalk18.yellow("\u25CB");
|
|
1707
|
+
const detail = s.error_detail ? chalk18.dim(` \u2014 ${s.error_detail}`) : "";
|
|
1708
|
+
console.log(` ${icon} ${s.server_name.padEnd(20)} ${chalk18.dim(s.config_source.padEnd(10))}${detail}`);
|
|
2164
1709
|
}
|
|
2165
|
-
|
|
2166
|
-
clearTimeout(timeout);
|
|
1710
|
+
console.log("");
|
|
2167
1711
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
const res = await fetch(url, { signal });
|
|
2172
|
-
if (!res.ok) {
|
|
2173
|
-
throw new Error(`npm registry returned ${res.status}`);
|
|
1712
|
+
if (rows.length === 0) {
|
|
1713
|
+
console.log(chalk18.yellow(" No MCP servers configured."));
|
|
1714
|
+
console.log(chalk18.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
|
|
2174
1715
|
}
|
|
2175
|
-
const tags = await res.json();
|
|
2176
|
-
const stable = tags.latest ?? null;
|
|
2177
|
-
const beta = tags.next ?? tags.beta ?? tags.rc ?? null;
|
|
2178
|
-
return { stable, beta };
|
|
2179
1716
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
1717
|
+
function formatUptime(seconds) {
|
|
1718
|
+
if (seconds < 60)
|
|
1719
|
+
return `${seconds}s`;
|
|
1720
|
+
if (seconds < 3600)
|
|
1721
|
+
return `${Math.floor(seconds / 60)}m`;
|
|
1722
|
+
const h = Math.floor(seconds / 3600);
|
|
1723
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
1724
|
+
return `${h}h ${m}m`;
|
|
1725
|
+
}
|
|
1726
|
+
async function mcpWatchCommand() {
|
|
1727
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
1728
|
+
const deviceId = await resolveDeviceId(supabase, userId);
|
|
1729
|
+
const deviceName = detectDeviceName();
|
|
1730
|
+
const myPid = process.pid;
|
|
1731
|
+
const myTty = detectTty();
|
|
1732
|
+
console.log(chalk18.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
1733
|
+
await supabase.from("mcp_watchers").upsert({
|
|
1734
|
+
device_id: deviceId,
|
|
1735
|
+
pid: myPid,
|
|
1736
|
+
tty: myTty,
|
|
1737
|
+
cli_version: CURRENT_VERSION,
|
|
1738
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1739
|
+
last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
|
|
1740
|
+
}, { onConflict: "device_id,pid" });
|
|
1741
|
+
const staleThreshold = new Date(Date.now() - 12e4).toISOString();
|
|
1742
|
+
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).lt("last_heartbeat", staleThreshold);
|
|
1743
|
+
async function cycle() {
|
|
1744
|
+
const configs = await readAllMcpConfigs();
|
|
1745
|
+
const rows = buildRows(configs);
|
|
1746
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1747
|
+
await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
|
|
1748
|
+
if (rows.length > 0) {
|
|
1749
|
+
await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
1750
|
+
device_id: deviceId,
|
|
1751
|
+
...row,
|
|
1752
|
+
checked_at: now
|
|
1753
|
+
})));
|
|
2201
1754
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
1755
|
+
await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
1756
|
+
printTable(rows, deviceName);
|
|
2204
1757
|
}
|
|
2205
|
-
|
|
1758
|
+
await cycle();
|
|
1759
|
+
let lastEnvHash = "";
|
|
1760
|
+
async function envCycle() {
|
|
1761
|
+
const state = await loadState();
|
|
1762
|
+
if (!state.lastFolderId)
|
|
1763
|
+
return;
|
|
1764
|
+
const { data: dp } = await supabase.from("device_paths").select("path").eq("folder_id", state.lastFolderId).eq("device_name", deviceName).maybeSingle();
|
|
1765
|
+
if (!dp?.path)
|
|
1766
|
+
return;
|
|
1767
|
+
const envManifest = await scanEnvManifest(dp.path);
|
|
1768
|
+
if (!envManifest)
|
|
1769
|
+
return;
|
|
1770
|
+
const hash = createHash2("sha256").update(JSON.stringify(envManifest)).digest("hex");
|
|
1771
|
+
if (hash === lastEnvHash)
|
|
1772
|
+
return;
|
|
1773
|
+
lastEnvHash = hash;
|
|
1774
|
+
await supabase.from("claude_folders").update({ env_manifest_json: envManifest }).eq("id", state.lastFolderId);
|
|
1775
|
+
}
|
|
1776
|
+
await envCycle();
|
|
1777
|
+
const envInterval = setInterval(envCycle, ENV_POLL_INTERVAL_MS);
|
|
1778
|
+
const interval = setInterval(cycle, POLL_INTERVAL_MS);
|
|
1779
|
+
const shutdown = async () => {
|
|
1780
|
+
clearInterval(interval);
|
|
1781
|
+
clearInterval(envInterval);
|
|
1782
|
+
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
1783
|
+
console.log(chalk18.dim("\nMCP monitor stopped."));
|
|
1784
|
+
process.exit(0);
|
|
1785
|
+
};
|
|
1786
|
+
process.on("SIGINT", () => {
|
|
1787
|
+
void shutdown();
|
|
1788
|
+
});
|
|
1789
|
+
process.on("SIGTERM", () => {
|
|
1790
|
+
void shutdown();
|
|
1791
|
+
});
|
|
2206
1792
|
}
|
|
1793
|
+
var POLL_INTERVAL_MS, ENV_POLL_INTERVAL_MS;
|
|
1794
|
+
var init_mcp_watch = __esm({
|
|
1795
|
+
"dist/commands/mcp-watch.js"() {
|
|
1796
|
+
"use strict";
|
|
1797
|
+
init_auth();
|
|
1798
|
+
init_device_utils();
|
|
1799
|
+
init_read_configs();
|
|
1800
|
+
init_scan_processes();
|
|
1801
|
+
init_check_update();
|
|
1802
|
+
init_env_manifest_scanner();
|
|
1803
|
+
init_config();
|
|
1804
|
+
POLL_INTERVAL_MS = 3e4;
|
|
1805
|
+
ENV_POLL_INTERVAL_MS = 3e5;
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
2207
1808
|
|
|
2208
|
-
// dist/
|
|
2209
|
-
import
|
|
2210
|
-
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1809
|
+
// dist/index.js
|
|
1810
|
+
import { Command } from "commander";
|
|
2211
1811
|
|
|
2212
|
-
// dist/
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
import
|
|
2216
|
-
import {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
1812
|
+
// dist/commands/login.js
|
|
1813
|
+
init_dist();
|
|
1814
|
+
init_config();
|
|
1815
|
+
import chalk from "chalk";
|
|
1816
|
+
import { input, password } from "@inquirer/prompts";
|
|
1817
|
+
async function loginCommand() {
|
|
1818
|
+
console.log(chalk.blue("MD4AI Login\n"));
|
|
1819
|
+
const email = await input({ message: "Email:" });
|
|
1820
|
+
const pwd = await password({ message: "Password:" });
|
|
1821
|
+
const anonKey = getAnonKey();
|
|
1822
|
+
const supabase = createSupabaseClient(anonKey);
|
|
1823
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
1824
|
+
email,
|
|
1825
|
+
password: pwd
|
|
1826
|
+
});
|
|
1827
|
+
if (error) {
|
|
1828
|
+
console.error(chalk.red(`Login failed: ${error.message}`));
|
|
1829
|
+
process.exit(1);
|
|
2226
1830
|
}
|
|
1831
|
+
await saveCredentials({
|
|
1832
|
+
accessToken: data.session.access_token,
|
|
1833
|
+
refreshToken: data.session.refresh_token,
|
|
1834
|
+
expiresAt: Date.now() + data.session.expires_in * 1e3,
|
|
1835
|
+
userId: data.user.id,
|
|
1836
|
+
email: data.user.email ?? email
|
|
1837
|
+
});
|
|
1838
|
+
console.log(chalk.green(`
|
|
1839
|
+
Logged in as ${data.user.email}`));
|
|
2227
1840
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
for (const arg of args) {
|
|
2236
|
-
if (!arg.startsWith("-"))
|
|
2237
|
-
return arg;
|
|
2238
|
-
}
|
|
2239
|
-
return null;
|
|
1841
|
+
|
|
1842
|
+
// dist/commands/logout.js
|
|
1843
|
+
init_config();
|
|
1844
|
+
import chalk2 from "chalk";
|
|
1845
|
+
async function logoutCommand() {
|
|
1846
|
+
await clearCredentials();
|
|
1847
|
+
console.log(chalk2.green("Logged out successfully."));
|
|
2240
1848
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
1849
|
+
|
|
1850
|
+
// dist/commands/status.js
|
|
1851
|
+
init_dist();
|
|
1852
|
+
init_config();
|
|
1853
|
+
import chalk3 from "chalk";
|
|
1854
|
+
async function statusCommand() {
|
|
1855
|
+
const creds = await loadCredentials();
|
|
1856
|
+
if (!creds?.accessToken) {
|
|
1857
|
+
console.log(chalk3.yellow("Not logged in. Run: md4ai login"));
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
console.log(chalk3.blue("MD4AI Status\n"));
|
|
1861
|
+
console.log(` User: ${creds.email}`);
|
|
1862
|
+
console.log(` Expires: ${new Date(creds.expiresAt).toLocaleString()}`);
|
|
1863
|
+
try {
|
|
1864
|
+
const anonKey = getAnonKey();
|
|
1865
|
+
const supabase = createSupabaseClient(anonKey, creds.accessToken);
|
|
1866
|
+
const { count: folderCount } = await supabase.from("claude_folders").select("*", { count: "exact", head: true });
|
|
1867
|
+
const { count: deviceCount } = await supabase.from("device_paths").select("*", { count: "exact", head: true });
|
|
1868
|
+
console.log(` Folders: ${folderCount ?? 0}`);
|
|
1869
|
+
console.log(` Devices: ${deviceCount ?? 0}`);
|
|
1870
|
+
const state = await loadState();
|
|
1871
|
+
console.log(` Last sync: ${state.lastSyncAt ?? "never"}`);
|
|
1872
|
+
} catch {
|
|
1873
|
+
console.log(chalk3.yellow(" (Could not fetch remote data)"));
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// dist/commands/add-folder.js
|
|
1878
|
+
init_auth();
|
|
1879
|
+
import chalk5 from "chalk";
|
|
1880
|
+
import { input as input2, select } from "@inquirer/prompts";
|
|
1881
|
+
async function addFolderCommand() {
|
|
1882
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
1883
|
+
const name = await input2({ message: "Folder name:" });
|
|
1884
|
+
const description = await input2({ message: "Description (optional):" });
|
|
1885
|
+
const { data: teams } = await supabase.from("team_members").select("team_id, teams(id, name)").eq("user_id", userId);
|
|
1886
|
+
const { data: ownedTeams } = await supabase.from("teams").select("id, name").eq("created_by", userId);
|
|
1887
|
+
const allTeams = /* @__PURE__ */ new Map();
|
|
1888
|
+
for (const t of ownedTeams ?? []) {
|
|
1889
|
+
allTeams.set(t.id, t.name);
|
|
1890
|
+
}
|
|
1891
|
+
for (const t of teams ?? []) {
|
|
1892
|
+
const team = t.teams;
|
|
1893
|
+
if (team)
|
|
1894
|
+
allTeams.set(team.id, team.name);
|
|
1895
|
+
}
|
|
1896
|
+
let teamId = null;
|
|
1897
|
+
if (allTeams.size > 0) {
|
|
1898
|
+
const choices = [
|
|
1899
|
+
{ name: "Personal (only you)", value: "__personal__" },
|
|
1900
|
+
...Array.from(allTeams.entries()).map(([id, teamName]) => ({
|
|
1901
|
+
name: teamName,
|
|
1902
|
+
value: id
|
|
1903
|
+
}))
|
|
1904
|
+
];
|
|
1905
|
+
const selected = await select({
|
|
1906
|
+
message: "Assign to a team?",
|
|
1907
|
+
choices
|
|
1908
|
+
});
|
|
1909
|
+
teamId = selected === "__personal__" ? null : selected;
|
|
1910
|
+
}
|
|
1911
|
+
const { data, error } = await supabase.from("claude_folders").insert({
|
|
1912
|
+
user_id: userId,
|
|
1913
|
+
name,
|
|
1914
|
+
description: description || null,
|
|
1915
|
+
team_id: teamId
|
|
1916
|
+
}).select().single();
|
|
1917
|
+
if (error) {
|
|
1918
|
+
console.error(chalk5.red(`Failed to create folder: ${error.message}`));
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
const teamLabel = teamId ? `(team: ${allTeams.get(teamId)})` : "(personal)";
|
|
1922
|
+
console.log(chalk5.green(`
|
|
1923
|
+
Folder "${name}" created ${teamLabel} (${data.id})`));
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// dist/commands/add-device.js
|
|
1927
|
+
init_auth();
|
|
1928
|
+
import { resolve } from "node:path";
|
|
1929
|
+
import { hostname, platform } from "node:os";
|
|
1930
|
+
import chalk6 from "chalk";
|
|
1931
|
+
import { input as input3, select as select2 } from "@inquirer/prompts";
|
|
1932
|
+
function detectOs() {
|
|
1933
|
+
const p = platform();
|
|
1934
|
+
if (p === "win32")
|
|
1935
|
+
return "windows";
|
|
1936
|
+
if (p === "darwin")
|
|
1937
|
+
return "macos";
|
|
1938
|
+
if (p === "linux")
|
|
1939
|
+
return "linux";
|
|
1940
|
+
return "other";
|
|
1941
|
+
}
|
|
1942
|
+
function suggestDeviceName() {
|
|
1943
|
+
const os = detectOs();
|
|
1944
|
+
const host = hostname().split(".")[0];
|
|
1945
|
+
const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
|
|
1946
|
+
return `${host}-${osLabel}`;
|
|
1947
|
+
}
|
|
1948
|
+
async function addDeviceCommand() {
|
|
1949
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
1950
|
+
const cwd = resolve(process.cwd());
|
|
1951
|
+
console.log(chalk6.blue.bold("\n\u{1F4C2} Where is this Claude project?\n"));
|
|
1952
|
+
const localPath = await input3({
|
|
1953
|
+
message: "Local project path:",
|
|
1954
|
+
default: cwd
|
|
1955
|
+
});
|
|
1956
|
+
const { data: folders, error: foldersErr } = await supabase.from("claude_folders").select("id, name").order("name");
|
|
1957
|
+
if (foldersErr || !folders?.length) {
|
|
1958
|
+
console.error(chalk6.red("No folders found. Run: md4ai add-folder"));
|
|
1959
|
+
process.exit(1);
|
|
1960
|
+
}
|
|
1961
|
+
const folderId = await select2({
|
|
1962
|
+
message: "Select folder:",
|
|
1963
|
+
choices: folders.map((f) => ({ name: f.name, value: f.id }))
|
|
1964
|
+
});
|
|
1965
|
+
const suggested = suggestDeviceName();
|
|
1966
|
+
const deviceName = await input3({
|
|
1967
|
+
message: "Device name:",
|
|
1968
|
+
default: suggested
|
|
1969
|
+
});
|
|
1970
|
+
const osType = await select2({
|
|
1971
|
+
message: "OS type:",
|
|
1972
|
+
choices: [
|
|
1973
|
+
{ name: "Windows", value: "windows" },
|
|
1974
|
+
{ name: "macOS", value: "macos" },
|
|
1975
|
+
{ name: "Ubuntu", value: "ubuntu" },
|
|
1976
|
+
{ name: "Linux", value: "linux" },
|
|
1977
|
+
{ name: "Other", value: "other" }
|
|
1978
|
+
],
|
|
1979
|
+
default: detectOs()
|
|
1980
|
+
});
|
|
1981
|
+
const description = await input3({ message: "Description (optional):" });
|
|
1982
|
+
const { error } = await supabase.from("device_paths").insert({
|
|
1983
|
+
user_id: userId,
|
|
1984
|
+
folder_id: folderId,
|
|
1985
|
+
device_name: deviceName,
|
|
1986
|
+
os_type: osType,
|
|
1987
|
+
path: localPath,
|
|
1988
|
+
description: description || null
|
|
1989
|
+
});
|
|
1990
|
+
if (error) {
|
|
1991
|
+
console.error(chalk6.red(`Failed to add device: ${error.message}`));
|
|
1992
|
+
process.exit(1);
|
|
1993
|
+
}
|
|
1994
|
+
console.log(chalk6.green(`
|
|
1995
|
+
Device "${deviceName}" added to folder.`));
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// dist/commands/list-devices.js
|
|
1999
|
+
init_auth();
|
|
2000
|
+
import chalk7 from "chalk";
|
|
2001
|
+
async function listDevicesCommand() {
|
|
2002
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2003
|
+
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
2004
|
+
id, device_name, os_type, path, last_synced, description,
|
|
2005
|
+
claude_folders!inner ( name )
|
|
2006
|
+
`).order("device_name");
|
|
2007
|
+
if (error) {
|
|
2008
|
+
console.error(chalk7.red(`Failed to list devices: ${error.message}`));
|
|
2009
|
+
process.exit(1);
|
|
2010
|
+
}
|
|
2011
|
+
if (!devices?.length) {
|
|
2012
|
+
console.log(chalk7.yellow("No devices found. Run: md4ai add-device"));
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2016
|
+
for (const d of devices) {
|
|
2017
|
+
const list = grouped.get(d.device_name) ?? [];
|
|
2018
|
+
list.push(d);
|
|
2019
|
+
grouped.set(d.device_name, list);
|
|
2020
|
+
}
|
|
2021
|
+
for (const [deviceName, entries] of grouped) {
|
|
2022
|
+
const first = entries[0];
|
|
2023
|
+
console.log(chalk7.bold(`
|
|
2024
|
+
${deviceName}`) + chalk7.dim(` (${first.os_type})`));
|
|
2025
|
+
for (const entry of entries) {
|
|
2026
|
+
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
2027
|
+
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
2028
|
+
console.log(` ${chalk7.cyan(folderName)} \u2192 ${entry.path}`);
|
|
2029
|
+
console.log(` Last synced: ${synced}`);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// dist/commands/map.js
|
|
2035
|
+
init_scanner();
|
|
2036
|
+
init_auth();
|
|
2037
|
+
init_config();
|
|
2038
|
+
import { resolve as resolve3 } from "node:path";
|
|
2039
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
2040
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2041
|
+
import chalk9 from "chalk";
|
|
2042
|
+
|
|
2043
|
+
// dist/output/html-generator.js
|
|
2044
|
+
function generateOfflineHtml(result, projectRoot) {
|
|
2045
|
+
const title = projectRoot.split("/").pop() ?? "MD4AI";
|
|
2046
|
+
const dataJson = JSON.stringify(result);
|
|
2047
|
+
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");
|
|
2048
|
+
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");
|
|
2049
|
+
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");
|
|
2050
|
+
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");
|
|
2051
|
+
return `<!DOCTYPE html>
|
|
2052
|
+
<html lang="en">
|
|
2053
|
+
<head>
|
|
2054
|
+
<meta charset="UTF-8">
|
|
2055
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2056
|
+
<title>MD4AI \u2014 ${escapeHtml(title)}</title>
|
|
2057
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
2058
|
+
<style>
|
|
2059
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2060
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
|
|
2061
|
+
h1 { color: #38bdf8; margin-bottom: 0.5rem; }
|
|
2062
|
+
.subtitle { color: #94a3b8; margin-bottom: 2rem; }
|
|
2063
|
+
.section { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
2064
|
+
.section h2 { color: #38bdf8; margin-bottom: 1rem; font-size: 1.2rem; }
|
|
2065
|
+
.mermaid { background: #0f172a; border-radius: 8px; padding: 1rem; overflow-x: auto; }
|
|
2066
|
+
.badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; margin-left: 0.5rem; }
|
|
2067
|
+
.badge-orphan { background: #ef4444; color: white; }
|
|
2068
|
+
.badge-stale { background: #f59e0b; color: black; }
|
|
2069
|
+
table { width: 100%; border-collapse: collapse; }
|
|
2070
|
+
th, td { padding: 0.5rem 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
2071
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
2072
|
+
.check { color: #22c55e; }
|
|
2073
|
+
.meta { color: #94a3b8; }
|
|
2074
|
+
.stale-meta { color: #f59e0b; }
|
|
2075
|
+
.stats { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
|
2076
|
+
.stat { text-align: center; }
|
|
2077
|
+
.stat-value { font-size: 2rem; font-weight: bold; color: #38bdf8; }
|
|
2078
|
+
.stat-label { color: #94a3b8; font-size: 0.85rem; }
|
|
2079
|
+
.file-list { list-style: none; }
|
|
2080
|
+
.file-list li { padding: 0.3rem 0; color: #cbd5e1; font-family: monospace; font-size: 0.9rem; }
|
|
2081
|
+
#search { width: 100%; padding: 0.75rem 1rem; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 1rem; margin-bottom: 1rem; }
|
|
2082
|
+
#search:focus { outline: none; border-color: #38bdf8; }
|
|
2083
|
+
@media print { body { background: white; color: black; } .section { border: 1px solid #ccc; } }
|
|
2084
|
+
</style>
|
|
2085
|
+
</head>
|
|
2086
|
+
<body>
|
|
2087
|
+
<h1>MD4AI \u2014 ${escapeHtml(title)}</h1>
|
|
2088
|
+
<p class="subtitle">Scanned: ${new Date(result.scannedAt).toLocaleString()} | Hash: ${result.dataHash.slice(0, 12)}</p>
|
|
2089
|
+
|
|
2090
|
+
<div class="stats">
|
|
2091
|
+
<div class="stat"><div class="stat-value">${result.graph.nodes.length}</div><div class="stat-label">Files</div></div>
|
|
2092
|
+
<div class="stat"><div class="stat-value">${result.graph.edges.length}</div><div class="stat-label">References</div></div>
|
|
2093
|
+
<div class="stat"><div class="stat-value">${result.orphans.length}</div><div class="stat-label">Orphans</div></div>
|
|
2094
|
+
<div class="stat"><div class="stat-value">${result.staleFiles.length}</div><div class="stat-label">Stale</div></div>
|
|
2095
|
+
<div class="stat"><div class="stat-value">${result.skills.length}</div><div class="stat-label">Skills</div></div>
|
|
2096
|
+
</div>
|
|
2097
|
+
|
|
2098
|
+
<input type="text" id="search" placeholder="Search files..." oninput="filterFiles(this.value)">
|
|
2099
|
+
|
|
2100
|
+
<div class="section">
|
|
2101
|
+
<h2>Dependency Graph</h2>
|
|
2102
|
+
<pre class="mermaid">${escapeHtml(result.graph.mermaid)}</pre>
|
|
2103
|
+
</div>
|
|
2104
|
+
|
|
2105
|
+
${result.orphans.length > 0 ? `
|
|
2106
|
+
<div class="section">
|
|
2107
|
+
<h2>Orphan Files <span class="badge badge-orphan">${result.orphans.length}</span></h2>
|
|
2108
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not reachable from any root configuration file.</p>
|
|
2109
|
+
<ul class="file-list" id="orphan-list">${orphanItems}</ul>
|
|
2110
|
+
</div>
|
|
2111
|
+
` : ""}
|
|
2112
|
+
|
|
2113
|
+
${result.staleFiles.length > 0 ? `
|
|
2114
|
+
<div class="section">
|
|
2115
|
+
<h2>Stale Files <span class="badge badge-stale">${result.staleFiles.length}</span></h2>
|
|
2116
|
+
<p class="meta" style="margin-bottom: 1rem;">Files not modified in over 90 days.</p>
|
|
2117
|
+
<ul class="file-list" id="stale-list">${staleItems}</ul>
|
|
2118
|
+
</div>
|
|
2119
|
+
` : ""}
|
|
2120
|
+
|
|
2121
|
+
<div class="section">
|
|
2122
|
+
<h2>Skills Comparison</h2>
|
|
2123
|
+
<table>
|
|
2124
|
+
<thead><tr><th>Skill/Plugin</th><th>Entire Machine</th><th>Project Specific</th><th>Source</th></tr></thead>
|
|
2125
|
+
<tbody>${skillRows}</tbody>
|
|
2126
|
+
</table>
|
|
2127
|
+
</div>
|
|
2128
|
+
|
|
2129
|
+
<div class="section">
|
|
2130
|
+
<h2>All Files</h2>
|
|
2131
|
+
<ul class="file-list" id="all-files">${fileItems}</ul>
|
|
2132
|
+
</div>
|
|
2133
|
+
|
|
2134
|
+
<script>
|
|
2135
|
+
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
2136
|
+
|
|
2137
|
+
function filterFiles(query) {
|
|
2138
|
+
var q = query.toLowerCase();
|
|
2139
|
+
document.querySelectorAll('.file-list li').forEach(function(li) {
|
|
2140
|
+
var path = li.getAttribute('data-path') || '';
|
|
2141
|
+
li.style.display = path.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
</script>
|
|
2145
|
+
|
|
2146
|
+
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
2147
|
+
</body>
|
|
2148
|
+
</html>`;
|
|
2149
|
+
}
|
|
2150
|
+
function escapeHtml(text) {
|
|
2151
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
// dist/commands/map.js
|
|
2155
|
+
init_check_update();
|
|
2156
|
+
init_push_toolings();
|
|
2157
|
+
async function mapCommand(path, options) {
|
|
2158
|
+
await checkForUpdate();
|
|
2159
|
+
const projectRoot = resolve3(path ?? process.cwd());
|
|
2160
|
+
if (!existsSync7(projectRoot)) {
|
|
2161
|
+
console.error(chalk9.red(`Path not found: ${projectRoot}`));
|
|
2162
|
+
process.exit(1);
|
|
2163
|
+
}
|
|
2164
|
+
console.log(chalk9.blue(`Scanning: ${projectRoot}
|
|
2165
|
+
`));
|
|
2166
|
+
const result = await scanProject(projectRoot);
|
|
2167
|
+
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
2168
|
+
console.log(` References: ${result.graph.edges.length}`);
|
|
2169
|
+
console.log(` Orphans: ${result.orphans.length}`);
|
|
2170
|
+
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
2171
|
+
console.log(` Skills: ${result.skills.length}`);
|
|
2172
|
+
console.log(` Toolings: ${result.toolings.length}`);
|
|
2173
|
+
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
2174
|
+
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
2175
|
+
const outputDir = resolve3(projectRoot, "output");
|
|
2176
|
+
if (!existsSync7(outputDir)) {
|
|
2177
|
+
await mkdir2(outputDir, { recursive: true });
|
|
2178
|
+
}
|
|
2179
|
+
const htmlPath = resolve3(outputDir, "index.html");
|
|
2180
|
+
const html = generateOfflineHtml(result, projectRoot);
|
|
2181
|
+
await writeFile2(htmlPath, html, "utf-8");
|
|
2182
|
+
console.log(chalk9.green(`
|
|
2183
|
+
Local preview: ${htmlPath}`));
|
|
2184
|
+
if (!options.offline) {
|
|
2185
|
+
try {
|
|
2186
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2187
|
+
const { data: devicePaths } = await supabase.from("device_paths").select("folder_id, device_name").eq("path", projectRoot);
|
|
2188
|
+
if (devicePaths?.length) {
|
|
2189
|
+
const { folder_id, device_name } = devicePaths[0];
|
|
2190
|
+
const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
|
|
2191
|
+
if (proposedFiles?.length) {
|
|
2192
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
2193
|
+
console.log(chalk9.yellow(`
|
|
2194
|
+
${proposedFiles.length} file(s) proposed for deletion:
|
|
2195
|
+
`));
|
|
2196
|
+
const toDelete = await checkbox({
|
|
2197
|
+
message: "Select files to delete (space to toggle, enter to confirm)",
|
|
2198
|
+
choices: proposedFiles.map((f) => ({
|
|
2199
|
+
name: `${f.file_path}${f.proposed_at ? ` (proposed ${new Date(f.proposed_at).toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", year: "numeric" })})` : ""}`,
|
|
2200
|
+
value: f
|
|
2201
|
+
}))
|
|
2202
|
+
});
|
|
2203
|
+
for (const file of toDelete) {
|
|
2204
|
+
const fullPath = resolve3(projectRoot, file.file_path);
|
|
2205
|
+
try {
|
|
2206
|
+
const { unlink } = await import("node:fs/promises");
|
|
2207
|
+
await unlink(fullPath);
|
|
2208
|
+
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
2209
|
+
console.log(chalk9.green(` Deleted: ${file.file_path}`));
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
2215
|
+
if (keptIds.length > 0) {
|
|
2216
|
+
for (const id of keptIds) {
|
|
2217
|
+
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
2218
|
+
}
|
|
2219
|
+
console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
2220
|
+
}
|
|
2221
|
+
console.log("");
|
|
2222
|
+
}
|
|
2223
|
+
const { error } = await supabase.from("claude_folders").update({
|
|
2224
|
+
graph_json: result.graph,
|
|
2225
|
+
orphans_json: result.orphans,
|
|
2226
|
+
skills_table_json: result.skills,
|
|
2227
|
+
stale_files_json: result.staleFiles,
|
|
2228
|
+
env_manifest_json: result.envManifest,
|
|
2229
|
+
last_scanned: result.scannedAt,
|
|
2230
|
+
data_hash: result.dataHash
|
|
2231
|
+
}).eq("id", folder_id);
|
|
2232
|
+
if (error) {
|
|
2233
|
+
console.error(chalk9.yellow(`Sync warning: ${error.message}`));
|
|
2234
|
+
} else {
|
|
2235
|
+
await pushToolings(supabase, folder_id, result.toolings);
|
|
2236
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
2237
|
+
await saveState({
|
|
2238
|
+
lastFolderId: folder_id,
|
|
2239
|
+
lastDeviceName: device_name,
|
|
2240
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2241
|
+
});
|
|
2242
|
+
console.log(chalk9.green("Synced to Supabase."));
|
|
2243
|
+
console.log(chalk9.cyan(`
|
|
2244
|
+
https://www.md4ai.com/project/${folder_id}
|
|
2245
|
+
`));
|
|
2246
|
+
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
2247
|
+
if (configFiles.length > 0) {
|
|
2248
|
+
for (const file of configFiles) {
|
|
2249
|
+
await supabase.from("folder_files").upsert({
|
|
2250
|
+
folder_id,
|
|
2251
|
+
file_path: file.filePath,
|
|
2252
|
+
content: file.content,
|
|
2253
|
+
size_bytes: file.sizeBytes,
|
|
2254
|
+
last_modified: file.lastModified
|
|
2255
|
+
}, { onConflict: "folder_id,file_path" });
|
|
2256
|
+
}
|
|
2257
|
+
console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
} else {
|
|
2261
|
+
console.log(chalk9.yellow("No device path matches this folder. Run: md4ai add-device\n(Local preview still generated.)"));
|
|
2262
|
+
}
|
|
2263
|
+
} catch {
|
|
2264
|
+
console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// dist/commands/simulate.js
|
|
2270
|
+
init_dist();
|
|
2271
|
+
import { join as join9 } from "node:path";
|
|
2272
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2273
|
+
import { homedir as homedir6 } from "node:os";
|
|
2274
|
+
import chalk10 from "chalk";
|
|
2275
|
+
async function simulateCommand(prompt) {
|
|
2276
|
+
const projectRoot = process.cwd();
|
|
2277
|
+
console.log(chalk10.blue(`Simulating prompt: "${prompt}"
|
|
2278
|
+
`));
|
|
2279
|
+
console.log(chalk10.dim("Files Claude would load:\n"));
|
|
2280
|
+
const globalClaude = join9(homedir6(), ".claude", "CLAUDE.md");
|
|
2281
|
+
if (existsSync8(globalClaude)) {
|
|
2282
|
+
console.log(chalk10.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
2283
|
+
}
|
|
2284
|
+
for (const rootFile of ROOT_FILES) {
|
|
2285
|
+
const fullPath = join9(projectRoot, rootFile);
|
|
2286
|
+
if (existsSync8(fullPath)) {
|
|
2287
|
+
console.log(chalk10.green(` \u2713 ${rootFile} (project root)`));
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
const settingsFiles = [
|
|
2291
|
+
join9(homedir6(), ".claude", "settings.json"),
|
|
2292
|
+
join9(homedir6(), ".claude", "settings.local.json"),
|
|
2293
|
+
join9(projectRoot, ".claude", "settings.json"),
|
|
2294
|
+
join9(projectRoot, ".claude", "settings.local.json")
|
|
2295
|
+
];
|
|
2296
|
+
for (const sf of settingsFiles) {
|
|
2297
|
+
if (existsSync8(sf)) {
|
|
2298
|
+
const display = sf.startsWith(homedir6()) ? sf.replace(homedir6(), "~") : sf.replace(projectRoot + "/", "");
|
|
2299
|
+
console.log(chalk10.green(` \u2713 ${display} (settings)`));
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const words = prompt.split(/\s+/);
|
|
2303
|
+
for (const word of words) {
|
|
2304
|
+
const cleaned = word.replace(/['"]/g, "");
|
|
2305
|
+
const candidatePath = join9(projectRoot, cleaned);
|
|
2306
|
+
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
2307
|
+
console.log(chalk10.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
console.log(chalk10.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// dist/commands/print.js
|
|
2314
|
+
import { join as join10 } from "node:path";
|
|
2315
|
+
import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
2316
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2317
|
+
import chalk11 from "chalk";
|
|
2318
|
+
async function printCommand(title) {
|
|
2319
|
+
const projectRoot = process.cwd();
|
|
2320
|
+
const scanDataPath = join10(projectRoot, "output", "index.html");
|
|
2321
|
+
if (!existsSync9(scanDataPath)) {
|
|
2322
|
+
console.error(chalk11.red("No scan data found. Run: md4ai scan"));
|
|
2323
|
+
process.exit(1);
|
|
2324
|
+
}
|
|
2325
|
+
const html = await readFile6(scanDataPath, "utf-8");
|
|
2326
|
+
const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
|
|
2327
|
+
if (!match) {
|
|
2328
|
+
console.error(chalk11.red("Could not extract scan data from output/index.html"));
|
|
2329
|
+
process.exit(1);
|
|
2330
|
+
}
|
|
2331
|
+
const result = JSON.parse(match[1]);
|
|
2332
|
+
const printHtml = generatePrintHtml(result, title);
|
|
2333
|
+
const outputPath = join10(projectRoot, "output", `print-${Date.now()}.html`);
|
|
2334
|
+
await writeFile3(outputPath, printHtml, "utf-8");
|
|
2335
|
+
console.log(chalk11.green(`Print-ready wall sheet: ${outputPath}`));
|
|
2336
|
+
}
|
|
2337
|
+
function escapeHtml2(text) {
|
|
2338
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2339
|
+
}
|
|
2340
|
+
function generatePrintHtml(result, title) {
|
|
2341
|
+
const skillRows = result.skills.map((s) => `<tr><td>${escapeHtml2(s.name)}</td><td>${s.machineWide ? '<span class="check">\u2713</span>' : ""}</td><td>${s.projectSpecific ? '<span class="check">\u2713</span>' : ""}</td></tr>`).join("\n");
|
|
2342
|
+
const orphanItems = result.orphans.map((o) => `<li class="orphan">${escapeHtml2(o.path)}</li>`).join("\n");
|
|
2343
|
+
const staleItems = result.staleFiles.map((s) => `<li class="stale">${escapeHtml2(s.path)} (${s.daysSinceModified}d)</li>`).join("\n");
|
|
2344
|
+
return `<!DOCTYPE html>
|
|
2345
|
+
<html lang="en">
|
|
2346
|
+
<head>
|
|
2347
|
+
<meta charset="UTF-8">
|
|
2348
|
+
<title>${escapeHtml2(title)} \u2014 MD4AI Wall Sheet</title>
|
|
2349
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
2350
|
+
<style>
|
|
2351
|
+
@page { size: A3 landscape; margin: 1cm; }
|
|
2352
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 1rem; }
|
|
2353
|
+
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
2354
|
+
.subtitle { color: #666; margin-bottom: 2rem; }
|
|
2355
|
+
.mermaid { margin: 2rem 0; }
|
|
2356
|
+
.columns { display: flex; gap: 2rem; }
|
|
2357
|
+
.column { flex: 1; }
|
|
2358
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
|
2359
|
+
th, td { padding: 0.3rem 0.5rem; text-align: left; border-bottom: 1px solid #ddd; }
|
|
2360
|
+
th { background: #f5f5f5; }
|
|
2361
|
+
.orphan { color: #dc2626; }
|
|
2362
|
+
.stale { color: #d97706; }
|
|
2363
|
+
.check { color: #16a34a; font-weight: bold; }
|
|
2364
|
+
</style>
|
|
2365
|
+
</head>
|
|
2366
|
+
<body>
|
|
2367
|
+
<h1>${escapeHtml2(title)}</h1>
|
|
2368
|
+
<p class="subtitle">Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()} | Files: ${result.graph.nodes.length} | Orphans: ${result.orphans.length}</p>
|
|
2369
|
+
|
|
2370
|
+
<pre class="mermaid">${escapeHtml2(result.graph.mermaid)}</pre>
|
|
2371
|
+
|
|
2372
|
+
<div class="columns">
|
|
2373
|
+
<div class="column">
|
|
2374
|
+
<h2>Skills Comparison</h2>
|
|
2375
|
+
<table>
|
|
2376
|
+
<tr><th>Skill</th><th>Machine</th><th>Project</th></tr>
|
|
2377
|
+
${skillRows}
|
|
2378
|
+
</table>
|
|
2379
|
+
</div>
|
|
2380
|
+
<div class="column">
|
|
2381
|
+
${result.orphans.length ? `<h2>Orphans</h2><ul>${orphanItems}</ul>` : ""}
|
|
2382
|
+
${result.staleFiles.length ? `<h2>Stale Files</h2><ul>${staleItems}</ul>` : ""}
|
|
2383
|
+
</div>
|
|
2384
|
+
</div>
|
|
2385
|
+
|
|
2386
|
+
<script>mermaid.initialize({ startOnLoad: true, theme: 'default' });</script>
|
|
2387
|
+
</body>
|
|
2388
|
+
</html>`;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
// dist/index.js
|
|
2392
|
+
init_sync();
|
|
2393
|
+
|
|
2394
|
+
// dist/commands/link.js
|
|
2395
|
+
init_auth();
|
|
2396
|
+
init_config();
|
|
2397
|
+
init_scanner();
|
|
2398
|
+
init_push_toolings();
|
|
2399
|
+
init_device_utils();
|
|
2400
|
+
import { resolve as resolve4 } from "node:path";
|
|
2401
|
+
import chalk13 from "chalk";
|
|
2402
|
+
async function linkCommand(projectId) {
|
|
2403
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
2404
|
+
const cwd = resolve4(process.cwd());
|
|
2405
|
+
const deviceName = detectDeviceName();
|
|
2406
|
+
const osType = detectOs2();
|
|
2407
|
+
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
2408
|
+
if (folderErr || !folder) {
|
|
2409
|
+
console.error(chalk13.red("Project not found, or you do not have access."));
|
|
2410
|
+
console.error(chalk13.yellow("Check the project ID in the MD4AI web dashboard."));
|
|
2411
|
+
process.exit(1);
|
|
2412
|
+
}
|
|
2413
|
+
console.log(chalk13.blue(`
|
|
2414
|
+
Linking "${folder.name}" to this device...
|
|
2415
|
+
`));
|
|
2416
|
+
console.log(` Project: ${folder.name}`);
|
|
2417
|
+
console.log(` Path: ${cwd}`);
|
|
2418
|
+
console.log(` Device: ${deviceName}`);
|
|
2419
|
+
console.log(` OS: ${osType}`);
|
|
2420
|
+
await supabase.from("devices").upsert({
|
|
2421
|
+
user_id: userId,
|
|
2422
|
+
device_name: deviceName,
|
|
2423
|
+
os_type: osType
|
|
2424
|
+
}, { onConflict: "user_id,device_name" });
|
|
2425
|
+
const { data: existing } = await supabase.from("device_paths").select("id").eq("folder_id", folder.id).eq("device_name", deviceName).maybeSingle();
|
|
2426
|
+
if (existing) {
|
|
2427
|
+
await supabase.from("device_paths").update({ path: cwd, os_type: osType, last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id);
|
|
2428
|
+
} else {
|
|
2429
|
+
const { error: pathErr } = await supabase.from("device_paths").insert({
|
|
2430
|
+
user_id: userId,
|
|
2431
|
+
folder_id: folder.id,
|
|
2432
|
+
device_name: deviceName,
|
|
2433
|
+
os_type: osType,
|
|
2434
|
+
path: cwd
|
|
2255
2435
|
});
|
|
2436
|
+
if (pathErr) {
|
|
2437
|
+
console.error(chalk13.red(`Failed to link: ${pathErr.message}`));
|
|
2438
|
+
process.exit(1);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
console.log(chalk13.green("\nLinked successfully."));
|
|
2442
|
+
console.log(chalk13.blue("\nRunning initial scan...\n"));
|
|
2443
|
+
const result = await scanProject(cwd);
|
|
2444
|
+
console.log(` Files: ${result.graph.nodes.length}`);
|
|
2445
|
+
console.log(` References:${result.graph.edges.length}`);
|
|
2446
|
+
console.log(` Orphans: ${result.orphans.length}`);
|
|
2447
|
+
console.log(` Skills: ${result.skills.length}`);
|
|
2448
|
+
console.log(` Toolings: ${result.toolings.length}`);
|
|
2449
|
+
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
2450
|
+
const { error: scanErr } = await supabase.from("claude_folders").update({
|
|
2451
|
+
graph_json: result.graph,
|
|
2452
|
+
orphans_json: result.orphans,
|
|
2453
|
+
skills_table_json: result.skills,
|
|
2454
|
+
stale_files_json: result.staleFiles,
|
|
2455
|
+
env_manifest_json: result.envManifest,
|
|
2456
|
+
last_scanned: result.scannedAt,
|
|
2457
|
+
data_hash: result.dataHash
|
|
2458
|
+
}).eq("id", folder.id);
|
|
2459
|
+
if (scanErr) {
|
|
2460
|
+
console.error(chalk13.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
2256
2461
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
name,
|
|
2271
|
-
source,
|
|
2272
|
-
type: serverType,
|
|
2273
|
-
command: server.command,
|
|
2274
|
-
args: server.args,
|
|
2275
|
-
env: server.env,
|
|
2276
|
-
url: server.url
|
|
2277
|
-
});
|
|
2462
|
+
await pushToolings(supabase, folder.id, result.toolings);
|
|
2463
|
+
const configFiles = await readClaudeConfigFiles(cwd);
|
|
2464
|
+
if (configFiles.length > 0) {
|
|
2465
|
+
for (const file of configFiles) {
|
|
2466
|
+
await supabase.from("folder_files").upsert({
|
|
2467
|
+
folder_id: folder.id,
|
|
2468
|
+
file_path: file.filePath,
|
|
2469
|
+
content: file.content,
|
|
2470
|
+
size_bytes: file.sizeBytes,
|
|
2471
|
+
last_modified: file.lastModified
|
|
2472
|
+
}, { onConflict: "folder_id,file_path" });
|
|
2473
|
+
}
|
|
2474
|
+
console.log(chalk13.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2278
2475
|
}
|
|
2279
|
-
|
|
2476
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
2477
|
+
await saveState({
|
|
2478
|
+
lastFolderId: folder.id,
|
|
2479
|
+
lastDeviceName: deviceName,
|
|
2480
|
+
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2481
|
+
});
|
|
2482
|
+
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
2483
|
+
console.log(chalk13.green("\nDone! Project linked and scanned."));
|
|
2484
|
+
console.log(chalk13.cyan(`
|
|
2485
|
+
${projectUrl}
|
|
2486
|
+
`));
|
|
2487
|
+
console.log(chalk13.grey('Run "md4ai scan" to rescan at any time.'));
|
|
2280
2488
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
try {
|
|
2293
|
-
const marketplaces = await readdir3(pluginsBase, { withFileTypes: true });
|
|
2294
|
-
for (const mp of marketplaces) {
|
|
2295
|
-
if (!mp.isDirectory())
|
|
2296
|
-
continue;
|
|
2297
|
-
const extDir = join12(pluginsBase, mp.name, "external_plugins");
|
|
2298
|
-
if (!existsSync11(extDir))
|
|
2299
|
-
continue;
|
|
2300
|
-
const plugins = await readdir3(extDir, { withFileTypes: true });
|
|
2301
|
-
for (const plugin of plugins) {
|
|
2302
|
-
if (!plugin.isDirectory())
|
|
2303
|
-
continue;
|
|
2304
|
-
const pluginMcp = await readJsonSafe(join12(extDir, plugin.name, ".mcp.json"));
|
|
2305
|
-
entries.push(...parseServers(pluginMcp, "plugin"));
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
} catch {
|
|
2309
|
-
}
|
|
2489
|
+
|
|
2490
|
+
// dist/commands/import-bundle.js
|
|
2491
|
+
import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir3 } from "node:fs/promises";
|
|
2492
|
+
import { join as join11, dirname as dirname2 } from "node:path";
|
|
2493
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2494
|
+
import chalk14 from "chalk";
|
|
2495
|
+
import { confirm, input as input4 } from "@inquirer/prompts";
|
|
2496
|
+
async function importBundleCommand(zipPath) {
|
|
2497
|
+
if (!existsSync10(zipPath)) {
|
|
2498
|
+
console.error(chalk14.red(`File not found: ${zipPath}`));
|
|
2499
|
+
process.exit(1);
|
|
2310
2500
|
}
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
break;
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
} catch {
|
|
2501
|
+
const JSZip = (await import("jszip")).default;
|
|
2502
|
+
const zipData = await readFile7(zipPath);
|
|
2503
|
+
const zip = await JSZip.loadAsync(zipData);
|
|
2504
|
+
const manifestFile = zip.file("manifest.json");
|
|
2505
|
+
if (!manifestFile) {
|
|
2506
|
+
console.error(chalk14.red("Invalid bundle: missing manifest.json"));
|
|
2507
|
+
process.exit(1);
|
|
2508
|
+
}
|
|
2509
|
+
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
2510
|
+
console.log(chalk14.blue(`
|
|
2511
|
+
Bundle: ${manifest.folderName}`));
|
|
2512
|
+
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
2513
|
+
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
2514
|
+
console.log(` Files: ${manifest.fileCount}`);
|
|
2515
|
+
const files = [];
|
|
2516
|
+
for (const [path, file] of Object.entries(zip.files)) {
|
|
2517
|
+
if (path.startsWith("claude-files/") && !file.dir) {
|
|
2518
|
+
const relativePath = path.replace("claude-files/", "");
|
|
2519
|
+
const content = await file.async("string");
|
|
2520
|
+
files.push({ filePath: relativePath, content });
|
|
2337
2521
|
}
|
|
2338
2522
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
if (seen.has(entry.name))
|
|
2343
|
-
continue;
|
|
2344
|
-
seen.add(entry.name);
|
|
2345
|
-
deduped.push(entry);
|
|
2523
|
+
if (files.length === 0) {
|
|
2524
|
+
console.log(chalk14.yellow("No Claude config files found in bundle."));
|
|
2525
|
+
return;
|
|
2346
2526
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2352
|
-
function parsePsOutput(output) {
|
|
2353
|
-
const lines = output.trim().split("\n").slice(1);
|
|
2354
|
-
const entries = [];
|
|
2355
|
-
for (const line of lines) {
|
|
2356
|
-
const trimmed = line.trim();
|
|
2357
|
-
const match = trimmed.match(/^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(.+)$/);
|
|
2358
|
-
if (!match)
|
|
2359
|
-
continue;
|
|
2360
|
-
entries.push({
|
|
2361
|
-
pid: parseInt(match[1], 10),
|
|
2362
|
-
tty: match[2],
|
|
2363
|
-
etimeRaw: match[3],
|
|
2364
|
-
rss: parseInt(match[4], 10),
|
|
2365
|
-
args: match[5]
|
|
2366
|
-
});
|
|
2527
|
+
console.log(chalk14.blue(`
|
|
2528
|
+
Files to extract:`));
|
|
2529
|
+
for (const f of files) {
|
|
2530
|
+
console.log(` ${f.filePath}`);
|
|
2367
2531
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2532
|
+
const targetDir = await input4({
|
|
2533
|
+
message: "Extract to directory:",
|
|
2534
|
+
default: process.cwd()
|
|
2535
|
+
});
|
|
2536
|
+
const proceed = await confirm({
|
|
2537
|
+
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
2538
|
+
});
|
|
2539
|
+
if (!proceed) {
|
|
2540
|
+
console.log(chalk14.yellow("Cancelled."));
|
|
2541
|
+
return;
|
|
2377
2542
|
}
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2543
|
+
for (const file of files) {
|
|
2544
|
+
const fullPath = join11(targetDir, file.filePath);
|
|
2545
|
+
const dir = dirname2(fullPath);
|
|
2546
|
+
if (!existsSync10(dir)) {
|
|
2547
|
+
await mkdir3(dir, { recursive: true });
|
|
2548
|
+
}
|
|
2549
|
+
await writeFile4(fullPath, file.content, "utf-8");
|
|
2550
|
+
console.log(chalk14.green(` \u2713 ${file.filePath}`));
|
|
2383
2551
|
}
|
|
2384
|
-
|
|
2552
|
+
console.log(chalk14.green(`
|
|
2553
|
+
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
2385
2554
|
}
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2555
|
+
|
|
2556
|
+
// dist/commands/admin-update-tool.js
|
|
2557
|
+
init_auth();
|
|
2558
|
+
import chalk15 from "chalk";
|
|
2559
|
+
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
2560
|
+
async function adminUpdateToolCommand(options) {
|
|
2561
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2562
|
+
if (!options.name) {
|
|
2563
|
+
console.error(chalk15.red("--name is required."));
|
|
2564
|
+
process.exit(1);
|
|
2565
|
+
}
|
|
2566
|
+
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
2567
|
+
console.error(chalk15.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
2568
|
+
process.exit(1);
|
|
2569
|
+
}
|
|
2570
|
+
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
2571
|
+
if (existing) {
|
|
2572
|
+
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2573
|
+
if (options.display)
|
|
2574
|
+
updates.display_name = options.display;
|
|
2575
|
+
if (options.category)
|
|
2576
|
+
updates.category = options.category;
|
|
2577
|
+
if (options.stable)
|
|
2578
|
+
updates.latest_stable = options.stable;
|
|
2579
|
+
if (options.beta)
|
|
2580
|
+
updates.latest_beta = options.beta;
|
|
2581
|
+
if (options.source)
|
|
2582
|
+
updates.source_url = options.source;
|
|
2583
|
+
if (options.install)
|
|
2584
|
+
updates.install_url = options.install;
|
|
2585
|
+
if (options.notes)
|
|
2586
|
+
updates.notes = options.notes;
|
|
2587
|
+
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
2588
|
+
if (error) {
|
|
2589
|
+
console.error(chalk15.red(`Failed to update: ${error.message}`));
|
|
2590
|
+
process.exit(1);
|
|
2408
2591
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2592
|
+
console.log(chalk15.green(`Updated "${existing.display_name}" in the registry.`));
|
|
2593
|
+
} else {
|
|
2594
|
+
if (!options.display || !options.category) {
|
|
2595
|
+
console.error(chalk15.red("New tools require --display and --category."));
|
|
2596
|
+
process.exit(1);
|
|
2597
|
+
}
|
|
2598
|
+
const { error } = await supabase.from("tools_registry").insert({
|
|
2599
|
+
name: options.name,
|
|
2600
|
+
display_name: options.display,
|
|
2601
|
+
category: options.category,
|
|
2602
|
+
latest_stable: options.stable ?? null,
|
|
2603
|
+
latest_beta: options.beta ?? null,
|
|
2604
|
+
source_url: options.source ?? null,
|
|
2605
|
+
install_url: options.install ?? null,
|
|
2606
|
+
notes: options.notes ?? null
|
|
2417
2607
|
});
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2608
|
+
if (error) {
|
|
2609
|
+
console.error(chalk15.red(`Failed to create: ${error.message}`));
|
|
2610
|
+
process.exit(1);
|
|
2611
|
+
}
|
|
2612
|
+
console.log(chalk15.green(`Added "${options.display}" to the registry.`));
|
|
2421
2613
|
}
|
|
2422
2614
|
}
|
|
2423
2615
|
|
|
2424
|
-
// dist/commands/
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
return
|
|
2616
|
+
// dist/commands/admin-list-tools.js
|
|
2617
|
+
init_auth();
|
|
2618
|
+
import chalk16 from "chalk";
|
|
2619
|
+
async function adminListToolsCommand() {
|
|
2620
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2621
|
+
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
2622
|
+
if (error) {
|
|
2623
|
+
console.error(chalk16.red(`Failed to fetch tools: ${error.message}`));
|
|
2624
|
+
process.exit(1);
|
|
2625
|
+
}
|
|
2626
|
+
if (!tools?.length) {
|
|
2627
|
+
console.log(chalk16.yellow("No tools in the registry."));
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2630
|
+
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
2631
|
+
const catW = Math.max(10, ...tools.map((t) => t.category.length));
|
|
2632
|
+
const stableW = Math.max(8, ...tools.map((t) => (t.latest_stable ?? "\u2014").length));
|
|
2633
|
+
const betaW = Math.max(8, ...tools.map((t) => (t.latest_beta ?? "\u2014").length));
|
|
2634
|
+
const header = [
|
|
2635
|
+
"Name".padEnd(nameW),
|
|
2636
|
+
"Category".padEnd(catW),
|
|
2637
|
+
"Stable".padEnd(stableW),
|
|
2638
|
+
"Beta".padEnd(betaW),
|
|
2639
|
+
"Last Checked"
|
|
2640
|
+
].join(" ");
|
|
2641
|
+
console.log(chalk16.bold(header));
|
|
2642
|
+
console.log("\u2500".repeat(header.length));
|
|
2643
|
+
for (const tool of tools) {
|
|
2644
|
+
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
2645
|
+
const row = [
|
|
2646
|
+
tool.display_name.padEnd(nameW),
|
|
2647
|
+
tool.category.padEnd(catW),
|
|
2648
|
+
(tool.latest_stable ?? "\u2014").padEnd(stableW),
|
|
2649
|
+
(tool.latest_beta ?? "\u2014").padEnd(betaW),
|
|
2650
|
+
lastChecked
|
|
2651
|
+
].join(" ");
|
|
2652
|
+
console.log(row);
|
|
2437
2653
|
}
|
|
2654
|
+
console.log(chalk16.grey(`
|
|
2655
|
+
${tools.length} tool(s) in registry.`));
|
|
2438
2656
|
}
|
|
2439
|
-
function
|
|
2440
|
-
const
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2657
|
+
function formatRelative(date) {
|
|
2658
|
+
const diff = Date.now() - date.getTime();
|
|
2659
|
+
const hours = Math.floor(diff / (1e3 * 60 * 60));
|
|
2660
|
+
if (hours < 1)
|
|
2661
|
+
return "just now";
|
|
2662
|
+
if (hours < 24)
|
|
2663
|
+
return `${hours}h ago`;
|
|
2664
|
+
const days = Math.floor(hours / 24);
|
|
2665
|
+
if (days < 7)
|
|
2666
|
+
return `${days}d ago`;
|
|
2667
|
+
return date.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
|
|
2443
2668
|
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
const matches = findProcessesForConfig(config, processes);
|
|
2489
|
-
if (matches.length === 0) {
|
|
2490
|
-
rows.push({
|
|
2491
|
-
server_name: config.name,
|
|
2492
|
-
config_source: config.source,
|
|
2493
|
-
server_type: "stdio",
|
|
2494
|
-
command: config.command ?? null,
|
|
2495
|
-
package_name: packageName,
|
|
2496
|
-
http_url: null,
|
|
2497
|
-
status: "stopped",
|
|
2498
|
-
pid: null,
|
|
2499
|
-
session_tty: null,
|
|
2500
|
-
uptime_seconds: null,
|
|
2501
|
-
memory_mb: null,
|
|
2502
|
-
env_vars_required: required.length ? required : null,
|
|
2503
|
-
env_vars_missing: null,
|
|
2504
|
-
error_detail: null
|
|
2505
|
-
});
|
|
2506
|
-
} else {
|
|
2507
|
-
for (const match of matches) {
|
|
2508
|
-
rows.push({
|
|
2509
|
-
server_name: config.name,
|
|
2510
|
-
config_source: config.source,
|
|
2511
|
-
server_type: "stdio",
|
|
2512
|
-
command: config.command ?? null,
|
|
2513
|
-
package_name: packageName,
|
|
2514
|
-
http_url: null,
|
|
2515
|
-
status: "running",
|
|
2516
|
-
pid: match.pid,
|
|
2517
|
-
session_tty: match.tty || null,
|
|
2518
|
-
uptime_seconds: match.uptimeSeconds,
|
|
2519
|
-
memory_mb: match.memoryMb,
|
|
2520
|
-
env_vars_required: required.length ? required : null,
|
|
2521
|
-
env_vars_missing: null,
|
|
2522
|
-
error_detail: null
|
|
2523
|
-
});
|
|
2524
|
-
}
|
|
2669
|
+
|
|
2670
|
+
// dist/commands/admin-fetch-versions.js
|
|
2671
|
+
init_auth();
|
|
2672
|
+
import chalk17 from "chalk";
|
|
2673
|
+
|
|
2674
|
+
// dist/commands/version-sources.js
|
|
2675
|
+
var VERSION_SOURCES = {
|
|
2676
|
+
"next": { type: "npm", package: "next" },
|
|
2677
|
+
"react": { type: "npm", package: "react" },
|
|
2678
|
+
"react-dom": { type: "npm", package: "react-dom" },
|
|
2679
|
+
"typescript": { type: "npm", package: "typescript" },
|
|
2680
|
+
"tailwindcss": { type: "npm", package: "tailwindcss" },
|
|
2681
|
+
"esbuild": { type: "npm", package: "esbuild" },
|
|
2682
|
+
"chalk": { type: "npm", package: "chalk" },
|
|
2683
|
+
"commander": { type: "npm", package: "commander" },
|
|
2684
|
+
"inquirer": { type: "npm", package: "inquirer" },
|
|
2685
|
+
"playwright": { type: "npm", package: "playwright" },
|
|
2686
|
+
"resend": { type: "npm", package: "resend" },
|
|
2687
|
+
"@supabase/supabase-js": { type: "npm", package: "@supabase/supabase-js" },
|
|
2688
|
+
"pnpm": { type: "npm", package: "pnpm" },
|
|
2689
|
+
"claude-code": { type: "npm", package: "@anthropic-ai/claude-code" },
|
|
2690
|
+
"turborepo": { type: "npm", package: "turbo" },
|
|
2691
|
+
"npm": { type: "npm", package: "npm" },
|
|
2692
|
+
"node": { type: "github", repo: "nodejs/node" },
|
|
2693
|
+
"supabase-cli": { type: "npm", package: "supabase" }
|
|
2694
|
+
};
|
|
2695
|
+
|
|
2696
|
+
// dist/commands/admin-fetch-versions.js
|
|
2697
|
+
async function adminFetchVersionsCommand() {
|
|
2698
|
+
const { supabase } = await getAuthenticatedClient();
|
|
2699
|
+
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
2700
|
+
if (error) {
|
|
2701
|
+
console.error(chalk17.red(`Failed to fetch tools: ${error.message}`));
|
|
2702
|
+
process.exit(1);
|
|
2703
|
+
}
|
|
2704
|
+
if (!tools?.length) {
|
|
2705
|
+
console.log(chalk17.yellow("No tools in the registry."));
|
|
2706
|
+
}
|
|
2707
|
+
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
2708
|
+
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
2709
|
+
const unregisteredNames = /* @__PURE__ */ new Set();
|
|
2710
|
+
for (const pt of allProjectToolings ?? []) {
|
|
2711
|
+
if (!registeredNames.has(pt.tool_name) && pt.detection_source === "package.json") {
|
|
2712
|
+
unregisteredNames.add(pt.tool_name);
|
|
2525
2713
|
}
|
|
2526
2714
|
}
|
|
2527
|
-
|
|
2528
|
-
}
|
|
2529
|
-
function printTable(rows, deviceName) {
|
|
2530
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
2531
|
-
console.log(chalk18.bold.cyan(`
|
|
2532
|
-
MCP Monitor \u2014 ${deviceName}`));
|
|
2533
|
-
console.log(chalk18.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
|
|
2715
|
+
if (unregisteredNames.size > 0) {
|
|
2716
|
+
console.log(chalk17.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
2534
2717
|
`));
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
2548
|
-
console.log(chalk18.green(` Session ${tty}`) + chalk18.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB`));
|
|
2549
|
-
for (const s of servers) {
|
|
2550
|
-
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
2551
|
-
console.log(` ${chalk18.green("\u25CF")} ${s.server_name.padEnd(20)} ${chalk18.dim((s.package_name ?? "").padEnd(30))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
|
|
2718
|
+
for (const name of unregisteredNames) {
|
|
2719
|
+
const displayName = name;
|
|
2720
|
+
const { data: inserted, error: insertError } = await supabase.from("tools_registry").upsert({
|
|
2721
|
+
name,
|
|
2722
|
+
display_name: displayName,
|
|
2723
|
+
category: "package",
|
|
2724
|
+
source_url: `https://www.npmjs.com/package/${name}`,
|
|
2725
|
+
install_url: `https://www.npmjs.com/package/${name}`
|
|
2726
|
+
}, { onConflict: "name" }).select().single();
|
|
2727
|
+
if (!insertError && inserted) {
|
|
2728
|
+
tools.push(inserted);
|
|
2729
|
+
await supabase.from("project_toolings").update({ tool_id: inserted.id }).eq("tool_name", name).is("tool_id", null);
|
|
2552
2730
|
}
|
|
2553
|
-
console.log("");
|
|
2554
2731
|
}
|
|
2555
2732
|
}
|
|
2556
|
-
if (
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
for (const s of notRunning) {
|
|
2560
|
-
const icon = s.status === "error" ? chalk18.red("\u2717") : chalk18.yellow("\u25CB");
|
|
2561
|
-
const detail = s.error_detail ? chalk18.dim(` \u2014 ${s.error_detail}`) : "";
|
|
2562
|
-
console.log(` ${icon} ${s.server_name.padEnd(20)} ${chalk18.dim(s.config_source.padEnd(10))}${detail}`);
|
|
2563
|
-
}
|
|
2564
|
-
console.log("");
|
|
2733
|
+
if (!tools?.length) {
|
|
2734
|
+
console.log(chalk17.yellow("No tools to fetch."));
|
|
2735
|
+
return;
|
|
2565
2736
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2737
|
+
console.log(chalk17.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
2738
|
+
`));
|
|
2739
|
+
const results = await Promise.all(tools.map(async (tool) => {
|
|
2740
|
+
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
2741
|
+
try {
|
|
2742
|
+
const { stable, beta } = await fetchVersions(source);
|
|
2743
|
+
const stableChanged = stable !== tool.latest_stable;
|
|
2744
|
+
const betaChanged = beta !== tool.latest_beta;
|
|
2745
|
+
const updates = {
|
|
2746
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2747
|
+
};
|
|
2748
|
+
if (stableChanged)
|
|
2749
|
+
updates.latest_stable = stable;
|
|
2750
|
+
if (betaChanged)
|
|
2751
|
+
updates.latest_beta = beta;
|
|
2752
|
+
const { error: updateError } = await supabase.from("tools_registry").update(updates).eq("id", tool.id);
|
|
2753
|
+
if (updateError) {
|
|
2754
|
+
return { displayName: tool.display_name, stable, beta, status: "failed" };
|
|
2755
|
+
}
|
|
2756
|
+
return {
|
|
2757
|
+
displayName: tool.display_name,
|
|
2758
|
+
stable,
|
|
2759
|
+
beta,
|
|
2760
|
+
status: stableChanged || betaChanged ? "updated" : "unchanged"
|
|
2761
|
+
};
|
|
2762
|
+
} catch {
|
|
2763
|
+
return { displayName: tool.display_name, stable: null, beta: null, status: "failed" };
|
|
2764
|
+
}
|
|
2765
|
+
}));
|
|
2766
|
+
const nameW = Math.max(16, ...results.map((r) => r.displayName.length));
|
|
2767
|
+
const stableW = Math.max(8, ...results.map((r) => (r.stable ?? "\u2014").length));
|
|
2768
|
+
const betaW = Math.max(8, ...results.map((r) => (r.beta ?? "\u2014").length));
|
|
2769
|
+
const statusW = 10;
|
|
2770
|
+
const header = [
|
|
2771
|
+
"Tool".padEnd(nameW),
|
|
2772
|
+
"Stable".padEnd(stableW),
|
|
2773
|
+
"Beta".padEnd(betaW),
|
|
2774
|
+
"Status".padEnd(statusW)
|
|
2775
|
+
].join(" ");
|
|
2776
|
+
console.log(chalk17.bold(header));
|
|
2777
|
+
console.log("\u2500".repeat(header.length));
|
|
2778
|
+
for (const result of results) {
|
|
2779
|
+
const statusColour = result.status === "updated" ? chalk17.green : result.status === "unchanged" ? chalk17.grey : result.status === "failed" ? chalk17.red : chalk17.yellow;
|
|
2780
|
+
const row = [
|
|
2781
|
+
result.displayName.padEnd(nameW),
|
|
2782
|
+
(result.stable ?? "\u2014").padEnd(stableW),
|
|
2783
|
+
(result.beta ?? "\u2014").padEnd(betaW),
|
|
2784
|
+
statusColour(result.status.padEnd(statusW))
|
|
2785
|
+
].join(" ");
|
|
2786
|
+
console.log(row);
|
|
2569
2787
|
}
|
|
2788
|
+
const updated = results.filter((r) => r.status === "updated").length;
|
|
2789
|
+
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
2790
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
2791
|
+
const noSource = results.filter((r) => r.status === "no source").length;
|
|
2792
|
+
console.log(chalk17.grey(`
|
|
2793
|
+
${results.length} tool(s): `) + chalk17.green(`${updated} updated`) + ", " + chalk17.grey(`${unchanged} unchanged`) + ", " + chalk17.red(`${failed} failed`) + ", " + chalk17.yellow(`${noSource} no source`));
|
|
2570
2794
|
}
|
|
2571
|
-
function
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
}
|
|
2580
|
-
async function mcpWatchCommand() {
|
|
2581
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
2582
|
-
const deviceId = await resolveDeviceId(supabase, userId);
|
|
2583
|
-
const deviceName = detectDeviceName();
|
|
2584
|
-
const myPid = process.pid;
|
|
2585
|
-
const myTty = detectTty();
|
|
2586
|
-
console.log(chalk18.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
2587
|
-
await supabase.from("mcp_watchers").upsert({
|
|
2588
|
-
device_id: deviceId,
|
|
2589
|
-
pid: myPid,
|
|
2590
|
-
tty: myTty,
|
|
2591
|
-
cli_version: CURRENT_VERSION,
|
|
2592
|
-
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2593
|
-
last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
|
|
2594
|
-
}, { onConflict: "device_id,pid" });
|
|
2595
|
-
const staleThreshold = new Date(Date.now() - 12e4).toISOString();
|
|
2596
|
-
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).lt("last_heartbeat", staleThreshold);
|
|
2597
|
-
async function cycle() {
|
|
2598
|
-
const configs = await readAllMcpConfigs();
|
|
2599
|
-
const rows = buildRows(configs);
|
|
2600
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2601
|
-
await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
|
|
2602
|
-
if (rows.length > 0) {
|
|
2603
|
-
await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
2604
|
-
device_id: deviceId,
|
|
2605
|
-
...row,
|
|
2606
|
-
checked_at: now
|
|
2607
|
-
})));
|
|
2795
|
+
async function fetchVersions(source) {
|
|
2796
|
+
const controller = new AbortController();
|
|
2797
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
2798
|
+
try {
|
|
2799
|
+
if (source.type === "npm") {
|
|
2800
|
+
return await fetchNpmVersions(source.package, controller.signal);
|
|
2801
|
+
} else {
|
|
2802
|
+
return await fetchGitHubVersions(source.repo, controller.signal);
|
|
2608
2803
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2804
|
+
} finally {
|
|
2805
|
+
clearTimeout(timeout);
|
|
2611
2806
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
const { data: dp } = await supabase.from("device_paths").select("path").eq("folder_id", state.lastFolderId).eq("device_name", deviceName).maybeSingle();
|
|
2619
|
-
if (!dp?.path)
|
|
2620
|
-
return;
|
|
2621
|
-
const envManifest = await scanEnvManifest(dp.path);
|
|
2622
|
-
if (!envManifest)
|
|
2623
|
-
return;
|
|
2624
|
-
const hash = createHash2("sha256").update(JSON.stringify(envManifest)).digest("hex");
|
|
2625
|
-
if (hash === lastEnvHash)
|
|
2626
|
-
return;
|
|
2627
|
-
lastEnvHash = hash;
|
|
2628
|
-
await supabase.from("claude_folders").update({ env_manifest_json: envManifest }).eq("id", state.lastFolderId);
|
|
2807
|
+
}
|
|
2808
|
+
async function fetchNpmVersions(packageName, signal) {
|
|
2809
|
+
const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(packageName)}/dist-tags`;
|
|
2810
|
+
const res = await fetch(url, { signal });
|
|
2811
|
+
if (!res.ok) {
|
|
2812
|
+
throw new Error(`npm registry returned ${res.status}`);
|
|
2629
2813
|
}
|
|
2630
|
-
await
|
|
2631
|
-
const
|
|
2632
|
-
const
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
process.on("SIGINT", () => {
|
|
2641
|
-
void shutdown();
|
|
2642
|
-
});
|
|
2643
|
-
process.on("SIGTERM", () => {
|
|
2644
|
-
void shutdown();
|
|
2814
|
+
const tags = await res.json();
|
|
2815
|
+
const stable = tags.latest ?? null;
|
|
2816
|
+
const beta = tags.next ?? tags.beta ?? tags.rc ?? null;
|
|
2817
|
+
return { stable, beta };
|
|
2818
|
+
}
|
|
2819
|
+
async function fetchGitHubVersions(repo, signal) {
|
|
2820
|
+
const url = `https://api.github.com/repos/${repo}/releases?per_page=20`;
|
|
2821
|
+
const res = await fetch(url, {
|
|
2822
|
+
signal,
|
|
2823
|
+
headers: { Accept: "application/vnd.github+json" }
|
|
2645
2824
|
});
|
|
2825
|
+
if (!res.ok) {
|
|
2826
|
+
throw new Error(`GitHub API returned ${res.status}`);
|
|
2827
|
+
}
|
|
2828
|
+
const releases = await res.json();
|
|
2829
|
+
let stable = null;
|
|
2830
|
+
let beta = null;
|
|
2831
|
+
for (const release of releases) {
|
|
2832
|
+
if (release.draft)
|
|
2833
|
+
continue;
|
|
2834
|
+
const version = release.tag_name.replace(/^v/, "");
|
|
2835
|
+
if (!release.prerelease && !stable) {
|
|
2836
|
+
stable = version;
|
|
2837
|
+
}
|
|
2838
|
+
if (release.prerelease && !beta) {
|
|
2839
|
+
beta = version;
|
|
2840
|
+
}
|
|
2841
|
+
if (stable && beta)
|
|
2842
|
+
break;
|
|
2843
|
+
}
|
|
2844
|
+
return { stable, beta };
|
|
2646
2845
|
}
|
|
2647
2846
|
|
|
2847
|
+
// dist/index.js
|
|
2848
|
+
init_mcp_watch();
|
|
2849
|
+
|
|
2648
2850
|
// dist/commands/init-manifest.js
|
|
2649
2851
|
import { resolve as resolve5, join as join13, relative as relative3, dirname as dirname3 } from "node:path";
|
|
2650
2852
|
import { readFile as readFile9, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
@@ -2770,14 +2972,132 @@ Manifest created: ${relative3(projectRoot, manifestPath)}`));
|
|
|
2770
2972
|
console.log(chalk19.dim("Then run `md4ai scan` to verify against your environments."));
|
|
2771
2973
|
}
|
|
2772
2974
|
|
|
2975
|
+
// dist/commands/update.js
|
|
2976
|
+
init_check_update();
|
|
2977
|
+
init_config();
|
|
2978
|
+
import chalk20 from "chalk";
|
|
2979
|
+
import { execFileSync as execFileSync6, spawn } from "node:child_process";
|
|
2980
|
+
async function fetchLatestVersion() {
|
|
2981
|
+
try {
|
|
2982
|
+
const controller = new AbortController();
|
|
2983
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
2984
|
+
const res = await fetch("https://registry.npmjs.org/md4ai/latest", {
|
|
2985
|
+
signal: controller.signal
|
|
2986
|
+
});
|
|
2987
|
+
clearTimeout(timeout);
|
|
2988
|
+
if (!res.ok)
|
|
2989
|
+
return null;
|
|
2990
|
+
const data = await res.json();
|
|
2991
|
+
return data.version ?? null;
|
|
2992
|
+
} catch {
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
function isNewer2(a, b) {
|
|
2997
|
+
const pa = a.split(".").map(Number);
|
|
2998
|
+
const pb = b.split(".").map(Number);
|
|
2999
|
+
for (let i = 0; i < 3; i++) {
|
|
3000
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
3001
|
+
return true;
|
|
3002
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
return false;
|
|
3006
|
+
}
|
|
3007
|
+
function runNpmInstall() {
|
|
3008
|
+
try {
|
|
3009
|
+
execFileSync6("npm", ["install", "-g", "md4ai"], {
|
|
3010
|
+
stdio: "inherit",
|
|
3011
|
+
timeout: 6e4
|
|
3012
|
+
});
|
|
3013
|
+
return true;
|
|
3014
|
+
} catch {
|
|
3015
|
+
return false;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
function spawnPostUpdate() {
|
|
3019
|
+
const child = spawn("md4ai", ["update", "--post-update"], {
|
|
3020
|
+
stdio: "inherit",
|
|
3021
|
+
shell: false
|
|
3022
|
+
});
|
|
3023
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
3024
|
+
}
|
|
3025
|
+
async function postUpdateFlow() {
|
|
3026
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3027
|
+
console.log(chalk20.green(`
|
|
3028
|
+
md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
|
|
3029
|
+
`));
|
|
3030
|
+
const state = await loadState();
|
|
3031
|
+
const hasProject = !!state.lastFolderId;
|
|
3032
|
+
if (hasProject) {
|
|
3033
|
+
const wantScan = await confirm2({
|
|
3034
|
+
message: "Rescan your project and refresh the dashboard?",
|
|
3035
|
+
default: true
|
|
3036
|
+
});
|
|
3037
|
+
if (wantScan) {
|
|
3038
|
+
console.log("");
|
|
3039
|
+
const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
3040
|
+
await syncCommand2({ all: false });
|
|
3041
|
+
console.log("");
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
const wantWatch = await confirm2({
|
|
3045
|
+
message: "Start monitoring MCP servers? (runs until you press Ctrl+C)",
|
|
3046
|
+
default: true
|
|
3047
|
+
});
|
|
3048
|
+
if (wantWatch) {
|
|
3049
|
+
console.log("");
|
|
3050
|
+
const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
|
|
3051
|
+
await mcpWatchCommand2();
|
|
3052
|
+
} else {
|
|
3053
|
+
console.log(chalk20.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
async function updateCommand(options) {
|
|
3057
|
+
if (options.postUpdate) {
|
|
3058
|
+
await postUpdateFlow();
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
console.log(chalk20.blue(`
|
|
3062
|
+
Current version: v${CURRENT_VERSION}`));
|
|
3063
|
+
console.log(chalk20.dim(" Checking for updates...\n"));
|
|
3064
|
+
const latest = await fetchLatestVersion();
|
|
3065
|
+
if (!latest) {
|
|
3066
|
+
console.log(chalk20.yellow(" Could not reach the npm registry. Check your internet connection."));
|
|
3067
|
+
process.exit(1);
|
|
3068
|
+
}
|
|
3069
|
+
if (!isNewer2(latest, CURRENT_VERSION)) {
|
|
3070
|
+
await postUpdateFlow();
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
console.log(chalk20.white(" Update available: ") + chalk20.dim(`v${CURRENT_VERSION}`) + chalk20.white(" \u2192 ") + chalk20.green.bold(`v${latest}
|
|
3074
|
+
`));
|
|
3075
|
+
const { confirm: confirm2 } = await import("@inquirer/prompts");
|
|
3076
|
+
const wantUpdate = await confirm2({
|
|
3077
|
+
message: `Install md4ai v${latest}?`,
|
|
3078
|
+
default: true
|
|
3079
|
+
});
|
|
3080
|
+
if (!wantUpdate) {
|
|
3081
|
+
console.log(chalk20.dim("\nUpdate skipped.\n"));
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
console.log(chalk20.blue("\n Installing...\n"));
|
|
3085
|
+
const ok = runNpmInstall();
|
|
3086
|
+
if (!ok) {
|
|
3087
|
+
console.log(chalk20.red("\n npm install failed. Try running manually:"));
|
|
3088
|
+
console.log(chalk20.cyan(" npm install -g md4ai\n"));
|
|
3089
|
+
process.exit(1);
|
|
3090
|
+
}
|
|
3091
|
+
console.log(chalk20.green("\n Updated successfully.\n"));
|
|
3092
|
+
spawnPostUpdate();
|
|
3093
|
+
}
|
|
3094
|
+
|
|
2773
3095
|
// dist/index.js
|
|
3096
|
+
init_check_update();
|
|
2774
3097
|
var program = new Command();
|
|
2775
3098
|
program.name("md4ai").description("MD4AI \u2014 Claude tooling visualiser").version(CURRENT_VERSION).addHelpText("after", `
|
|
2776
3099
|
Update to the latest version:
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
Check for updates:
|
|
2780
|
-
md4ai check-update`);
|
|
3100
|
+
md4ai update`);
|
|
2781
3101
|
program.command("login").description("Log in to MD4AI with email and password").action(loginCommand);
|
|
2782
3102
|
program.command("logout").description("Log out and clear stored credentials").action(logoutCommand);
|
|
2783
3103
|
program.command("status").description("Show login status, device count, folder count, last sync").action(statusCommand);
|
|
@@ -2790,6 +3110,7 @@ program.command("simulate <prompt>").description("Show which files Claude would
|
|
|
2790
3110
|
program.command("print <title>").description("Generate a printable wall-chart HTML from the last scan").action(printCommand);
|
|
2791
3111
|
program.command("sync").description("Re-push latest scan data to Supabase").option("--all", "Sync all folders on all devices").action(syncCommand);
|
|
2792
3112
|
program.command("import <zipfile>").description("Import a Claude setup bundle exported from the web dashboard").action(importBundleCommand);
|
|
3113
|
+
program.command("update").description("Check for updates, install, and optionally rescan and start watching").option("--post-update", "Internal: run post-update flow (called by the update command)").action(updateCommand);
|
|
2793
3114
|
program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
|
|
2794
3115
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
2795
3116
|
program.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
|
|
@@ -2798,3 +3119,8 @@ admin.command("update-tool").description("Add or update a tool in the master reg
|
|
|
2798
3119
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|
|
2799
3120
|
admin.command("fetch-versions").description("Fetch latest stable and beta versions from npm and GitHub").action(adminFetchVersionsCommand);
|
|
2800
3121
|
program.parse();
|
|
3122
|
+
var ran = program.args[0];
|
|
3123
|
+
var skipAutoCheck = ["update", "check-update", "mcp-watch"];
|
|
3124
|
+
if (!skipAutoCheck.includes(ran)) {
|
|
3125
|
+
autoCheckForUpdate();
|
|
3126
|
+
}
|