@algosuite/vo-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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/README.md +29 -3
- package/bin/vo-mcp +6 -3
- package/dist/agent-auth-probe-cli.mjs +1754 -0
- package/dist/autostart-cli.js +118 -60
- package/dist/autostart-cli.js.map +1 -2
- package/dist/ci/check-local-pr-overlap.js +107644 -0
- package/dist/cli.js +2905 -515
- package/dist/cli.js.map +3 -4
- package/dist/index.js +2495 -370
- package/dist/index.js.map +3 -4
- package/dist/install-cli.js +660 -115
- package/dist/install-cli.js.map +3 -4
- package/dist/login-cli.js +4 -4
- package/dist/login-cli.js.map +1 -2
- package/dist/pair-cli.js +1 -1
- package/dist/pair-cli.js.map +1 -2
- package/dist/runner-cli.js +14594 -3254
- package/dist/runner-cli.js.map +3 -4
- package/dist/runner-supervisor.js +3780 -0
- package/dist/runner-supervisor.js.map +6 -0
- package/dist/set-key-cli.js +82 -8
- package/dist/set-key-cli.js.map +1 -2
- package/dist/supervisor-credential-helper.js +233 -0
- package/dist/supervisor-credential-helper.js.map +6 -0
- package/dist/thresholds.json +64 -0
- package/dist/update-cli.js +75 -12
- package/dist/update-cli.js.map +3 -4
- package/package.json +5 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/cloud/credential-store.ts
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
chmodSync,
|
|
12
|
+
rmSync
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
|
|
15
|
+
// src/cloud/keychain.ts
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
var SERVICE = "vo-mcp";
|
|
18
|
+
var ACCOUNT = "refresh-credential";
|
|
19
|
+
var cached;
|
|
20
|
+
function loadKeyring() {
|
|
21
|
+
if (cached !== void 0) return cached;
|
|
22
|
+
try {
|
|
23
|
+
const req = createRequire(import.meta.url);
|
|
24
|
+
const mod = req("@napi-rs/keyring");
|
|
25
|
+
cached = mod && typeof mod.Entry === "function" ? mod : null;
|
|
26
|
+
} catch {
|
|
27
|
+
cached = null;
|
|
28
|
+
}
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
function keychainAvailable() {
|
|
32
|
+
return loadKeyring() !== null;
|
|
33
|
+
}
|
|
34
|
+
function keychainGet() {
|
|
35
|
+
const k = loadKeyring();
|
|
36
|
+
if (!k) return null;
|
|
37
|
+
try {
|
|
38
|
+
return new k.Entry(SERVICE, ACCOUNT).getPassword();
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function keychainSet(secret) {
|
|
44
|
+
const k = loadKeyring();
|
|
45
|
+
if (!k) return false;
|
|
46
|
+
try {
|
|
47
|
+
new k.Entry(SERVICE, ACCOUNT).setPassword(secret);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function keychainDelete() {
|
|
54
|
+
const k = loadKeyring();
|
|
55
|
+
if (!k) return false;
|
|
56
|
+
try {
|
|
57
|
+
return new k.Entry(SERVICE, ACCOUNT).deletePassword();
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/cloud/credential-store.ts
|
|
64
|
+
var realKeychain = {
|
|
65
|
+
available: keychainAvailable,
|
|
66
|
+
get: keychainGet,
|
|
67
|
+
set: keychainSet,
|
|
68
|
+
delete: keychainDelete
|
|
69
|
+
};
|
|
70
|
+
function credentialPath(env = process.env) {
|
|
71
|
+
const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
|
|
72
|
+
if (override) return override;
|
|
73
|
+
return join(homedir(), ".config", "vo-mcp", "credentials.json");
|
|
74
|
+
}
|
|
75
|
+
function keychainEnabled(env, keychain) {
|
|
76
|
+
const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
|
|
77
|
+
if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
|
|
78
|
+
return keychain.available();
|
|
79
|
+
}
|
|
80
|
+
function deserialize(raw) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(raw);
|
|
83
|
+
const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
|
|
84
|
+
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
85
|
+
const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
|
|
86
|
+
if (!voCred && (!refresh || !apiKey)) return null;
|
|
87
|
+
return {
|
|
88
|
+
...refresh ? { refresh_token: refresh } : {},
|
|
89
|
+
...apiKey ? { api_key: apiKey } : {},
|
|
90
|
+
...voCred ? { vo_credential: voCred } : {},
|
|
91
|
+
...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
|
|
92
|
+
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
93
|
+
...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function readFromFile(env) {
|
|
100
|
+
try {
|
|
101
|
+
const p = credentialPath(env);
|
|
102
|
+
if (!existsSync(p)) return null;
|
|
103
|
+
return deserialize(readFileSync(p, "utf8"));
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function readStoredCredential(env = process.env, keychain = realKeychain) {
|
|
109
|
+
if (keychainEnabled(env, keychain)) {
|
|
110
|
+
const raw = keychain.get();
|
|
111
|
+
const fromKeychain = raw ? deserialize(raw) : null;
|
|
112
|
+
if (fromKeychain) return fromKeychain;
|
|
113
|
+
}
|
|
114
|
+
return readFromFile(env);
|
|
115
|
+
}
|
|
116
|
+
function readStoredCredentialKeychainOnly(env = process.env, keychain = realKeychain) {
|
|
117
|
+
if (!keychainEnabled(env, keychain)) return null;
|
|
118
|
+
const raw = keychain.get();
|
|
119
|
+
return raw ? deserialize(raw) : null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/cloud/auth-token-source.ts
|
|
123
|
+
var FIREBASE_SECURETOKEN_URL = "https://securetoken.googleapis.com/v1/token";
|
|
124
|
+
var FIREBASE_TOKEN_REFERER = "https://algosuite.ai/";
|
|
125
|
+
var REFRESH_SKEW_MS = 6e4;
|
|
126
|
+
function createStaticTokenSource(token, kind = "admin-token") {
|
|
127
|
+
const value = token.trim();
|
|
128
|
+
return { kind, getToken: async () => value.length > 0 ? value : null };
|
|
129
|
+
}
|
|
130
|
+
function createFirebaseRefreshTokenSource(opts) {
|
|
131
|
+
const refreshToken = opts.refreshToken.trim();
|
|
132
|
+
const apiKey = opts.apiKey.trim();
|
|
133
|
+
const now = opts.now ?? (() => Date.now());
|
|
134
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
135
|
+
let cachedToken = null;
|
|
136
|
+
let expiresAtMs = 0;
|
|
137
|
+
let inFlight = null;
|
|
138
|
+
async function refresh() {
|
|
139
|
+
try {
|
|
140
|
+
const res = await fetchFn(`${FIREBASE_SECURETOKEN_URL}?key=${encodeURIComponent(apiKey)}`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
144
|
+
referer: FIREBASE_TOKEN_REFERER
|
|
145
|
+
},
|
|
146
|
+
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`
|
|
147
|
+
});
|
|
148
|
+
const text = await res.text();
|
|
149
|
+
if (res.status < 200 || res.status >= 300) {
|
|
150
|
+
cachedToken = null;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const parsed = JSON.parse(text);
|
|
154
|
+
const idToken = typeof parsed.id_token === "string" ? parsed.id_token : "";
|
|
155
|
+
if (!idToken) {
|
|
156
|
+
cachedToken = null;
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const expiresInSec = Number(parsed.expires_in);
|
|
160
|
+
const ttlMs = Number.isFinite(expiresInSec) && expiresInSec > 0 ? expiresInSec * 1e3 : 36e5;
|
|
161
|
+
cachedToken = idToken;
|
|
162
|
+
expiresAtMs = now() + ttlMs;
|
|
163
|
+
return idToken;
|
|
164
|
+
} catch {
|
|
165
|
+
cachedToken = null;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
kind: "firebase-refresh",
|
|
171
|
+
async getToken() {
|
|
172
|
+
if (cachedToken && now() < expiresAtMs - REFRESH_SKEW_MS) return cachedToken;
|
|
173
|
+
if (!inFlight) {
|
|
174
|
+
inFlight = refresh().finally(() => {
|
|
175
|
+
inFlight = null;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return inFlight;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function createAuthTokenSourceFromEnv(env = process.env, fetchFn, readStoredCred = () => null) {
|
|
183
|
+
const refreshToken = env["VO_USER_REFRESH_TOKEN"]?.trim();
|
|
184
|
+
const apiKey = env["VO_FIREBASE_API_KEY"]?.trim();
|
|
185
|
+
const idToken = env["VO_USER_ID_TOKEN"]?.trim();
|
|
186
|
+
const adminToken = env["VO_CONTROL_PLANE_ADMIN_TOKEN"]?.trim();
|
|
187
|
+
if (refreshToken || apiKey) {
|
|
188
|
+
if (!refreshToken || !apiKey) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Per-user refresh auth requires BOTH VO_USER_REFRESH_TOKEN and VO_FIREBASE_API_KEY"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return createFirebaseRefreshTokenSource({
|
|
194
|
+
refreshToken,
|
|
195
|
+
apiKey,
|
|
196
|
+
...fetchFn ? { fetchFn } : {}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (idToken) return createStaticTokenSource(idToken, "firebase-id-token");
|
|
200
|
+
const stored = readStoredCred();
|
|
201
|
+
if (stored?.vo_credential && stored.vo_credential.trim()) {
|
|
202
|
+
return createStaticTokenSource(stored.vo_credential.trim(), "vo-credential");
|
|
203
|
+
}
|
|
204
|
+
if (stored && stored.refresh_token?.trim() && stored.api_key?.trim()) {
|
|
205
|
+
return createFirebaseRefreshTokenSource({
|
|
206
|
+
refreshToken: stored.refresh_token.trim(),
|
|
207
|
+
apiKey: stored.api_key.trim(),
|
|
208
|
+
...fetchFn ? { fetchFn } : {}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (adminToken) return createStaticTokenSource(adminToken, "admin-token");
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/supervisor-credential-helper.mjs
|
|
216
|
+
var keychainOnly = process.argv.includes("--control-plane-auth-keychain-only");
|
|
217
|
+
var credential = keychainOnly ? readStoredCredentialKeychainOnly() : readStoredCredential();
|
|
218
|
+
if (process.argv.includes("--control-plane-auth") || keychainOnly) {
|
|
219
|
+
const source = createAuthTokenSourceFromEnv(process.env, void 0, () => credential);
|
|
220
|
+
const token = await source?.getToken();
|
|
221
|
+
if (!token) {
|
|
222
|
+
process.stderr.write("control-plane credential was not found\n");
|
|
223
|
+
process.exitCode = 2;
|
|
224
|
+
} else {
|
|
225
|
+
process.stdout.write(JSON.stringify({ token }));
|
|
226
|
+
}
|
|
227
|
+
} else if (!credential) {
|
|
228
|
+
process.stderr.write("paired runner credential was not found\n");
|
|
229
|
+
process.exitCode = 2;
|
|
230
|
+
} else {
|
|
231
|
+
process.stdout.write(JSON.stringify(credential));
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=supervisor-credential-helper.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cloud/credential-store.ts", "../src/cloud/keychain.ts", "../src/cloud/auth-token-source.ts", "../src/supervisor-credential-helper.mjs"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": []
|
|
6
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"comment": "ADR-003 auto-router tunables. Calibration PRs edit THIS FILE (data, not code). rungBounds = minimum difficulty for each rung (R1 is everything below R2). bands = rung range the router may use per global dispatch mode; explicit task.tier is NEVER clamped.",
|
|
4
|
+
"rungBounds": { "R2": 25, "R3": 45, "R4": 65, "R5": 80 },
|
|
5
|
+
"bands": {
|
|
6
|
+
"fast": ["R1", "R2"],
|
|
7
|
+
"standard": ["R1", "R4"],
|
|
8
|
+
"deep": ["R2", "R5"],
|
|
9
|
+
"ultra": ["R3", "R5"],
|
|
10
|
+
"marathon": ["R4", "R5"],
|
|
11
|
+
"ultracode": ["R4", "R5"]
|
|
12
|
+
},
|
|
13
|
+
"bandsComment": "'ultracode' is the pre-2026-08-13 name for 'marathon'; kept so legacy task docs and stored configs still resolve a band.",
|
|
14
|
+
"rungs": {
|
|
15
|
+
"R1": { "tier": "cheap", "claudeEffort": "low", "codexEffort": "low", "maxTurns": 80, "maxBudgetUsd": 1 },
|
|
16
|
+
"R2": { "tier": "cheap", "claudeEffort": "medium", "codexEffort": "medium", "maxTurns": 160, "maxBudgetUsd": 3 },
|
|
17
|
+
"R3": { "tier": "mid", "claudeEffort": "high", "codexEffort": "medium", "maxTurns": 240, "maxBudgetUsd": 10 },
|
|
18
|
+
"R4": { "tier": "best", "claudeEffort": "high", "codexEffort": "high", "maxTurns": 400, "maxBudgetUsd": 25 },
|
|
19
|
+
"R5": { "tier": "best", "claudeEffort": "xhigh", "codexEffort": "xhigh", "maxTurns": 800, "maxBudgetUsd": 50 }
|
|
20
|
+
},
|
|
21
|
+
"classBaseDifficulty": {
|
|
22
|
+
"chore": 15,
|
|
23
|
+
"docs": 20,
|
|
24
|
+
"test": 30,
|
|
25
|
+
"bugfix": 45,
|
|
26
|
+
"feature": 60,
|
|
27
|
+
"refactor": 60,
|
|
28
|
+
"research": 65,
|
|
29
|
+
"incident": 72,
|
|
30
|
+
"architecture": 80,
|
|
31
|
+
"general": 50
|
|
32
|
+
},
|
|
33
|
+
"modifiers": {
|
|
34
|
+
"evidenceRichBugfixDelta": -15,
|
|
35
|
+
"evidencePoorBugfixDelta": 20,
|
|
36
|
+
"scopeAmplifierDelta": 5,
|
|
37
|
+
"scopeAmplifierCap": 15,
|
|
38
|
+
"manyPathsDelta": 10,
|
|
39
|
+
"manyPathsThreshold": 3,
|
|
40
|
+
"veryManyPathsDelta": 15,
|
|
41
|
+
"veryManyPathsThreshold": 6,
|
|
42
|
+
"downMarkerDelta": -15,
|
|
43
|
+
"upMarkerDelta": 15,
|
|
44
|
+
"shortVaguePromptDelta": 10,
|
|
45
|
+
"shortVaguePromptMaxChars": 200,
|
|
46
|
+
"longPromptDelta": 5,
|
|
47
|
+
"longPromptMinChars": 4000,
|
|
48
|
+
"lowBudgetDelta": -10,
|
|
49
|
+
"lowBudgetMaxUsd": 2,
|
|
50
|
+
"roadmapPhaseDelta": 5,
|
|
51
|
+
"difficultyFloor": 5,
|
|
52
|
+
"difficultyCeiling": 100
|
|
53
|
+
},
|
|
54
|
+
"escapeHatch": { "minDifficulty": 65, "minCeilingRung": "R3" },
|
|
55
|
+
"operatorTierDefaultRung": { "cheap": "R2", "mid": "R3", "best": "R4" },
|
|
56
|
+
"shadowFanOut": {
|
|
57
|
+
"comment": "SHADOW-ONLY (never read by live routing). v0 fan-out gate inspired by the information-bottleneck framing of arXiv 2607.16133 — a conservative documented heuristic, NOT the paper's exact criterion. Fan-out is judged to add signal only when expected disagreement is high AND single-model confidence is low. Kept in sync with DEFAULT_SHADOW_FAN_OUT in role-cost-shadow.mjs (the in-code safe defaults when this key is absent).",
|
|
58
|
+
"minDisagreementSignal": 0.4,
|
|
59
|
+
"maxSingleModelConfidence": 0.6,
|
|
60
|
+
"minPanelSize": 2,
|
|
61
|
+
"defaultPanelSize": 3,
|
|
62
|
+
"neverFanOutClasses": ["chore", "docs"]
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/update-cli.js
CHANGED
|
@@ -2,8 +2,61 @@
|
|
|
2
2
|
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/update.ts
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
+
|
|
8
|
+
// ../../scripts/virtual-office/code-runner/runner-host-maintenance.mjs
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { win32 } from "node:path";
|
|
12
|
+
var DEFAULT_RUNNER_PACKAGE = "@algosuite/vo-mcp@beta";
|
|
13
|
+
var PACKAGE_SPEC_RE = /^@algosuite\/vo-mcp@(beta|latest|\d+(?:\.\d+){0,2}(?:-[\w.-]+)?)$/u;
|
|
14
|
+
var NPM_CLI_SUFFIX = `\\${win32.join("node_modules", "npm", "bin", "npm-cli.js").toLowerCase()}`;
|
|
15
|
+
function isNpmCliPath(value) {
|
|
16
|
+
return typeof value === "string" && win32.isAbsolute(value) && win32.normalize(value).toLowerCase().endsWith(NPM_CLI_SUFFIX);
|
|
17
|
+
}
|
|
18
|
+
function resolveNpmCli({
|
|
19
|
+
env = process.env,
|
|
20
|
+
execPath = process.execPath,
|
|
21
|
+
fileExists = existsSync
|
|
22
|
+
} = {}) {
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (isNpmCliPath(env.npm_execpath)) candidates.push(env.npm_execpath);
|
|
25
|
+
candidates.push(win32.join(win32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
26
|
+
const pathValue = env.PATH ?? env.Path ?? env.path ?? "";
|
|
27
|
+
for (const entry of pathValue.split(";")) {
|
|
28
|
+
const trimmed = entry.trim();
|
|
29
|
+
if (!win32.isAbsolute(trimmed)) continue;
|
|
30
|
+
candidates.push(win32.join(trimmed, "node_modules", "npm", "bin", "npm-cli.js"));
|
|
31
|
+
}
|
|
32
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
const normalized = win32.normalize(candidate);
|
|
35
|
+
const key = normalized.toLowerCase();
|
|
36
|
+
if (seen.has(key) || !isNpmCliPath(normalized)) continue;
|
|
37
|
+
seen.add(key);
|
|
38
|
+
if (fileExists(normalized)) return normalized;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function buildMaintenanceCommand(kind, {
|
|
43
|
+
platform = process.platform,
|
|
44
|
+
packageSpec: packageSpec2 = DEFAULT_RUNNER_PACKAGE,
|
|
45
|
+
env = process.env,
|
|
46
|
+
execPath = process.execPath,
|
|
47
|
+
fileExists = existsSync
|
|
48
|
+
} = {}) {
|
|
49
|
+
if (!["update", "reinstall"].includes(kind)) return null;
|
|
50
|
+
if (!PACKAGE_SPEC_RE.test(packageSpec2)) throw new Error("unsafe runner package spec");
|
|
51
|
+
const npmCli = platform === "win32" ? resolveNpmCli({ env, execPath, fileExists }) : null;
|
|
52
|
+
if (platform === "win32" && !npmCli) throw new Error("trusted npm CLI not found");
|
|
53
|
+
const command = platform === "win32" ? execPath : "npm";
|
|
54
|
+
const args = [...npmCli ? [npmCli] : [], "install", "-g", packageSpec2];
|
|
55
|
+
if (kind === "reinstall") args.push("--force");
|
|
56
|
+
return { command, args };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/update.ts
|
|
7
60
|
var DEFAULT_UPDATE_SPEC = "@algosuite/vo-mcp@beta";
|
|
8
61
|
function packageVersion() {
|
|
9
62
|
try {
|
|
@@ -12,20 +65,29 @@ function packageVersion() {
|
|
|
12
65
|
return "unknown";
|
|
13
66
|
}
|
|
14
67
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
68
|
+
function buildUpdateCommand(packageSpec2 = DEFAULT_UPDATE_SPEC, platform = process.platform, runtime = {}) {
|
|
69
|
+
const command = buildMaintenanceCommand("update", {
|
|
70
|
+
packageSpec: packageSpec2,
|
|
71
|
+
platform,
|
|
72
|
+
env: runtime.env,
|
|
73
|
+
execPath: runtime.execPath,
|
|
74
|
+
fileExists: runtime.fileExists
|
|
75
|
+
});
|
|
76
|
+
if (!command) throw new Error("update command unavailable");
|
|
77
|
+
return command;
|
|
23
78
|
}
|
|
24
79
|
function updateVoMcp(opts = {}) {
|
|
25
80
|
const log = opts.log ?? ((message) => console.error(message));
|
|
26
81
|
const platform = opts.platform ?? process.platform;
|
|
27
82
|
const packageSpec2 = opts.packageSpec?.trim() || DEFAULT_UPDATE_SPEC;
|
|
28
|
-
|
|
83
|
+
let command;
|
|
84
|
+
let args;
|
|
85
|
+
try {
|
|
86
|
+
({ command, args } = buildUpdateCommand(packageSpec2, platform, opts));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
log(`Update unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
return { ok: false, status: 2, command: "", args: [], skipped: false };
|
|
90
|
+
}
|
|
29
91
|
log("=== vo-mcp updater ===");
|
|
30
92
|
log(`Current installed package version: ${packageVersion()}`);
|
|
31
93
|
log(`Updating MCP + runner with: ${command} ${args.join(" ")}`);
|
|
@@ -33,10 +95,11 @@ function updateVoMcp(opts = {}) {
|
|
|
33
95
|
log("Dry run only - no package changes made.");
|
|
34
96
|
return { ok: true, status: 0, command, args, skipped: true };
|
|
35
97
|
}
|
|
36
|
-
const run = opts.spawn ??
|
|
98
|
+
const run = opts.spawn ?? spawnSync2;
|
|
37
99
|
const result2 = run(command, args, {
|
|
38
100
|
stdio: "inherit",
|
|
39
|
-
shell:
|
|
101
|
+
shell: false,
|
|
102
|
+
windowsHide: true,
|
|
40
103
|
env: opts.env ?? process.env
|
|
41
104
|
});
|
|
42
105
|
const status = typeof result2.status === "number" ? result2.status : 1;
|
package/dist/update-cli.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/update.ts", "../src/update-cli.ts"],
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"names": ["packageSpec", "result"]
|
|
3
|
+
"sources": ["../src/update.ts", "../../../scripts/virtual-office/code-runner/runner-host-maintenance.mjs", "../src/update-cli.ts"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": ["spawnSync", "packageSpec", "packageSpec", "spawnSync", "result"]
|
|
7
6
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@algosuite/vo-mcp",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0-beta.71",
|
|
4
|
+
"description": "AlgoHQ MCP server — open protocol surface for the HQ consensus and ratchet tool family. Stdio transport, cross-vendor MCP client compatible (Claude Code, Claude Desktop, Codex, Cursor, Continue).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
26
26
|
"zod": "^4.4.3"
|
|
27
27
|
},
|
|
28
28
|
"optionalDependencies": {
|
|
@@ -34,11 +34,13 @@
|
|
|
34
34
|
"keywords": [
|
|
35
35
|
"mcp",
|
|
36
36
|
"model-context-protocol",
|
|
37
|
+
"algohq",
|
|
37
38
|
"virtual-office",
|
|
38
39
|
"consensus",
|
|
39
40
|
"ratchet",
|
|
40
41
|
"claude",
|
|
41
42
|
"claude-desktop",
|
|
43
|
+
"codex",
|
|
42
44
|
"cursor",
|
|
43
45
|
"continue"
|
|
44
46
|
],
|