@suveren/gateway 0.11.2 → 0.13.0
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 +35 -1
- package/bin/suveren-gateway.js +137 -12
- package/content/integrations/calendar.json +1 -0
- package/content/integrations/crm.json +1 -67
- package/content/integrations/deploy-github.json +3 -5
- package/content/integrations/erp.json +1 -80
- package/content/integrations/gmail.json +1 -0
- package/content/integrations/linkedin.json +1 -0
- package/content/integrations/mail.json +1 -48
- package/content/integrations/mollie.json +1 -0
- package/content/integrations/records.json +1 -0
- package/dist/control-plane/index.mjs +555 -122
- package/dist/mcp-server/http.mjs +643 -168
- package/dist/ui/assets/{index-BzcgeHlq.js → index-CmLqzgqA.js} +21 -21
- package/dist/ui/assets/{index-BFOprnFZ.css → index-y1OFeYnS.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/lib/autostart-templates.d.mts +6 -0
- package/lib/autostart-templates.mjs +21 -4
- package/lib/config.d.mts +10 -0
- package/lib/config.mjs +79 -18
- package/lib/node-version.d.mts +7 -0
- package/lib/node-version.mjs +28 -0
- package/lib/policy.d.mts +34 -0
- package/lib/policy.mjs +369 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js +17 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js +23 -10
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js +77 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js +18 -5
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js +24 -11
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js +72 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/package.json +2 -2
- package/server.js +60 -8
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -142,18 +142,237 @@ function fingerprintOf(publicKeyHex) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// src/lib/as-config.ts
|
|
145
|
-
import { existsSync as
|
|
146
|
-
import { dirname as dirname2, join as
|
|
145
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
146
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
147
|
+
|
|
148
|
+
// src/lib/policy.ts
|
|
149
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
150
|
+
import { join as join3 } from "path";
|
|
151
|
+
import { spawnSync } from "child_process";
|
|
152
|
+
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
153
|
+
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
154
|
+
function registryKeyPath(hive) {
|
|
155
|
+
const baseKey = process.env.SUVEREN_POLICY_REGISTRY_KEY || DEFAULT_REGISTRY_BASE_KEY;
|
|
156
|
+
return `${hive}\\${baseKey}`;
|
|
157
|
+
}
|
|
158
|
+
function validateAsUrl(candidate) {
|
|
159
|
+
const trimmed = candidate.trim();
|
|
160
|
+
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
161
|
+
let parsed;
|
|
162
|
+
try {
|
|
163
|
+
parsed = new URL(trimmed);
|
|
164
|
+
} catch {
|
|
165
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
166
|
+
}
|
|
167
|
+
const isLocalHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
168
|
+
if (parsed.protocol !== "https:" && !isLocalHttp) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
error: `"${trimmed}" must use https:// (http:// is only allowed for http://localhost or http://127.0.0.1).`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
let normalized = `${parsed.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
175
|
+
if (normalized.endsWith("/") && normalized !== `${parsed.origin}/`) {
|
|
176
|
+
normalized = normalized.slice(0, -1);
|
|
177
|
+
} else if (normalized === `${parsed.origin}/`) {
|
|
178
|
+
normalized = parsed.origin;
|
|
179
|
+
}
|
|
180
|
+
return { ok: true, url: normalized };
|
|
181
|
+
}
|
|
182
|
+
function validateCaFile(candidate) {
|
|
183
|
+
const trimmed = candidate.trim();
|
|
184
|
+
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
185
|
+
if (!existsSync3(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
186
|
+
return { ok: true, path: trimmed };
|
|
187
|
+
}
|
|
188
|
+
function validateProxyUrl(candidate) {
|
|
189
|
+
const trimmed = candidate.trim();
|
|
190
|
+
if (!trimmed) return { ok: false, error: "The proxy URL is empty." };
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = new URL(trimmed);
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
196
|
+
}
|
|
197
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
198
|
+
return { ok: false, error: `"${trimmed}" must use http:// or https://.` };
|
|
199
|
+
}
|
|
200
|
+
return { ok: true, url: trimmed };
|
|
201
|
+
}
|
|
202
|
+
function coerceBoolean(value) {
|
|
203
|
+
if (typeof value === "boolean") return value;
|
|
204
|
+
if (typeof value === "number") {
|
|
205
|
+
if (value === 0) return false;
|
|
206
|
+
if (value === 1) return true;
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === "string") {
|
|
210
|
+
const v = value.trim().toLowerCase();
|
|
211
|
+
if (v === "true" || v === "1") return true;
|
|
212
|
+
if (v === "false" || v === "0") return false;
|
|
213
|
+
}
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
function parseRegQueryOutput(stdout) {
|
|
217
|
+
const out = {};
|
|
218
|
+
const lineRe = /^ {4}(\S+)\s+(REG_SZ|REG_EXPAND_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_MULTI_SZ)\s+(.*)$/;
|
|
219
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
220
|
+
const m = lineRe.exec(line);
|
|
221
|
+
if (!m) continue;
|
|
222
|
+
const [, name, type, rawValue] = m;
|
|
223
|
+
if (!POLICY_KEYS.includes(name)) continue;
|
|
224
|
+
const key = name;
|
|
225
|
+
if (type === "REG_DWORD" || type === "REG_QWORD") {
|
|
226
|
+
out[key] = parseInt(rawValue, 16);
|
|
227
|
+
} else {
|
|
228
|
+
out[key] = rawValue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
function readRegistryHive(hive) {
|
|
234
|
+
const path = registryKeyPath(hive);
|
|
235
|
+
const source = `the registry policy (${path})`;
|
|
236
|
+
if (process.env.SUVEREN_POLICY_REGISTRY === "off") return { values: {}, source };
|
|
237
|
+
if (process.platform !== "win32") return { values: {}, source };
|
|
238
|
+
let result;
|
|
239
|
+
try {
|
|
240
|
+
result = spawnSync("reg", ["query", path], { encoding: "utf8" });
|
|
241
|
+
} catch {
|
|
242
|
+
return { values: {}, source };
|
|
243
|
+
}
|
|
244
|
+
if (!result || result.status !== 0 || !result.stdout) return { values: {}, source };
|
|
245
|
+
return { values: parseRegQueryOutput(result.stdout), source };
|
|
246
|
+
}
|
|
247
|
+
function policyFilePath() {
|
|
248
|
+
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
249
|
+
if (process.platform === "win32") {
|
|
250
|
+
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
251
|
+
return join3(programData, "Suveren", "gateway-policy.json");
|
|
252
|
+
}
|
|
253
|
+
if (process.platform === "darwin") {
|
|
254
|
+
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
255
|
+
}
|
|
256
|
+
return "/etc/suveren/gateway-policy.json";
|
|
257
|
+
}
|
|
258
|
+
function readPolicyFile() {
|
|
259
|
+
const path = policyFilePath();
|
|
260
|
+
const source = `the policy file (${path})`;
|
|
261
|
+
if (!existsSync3(path)) return { values: {}, source };
|
|
262
|
+
let data;
|
|
263
|
+
try {
|
|
264
|
+
data = JSON.parse(readFileSync3(path, "utf8"));
|
|
265
|
+
} catch (err) {
|
|
266
|
+
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
|
+
}
|
|
268
|
+
const values = {};
|
|
269
|
+
for (const key of POLICY_KEYS) {
|
|
270
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
271
|
+
values[key] = data[key];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { values, source };
|
|
275
|
+
}
|
|
276
|
+
function mergeBySource(entries) {
|
|
277
|
+
const merged = {};
|
|
278
|
+
const sourceOf = {};
|
|
279
|
+
for (const key of POLICY_KEYS) {
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
if (Object.prototype.hasOwnProperty.call(entry.values, key) && entry.values[key] !== void 0) {
|
|
282
|
+
merged[key] = entry.values[key];
|
|
283
|
+
sourceOf[key] = entry.source;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { merged, sourceOf };
|
|
289
|
+
}
|
|
290
|
+
var cached = null;
|
|
291
|
+
function readPolicy() {
|
|
292
|
+
if (cached) return cached;
|
|
293
|
+
const hklm = readRegistryHive("HKLM");
|
|
294
|
+
const hkcu = readRegistryHive("HKCU");
|
|
295
|
+
const file = readPolicyFile();
|
|
296
|
+
const { merged, sourceOf } = mergeBySource([hklm, hkcu, file]);
|
|
297
|
+
const policy = {};
|
|
298
|
+
const locked = /* @__PURE__ */ new Set();
|
|
299
|
+
if (merged.AsUrl !== void 0) {
|
|
300
|
+
const v = validateAsUrl(String(merged.AsUrl));
|
|
301
|
+
if (!v.ok) {
|
|
302
|
+
throw new Error(`Invalid policy AsUrl ("${merged.AsUrl}") from ${sourceOf.AsUrl}: ${v.error}`);
|
|
303
|
+
}
|
|
304
|
+
policy.asUrl = v.url;
|
|
305
|
+
locked.add("asUrl");
|
|
306
|
+
}
|
|
307
|
+
if (merged.CaFile !== void 0) {
|
|
308
|
+
const v = validateCaFile(String(merged.CaFile));
|
|
309
|
+
if (!v.ok) {
|
|
310
|
+
throw new Error(`Invalid policy CaFile ("${merged.CaFile}") from ${sourceOf.CaFile}: ${v.error}`);
|
|
311
|
+
}
|
|
312
|
+
policy.caFile = v.path;
|
|
313
|
+
locked.add("caFile");
|
|
314
|
+
}
|
|
315
|
+
if (merged.PinTls !== void 0) {
|
|
316
|
+
const b = coerceBoolean(merged.PinTls);
|
|
317
|
+
if (b === void 0) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Invalid policy PinTls ("${String(merged.PinTls)}") from ${sourceOf.PinTls}: must be a boolean or 0/1.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
policy.pinTls = b;
|
|
323
|
+
locked.add("pinTls");
|
|
324
|
+
}
|
|
325
|
+
if (merged.Proxy !== void 0) {
|
|
326
|
+
const v = validateProxyUrl(String(merged.Proxy));
|
|
327
|
+
if (!v.ok) {
|
|
328
|
+
throw new Error(`Invalid policy Proxy ("${String(merged.Proxy)}") from ${sourceOf.Proxy}: ${v.error}`);
|
|
329
|
+
}
|
|
330
|
+
policy.proxy = v.url;
|
|
331
|
+
locked.add("proxy");
|
|
332
|
+
}
|
|
333
|
+
if (merged.NoProxy !== void 0) {
|
|
334
|
+
const raw = String(merged.NoProxy).trim();
|
|
335
|
+
if (!raw) {
|
|
336
|
+
throw new Error(`Invalid policy NoProxy ("${String(merged.NoProxy)}") from ${sourceOf.NoProxy}: must not be empty.`);
|
|
337
|
+
}
|
|
338
|
+
policy.noProxy = raw;
|
|
339
|
+
locked.add("noProxy");
|
|
340
|
+
}
|
|
341
|
+
if (merged.Simulation !== void 0) {
|
|
342
|
+
const b = coerceBoolean(merged.Simulation);
|
|
343
|
+
if (b === void 0) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Invalid policy Simulation ("${String(merged.Simulation)}") from ${sourceOf.Simulation}: must be a boolean or 0/1.`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
policy.simulation = b;
|
|
349
|
+
locked.add("simulation");
|
|
350
|
+
}
|
|
351
|
+
if (merged.InstallMethod !== void 0) {
|
|
352
|
+
const raw = String(merged.InstallMethod).trim().toLowerCase();
|
|
353
|
+
if (raw !== "managed") {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Invalid policy InstallMethod ("${String(merged.InstallMethod)}") from ${sourceOf.InstallMethod}: only "managed" is supported.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
policy.installMethod = "managed";
|
|
359
|
+
locked.add("installMethod");
|
|
360
|
+
}
|
|
361
|
+
cached = { policy, locked };
|
|
362
|
+
return cached;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/lib/as-config.ts
|
|
147
366
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
148
367
|
function configPath(dataDir2) {
|
|
149
|
-
return
|
|
368
|
+
return join4(dataDir2, "config.json");
|
|
150
369
|
}
|
|
151
370
|
function readAsConfigStrict(dataDir2) {
|
|
152
371
|
const path = configPath(dataDir2);
|
|
153
|
-
if (!
|
|
372
|
+
if (!existsSync4(path)) return {};
|
|
154
373
|
let data;
|
|
155
374
|
try {
|
|
156
|
-
data = JSON.parse(
|
|
375
|
+
data = JSON.parse(readFileSync4(path, "utf-8"));
|
|
157
376
|
} catch (err) {
|
|
158
377
|
throw new Error(
|
|
159
378
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -168,9 +387,9 @@ function readAsConfigStrict(dataDir2) {
|
|
|
168
387
|
}
|
|
169
388
|
function readAsConfig(dataDir2) {
|
|
170
389
|
const path = configPath(dataDir2);
|
|
171
|
-
if (!
|
|
390
|
+
if (!existsSync4(path)) return {};
|
|
172
391
|
try {
|
|
173
|
-
const data = JSON.parse(
|
|
392
|
+
const data = JSON.parse(readFileSync4(path, "utf-8"));
|
|
174
393
|
const out = {};
|
|
175
394
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
176
395
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -181,7 +400,7 @@ function readAsConfig(dataDir2) {
|
|
|
181
400
|
return {};
|
|
182
401
|
}
|
|
183
402
|
}
|
|
184
|
-
function
|
|
403
|
+
function validateAsUrl2(candidate) {
|
|
185
404
|
const trimmed = candidate.trim();
|
|
186
405
|
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
187
406
|
let parsed;
|
|
@@ -206,26 +425,30 @@ function validateAsUrl(candidate) {
|
|
|
206
425
|
return { ok: true, url: normalized };
|
|
207
426
|
}
|
|
208
427
|
function resolveAsUrl(dataDir2, flag) {
|
|
428
|
+
const policyUrl = readPolicy().policy.asUrl;
|
|
429
|
+
if (policyUrl) return policyUrl;
|
|
209
430
|
if (flag) {
|
|
210
|
-
const v =
|
|
431
|
+
const v = validateAsUrl2(flag);
|
|
211
432
|
if (!v.ok) throw new Error(`Invalid --as-url: ${v.error}`);
|
|
212
433
|
return v.url;
|
|
213
434
|
}
|
|
214
435
|
const envUrl = process.env.SUVEREN_AS_URL;
|
|
215
436
|
if (envUrl) {
|
|
216
|
-
const v =
|
|
437
|
+
const v = validateAsUrl2(envUrl);
|
|
217
438
|
if (!v.ok) throw new Error(`Invalid SUVEREN_AS_URL: ${v.error}`);
|
|
218
439
|
return v.url;
|
|
219
440
|
}
|
|
220
441
|
const saved = readAsConfigStrict(dataDir2).asUrl;
|
|
221
442
|
if (saved) {
|
|
222
|
-
const v =
|
|
443
|
+
const v = validateAsUrl2(saved);
|
|
223
444
|
if (!v.ok) throw new Error(`Invalid saved as-url "${saved}" in ${dataDir2}/config.json: ${v.error}`);
|
|
224
445
|
return v.url;
|
|
225
446
|
}
|
|
226
447
|
return DEFAULT_AS_URL;
|
|
227
448
|
}
|
|
228
449
|
function resolvePinTls(dataDir2) {
|
|
450
|
+
const policyPinTls = readPolicy().policy.pinTls;
|
|
451
|
+
if (policyPinTls !== void 0) return policyPinTls;
|
|
229
452
|
return readAsConfig(dataDir2).pinTls === true;
|
|
230
453
|
}
|
|
231
454
|
function validatePinTlsForUrl(pinTls, asUrl) {
|
|
@@ -240,14 +463,58 @@ function validatePinTlsForUrl(pinTls, asUrl) {
|
|
|
240
463
|
// src/lib/as-tls-pin.ts
|
|
241
464
|
import { createHash as createHash2 } from "crypto";
|
|
242
465
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
466
|
+
import { Agent as HttpsAgent2 } from "https";
|
|
467
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
468
|
+
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
469
|
+
|
|
470
|
+
// src/lib/proxy-env.ts
|
|
471
|
+
function isLoopbackHostname(hostname) {
|
|
472
|
+
const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
473
|
+
if (h === "localhost") return true;
|
|
474
|
+
if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
|
|
475
|
+
return /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.test(h);
|
|
476
|
+
}
|
|
477
|
+
function readProxyEnv() {
|
|
478
|
+
const http = process.env.http_proxy || process.env.HTTP_PROXY || void 0;
|
|
479
|
+
const https = process.env.https_proxy || process.env.HTTPS_PROXY || void 0;
|
|
480
|
+
const noProxy = process.env.no_proxy ?? process.env.NO_PROXY ?? "";
|
|
481
|
+
return { http, https, noProxy };
|
|
482
|
+
}
|
|
483
|
+
function matchesNoProxy(hostname, port2, noProxy) {
|
|
484
|
+
const entries = noProxy.split(/[,\s]+/).filter(Boolean);
|
|
485
|
+
if (entries.length === 0) return false;
|
|
486
|
+
if (entries.includes("*")) return true;
|
|
487
|
+
const h = hostname.toLowerCase();
|
|
488
|
+
for (const raw of entries) {
|
|
489
|
+
const m = raw.match(/^(.+):(\d+)$/);
|
|
490
|
+
const entryHost = (m ? m[1] : raw).replace(/^\*?\./, "").toLowerCase();
|
|
491
|
+
const entryPort = m ? Number(m[2]) : 0;
|
|
492
|
+
if (entryPort && entryPort !== port2) continue;
|
|
493
|
+
if (h === entryHost || h.endsWith(`.${entryHost}`)) return true;
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
function selectProxyUrl(targetUrl) {
|
|
498
|
+
const url = typeof targetUrl === "string" ? new URL(targetUrl) : targetUrl;
|
|
499
|
+
if (isLoopbackHostname(url.hostname)) return void 0;
|
|
500
|
+
const { http, https, noProxy } = readProxyEnv();
|
|
501
|
+
const port2 = url.port ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
502
|
+
if (matchesNoProxy(url.hostname, port2, noProxy)) return void 0;
|
|
503
|
+
return url.protocol === "https:" ? https ?? http : http;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/lib/proxy-https-agent.ts
|
|
243
507
|
import { Agent as HttpsAgent } from "https";
|
|
244
|
-
import {
|
|
245
|
-
import {
|
|
508
|
+
import { connect as netConnect } from "net";
|
|
509
|
+
import { connect as tlsConnect } from "tls";
|
|
510
|
+
var MAX_CONNECT_RESPONSE_BYTES = 64 * 1024;
|
|
511
|
+
|
|
512
|
+
// src/lib/as-tls-pin.ts
|
|
246
513
|
function effectiveCa() {
|
|
247
514
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
248
515
|
if (!path) return void 0;
|
|
249
516
|
try {
|
|
250
|
-
return [...rootCertificates,
|
|
517
|
+
return [...rootCertificates, readFileSync5(path, "utf-8")];
|
|
251
518
|
} catch {
|
|
252
519
|
return void 0;
|
|
253
520
|
}
|
|
@@ -302,24 +569,27 @@ function pinnedCheckServerIdentity(pinnedSpkiHex) {
|
|
|
302
569
|
return void 0;
|
|
303
570
|
};
|
|
304
571
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
572
|
+
function buildTlsDispatcher(url, connectOpts) {
|
|
573
|
+
const proxyUrl = selectProxyUrl(url);
|
|
574
|
+
if (proxyUrl) {
|
|
575
|
+
return new ProxyAgent({ uri: proxyUrl, requestTls: connectOpts });
|
|
576
|
+
}
|
|
577
|
+
return new Agent({ connect: connectOpts });
|
|
578
|
+
}
|
|
579
|
+
function buildPinnedDispatcher(pinnedSpkiHex, url) {
|
|
580
|
+
return buildTlsDispatcher(url, {
|
|
581
|
+
ca: effectiveCa(),
|
|
582
|
+
checkServerIdentity: pinnedCheckServerIdentity(pinnedSpkiHex)
|
|
311
583
|
});
|
|
312
584
|
}
|
|
313
|
-
function buildCapturingDispatcher() {
|
|
585
|
+
function buildCapturingDispatcher(url) {
|
|
314
586
|
let captured;
|
|
315
|
-
const dispatcher =
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
322
|
-
}
|
|
587
|
+
const dispatcher = buildTlsDispatcher(url, {
|
|
588
|
+
ca: effectiveCa(),
|
|
589
|
+
checkServerIdentity: (hostname, cert) => {
|
|
590
|
+
captured = cert;
|
|
591
|
+
const defaultErr = defaultCheckServerIdentity(hostname, cert);
|
|
592
|
+
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
323
593
|
}
|
|
324
594
|
});
|
|
325
595
|
return { dispatcher, getCapturedCert: () => captured };
|
|
@@ -330,6 +600,34 @@ function withBoundedSignal(init) {
|
|
|
330
600
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
331
601
|
return { ...init, signal };
|
|
332
602
|
}
|
|
603
|
+
var UNKNOWN_ISSUER_CODES = /* @__PURE__ */ new Set([
|
|
604
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
605
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
606
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
607
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
608
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
609
|
+
"CERT_UNTRUSTED"
|
|
610
|
+
]);
|
|
611
|
+
function tlsErrorCode(err) {
|
|
612
|
+
if (!(err instanceof Error)) return void 0;
|
|
613
|
+
const cause = err.cause;
|
|
614
|
+
const withCode = cause instanceof Error ? cause : err;
|
|
615
|
+
return withCode.code;
|
|
616
|
+
}
|
|
617
|
+
function isUnknownIssuerError(err) {
|
|
618
|
+
const code = tlsErrorCode(err);
|
|
619
|
+
return !!code && UNKNOWN_ISSUER_CODES.has(code);
|
|
620
|
+
}
|
|
621
|
+
function rethrowWithIssuerHint(err, url) {
|
|
622
|
+
if (isUnknownIssuerError(err)) {
|
|
623
|
+
const hinted = new Error(
|
|
624
|
+
`Could not verify the certificate presented for ${url} \u2014 its issuer is not trusted (${tlsErrorCode(err)}). This is the typical signature of a company network that inspects HTTPS traffic with its own root certificate: trust it with \`suveren-gateway config set ca-file <path-to-company-root.pem>\` (or --ca-file / NODE_EXTRA_CA_CERTS), then try again. Original error: ${describeFetchError(err)}`
|
|
625
|
+
);
|
|
626
|
+
hinted.cause = err;
|
|
627
|
+
throw hinted;
|
|
628
|
+
}
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
333
631
|
function closeAfter(dispatcher, pending) {
|
|
334
632
|
pending.catch(() => {
|
|
335
633
|
}).finally(() => {
|
|
@@ -339,8 +637,24 @@ function closeAfter(dispatcher, pending) {
|
|
|
339
637
|
}
|
|
340
638
|
async function fetchAs(url, init, pinning) {
|
|
341
639
|
if (!pinning.enforce && !pinning.capture) {
|
|
342
|
-
const
|
|
343
|
-
|
|
640
|
+
const proxyUrl = selectProxyUrl(url);
|
|
641
|
+
if (!proxyUrl) {
|
|
642
|
+
try {
|
|
643
|
+
const res2 = await fetch(url, withBoundedSignal(init));
|
|
644
|
+
return { res: res2 };
|
|
645
|
+
} catch (err) {
|
|
646
|
+
rethrowWithIssuerHint(err, url);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const dispatcher2 = new ProxyAgent({ uri: proxyUrl, requestTls: { ca: effectiveCa() } });
|
|
650
|
+
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
651
|
+
closeAfter(dispatcher2, pending2);
|
|
652
|
+
try {
|
|
653
|
+
const res2 = await pending2;
|
|
654
|
+
return { res: res2 };
|
|
655
|
+
} catch (err) {
|
|
656
|
+
rethrowWithIssuerHint(err, url);
|
|
657
|
+
}
|
|
344
658
|
}
|
|
345
659
|
if (pinning.enforce && !pinning.capture && !pinning.pinnedSpkiHex) {
|
|
346
660
|
throw new AsTlsMismatchError(
|
|
@@ -348,27 +662,27 @@ async function fetchAs(url, init, pinning) {
|
|
|
348
662
|
);
|
|
349
663
|
}
|
|
350
664
|
if (pinning.pinnedSpkiHex && (pinning.enforce || !pinning.capture)) {
|
|
351
|
-
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex);
|
|
665
|
+
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex, url);
|
|
352
666
|
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
353
667
|
closeAfter(dispatcher2, pending2);
|
|
354
668
|
try {
|
|
355
669
|
const res2 = await pending2;
|
|
356
670
|
return { res: res2 };
|
|
357
671
|
} catch (err) {
|
|
358
|
-
if (!isPinCheckError(err))
|
|
672
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
359
673
|
throw new AsTlsMismatchError(
|
|
360
|
-
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent.
|
|
674
|
+
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent. Two things produce this: (1) the Authority Server's TLS certificate genuinely changed (a new signing key, not just a renewal) \u2014 clear the pairing and sign in again to re-pin it; or (2) a TLS-inspecting proxy on this network is re-signing traffic with its own certificate \u2014 ask network/IT to exempt the Authority Server's host from TLS inspection (pin-tls refusing this, even with a trusted --ca-file, is working as intended).`
|
|
361
675
|
);
|
|
362
676
|
}
|
|
363
677
|
}
|
|
364
|
-
const { dispatcher, getCapturedCert } = buildCapturingDispatcher();
|
|
678
|
+
const { dispatcher, getCapturedCert } = buildCapturingDispatcher(url);
|
|
365
679
|
const pending = undiciFetch(url, { ...withBoundedSignal(init), dispatcher });
|
|
366
680
|
closeAfter(dispatcher, pending);
|
|
367
681
|
let res;
|
|
368
682
|
try {
|
|
369
683
|
res = await pending;
|
|
370
684
|
} catch (err) {
|
|
371
|
-
if (!isPinCheckError(err))
|
|
685
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
372
686
|
throw new AsTlsMismatchError(
|
|
373
687
|
`could not verify the Authority Server at ${url} while establishing its TLS pin \u2014 ${describeFetchError(err)}.`
|
|
374
688
|
);
|
|
@@ -834,11 +1148,11 @@ var AttestationCache = class {
|
|
|
834
1148
|
};
|
|
835
1149
|
|
|
836
1150
|
// src/lib/gate-store.ts
|
|
837
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
1151
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
838
1152
|
import { homedir } from "os";
|
|
839
|
-
import { dirname as dirname3, join as
|
|
1153
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
840
1154
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
841
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1155
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join5(homedir(), ".suveren");
|
|
842
1156
|
var GateStore = class {
|
|
843
1157
|
entries = /* @__PURE__ */ new Map();
|
|
844
1158
|
baseDir;
|
|
@@ -856,11 +1170,11 @@ var GateStore = class {
|
|
|
856
1170
|
*/
|
|
857
1171
|
setVaultKey(key) {
|
|
858
1172
|
this.vaultKey = key;
|
|
859
|
-
if (
|
|
1173
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
860
1174
|
this.loadEncrypted();
|
|
861
1175
|
} else if (this.entries.size > 0) {
|
|
862
1176
|
this.persistEncrypted();
|
|
863
|
-
if (
|
|
1177
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
864
1178
|
try {
|
|
865
1179
|
unlinkSync2(this.plaintextFilePath);
|
|
866
1180
|
} catch {
|
|
@@ -898,11 +1212,11 @@ var GateStore = class {
|
|
|
898
1212
|
clearAll() {
|
|
899
1213
|
this.entries = /* @__PURE__ */ new Map();
|
|
900
1214
|
try {
|
|
901
|
-
if (
|
|
1215
|
+
if (existsSync5(this.plaintextFilePath)) unlinkSync2(this.plaintextFilePath);
|
|
902
1216
|
} catch {
|
|
903
1217
|
}
|
|
904
1218
|
try {
|
|
905
|
-
if (
|
|
1219
|
+
if (existsSync5(this.encryptedFilePath)) unlinkSync2(this.encryptedFilePath);
|
|
906
1220
|
} catch {
|
|
907
1221
|
}
|
|
908
1222
|
}
|
|
@@ -935,19 +1249,19 @@ var GateStore = class {
|
|
|
935
1249
|
}
|
|
936
1250
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
937
1251
|
get plaintextFilePath() {
|
|
938
|
-
return
|
|
1252
|
+
return join5(this.baseDir, "gates.json");
|
|
939
1253
|
}
|
|
940
1254
|
get encryptedFilePath() {
|
|
941
|
-
return
|
|
1255
|
+
return join5(this.baseDir, "gates.enc.json");
|
|
942
1256
|
}
|
|
943
1257
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
944
1258
|
loadPlaintext() {
|
|
945
|
-
if (!
|
|
1259
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
946
1260
|
mkdirSync3(this.baseDir, { recursive: true });
|
|
947
1261
|
return;
|
|
948
1262
|
}
|
|
949
1263
|
try {
|
|
950
|
-
const raw =
|
|
1264
|
+
const raw = readFileSync6(this.plaintextFilePath, "utf-8");
|
|
951
1265
|
const data = JSON.parse(raw);
|
|
952
1266
|
this.entries = new Map(Object.entries(data.entries));
|
|
953
1267
|
} catch {
|
|
@@ -956,9 +1270,9 @@ var GateStore = class {
|
|
|
956
1270
|
}
|
|
957
1271
|
}
|
|
958
1272
|
loadEncrypted() {
|
|
959
|
-
if (!
|
|
1273
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
960
1274
|
try {
|
|
961
|
-
const raw =
|
|
1275
|
+
const raw = readFileSync6(this.encryptedFilePath, "utf-8");
|
|
962
1276
|
const data = JSON.parse(raw);
|
|
963
1277
|
this.entries = /* @__PURE__ */ new Map();
|
|
964
1278
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -999,11 +1313,11 @@ var GateStore = class {
|
|
|
999
1313
|
};
|
|
1000
1314
|
|
|
1001
1315
|
// src/lib/execution-log.ts
|
|
1002
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
1316
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
1003
1317
|
import { homedir as homedir2 } from "os";
|
|
1004
|
-
import { dirname as dirname4, join as
|
|
1318
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1005
1319
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
1006
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
1320
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join6(homedir2(), ".suveren");
|
|
1007
1321
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
1008
1322
|
var ExecutionLog = class {
|
|
1009
1323
|
entries = [];
|
|
@@ -1022,11 +1336,11 @@ var ExecutionLog = class {
|
|
|
1022
1336
|
*/
|
|
1023
1337
|
setVaultKey(key) {
|
|
1024
1338
|
this.vaultKey = key;
|
|
1025
|
-
if (
|
|
1339
|
+
if (existsSync6(this.encryptedFilePath)) {
|
|
1026
1340
|
this.loadEncrypted();
|
|
1027
1341
|
} else if (this.entries.length > 0) {
|
|
1028
1342
|
this.persistEncrypted();
|
|
1029
|
-
if (
|
|
1343
|
+
if (existsSync6(this.plaintextFilePath)) {
|
|
1030
1344
|
try {
|
|
1031
1345
|
unlinkSync3(this.plaintextFilePath);
|
|
1032
1346
|
} catch {
|
|
@@ -1130,19 +1444,19 @@ var ExecutionLog = class {
|
|
|
1130
1444
|
}
|
|
1131
1445
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1132
1446
|
get plaintextFilePath() {
|
|
1133
|
-
return
|
|
1447
|
+
return join6(this.baseDir, "execution-log.json");
|
|
1134
1448
|
}
|
|
1135
1449
|
get encryptedFilePath() {
|
|
1136
|
-
return
|
|
1450
|
+
return join6(this.baseDir, "execution-log.enc.json");
|
|
1137
1451
|
}
|
|
1138
1452
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1139
1453
|
loadPlaintext() {
|
|
1140
|
-
if (!
|
|
1454
|
+
if (!existsSync6(this.plaintextFilePath)) {
|
|
1141
1455
|
mkdirSync4(this.baseDir, { recursive: true });
|
|
1142
1456
|
return;
|
|
1143
1457
|
}
|
|
1144
1458
|
try {
|
|
1145
|
-
const raw =
|
|
1459
|
+
const raw = readFileSync7(this.plaintextFilePath, "utf-8");
|
|
1146
1460
|
const data = JSON.parse(raw);
|
|
1147
1461
|
this.entries = data.entries;
|
|
1148
1462
|
this.prune();
|
|
@@ -1152,9 +1466,9 @@ var ExecutionLog = class {
|
|
|
1152
1466
|
}
|
|
1153
1467
|
}
|
|
1154
1468
|
loadEncrypted() {
|
|
1155
|
-
if (!
|
|
1469
|
+
if (!existsSync6(this.encryptedFilePath)) return;
|
|
1156
1470
|
try {
|
|
1157
|
-
const raw =
|
|
1471
|
+
const raw = readFileSync7(this.encryptedFilePath, "utf-8");
|
|
1158
1472
|
const data = JSON.parse(raw);
|
|
1159
1473
|
const decrypted = this.decrypt(data.blob);
|
|
1160
1474
|
const logFile = JSON.parse(decrypted);
|
|
@@ -1201,11 +1515,11 @@ function windowCutoff(window, now) {
|
|
|
1201
1515
|
}
|
|
1202
1516
|
|
|
1203
1517
|
// src/lib/denial-log.ts
|
|
1204
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
1518
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
1205
1519
|
import { homedir as homedir3 } from "os";
|
|
1206
|
-
import { dirname as dirname5, join as
|
|
1520
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1207
1521
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
1208
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
1522
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join7(homedir3(), ".suveren");
|
|
1209
1523
|
var MAX_RECORDS = 200;
|
|
1210
1524
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1211
1525
|
var DenialLog = class {
|
|
@@ -1218,11 +1532,11 @@ var DenialLog = class {
|
|
|
1218
1532
|
}
|
|
1219
1533
|
setVaultKey(key) {
|
|
1220
1534
|
this.vaultKey = key;
|
|
1221
|
-
if (
|
|
1535
|
+
if (existsSync7(this.encryptedFilePath)) {
|
|
1222
1536
|
this.loadEncrypted();
|
|
1223
1537
|
} else if (this.records.length > 0) {
|
|
1224
1538
|
this.persistEncrypted();
|
|
1225
|
-
if (
|
|
1539
|
+
if (existsSync7(this.plaintextFilePath)) {
|
|
1226
1540
|
try {
|
|
1227
1541
|
unlinkSync4(this.plaintextFilePath);
|
|
1228
1542
|
} catch {
|
|
@@ -1272,18 +1586,18 @@ var DenialLog = class {
|
|
|
1272
1586
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
1273
1587
|
}
|
|
1274
1588
|
get plaintextFilePath() {
|
|
1275
|
-
return
|
|
1589
|
+
return join7(this.baseDir, "denials.json");
|
|
1276
1590
|
}
|
|
1277
1591
|
get encryptedFilePath() {
|
|
1278
|
-
return
|
|
1592
|
+
return join7(this.baseDir, "denials.enc.json");
|
|
1279
1593
|
}
|
|
1280
1594
|
loadPlaintext() {
|
|
1281
|
-
if (!
|
|
1595
|
+
if (!existsSync7(this.plaintextFilePath)) {
|
|
1282
1596
|
mkdirSync5(this.baseDir, { recursive: true });
|
|
1283
1597
|
return;
|
|
1284
1598
|
}
|
|
1285
1599
|
try {
|
|
1286
|
-
const data = JSON.parse(
|
|
1600
|
+
const data = JSON.parse(readFileSync8(this.plaintextFilePath, "utf-8"));
|
|
1287
1601
|
this.records = data.records ?? [];
|
|
1288
1602
|
} catch {
|
|
1289
1603
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -1291,9 +1605,9 @@ var DenialLog = class {
|
|
|
1291
1605
|
}
|
|
1292
1606
|
}
|
|
1293
1607
|
loadEncrypted() {
|
|
1294
|
-
if (!
|
|
1608
|
+
if (!existsSync7(this.encryptedFilePath)) return;
|
|
1295
1609
|
try {
|
|
1296
|
-
const data = JSON.parse(
|
|
1610
|
+
const data = JSON.parse(readFileSync8(this.encryptedFilePath, "utf-8"));
|
|
1297
1611
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
1298
1612
|
this.records = logFile.records ?? [];
|
|
1299
1613
|
} catch (err) {
|
|
@@ -1319,11 +1633,11 @@ var DenialLog = class {
|
|
|
1319
1633
|
};
|
|
1320
1634
|
|
|
1321
1635
|
// src/lib/receipt-archive.ts
|
|
1322
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
1636
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync5 } from "fs";
|
|
1323
1637
|
import { homedir as homedir4 } from "os";
|
|
1324
|
-
import { dirname as dirname6, join as
|
|
1638
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1325
1639
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
1326
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
1640
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join8(homedir4(), ".suveren");
|
|
1327
1641
|
function stripPreviews(proposal) {
|
|
1328
1642
|
const args = proposal.toolArgs;
|
|
1329
1643
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -1348,11 +1662,11 @@ var ReceiptArchive = class {
|
|
|
1348
1662
|
*/
|
|
1349
1663
|
setVaultKey(key) {
|
|
1350
1664
|
this.vaultKey = key;
|
|
1351
|
-
if (
|
|
1665
|
+
if (existsSync8(this.encryptedFilePath)) {
|
|
1352
1666
|
this.loadEncrypted();
|
|
1353
1667
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
1354
1668
|
this.persistEncrypted();
|
|
1355
|
-
if (
|
|
1669
|
+
if (existsSync8(this.plaintextFilePath)) {
|
|
1356
1670
|
try {
|
|
1357
1671
|
unlinkSync5(this.plaintextFilePath);
|
|
1358
1672
|
} catch {
|
|
@@ -1366,7 +1680,7 @@ var ReceiptArchive = class {
|
|
|
1366
1680
|
* this state — that would report a hollow success over unreadable evidence.
|
|
1367
1681
|
*/
|
|
1368
1682
|
isLocked() {
|
|
1369
|
-
return this.vaultKey === null &&
|
|
1683
|
+
return this.vaultKey === null && existsSync8(this.encryptedFilePath);
|
|
1370
1684
|
}
|
|
1371
1685
|
/**
|
|
1372
1686
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -1467,19 +1781,19 @@ var ReceiptArchive = class {
|
|
|
1467
1781
|
}
|
|
1468
1782
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1469
1783
|
get plaintextFilePath() {
|
|
1470
|
-
return
|
|
1784
|
+
return join8(this.baseDir, "receipt-archive.json");
|
|
1471
1785
|
}
|
|
1472
1786
|
get encryptedFilePath() {
|
|
1473
|
-
return
|
|
1787
|
+
return join8(this.baseDir, "receipt-archive.enc.json");
|
|
1474
1788
|
}
|
|
1475
1789
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1476
1790
|
loadPlaintext() {
|
|
1477
|
-
if (!
|
|
1791
|
+
if (!existsSync8(this.plaintextFilePath)) {
|
|
1478
1792
|
mkdirSync6(this.baseDir, { recursive: true });
|
|
1479
1793
|
return;
|
|
1480
1794
|
}
|
|
1481
1795
|
try {
|
|
1482
|
-
const raw =
|
|
1796
|
+
const raw = readFileSync9(this.plaintextFilePath, "utf-8");
|
|
1483
1797
|
const parsed = JSON.parse(raw);
|
|
1484
1798
|
this.data = {
|
|
1485
1799
|
version: 1,
|
|
@@ -1491,16 +1805,16 @@ var ReceiptArchive = class {
|
|
|
1491
1805
|
try {
|
|
1492
1806
|
writeFileSync6(
|
|
1493
1807
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1494
|
-
|
|
1808
|
+
readFileSync9(this.plaintextFilePath)
|
|
1495
1809
|
);
|
|
1496
1810
|
} catch {
|
|
1497
1811
|
}
|
|
1498
1812
|
}
|
|
1499
1813
|
}
|
|
1500
1814
|
loadEncrypted() {
|
|
1501
|
-
if (!
|
|
1815
|
+
if (!existsSync8(this.encryptedFilePath)) return;
|
|
1502
1816
|
try {
|
|
1503
|
-
const raw =
|
|
1817
|
+
const raw = readFileSync9(this.encryptedFilePath, "utf-8");
|
|
1504
1818
|
const data = JSON.parse(raw);
|
|
1505
1819
|
const decrypted = this.decrypt(data.blob);
|
|
1506
1820
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1514,7 +1828,7 @@ var ReceiptArchive = class {
|
|
|
1514
1828
|
try {
|
|
1515
1829
|
writeFileSync6(
|
|
1516
1830
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1517
|
-
|
|
1831
|
+
readFileSync9(this.encryptedFilePath)
|
|
1518
1832
|
);
|
|
1519
1833
|
} catch {
|
|
1520
1834
|
}
|
|
@@ -1542,11 +1856,11 @@ var ReceiptArchive = class {
|
|
|
1542
1856
|
};
|
|
1543
1857
|
|
|
1544
1858
|
// src/lib/execution-journal.ts
|
|
1545
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
1859
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, renameSync } from "fs";
|
|
1546
1860
|
import { homedir as homedir5 } from "os";
|
|
1547
|
-
import { dirname as dirname7, join as
|
|
1861
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
1548
1862
|
import { createHash as createHash3 } from "crypto";
|
|
1549
|
-
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ??
|
|
1863
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join9(homedir5(), ".suveren");
|
|
1550
1864
|
var FILE_NAME = "execution-journal.json";
|
|
1551
1865
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1552
1866
|
function hashToolArgs(args) {
|
|
@@ -1563,7 +1877,7 @@ var ExecutionJournal = class {
|
|
|
1563
1877
|
filePath;
|
|
1564
1878
|
constructor(filePath) {
|
|
1565
1879
|
const baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1566
|
-
this.filePath =
|
|
1880
|
+
this.filePath = join9(baseDir, FILE_NAME);
|
|
1567
1881
|
}
|
|
1568
1882
|
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1569
1883
|
get(ticketId) {
|
|
@@ -1602,9 +1916,9 @@ var ExecutionJournal = class {
|
|
|
1602
1916
|
}
|
|
1603
1917
|
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1604
1918
|
load() {
|
|
1605
|
-
if (!
|
|
1919
|
+
if (!existsSync9(this.filePath)) return { version: 1, entries: [] };
|
|
1606
1920
|
try {
|
|
1607
|
-
const parsed = JSON.parse(
|
|
1921
|
+
const parsed = JSON.parse(readFileSync10(this.filePath, "utf8"));
|
|
1608
1922
|
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1609
1923
|
throw new Error("unexpected shape");
|
|
1610
1924
|
}
|
|
@@ -1629,10 +1943,10 @@ function nowSec() {
|
|
|
1629
1943
|
}
|
|
1630
1944
|
|
|
1631
1945
|
// src/lib/proposal-submission-store.ts
|
|
1632
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
1946
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync2 } from "fs";
|
|
1633
1947
|
import { homedir as homedir6 } from "os";
|
|
1634
|
-
import { dirname as dirname8, join as
|
|
1635
|
-
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ??
|
|
1948
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
1949
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join10(homedir6(), ".suveren");
|
|
1636
1950
|
var FILE_NAME2 = "proposal-submissions.json";
|
|
1637
1951
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
1638
1952
|
function nowSec2() {
|
|
@@ -1642,7 +1956,7 @@ var ProposalSubmissionStore = class {
|
|
|
1642
1956
|
filePath;
|
|
1643
1957
|
constructor(filePath) {
|
|
1644
1958
|
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR6;
|
|
1645
|
-
this.filePath =
|
|
1959
|
+
this.filePath = join10(baseDir, FILE_NAME2);
|
|
1646
1960
|
}
|
|
1647
1961
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
1648
1962
|
record(entry) {
|
|
@@ -1665,9 +1979,9 @@ var ProposalSubmissionStore = class {
|
|
|
1665
1979
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
1666
1980
|
}
|
|
1667
1981
|
load() {
|
|
1668
|
-
if (!
|
|
1982
|
+
if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
|
|
1669
1983
|
try {
|
|
1670
|
-
const parsed = JSON.parse(
|
|
1984
|
+
const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
|
|
1671
1985
|
const cutoff = nowSec2() - MAX_AGE_SECONDS3;
|
|
1672
1986
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
1673
1987
|
return parsed;
|
|
@@ -1956,17 +2270,17 @@ function formatConsumptionFull(entries) {
|
|
|
1956
2270
|
}
|
|
1957
2271
|
|
|
1958
2272
|
// src/lib/context-loader.ts
|
|
1959
|
-
import { readFileSync as
|
|
2273
|
+
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
1960
2274
|
import { homedir as homedir7 } from "os";
|
|
1961
|
-
import { join as
|
|
1962
|
-
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ??
|
|
2275
|
+
import { join as join11 } from "path";
|
|
2276
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join11(homedir7(), ".suveren");
|
|
1963
2277
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1964
2278
|
function readContextFile(dataDir2) {
|
|
1965
2279
|
const dir = dataDir2 ?? DEFAULT_DIR7;
|
|
1966
|
-
const filePath =
|
|
1967
|
-
if (!
|
|
2280
|
+
const filePath = join11(dir, "context.md");
|
|
2281
|
+
if (!existsSync11(filePath)) return null;
|
|
1968
2282
|
try {
|
|
1969
|
-
const content =
|
|
2283
|
+
const content = readFileSync12(filePath, "utf-8").trim();
|
|
1970
2284
|
return content || null;
|
|
1971
2285
|
} catch {
|
|
1972
2286
|
return null;
|
|
@@ -2605,30 +2919,49 @@ import { extname } from "path";
|
|
|
2605
2919
|
import { randomUUID } from "crypto";
|
|
2606
2920
|
|
|
2607
2921
|
// src/lib/manifest-loader.ts
|
|
2608
|
-
import { readFileSync as
|
|
2609
|
-
import { join as
|
|
2922
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
2923
|
+
import { join as join12, resolve } from "path";
|
|
2924
|
+
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2925
|
+
function isExactSemver(value) {
|
|
2926
|
+
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
2927
|
+
}
|
|
2928
|
+
function invalidNpmPinReason(manifest) {
|
|
2929
|
+
if (!manifest.npmPackage) return null;
|
|
2930
|
+
if (manifest.npmVersion === void 0) {
|
|
2931
|
+
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
2932
|
+
}
|
|
2933
|
+
if (!isExactSemver(manifest.npmVersion)) {
|
|
2934
|
+
return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
|
|
2935
|
+
}
|
|
2936
|
+
return null;
|
|
2937
|
+
}
|
|
2610
2938
|
var manifests = /* @__PURE__ */ new Map();
|
|
2611
2939
|
function loadManifests(integrationsDir) {
|
|
2612
2940
|
const dir = resolve(
|
|
2613
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
2941
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join12(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
2614
2942
|
);
|
|
2615
|
-
const indexPath =
|
|
2616
|
-
if (!
|
|
2943
|
+
const indexPath = join12(dir, "index.json");
|
|
2944
|
+
if (!existsSync12(indexPath)) {
|
|
2617
2945
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
2618
2946
|
return 0;
|
|
2619
2947
|
}
|
|
2620
2948
|
let index;
|
|
2621
2949
|
try {
|
|
2622
|
-
index = JSON.parse(
|
|
2950
|
+
index = JSON.parse(readFileSync13(indexPath, "utf-8"));
|
|
2623
2951
|
} catch (err) {
|
|
2624
2952
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
2625
2953
|
return 0;
|
|
2626
2954
|
}
|
|
2627
2955
|
let loaded = 0;
|
|
2628
2956
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
2629
|
-
const manifestPath =
|
|
2957
|
+
const manifestPath = join12(dir, relativePath);
|
|
2630
2958
|
try {
|
|
2631
|
-
const manifest = JSON.parse(
|
|
2959
|
+
const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
|
|
2960
|
+
const pinReason = invalidNpmPinReason(manifest);
|
|
2961
|
+
if (pinReason) {
|
|
2962
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2632
2965
|
manifests.set(id, manifest);
|
|
2633
2966
|
loaded++;
|
|
2634
2967
|
} catch (err) {
|
|
@@ -2647,6 +2980,8 @@ function getAllManifests() {
|
|
|
2647
2980
|
|
|
2648
2981
|
// src/lib/simulation-mode.ts
|
|
2649
2982
|
function isSimulationMode() {
|
|
2983
|
+
const policySimulation = readPolicy().policy.simulation;
|
|
2984
|
+
if (policySimulation !== void 0) return policySimulation;
|
|
2650
2985
|
return process.env.SUVEREN_SIMULATION === "1";
|
|
2651
2986
|
}
|
|
2652
2987
|
function manifestIsSimulated(manifest) {
|
|
@@ -4176,16 +4511,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
4176
4511
|
}
|
|
4177
4512
|
|
|
4178
4513
|
// src/lib/integration-registry.ts
|
|
4179
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
4514
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "fs";
|
|
4180
4515
|
import { homedir as homedir8 } from "os";
|
|
4181
|
-
import { dirname as dirname9, join as
|
|
4182
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
4516
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
4517
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join13(homedir8(), ".suveren");
|
|
4183
4518
|
var IntegrationRegistry = class {
|
|
4184
4519
|
integrations = /* @__PURE__ */ new Map();
|
|
4185
4520
|
filePath;
|
|
4186
4521
|
constructor(dataDir2) {
|
|
4187
4522
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
4188
|
-
this.filePath =
|
|
4523
|
+
this.filePath = join13(dir, "integrations.json");
|
|
4189
4524
|
this.load();
|
|
4190
4525
|
}
|
|
4191
4526
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -4216,12 +4551,12 @@ var IntegrationRegistry = class {
|
|
|
4216
4551
|
}
|
|
4217
4552
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
4218
4553
|
load() {
|
|
4219
|
-
if (!
|
|
4554
|
+
if (!existsSync13(this.filePath)) {
|
|
4220
4555
|
mkdirSync9(dirname9(this.filePath), { recursive: true });
|
|
4221
4556
|
return;
|
|
4222
4557
|
}
|
|
4223
4558
|
try {
|
|
4224
|
-
const raw =
|
|
4559
|
+
const raw = readFileSync14(this.filePath, "utf-8");
|
|
4225
4560
|
const data = JSON.parse(raw);
|
|
4226
4561
|
const migrated = data.integrations.map((i) => {
|
|
4227
4562
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -4257,8 +4592,8 @@ var IntegrationRegistry = class {
|
|
|
4257
4592
|
|
|
4258
4593
|
// src/lib/integration-manager.ts
|
|
4259
4594
|
import { homedir as homedir9 } from "os";
|
|
4260
|
-
import { join as
|
|
4261
|
-
import { existsSync as
|
|
4595
|
+
import { join as join14, delimiter } from "path";
|
|
4596
|
+
import { existsSync as existsSync14, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync } from "fs";
|
|
4262
4597
|
import { execFile } from "child_process";
|
|
4263
4598
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4264
4599
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -4335,25 +4670,26 @@ function safeHost(url) {
|
|
|
4335
4670
|
}
|
|
4336
4671
|
|
|
4337
4672
|
// src/lib/integration-manager.ts
|
|
4338
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
4339
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
4673
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(homedir9(), ".suveren");
|
|
4674
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join14(DEFAULT_DATA_DIR2, "integrations");
|
|
4675
|
+
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
4340
4676
|
var installLock = Promise.resolve();
|
|
4341
4677
|
function withInstallLock(task) {
|
|
4342
4678
|
const run = installLock.then(task, task);
|
|
4343
4679
|
installLock = run.then(() => void 0, () => void 0);
|
|
4344
4680
|
return run;
|
|
4345
4681
|
}
|
|
4346
|
-
var INTEGRATIONS_BIN =
|
|
4682
|
+
var INTEGRATIONS_BIN = join14(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
4347
4683
|
function buildPath() {
|
|
4348
4684
|
const base = process.env.PATH ?? "";
|
|
4349
4685
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
4350
4686
|
}
|
|
4351
4687
|
function ensureIntegrationsDir() {
|
|
4352
|
-
if (!
|
|
4688
|
+
if (!existsSync14(INTEGRATIONS_DIR)) {
|
|
4353
4689
|
mkdirSync10(INTEGRATIONS_DIR, { recursive: true });
|
|
4354
4690
|
}
|
|
4355
|
-
const pkgPath =
|
|
4356
|
-
if (!
|
|
4691
|
+
const pkgPath = join14(INTEGRATIONS_DIR, "package.json");
|
|
4692
|
+
if (!existsSync14(pkgPath)) {
|
|
4357
4693
|
writeFileSync10(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
4358
4694
|
}
|
|
4359
4695
|
}
|
|
@@ -4383,6 +4719,17 @@ var IntegrationManager = class {
|
|
|
4383
4719
|
}
|
|
4384
4720
|
running = /* @__PURE__ */ new Map();
|
|
4385
4721
|
onToolsChanged = null;
|
|
4722
|
+
/**
|
|
4723
|
+
* Called when a persisted config was migrated to the manifest's command/args
|
|
4724
|
+
* (see `migrateNpxConfig`) so the caller can write the fix back to
|
|
4725
|
+
* `integrations.json` — the manager itself has no registry/persistence
|
|
4726
|
+
* handle, by design (mirrors `onToolsChanged`). Without a subscriber the
|
|
4727
|
+
* migration still takes effect for THIS start (the in-memory config is
|
|
4728
|
+
* still corrected before spawn); only the one-time persistence is skipped,
|
|
4729
|
+
* so every later start keeps re-migrating in memory, which is safe, if
|
|
4730
|
+
* redundant.
|
|
4731
|
+
*/
|
|
4732
|
+
onConfigMigrated = null;
|
|
4386
4733
|
/**
|
|
4387
4734
|
* Per-integration operation queue. Start/stop for one id are serialized
|
|
4388
4735
|
* through here; without it, a second start issued while the first is still
|
|
@@ -4408,6 +4755,16 @@ var IntegrationManager = class {
|
|
|
4408
4755
|
setOnToolsChanged(cb) {
|
|
4409
4756
|
this.onToolsChanged = cb;
|
|
4410
4757
|
}
|
|
4758
|
+
/**
|
|
4759
|
+
* Register a callback invoked when a persisted config is migrated off a
|
|
4760
|
+
* stale `npx` command to the current manifest's command/args (see
|
|
4761
|
+
* `migrateNpxConfig`). The caller is expected to persist `updates` against
|
|
4762
|
+
* `id` in `integrations.json` so the fix survives the next boot instead of
|
|
4763
|
+
* silently reapplying on every start.
|
|
4764
|
+
*/
|
|
4765
|
+
setOnConfigMigrated(cb) {
|
|
4766
|
+
this.onConfigMigrated = cb;
|
|
4767
|
+
}
|
|
4411
4768
|
/**
|
|
4412
4769
|
* Is `npmPackage` present AND usable?
|
|
4413
4770
|
*
|
|
@@ -4420,24 +4777,41 @@ var IntegrationManager = class {
|
|
|
4420
4777
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
4421
4778
|
*/
|
|
4422
4779
|
isUsableInstall(npmPackage) {
|
|
4423
|
-
const pkgDir =
|
|
4424
|
-
const pkgJson =
|
|
4425
|
-
if (!
|
|
4780
|
+
const pkgDir = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4781
|
+
const pkgJson = join14(pkgDir, "package.json");
|
|
4782
|
+
if (!existsSync14(pkgDir) || !existsSync14(pkgJson)) return false;
|
|
4426
4783
|
try {
|
|
4427
|
-
const pkg = JSON.parse(
|
|
4784
|
+
const pkg = JSON.parse(readFileSync15(pkgJson, "utf8"));
|
|
4428
4785
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
4429
4786
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
4430
4787
|
const binEntries = Object.entries(binMap);
|
|
4431
4788
|
for (const [binName, target] of binEntries) {
|
|
4432
|
-
if (!
|
|
4789
|
+
if (!existsSync14(join14(pkgDir, target))) return false;
|
|
4433
4790
|
if (!this.binShimExists(binName)) return false;
|
|
4434
4791
|
}
|
|
4435
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
4792
|
+
if (binEntries.length === 0 && pkg.main && !existsSync14(join14(pkgDir, pkg.main))) return false;
|
|
4436
4793
|
return true;
|
|
4437
4794
|
} catch {
|
|
4438
4795
|
return false;
|
|
4439
4796
|
}
|
|
4440
4797
|
}
|
|
4798
|
+
/**
|
|
4799
|
+
* The installed version of `npmPackage`, or null if it isn't usably
|
|
4800
|
+
* installed at all (see `isUsableInstall`) or its package.json has no
|
|
4801
|
+
* parseable `version`. Never throws — an unreadable version is treated the
|
|
4802
|
+
* same as "not installed", which is the safe direction: it drives a
|
|
4803
|
+
* (re)install rather than a false "already pinned".
|
|
4804
|
+
*/
|
|
4805
|
+
readInstalledVersion(npmPackage) {
|
|
4806
|
+
if (!this.isUsableInstall(npmPackage)) return null;
|
|
4807
|
+
const pkgJson = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
4808
|
+
try {
|
|
4809
|
+
const pkg = JSON.parse(readFileSync15(pkgJson, "utf8"));
|
|
4810
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
4811
|
+
} catch {
|
|
4812
|
+
return null;
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4441
4815
|
/**
|
|
4442
4816
|
* Does the node_modules/.bin shim for `binName` exist? On Windows npm writes
|
|
4443
4817
|
* `<name>`, `<name>.cmd`, and `<name>.ps1`; the .cmd variant is what actually
|
|
@@ -4446,11 +4820,34 @@ var IntegrationManager = class {
|
|
|
4446
4820
|
binShimExists(binName) {
|
|
4447
4821
|
if (!binName) return false;
|
|
4448
4822
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
4449
|
-
return candidates.some((c) =>
|
|
4823
|
+
return candidates.some((c) => existsSync14(join14(INTEGRATIONS_BIN, c)));
|
|
4824
|
+
}
|
|
4825
|
+
/**
|
|
4826
|
+
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
4827
|
+
* `config.npmPackage` to — or undefined when there's no npmPackage, no
|
|
4828
|
+
* manifest for this id, or the manifest's `npmPackage` no longer names the
|
|
4829
|
+
* same package as `config` (a manually re-pointed integration: nothing in
|
|
4830
|
+
* the manifest vouches for a version of config's actual package).
|
|
4831
|
+
*
|
|
4832
|
+
* Read fresh from `getManifest(config.id)` on every call, never from
|
|
4833
|
+
* anything persisted on `config` itself — manifests are reloaded fresh on
|
|
4834
|
+
* every boot, `integrations.json` is not, so this is what keeps the pin
|
|
4835
|
+
* from going stale the day after an integration was first activated. Used
|
|
4836
|
+
* both to decide the npm install/update (`ensureInstalled`) and to decide
|
|
4837
|
+
* whether a persisted `npx …` command must be refused/migrated
|
|
4838
|
+
* (`migrateNpxConfig`) — one definition of "pinned", so the two can't
|
|
4839
|
+
* disagree about which connectors it applies to.
|
|
4840
|
+
*/
|
|
4841
|
+
pinnedVersionFor(config) {
|
|
4842
|
+
if (!config.npmPackage) return void 0;
|
|
4843
|
+
const manifest = getManifest(config.id);
|
|
4844
|
+
return manifest?.npmPackage === config.npmPackage ? manifest.npmVersion : void 0;
|
|
4450
4845
|
}
|
|
4451
4846
|
/**
|
|
4452
4847
|
* Install an npm package into the managed integrations directory if not
|
|
4453
|
-
* already present
|
|
4848
|
+
* already present, and — when `config`'s manifest pins an exact
|
|
4849
|
+
* `npmVersion` for this same package — bring the installed copy to EXACTLY
|
|
4850
|
+
* that version, older or newer. Called automatically before spawning when
|
|
4454
4851
|
* config.npmPackage is set.
|
|
4455
4852
|
*
|
|
4456
4853
|
* Asynchronous ON PURPOSE. This used to be execSync, which blocked the MCP
|
|
@@ -4460,19 +4857,62 @@ var IntegrationManager = class {
|
|
|
4460
4857
|
* CI runners: ~3.6s (Linux), ~5.4s (macOS), ~14s (Windows), and far worse
|
|
4461
4858
|
* on machines with real-time antivirus.
|
|
4462
4859
|
*/
|
|
4463
|
-
async ensureInstalled(
|
|
4860
|
+
async ensureInstalled(config) {
|
|
4861
|
+
const npmPackage = config.npmPackage;
|
|
4862
|
+
if (!npmPackage) return;
|
|
4464
4863
|
ensureIntegrationsDir();
|
|
4864
|
+
const pinnedVersion = this.pinnedVersionFor(config);
|
|
4865
|
+
if (OFFLINE_MODE) {
|
|
4866
|
+
const current = this.readInstalledVersion(npmPackage);
|
|
4867
|
+
if (pinnedVersion) {
|
|
4868
|
+
if (current === pinnedVersion) return;
|
|
4869
|
+
throw new Error(
|
|
4870
|
+
`${config.id}: connector ${npmPackage} version ${pinnedVersion} is not part of this installation (found ${current ?? "nothing installed"} at ${INTEGRATIONS_DIR}; SUVEREN_OFFLINE=1 so no npm install was attempted).`
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
if (current !== null) return;
|
|
4874
|
+
throw new Error(
|
|
4875
|
+
`${config.id}: connector ${npmPackage} is not part of this installation (nothing installed at ${INTEGRATIONS_DIR}; SUVEREN_OFFLINE=1 so no npm install was attempted).`
|
|
4876
|
+
);
|
|
4877
|
+
}
|
|
4878
|
+
if (pinnedVersion) {
|
|
4879
|
+
if (this.readInstalledVersion(npmPackage) === pinnedVersion) return;
|
|
4880
|
+
return withInstallLock(async () => {
|
|
4881
|
+
const current = this.readInstalledVersion(npmPackage);
|
|
4882
|
+
if (current === pinnedVersion) return;
|
|
4883
|
+
console.error(
|
|
4884
|
+
`[IntegrationManager] ${npmPackage} ${current ?? "(not installed)"} \u2192 ${pinnedVersion} (pinned by manifest)`
|
|
4885
|
+
);
|
|
4886
|
+
const cleanFirst = !this.isUsableInstall(npmPackage);
|
|
4887
|
+
await this.installNow(npmPackage, pinnedVersion, cleanFirst);
|
|
4888
|
+
});
|
|
4889
|
+
}
|
|
4465
4890
|
if (this.isUsableInstall(npmPackage)) return;
|
|
4466
4891
|
return withInstallLock(async () => {
|
|
4467
4892
|
if (this.isUsableInstall(npmPackage)) return;
|
|
4468
4893
|
await this.installNow(npmPackage);
|
|
4469
4894
|
});
|
|
4470
4895
|
}
|
|
4471
|
-
/**
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4896
|
+
/**
|
|
4897
|
+
* The actual install. Callers MUST hold the install lock.
|
|
4898
|
+
*
|
|
4899
|
+
* `exactVersion`, when given, is a pin already validated as an exact semver
|
|
4900
|
+
* by the manifest loader (`isExactSemver`) — re-validated here anyway
|
|
4901
|
+
* because this method does not trust its caller's caller, and because the
|
|
4902
|
+
* version reaches a shell on Windows (see the package-name check below).
|
|
4903
|
+
*
|
|
4904
|
+
* `cleanFirst` (default true, i.e. today's behaviour for every caller
|
|
4905
|
+
* except a version-only pin correction): wipe any existing package
|
|
4906
|
+
* directory before installing. Needed for a genuinely broken/partial
|
|
4907
|
+
* install, where stale files can make the reinstall itself fail; skipped
|
|
4908
|
+
* for a USABLE install being corrected to a different pinned version, so a
|
|
4909
|
+
* failed update leaves the previously-working version on disk instead of
|
|
4910
|
+
* nothing at all.
|
|
4911
|
+
*/
|
|
4912
|
+
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
4913
|
+
const pkgDir = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4914
|
+
if (cleanFirst && existsSync14(pkgDir)) {
|
|
4915
|
+
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
4476
4916
|
try {
|
|
4477
4917
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
4478
4918
|
} catch (err) {
|
|
@@ -4482,18 +4922,22 @@ var IntegrationManager = class {
|
|
|
4482
4922
|
if (!/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i.test(npmPackage)) {
|
|
4483
4923
|
throw new Error(`Refusing to install unsafe package name: ${JSON.stringify(npmPackage)}`);
|
|
4484
4924
|
}
|
|
4485
|
-
|
|
4925
|
+
if (exactVersion !== void 0 && !isExactSemver(exactVersion)) {
|
|
4926
|
+
throw new Error(`Refusing to install ${npmPackage} at unsafe/non-exact version spec: ${JSON.stringify(exactVersion)}`);
|
|
4927
|
+
}
|
|
4928
|
+
const spec = exactVersion ? `${npmPackage}@${exactVersion}` : npmPackage;
|
|
4929
|
+
console.error(`[IntegrationManager] Installing ${spec}...`);
|
|
4486
4930
|
const startedAt = Date.now();
|
|
4487
4931
|
const isWin = process.platform === "win32";
|
|
4488
4932
|
await new Promise((resolve3, reject) => {
|
|
4489
4933
|
const child = execFile(
|
|
4490
4934
|
"npm",
|
|
4491
|
-
["install", "--no-fund", "--no-audit",
|
|
4935
|
+
["install", "--no-fund", "--no-audit", spec],
|
|
4492
4936
|
{
|
|
4493
4937
|
cwd: INTEGRATIONS_DIR,
|
|
4494
4938
|
// Windows: a shell so cmd.exe finds npm.cmd (see EINVAL note above).
|
|
4495
4939
|
// POSIX: no shell — npm is a normal executable and keeping shell off
|
|
4496
|
-
// means the validated package
|
|
4940
|
+
// means the validated package spec is still passed as a bare argv.
|
|
4497
4941
|
shell: isWin,
|
|
4498
4942
|
// No timeout: a slow install on a machine with antivirus is not an
|
|
4499
4943
|
// error, and killing it midway is what created broken installs in
|
|
@@ -4503,20 +4947,28 @@ var IntegrationManager = class {
|
|
|
4503
4947
|
},
|
|
4504
4948
|
(err, _stdout, stderr) => {
|
|
4505
4949
|
if (err) {
|
|
4506
|
-
reject(new Error(`Failed to install ${
|
|
4950
|
+
reject(new Error(`Failed to install ${spec}: ${err.message}${stderr ? ` \u2014 ${stderr.trim()}` : ""}`));
|
|
4507
4951
|
return;
|
|
4508
4952
|
}
|
|
4509
4953
|
resolve3();
|
|
4510
4954
|
}
|
|
4511
4955
|
);
|
|
4512
|
-
child.on("error", (err) => reject(new Error(`Failed to run npm for ${
|
|
4956
|
+
child.on("error", (err) => reject(new Error(`Failed to run npm for ${spec}: ${err.message}`)));
|
|
4513
4957
|
});
|
|
4514
4958
|
if (!this.isUsableInstall(npmPackage)) {
|
|
4515
4959
|
throw new Error(
|
|
4516
|
-
`Installed ${
|
|
4960
|
+
`Installed ${spec} but it is not usable \u2014 package.json or its bin target is missing. Check ${INTEGRATIONS_DIR} for a partial install.`
|
|
4517
4961
|
);
|
|
4518
4962
|
}
|
|
4519
|
-
|
|
4963
|
+
if (exactVersion !== void 0) {
|
|
4964
|
+
const installed = this.readInstalledVersion(npmPackage);
|
|
4965
|
+
if (installed !== exactVersion) {
|
|
4966
|
+
throw new Error(
|
|
4967
|
+
`Installed ${npmPackage} but got version ${installed ?? "unknown"}, expected the pinned ${exactVersion}.`
|
|
4968
|
+
);
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
console.error(`[IntegrationManager] Installed ${spec} (${((Date.now() - startedAt) / 1e3).toFixed(1)}s)`);
|
|
4520
4972
|
}
|
|
4521
4973
|
/**
|
|
4522
4974
|
* Connect a client to its transport, but never wait indefinitely. Resolves
|
|
@@ -4575,7 +5027,26 @@ var IntegrationManager = class {
|
|
|
4575
5027
|
throw new Error(`${config.id}: ${SIMULATION_BLOCK_REASON}`);
|
|
4576
5028
|
}
|
|
4577
5029
|
if (config.npmPackage) {
|
|
4578
|
-
await this.ensureInstalled(config
|
|
5030
|
+
await this.ensureInstalled(config);
|
|
5031
|
+
}
|
|
5032
|
+
const pinnedVersion = this.pinnedVersionFor(config);
|
|
5033
|
+
if (pinnedVersion && config.command === "npx") {
|
|
5034
|
+
const manifest = getManifest(config.id);
|
|
5035
|
+
const migrated = {
|
|
5036
|
+
command: manifest.mcp.command,
|
|
5037
|
+
args: manifest.mcp.args,
|
|
5038
|
+
env: manifest.mcp.env ?? config.env
|
|
5039
|
+
};
|
|
5040
|
+
console.error(
|
|
5041
|
+
`[IntegrationManager] ${config.id}: command migrated to manifest (was npx ${config.args.join(" ")})`
|
|
5042
|
+
);
|
|
5043
|
+
config = { ...config, ...migrated };
|
|
5044
|
+
this.onConfigMigrated?.(config.id, migrated);
|
|
5045
|
+
}
|
|
5046
|
+
if (pinnedVersion && config.command === "npx") {
|
|
5047
|
+
throw new Error(
|
|
5048
|
+
`${config.id}: refusing to spawn a pinned connector ("${config.npmPackage}"@${pinnedVersion}) via npx \u2014 this would ignore the pinned local install and fetch the package fresh on every start. The manifest's mcp.command must name the installed binary, not "npx".`
|
|
5049
|
+
);
|
|
4579
5050
|
}
|
|
4580
5051
|
const env = this.resolveEnvKeys(config);
|
|
4581
5052
|
if (isSimulationMode()) {
|
|
@@ -4929,28 +5400,28 @@ var IntegrationManager = class {
|
|
|
4929
5400
|
};
|
|
4930
5401
|
|
|
4931
5402
|
// src/lib/profile-loader.ts
|
|
4932
|
-
import { readFileSync as
|
|
4933
|
-
import { join as
|
|
5403
|
+
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
5404
|
+
import { join as join15, resolve as resolve2 } from "path";
|
|
4934
5405
|
import { registerProfile } from "@hap/core";
|
|
4935
5406
|
function loadProfiles(profilesDir) {
|
|
4936
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
4937
|
-
const indexPath =
|
|
4938
|
-
if (!
|
|
5407
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join15(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
5408
|
+
const indexPath = join15(dir, "index.json");
|
|
5409
|
+
if (!existsSync15(indexPath)) {
|
|
4939
5410
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
4940
5411
|
return 0;
|
|
4941
5412
|
}
|
|
4942
5413
|
let index;
|
|
4943
5414
|
try {
|
|
4944
|
-
index = JSON.parse(
|
|
5415
|
+
index = JSON.parse(readFileSync16(indexPath, "utf-8"));
|
|
4945
5416
|
} catch (err) {
|
|
4946
5417
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
4947
5418
|
return 0;
|
|
4948
5419
|
}
|
|
4949
5420
|
let loaded = 0;
|
|
4950
5421
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
4951
|
-
const profilePath =
|
|
5422
|
+
const profilePath = join15(dir, relativePath);
|
|
4952
5423
|
try {
|
|
4953
|
-
const profile = JSON.parse(
|
|
5424
|
+
const profile = JSON.parse(readFileSync16(profilePath, "utf-8"));
|
|
4954
5425
|
registerProfile(profileId, profile);
|
|
4955
5426
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
4956
5427
|
if (shortName && shortName !== profileId) {
|
|
@@ -4986,8 +5457,8 @@ function warnNoProfiles(reason) {
|
|
|
4986
5457
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
4987
5458
|
|
|
4988
5459
|
// src/lib/committed-executor.ts
|
|
4989
|
-
import { existsSync as
|
|
4990
|
-
import { dirname as dirname10, join as
|
|
5460
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
5461
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
4991
5462
|
var CommittedExecutor = class {
|
|
4992
5463
|
constructor(run) {
|
|
4993
5464
|
this.run = run;
|
|
@@ -5015,7 +5486,7 @@ var ExecutorLock = class {
|
|
|
5015
5486
|
path;
|
|
5016
5487
|
held = false;
|
|
5017
5488
|
constructor(dataDir2) {
|
|
5018
|
-
this.path =
|
|
5489
|
+
this.path = join16(dataDir2, "executor.lock");
|
|
5019
5490
|
}
|
|
5020
5491
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
5021
5492
|
acquire() {
|
|
@@ -5047,9 +5518,9 @@ var ExecutorLock = class {
|
|
|
5047
5518
|
}
|
|
5048
5519
|
}
|
|
5049
5520
|
read() {
|
|
5050
|
-
if (!
|
|
5521
|
+
if (!existsSync16(this.path)) return null;
|
|
5051
5522
|
try {
|
|
5052
|
-
const parsed = JSON.parse(
|
|
5523
|
+
const parsed = JSON.parse(readFileSync17(this.path, "utf8"));
|
|
5053
5524
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
5054
5525
|
} catch {
|
|
5055
5526
|
return null;
|
|
@@ -5067,19 +5538,19 @@ function isAlive(pid) {
|
|
|
5067
5538
|
|
|
5068
5539
|
// bin/http.ts
|
|
5069
5540
|
import { homedir as homedir10 } from "os";
|
|
5070
|
-
import { join as
|
|
5541
|
+
import { join as join18 } from "path";
|
|
5071
5542
|
|
|
5072
5543
|
// src/lib/mcp-as-tracker.ts
|
|
5073
|
-
import { existsSync as
|
|
5074
|
-
import { dirname as dirname11, join as
|
|
5544
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
5545
|
+
import { dirname as dirname11, join as join17 } from "path";
|
|
5075
5546
|
function trackerPath(dataDir2) {
|
|
5076
|
-
return
|
|
5547
|
+
return join17(dataDir2, "mcp-as-pairing.json");
|
|
5077
5548
|
}
|
|
5078
5549
|
function readMcpPairedAsUrl(dataDir2) {
|
|
5079
5550
|
const path = trackerPath(dataDir2);
|
|
5080
|
-
if (!
|
|
5551
|
+
if (!existsSync17(path)) return null;
|
|
5081
5552
|
try {
|
|
5082
|
-
const data = JSON.parse(
|
|
5553
|
+
const data = JSON.parse(readFileSync18(path, "utf-8"));
|
|
5083
5554
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
5084
5555
|
} catch {
|
|
5085
5556
|
return null;
|
|
@@ -5092,7 +5563,7 @@ function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
|
5092
5563
|
}
|
|
5093
5564
|
|
|
5094
5565
|
// bin/http.ts
|
|
5095
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
5566
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join18(homedir10(), ".suveren");
|
|
5096
5567
|
var spUrl = resolveAsUrl(dataDir);
|
|
5097
5568
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
5098
5569
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -5129,6 +5600,10 @@ function refreshAllSessions() {
|
|
|
5129
5600
|
integrationManager.setOnToolsChanged(() => {
|
|
5130
5601
|
refreshAllSessions();
|
|
5131
5602
|
});
|
|
5603
|
+
integrationManager.setOnConfigMigrated((id, updates) => {
|
|
5604
|
+
integrationRegistry.update(id, updates);
|
|
5605
|
+
console.error(`[Suveren MCP] ${id}: persisted config migrated (${Object.keys(updates).join(", ")})`);
|
|
5606
|
+
});
|
|
5132
5607
|
var app = express();
|
|
5133
5608
|
app.use(express.json());
|
|
5134
5609
|
app.use((_req, res, next) => {
|
|
@@ -5615,7 +6090,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
5615
6090
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
5616
6091
|
);
|
|
5617
6092
|
installCommittedExecutor(committedExecutor);
|
|
5618
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
6093
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join18(homedir10(), ".suveren");
|
|
5619
6094
|
const executorLock = new ExecutorLock(dataDir2);
|
|
5620
6095
|
const lock = executorLock.acquire();
|
|
5621
6096
|
if (!lock.held) {
|