metheus-governance-mcp-cli 0.2.11 → 0.2.13
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 +201 -18
- 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
|
|
|
@@ -30,12 +35,12 @@ function printUsage() {
|
|
|
30
35
|
"Metheus Governance MCP CLI",
|
|
31
36
|
"",
|
|
32
37
|
"Usage:",
|
|
33
|
-
` ${cmd} [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--flow <auto|device|callback|manual>]`,
|
|
38
|
+
` ${cmd} [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--flow <auto|device|callback|manual>]`,
|
|
34
39
|
` ${cmd} init [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--flow <auto|device|callback|manual>]`,
|
|
35
|
-
` ${cmd} setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--name <server_name>]`,
|
|
40
|
+
` ${cmd} setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--name <server_name>]`,
|
|
36
41
|
` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>]`,
|
|
37
|
-
` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
|
|
38
|
-
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
42
|
+
` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
|
|
43
|
+
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path|auto>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
39
44
|
` ${cmd} auth status`,
|
|
40
45
|
` ${cmd} auth login [--base-url <url>] [--flow <auto|device|callback|manual>] [--keycloak-url <url>] [--realm <name>] [--client-id <id>] [--open-browser <true|false>] [--callback-port <n>] [--timeout-seconds <n>] [--manual <true|false>]`,
|
|
41
46
|
` ${cmd} auth set --token <jwt> [--refresh-token <token>] [--base-url <url>]`,
|
|
@@ -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) {
|
|
@@ -113,6 +285,13 @@ function resolveWorkspaceDir(rawPath) {
|
|
|
113
285
|
return path.resolve(process.cwd());
|
|
114
286
|
}
|
|
115
287
|
|
|
288
|
+
function isAutoWorkspaceMode(rawValue) {
|
|
289
|
+
const value = String(rawValue || "")
|
|
290
|
+
.trim()
|
|
291
|
+
.toLowerCase();
|
|
292
|
+
return value === "auto" || value === "dynamic" || value === "client";
|
|
293
|
+
}
|
|
294
|
+
|
|
116
295
|
function fileURIToLocalPath(rawValue) {
|
|
117
296
|
const value = String(rawValue || "").trim();
|
|
118
297
|
if (!value || !/^file:\/\//i.test(value)) return "";
|
|
@@ -3832,13 +4011,17 @@ function resolveSetupContext(flags) {
|
|
|
3832
4011
|
const ctxpackKey = String(flags["ctxpack-key"] || buildCtxpackKeyFromMeta(workspaceMeta) || "").trim();
|
|
3833
4012
|
const baseURL = String(flags["base-url"] || DEFAULT_SITE_URL).trim().replace(/\/+$/, "");
|
|
3834
4013
|
const workspaceDirRaw = String(flags["workspace-dir"] || "").trim();
|
|
3835
|
-
const
|
|
3836
|
-
const
|
|
4014
|
+
const workspaceAutoMode = isAutoWorkspaceMode(workspaceDirRaw);
|
|
4015
|
+
const hasWorkspaceDirFlag = workspaceDirRaw.length > 0;
|
|
4016
|
+
const shouldPinWorkspaceDir = !workspaceAutoMode;
|
|
4017
|
+
const workspaceDir = resolveWorkspaceDir(
|
|
4018
|
+
hasWorkspaceDirFlag && !workspaceAutoMode ? workspaceDirRaw : process.cwd(),
|
|
4019
|
+
);
|
|
3837
4020
|
const serverName = String(flags.name || DEFAULT_SERVER_NAME).trim() || DEFAULT_SERVER_NAME;
|
|
3838
4021
|
const proxyArgs = ["--base-url", `${baseURL}/governance/mcp`];
|
|
3839
|
-
// Default mode
|
|
3840
|
-
//
|
|
3841
|
-
if (
|
|
4022
|
+
// Default mode pins workspace path to where setup/init runs.
|
|
4023
|
+
// Use --workspace-dir auto when runtime auto-detection is preferred.
|
|
4024
|
+
if (shouldPinWorkspaceDir) {
|
|
3842
4025
|
proxyArgs.push("--workspace-dir", workspaceDir);
|
|
3843
4026
|
}
|
|
3844
4027
|
if (projectID) proxyArgs.push("--project-id", projectID);
|
|
@@ -3848,7 +4031,8 @@ function resolveSetupContext(flags) {
|
|
|
3848
4031
|
ctxpackKey,
|
|
3849
4032
|
baseURL,
|
|
3850
4033
|
workspaceDir,
|
|
3851
|
-
|
|
4034
|
+
shouldPinWorkspaceDir,
|
|
4035
|
+
workspaceAutoMode,
|
|
3852
4036
|
serverName,
|
|
3853
4037
|
proxyArgs,
|
|
3854
4038
|
};
|
|
@@ -3880,9 +4064,7 @@ function runSetupInternal(flags, options = {}) {
|
|
|
3880
4064
|
process.stdout.write(ensureOnly ? "\nEnsure complete.\n" : "\nInstall complete.\n");
|
|
3881
4065
|
process.stdout.write(`Server: ${context.serverName}\n`);
|
|
3882
4066
|
process.stdout.write(`Gateway: ${context.baseURL}/governance/mcp\n`);
|
|
3883
|
-
process.stdout.write(
|
|
3884
|
-
`Workspace: ${context.hasExplicitWorkspaceDir ? context.workspaceDir : "auto (client current folder)"}\n`,
|
|
3885
|
-
);
|
|
4067
|
+
process.stdout.write(`Workspace: ${context.shouldPinWorkspaceDir ? context.workspaceDir : "auto (client current folder)"}\n`);
|
|
3886
4068
|
process.stdout.write(`Project: ${context.projectID || "auto-detect from .metheus_ctxpack_sync.json"}\n`);
|
|
3887
4069
|
if (context.ctxpackKey) {
|
|
3888
4070
|
process.stdout.write(`Ctxpack: ${context.ctxpackKey}\n`);
|
|
@@ -3923,6 +4105,7 @@ async function runBootstrap(flags) {
|
|
|
3923
4105
|
async function main() {
|
|
3924
4106
|
const [, , rawCommand, ...rest] = process.argv;
|
|
3925
4107
|
const command = String(rawCommand || "");
|
|
4108
|
+
await maybeAutoUpdate(command, process.argv.slice(2));
|
|
3926
4109
|
|
|
3927
4110
|
if (command === "-v" || command === "--version" || command === "version") {
|
|
3928
4111
|
printVersion();
|