md4ai 0.10.0 → 0.10.2
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 +133 -5
- package/package.json +5 -5
package/dist/index.bundled.js
CHANGED
|
@@ -45,6 +45,23 @@ function createSupabaseClient(anonKey, accessToken) {
|
|
|
45
45
|
const options = accessToken ? { global: { headers: { Authorization: `Bearer ${accessToken}` } } } : {};
|
|
46
46
|
return createClient(SUPABASE_URL, anonKey, options);
|
|
47
47
|
}
|
|
48
|
+
async function createSupabaseClientWithSession(anonKey, accessToken, refreshToken) {
|
|
49
|
+
const client = createClient(SUPABASE_URL, anonKey);
|
|
50
|
+
const { data, error } = await client.auth.setSession({
|
|
51
|
+
access_token: accessToken,
|
|
52
|
+
refresh_token: refreshToken
|
|
53
|
+
});
|
|
54
|
+
if (error || !data.session) {
|
|
55
|
+
throw new Error(error?.message ?? "Failed to establish session");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
client,
|
|
59
|
+
userId: data.session.user.id,
|
|
60
|
+
newAccessToken: data.session.access_token,
|
|
61
|
+
newRefreshToken: data.session.refresh_token,
|
|
62
|
+
expiresAt: Date.now() + data.session.expires_in * 1e3
|
|
63
|
+
};
|
|
64
|
+
}
|
|
48
65
|
var init_supabase = __esm({
|
|
49
66
|
"../packages/shared/dist/supabase.js"() {
|
|
50
67
|
"use strict";
|
|
@@ -192,6 +209,51 @@ async function getAuthenticatedClient() {
|
|
|
192
209
|
const supabase = createSupabaseClient(anonKey, creds.accessToken);
|
|
193
210
|
return { supabase, userId: creds.userId };
|
|
194
211
|
}
|
|
212
|
+
async function refreshSession() {
|
|
213
|
+
const creds = await loadCredentials();
|
|
214
|
+
if (!creds?.refreshToken)
|
|
215
|
+
return null;
|
|
216
|
+
try {
|
|
217
|
+
const anonKey = getAnonKey();
|
|
218
|
+
const result = await createSupabaseClientWithSession(anonKey, creds.accessToken, creds.refreshToken);
|
|
219
|
+
await saveCredentials({
|
|
220
|
+
...creds,
|
|
221
|
+
accessToken: result.newAccessToken,
|
|
222
|
+
refreshToken: result.newRefreshToken,
|
|
223
|
+
expiresAt: result.expiresAt
|
|
224
|
+
});
|
|
225
|
+
return { supabase: result.client, userId: result.userId };
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function getLongLivedClient() {
|
|
231
|
+
const creds = await loadCredentials();
|
|
232
|
+
if (!creds?.accessToken || !creds?.refreshToken) {
|
|
233
|
+
console.log(chalk4.yellow("Not logged in."));
|
|
234
|
+
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
235
|
+
if (!shouldLogin)
|
|
236
|
+
process.exit(0);
|
|
237
|
+
return promptLogin();
|
|
238
|
+
}
|
|
239
|
+
const anonKey = getAnonKey();
|
|
240
|
+
try {
|
|
241
|
+
const result = await createSupabaseClientWithSession(anonKey, creds.accessToken, creds.refreshToken);
|
|
242
|
+
await saveCredentials({
|
|
243
|
+
...creds,
|
|
244
|
+
accessToken: result.newAccessToken,
|
|
245
|
+
refreshToken: result.newRefreshToken,
|
|
246
|
+
expiresAt: result.expiresAt
|
|
247
|
+
});
|
|
248
|
+
return { supabase: result.client, userId: result.userId };
|
|
249
|
+
} catch {
|
|
250
|
+
console.log(chalk4.yellow("Session expired \u2014 please log in again."));
|
|
251
|
+
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
252
|
+
if (!shouldLogin)
|
|
253
|
+
process.exit(0);
|
|
254
|
+
return promptLogin();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
195
257
|
async function promptLogin() {
|
|
196
258
|
console.log("");
|
|
197
259
|
const email = await input2({ message: "Email:" });
|
|
@@ -1723,7 +1785,7 @@ var CURRENT_VERSION;
|
|
|
1723
1785
|
var init_check_update = __esm({
|
|
1724
1786
|
"dist/check-update.js"() {
|
|
1725
1787
|
"use strict";
|
|
1726
|
-
CURRENT_VERSION = true ? "0.10.
|
|
1788
|
+
CURRENT_VERSION = true ? "0.10.2" : "0.0.0-dev";
|
|
1727
1789
|
}
|
|
1728
1790
|
});
|
|
1729
1791
|
|
|
@@ -2687,8 +2749,14 @@ function formatUptime(seconds) {
|
|
|
2687
2749
|
const m = Math.floor(seconds % 3600 / 60);
|
|
2688
2750
|
return `${h}h ${m}m`;
|
|
2689
2751
|
}
|
|
2752
|
+
function isJwtError(error) {
|
|
2753
|
+
if (!error?.message)
|
|
2754
|
+
return false;
|
|
2755
|
+
const msg = error.message.toLowerCase();
|
|
2756
|
+
return msg.includes("jwt expired") || msg.includes("invalid jwt") || msg.includes("invalid token") || msg.includes("not authenticated") || msg.includes("invalid refresh_token");
|
|
2757
|
+
}
|
|
2690
2758
|
async function mcpWatchCommand() {
|
|
2691
|
-
|
|
2759
|
+
let { supabase, userId } = await getLongLivedClient();
|
|
2692
2760
|
const deviceId = await resolveDeviceId(supabase, userId);
|
|
2693
2761
|
const deviceName = detectDeviceName();
|
|
2694
2762
|
const myPid = process.pid;
|
|
@@ -2740,6 +2808,10 @@ async function mcpWatchCommand() {
|
|
|
2740
2808
|
const rows = buildRows(configs, httpResults, cdpStatus);
|
|
2741
2809
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2742
2810
|
const { error: deleteError } = await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
|
|
2811
|
+
if (deleteError && isJwtError(deleteError)) {
|
|
2812
|
+
await handleJwtExpiry();
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2743
2815
|
if (deleteError) {
|
|
2744
2816
|
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
2745
2817
|
}
|
|
@@ -2749,13 +2821,69 @@ async function mcpWatchCommand() {
|
|
|
2749
2821
|
...row,
|
|
2750
2822
|
checked_at: now
|
|
2751
2823
|
})));
|
|
2824
|
+
if (insertError && isJwtError(insertError)) {
|
|
2825
|
+
await handleJwtExpiry();
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2752
2828
|
if (insertError) {
|
|
2753
2829
|
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
2754
2830
|
}
|
|
2755
2831
|
}
|
|
2756
|
-
await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
2832
|
+
const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
2833
|
+
if (heartbeatError && isJwtError(heartbeatError)) {
|
|
2834
|
+
await handleJwtExpiry();
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2757
2837
|
printTable(rows, deviceName, myPid, cdpStatus);
|
|
2758
2838
|
}
|
|
2839
|
+
let jwtRefreshAttempted = false;
|
|
2840
|
+
async function handleJwtExpiry() {
|
|
2841
|
+
if (jwtRefreshAttempted) {
|
|
2842
|
+
printSessionExpired();
|
|
2843
|
+
await forceShutdown();
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
jwtRefreshAttempted = true;
|
|
2847
|
+
console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
|
|
2848
|
+
const refreshed = await refreshSession();
|
|
2849
|
+
if (refreshed) {
|
|
2850
|
+
supabase = refreshed.supabase;
|
|
2851
|
+
console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
|
|
2852
|
+
jwtRefreshAttempted = false;
|
|
2853
|
+
await supabase.from("mcp_watchers").upsert({
|
|
2854
|
+
device_id: deviceId,
|
|
2855
|
+
pid: myPid,
|
|
2856
|
+
tty: myTty,
|
|
2857
|
+
cli_version: CURRENT_VERSION,
|
|
2858
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2859
|
+
last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
|
|
2860
|
+
}, { onConflict: "device_id,pid" });
|
|
2861
|
+
} else {
|
|
2862
|
+
printSessionExpired();
|
|
2863
|
+
await forceShutdown();
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
function printSessionExpired() {
|
|
2867
|
+
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
2868
|
+
console.log("");
|
|
2869
|
+
console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
|
|
2870
|
+
console.log("");
|
|
2871
|
+
console.log(chalk20.yellow(" Your authentication token has expired and could not be refreshed."));
|
|
2872
|
+
console.log(chalk20.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
|
|
2873
|
+
console.log("");
|
|
2874
|
+
console.log(chalk20.white(" To fix this:"));
|
|
2875
|
+
console.log(chalk20.cyan(" 1. Run: md4ai login"));
|
|
2876
|
+
console.log(chalk20.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
|
|
2877
|
+
console.log("");
|
|
2878
|
+
}
|
|
2879
|
+
let interval;
|
|
2880
|
+
let envInterval;
|
|
2881
|
+
async function forceShutdown() {
|
|
2882
|
+
clearInterval(interval);
|
|
2883
|
+
clearInterval(envInterval);
|
|
2884
|
+
process.stdout.write(`\x1B]0;MCP mon (EXPIRED)\x07`);
|
|
2885
|
+
process.exit(1);
|
|
2886
|
+
}
|
|
2759
2887
|
await cycle();
|
|
2760
2888
|
let lastEnvHash = "";
|
|
2761
2889
|
async function envCycle() {
|
|
@@ -2775,7 +2903,7 @@ async function mcpWatchCommand() {
|
|
|
2775
2903
|
await supabase.from("claude_folders").update({ env_manifest_json: envManifest }).eq("id", state.lastFolderId);
|
|
2776
2904
|
}
|
|
2777
2905
|
await envCycle();
|
|
2778
|
-
|
|
2906
|
+
envInterval = setInterval(envCycle, ENV_POLL_INTERVAL_MS);
|
|
2779
2907
|
async function rescanCycle() {
|
|
2780
2908
|
await checkPendingRescans(supabase, deviceId, deviceName);
|
|
2781
2909
|
}
|
|
@@ -2783,7 +2911,7 @@ async function mcpWatchCommand() {
|
|
|
2783
2911
|
await cycle();
|
|
2784
2912
|
await rescanCycle();
|
|
2785
2913
|
}
|
|
2786
|
-
|
|
2914
|
+
interval = setInterval(fullCycle, POLL_INTERVAL_MS);
|
|
2787
2915
|
const shutdown = async () => {
|
|
2788
2916
|
clearInterval(interval);
|
|
2789
2917
|
clearInterval(envInterval);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md4ai",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"node": ">=22"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@inquirer/prompts": "^
|
|
36
|
+
"@inquirer/prompts": "^8.3.0",
|
|
37
37
|
"@supabase/supabase-js": "^2.98.0",
|
|
38
|
-
"chalk": "^5.
|
|
39
|
-
"commander": "^14.0.
|
|
38
|
+
"chalk": "^5.6.2",
|
|
39
|
+
"commander": "^14.0.3",
|
|
40
40
|
"jszip": "^3.10.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@md4ai/shared": "workspace:*",
|
|
44
|
-
"@types/node": "^22.
|
|
44
|
+
"@types/node": "^22.15.31",
|
|
45
45
|
"esbuild": "^0.27.3",
|
|
46
46
|
"typescript": "^5.7.0"
|
|
47
47
|
}
|