metheus-governance-mcp-cli 0.2.10 → 0.2.12
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/cli.mjs +216 -33
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -14,12 +14,17 @@ const DEFAULT_SITE_URL = "https://metheus.gesiaplatform.com";
|
|
|
14
14
|
const DEFAULT_BASE_URL = `${DEFAULT_SITE_URL}/governance/mcp`;
|
|
15
15
|
const DEFAULT_SERVER_NAME = "metheus-governance-mcp";
|
|
16
16
|
const AUTH_STORE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-auth.json");
|
|
17
|
+
const SELF_UPDATE_STATE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-cli-update.json");
|
|
17
18
|
const CTXPACK_CACHE_RELATIVE_DIR = path.join(".metheus", "ctxpack-cache");
|
|
18
19
|
const CTXPACK_META_FILENAME = ".metheus_ctxpack_sync.json";
|
|
19
20
|
const CTXPACK_PUSH_TOOL_NAMES = ["ctxpack.push", "ctxpack.update", "ctxpack.save"];
|
|
20
21
|
const CLI_META = loadCLIMeta();
|
|
21
22
|
const CLI_NAME = CLI_META.name || "metheus-governance-mcp-cli";
|
|
22
23
|
const CLI_VERSION = CLI_META.version || "0.0.0";
|
|
24
|
+
const SELF_UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
25
|
+
const SELF_UPDATE_TIMEOUT_SECONDS = 6;
|
|
26
|
+
const SELF_UPDATE_ENV_KEY = "METHEUS_CLI_AUTO_UPDATE";
|
|
27
|
+
const SELF_UPDATE_GUARD_ENV_KEY = "METHEUS_CLI_SELF_UPDATE_ATTEMPTED";
|
|
23
28
|
const AUTO_CTXPACK_SYNC_INTERVAL_MS = 60 * 1000;
|
|
24
29
|
const autoCtxpackSyncTracker = new Map();
|
|
25
30
|
|
|
@@ -44,6 +49,7 @@ function printUsage() {
|
|
|
44
49
|
"",
|
|
45
50
|
"Environment:",
|
|
46
51
|
" METHEUS_TOKEN or MCP_AUTH_TOKEN is used first for proxy requests.",
|
|
52
|
+
` ${SELF_UPDATE_ENV_KEY}=0 to disable startup auto-update check.`,
|
|
47
53
|
" If env is missing, stored token file is used:",
|
|
48
54
|
` ${AUTH_STORE_RELATIVE_PATH}`,
|
|
49
55
|
"",
|
|
@@ -69,6 +75,176 @@ function printVersion() {
|
|
|
69
75
|
process.stdout.write(`${CLI_NAME} ${CLI_VERSION}\n`);
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
function resolveHomeFilePath(relativePath) {
|
|
79
|
+
const home = String(process.env.USERPROFILE || process.env.HOME || "").trim();
|
|
80
|
+
if (!home) {
|
|
81
|
+
return path.resolve(process.cwd(), relativePath);
|
|
82
|
+
}
|
|
83
|
+
return path.join(home, relativePath);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function updateStateFilePath() {
|
|
87
|
+
return resolveHomeFilePath(SELF_UPDATE_STATE_RELATIVE_PATH);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function loadSelfUpdateState() {
|
|
91
|
+
const filePath = updateStateFilePath();
|
|
92
|
+
try {
|
|
93
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
96
|
+
return { filePath, checkedAt: "", latestVersion: "", installedVersion: "", updatedAt: "" };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
filePath,
|
|
100
|
+
checkedAt: String(parsed.checked_at || "").trim(),
|
|
101
|
+
latestVersion: String(parsed.latest_version || "").trim(),
|
|
102
|
+
installedVersion: String(parsed.installed_version || "").trim(),
|
|
103
|
+
updatedAt: String(parsed.updated_at || "").trim(),
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return { filePath, checkedAt: "", latestVersion: "", installedVersion: "", updatedAt: "" };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function saveSelfUpdateState(nextState) {
|
|
111
|
+
const filePath = updateStateFilePath();
|
|
112
|
+
const payload = {
|
|
113
|
+
package_name: CLI_NAME,
|
|
114
|
+
checked_at: String(nextState?.checkedAt || "").trim() || new Date().toISOString(),
|
|
115
|
+
latest_version: String(nextState?.latestVersion || "").trim(),
|
|
116
|
+
installed_version: String(nextState?.installedVersion || "").trim(),
|
|
117
|
+
updated_at: String(nextState?.updatedAt || "").trim(),
|
|
118
|
+
};
|
|
119
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
120
|
+
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseSemver(rawVersion) {
|
|
124
|
+
const match = String(rawVersion || "")
|
|
125
|
+
.trim()
|
|
126
|
+
.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
127
|
+
if (!match) return null;
|
|
128
|
+
return {
|
|
129
|
+
major: Number.parseInt(match[1], 10),
|
|
130
|
+
minor: Number.parseInt(match[2], 10),
|
|
131
|
+
patch: Number.parseInt(match[3], 10),
|
|
132
|
+
prerelease: String(match[4] || "").trim(),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function compareSemver(leftRaw, rightRaw) {
|
|
137
|
+
const left = parseSemver(leftRaw);
|
|
138
|
+
const right = parseSemver(rightRaw);
|
|
139
|
+
if (!left || !right) {
|
|
140
|
+
return String(leftRaw || "").localeCompare(String(rightRaw || ""));
|
|
141
|
+
}
|
|
142
|
+
if (left.major !== right.major) return left.major - right.major;
|
|
143
|
+
if (left.minor !== right.minor) return left.minor - right.minor;
|
|
144
|
+
if (left.patch !== right.patch) return left.patch - right.patch;
|
|
145
|
+
const leftPre = left.prerelease;
|
|
146
|
+
const rightPre = right.prerelease;
|
|
147
|
+
if (leftPre === rightPre) return 0;
|
|
148
|
+
if (!leftPre) return 1;
|
|
149
|
+
if (!rightPre) return -1;
|
|
150
|
+
return leftPre.localeCompare(rightPre);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function hasNoUpdateFlag(argv) {
|
|
154
|
+
return argv.some((arg) => String(arg || "").trim().toLowerCase() === "--no-update");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseIsoMs(raw) {
|
|
158
|
+
const parsed = Date.parse(String(raw || "").trim());
|
|
159
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 0;
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function fetchLatestCLIVersion(timeoutSeconds = SELF_UPDATE_TIMEOUT_SECONDS) {
|
|
164
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(CLI_NAME)}/latest`;
|
|
165
|
+
const body = await getJSON(url, timeoutSeconds);
|
|
166
|
+
return String(body?.version || "").trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function applyRerunExitResult(runResult) {
|
|
170
|
+
if (runResult?.error) {
|
|
171
|
+
process.stderr.write(`[${CLI_NAME}] restart failed: ${String(runResult.error.message || runResult.error)}\n`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (typeof runResult?.status === "number") {
|
|
175
|
+
process.exit(runResult.status);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (runResult?.signal) {
|
|
179
|
+
process.kill(process.pid, runResult.signal);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function maybeAutoUpdate(commandToken, argv) {
|
|
186
|
+
const command = String(commandToken || "").trim().toLowerCase();
|
|
187
|
+
if (command === "proxy") return;
|
|
188
|
+
if (command === "-v" || command === "--version" || command === "version") return;
|
|
189
|
+
if (command === "-h" || command === "--help") return;
|
|
190
|
+
if (String(process.env[SELF_UPDATE_GUARD_ENV_KEY] || "").trim() === "1") return;
|
|
191
|
+
if (hasNoUpdateFlag(argv)) return;
|
|
192
|
+
if (!boolFromRaw(process.env[SELF_UPDATE_ENV_KEY], true)) return;
|
|
193
|
+
|
|
194
|
+
const nowIso = new Date().toISOString();
|
|
195
|
+
const cached = loadSelfUpdateState();
|
|
196
|
+
let latestVersion = "";
|
|
197
|
+
const checkedMs = parseIsoMs(cached.checkedAt);
|
|
198
|
+
if (checkedMs > 0 && Date.now() - checkedMs < SELF_UPDATE_CHECK_INTERVAL_MS) {
|
|
199
|
+
latestVersion = cached.latestVersion;
|
|
200
|
+
}
|
|
201
|
+
if (!latestVersion) {
|
|
202
|
+
try {
|
|
203
|
+
latestVersion = await fetchLatestCLIVersion(SELF_UPDATE_TIMEOUT_SECONDS);
|
|
204
|
+
} catch {
|
|
205
|
+
latestVersion = "";
|
|
206
|
+
}
|
|
207
|
+
saveSelfUpdateState({
|
|
208
|
+
checkedAt: nowIso,
|
|
209
|
+
latestVersion,
|
|
210
|
+
installedVersion: CLI_VERSION,
|
|
211
|
+
updatedAt: cached.updatedAt,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!latestVersion || compareSemver(latestVersion, CLI_VERSION) <= 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
process.stderr.write(`[${CLI_NAME}] update available (${CLI_VERSION} -> ${latestVersion}). Installing...\n`);
|
|
220
|
+
const run = runCLICommand("npm", ["install", "-g", `${CLI_NAME}@latest`], {
|
|
221
|
+
stdio: "inherit",
|
|
222
|
+
});
|
|
223
|
+
if (run.status !== 0) {
|
|
224
|
+
process.stderr.write(`[${CLI_NAME}] auto-update failed. Continue with current version ${CLI_VERSION}.\n`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const updatedAt = new Date().toISOString();
|
|
229
|
+
saveSelfUpdateState({
|
|
230
|
+
checkedAt: updatedAt,
|
|
231
|
+
latestVersion,
|
|
232
|
+
installedVersion: latestVersion,
|
|
233
|
+
updatedAt,
|
|
234
|
+
});
|
|
235
|
+
process.stderr.write(`[${CLI_NAME}] updated to ${latestVersion}. Restarting...\n`);
|
|
236
|
+
|
|
237
|
+
const selfPath = fileURLToPath(import.meta.url);
|
|
238
|
+
const rerun = spawnSync(process.execPath, [selfPath, ...argv], {
|
|
239
|
+
stdio: "inherit",
|
|
240
|
+
env: {
|
|
241
|
+
...process.env,
|
|
242
|
+
[SELF_UPDATE_GUARD_ENV_KEY]: "1",
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
applyRerunExitResult(rerun);
|
|
246
|
+
}
|
|
247
|
+
|
|
72
248
|
function parseArgs(argv) {
|
|
73
249
|
const out = {};
|
|
74
250
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -98,11 +274,7 @@ function normalizeToken(raw) {
|
|
|
98
274
|
}
|
|
99
275
|
|
|
100
276
|
function authStoreFilePath() {
|
|
101
|
-
|
|
102
|
-
if (!home) {
|
|
103
|
-
return path.resolve(process.cwd(), AUTH_STORE_RELATIVE_PATH);
|
|
104
|
-
}
|
|
105
|
-
return path.join(home, AUTH_STORE_RELATIVE_PATH);
|
|
277
|
+
return resolveHomeFilePath(AUTH_STORE_RELATIVE_PATH);
|
|
106
278
|
}
|
|
107
279
|
|
|
108
280
|
function resolveWorkspaceDir(rawPath) {
|
|
@@ -140,12 +312,33 @@ function firstNonEmptyString(values) {
|
|
|
140
312
|
return "";
|
|
141
313
|
}
|
|
142
314
|
|
|
315
|
+
function isEditorInstallDirectory(candidatePath) {
|
|
316
|
+
const normalized = String(candidatePath || "").replace(/\//g, "\\").toLowerCase();
|
|
317
|
+
if (!normalized) return false;
|
|
318
|
+
// Guard against runtime cwd/env resolving to editor installation directory
|
|
319
|
+
// instead of the actual opened workspace.
|
|
320
|
+
if (normalized.includes("\\appdata\\local\\programs\\microsoft vs code")) return true;
|
|
321
|
+
if (normalized.includes("\\program files\\microsoft vs code")) return true;
|
|
322
|
+
if (normalized.includes("\\program files (x86)\\microsoft vs code")) return true;
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function sanitizeWorkspaceCandidate(rawCandidate) {
|
|
327
|
+
const fileCandidate = fileURIToLocalPath(rawCandidate);
|
|
328
|
+
const candidate = firstNonEmptyString([fileCandidate, rawCandidate]);
|
|
329
|
+
if (!candidate) return "";
|
|
330
|
+
const resolved = resolveWorkspaceDir(candidate);
|
|
331
|
+
if (!resolved) return "";
|
|
332
|
+
if (isEditorInstallDirectory(resolved)) return "";
|
|
333
|
+
return resolved;
|
|
334
|
+
}
|
|
335
|
+
|
|
143
336
|
function extractWorkspaceCandidateFromFolders(rawFolders) {
|
|
144
337
|
if (!Array.isArray(rawFolders)) return "";
|
|
145
338
|
for (const folder of rawFolders) {
|
|
146
339
|
if (typeof folder === "string") {
|
|
147
|
-
const direct =
|
|
148
|
-
if (direct) return
|
|
340
|
+
const direct = sanitizeWorkspaceCandidate(folder);
|
|
341
|
+
if (direct) return direct;
|
|
149
342
|
continue;
|
|
150
343
|
}
|
|
151
344
|
if (!folder || typeof folder !== "object" || Array.isArray(folder)) continue;
|
|
@@ -157,8 +350,8 @@ function extractWorkspaceCandidateFromFolders(rawFolders) {
|
|
|
157
350
|
folder.root_path,
|
|
158
351
|
folder.rootPath,
|
|
159
352
|
]);
|
|
160
|
-
const candidate =
|
|
161
|
-
if (candidate) return
|
|
353
|
+
const candidate = sanitizeWorkspaceCandidate(uriValue);
|
|
354
|
+
if (candidate) return candidate;
|
|
162
355
|
}
|
|
163
356
|
return "";
|
|
164
357
|
}
|
|
@@ -192,10 +385,7 @@ function extractWorkspaceCandidateFromRequest(requestObj, toolArgs) {
|
|
|
192
385
|
meta.root_uri,
|
|
193
386
|
meta.rootUri,
|
|
194
387
|
]);
|
|
195
|
-
|
|
196
|
-
const candidate = firstNonEmptyString([fileCandidate, rawCandidate]);
|
|
197
|
-
if (!candidate) return "";
|
|
198
|
-
return resolveWorkspaceDir(candidate);
|
|
388
|
+
return sanitizeWorkspaceCandidate(rawCandidate);
|
|
199
389
|
}
|
|
200
390
|
|
|
201
391
|
function extractWorkspaceCandidateFromEnv() {
|
|
@@ -208,26 +398,22 @@ function extractWorkspaceCandidateFromEnv() {
|
|
|
208
398
|
process.env.WORKSPACE_DIR,
|
|
209
399
|
process.env.WORKSPACE_FOLDER,
|
|
210
400
|
process.env.VSCODE_WORKSPACE_FOLDER,
|
|
211
|
-
process.env.VSCODE_CWD,
|
|
212
401
|
process.env.PWD,
|
|
213
402
|
process.env.INIT_CWD,
|
|
403
|
+
process.env.VSCODE_CWD,
|
|
214
404
|
]);
|
|
215
|
-
|
|
216
|
-
const candidate = firstNonEmptyString([fileCandidate, rawCandidate]);
|
|
217
|
-
if (!candidate) return "";
|
|
218
|
-
return resolveWorkspaceDir(candidate);
|
|
405
|
+
return sanitizeWorkspaceCandidate(rawCandidate);
|
|
219
406
|
}
|
|
220
407
|
|
|
221
408
|
function resolveWorkspaceDirForRequest(defaultWorkspaceDir, requestObj, toolArgs) {
|
|
222
409
|
const requestCandidate = extractWorkspaceCandidateFromRequest(requestObj, toolArgs);
|
|
223
410
|
const envCandidate = extractWorkspaceCandidateFromEnv();
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return resolveWorkspaceDir(candidate);
|
|
411
|
+
const homeCandidate = firstNonEmptyString([process.env.USERPROFILE, process.env.HOME]);
|
|
412
|
+
for (const rawCandidate of [requestCandidate, envCandidate, defaultWorkspaceDir, process.cwd(), homeCandidate]) {
|
|
413
|
+
const resolved = sanitizeWorkspaceCandidate(rawCandidate);
|
|
414
|
+
if (resolved) return resolved;
|
|
415
|
+
}
|
|
416
|
+
return resolveWorkspaceDir(process.cwd());
|
|
231
417
|
}
|
|
232
418
|
|
|
233
419
|
function resolveProjectIDForRequest({
|
|
@@ -3508,14 +3694,10 @@ async function runProxy(flags) {
|
|
|
3508
3694
|
} else if (envWorkspaceCandidate) {
|
|
3509
3695
|
sessionWorkspaceDir = envWorkspaceCandidate;
|
|
3510
3696
|
}
|
|
3511
|
-
const requestWorkspaceDir =
|
|
3512
|
-
firstNonEmptyString([
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
sessionWorkspaceDir,
|
|
3516
|
-
args.workspaceDir,
|
|
3517
|
-
process.cwd(),
|
|
3518
|
-
]),
|
|
3697
|
+
const requestWorkspaceDir = resolveWorkspaceDirForRequest(
|
|
3698
|
+
firstNonEmptyString([sessionWorkspaceDir, args.workspaceDir, process.cwd()]),
|
|
3699
|
+
requestObj,
|
|
3700
|
+
toolArgs,
|
|
3519
3701
|
);
|
|
3520
3702
|
let autoSyncSummary = null;
|
|
3521
3703
|
if (isJsonRpcMethod(requestObj, "tools/call")) {
|
|
@@ -3913,6 +4095,7 @@ async function runBootstrap(flags) {
|
|
|
3913
4095
|
async function main() {
|
|
3914
4096
|
const [, , rawCommand, ...rest] = process.argv;
|
|
3915
4097
|
const command = String(rawCommand || "");
|
|
4098
|
+
await maybeAutoUpdate(command, process.argv.slice(2));
|
|
3916
4099
|
|
|
3917
4100
|
if (command === "-v" || command === "--version" || command === "version") {
|
|
3918
4101
|
printVersion();
|