@haven_ai/connect 0.1.21-alpha.0 → 0.1.23-alpha.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 +62 -0
- package/dist/cli.cjs +442 -63
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +7 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +442 -64
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +448 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -7
- package/dist/index.d.ts +63 -7
- package/dist/index.js +446 -56
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -7,10 +7,13 @@ var os = require('os');
|
|
|
7
7
|
var path = require('path');
|
|
8
8
|
var child_process = require('child_process');
|
|
9
9
|
var util = require('util');
|
|
10
|
+
var yaml = require('yaml');
|
|
10
11
|
var mcp = require('@haven_ai/mcp');
|
|
11
12
|
var sdk = require('@haven_ai/sdk');
|
|
12
13
|
var signer = require('@haven_ai/signer');
|
|
14
|
+
var url = require('url');
|
|
13
15
|
|
|
16
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
17
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
18
|
|
|
16
19
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
@@ -179,11 +182,12 @@ async function writeCredentialFiles(input) {
|
|
|
179
182
|
agentPath,
|
|
180
183
|
{
|
|
181
184
|
agent_id: input.agentId,
|
|
185
|
+
delegate_address: input.delegateAddress,
|
|
182
186
|
safe_address: input.safeAddress,
|
|
183
187
|
chain_id: input.chainId,
|
|
184
188
|
network: input.network,
|
|
185
189
|
agent_budget: input.agentBudget,
|
|
186
|
-
note: "Non-secret orientation for the agent: identity + configured budget. Contains no API key or signing key. For the live remaining budget, call haven_get_allowances."
|
|
190
|
+
note: "Non-secret orientation for the agent: public delegate/Haven wallet identity + configured budget. Contains no API key or signing key. For the live remaining budget, call haven_get_allowances."
|
|
187
191
|
},
|
|
188
192
|
input.warn
|
|
189
193
|
);
|
|
@@ -235,9 +239,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
235
239
|
mcpPackage: "@haven_ai/mcp",
|
|
236
240
|
mcpVersion: mcp.MCP_VERSION,
|
|
237
241
|
sdkPackage: "@haven_ai/sdk",
|
|
238
|
-
sdkVersion: "0.1.
|
|
242
|
+
sdkVersion: "0.1.23-alpha.0",
|
|
239
243
|
signerPackage: "@haven_ai/signer",
|
|
240
|
-
signerVersion: "0.1.
|
|
244
|
+
signerVersion: "0.1.23-alpha.0",
|
|
241
245
|
// Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
|
|
242
246
|
// while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
|
|
243
247
|
// so the guard that was supposed to enforce the floor waved Node v23 through
|
|
@@ -259,13 +263,20 @@ function signerPackageSpec() {
|
|
|
259
263
|
}
|
|
260
264
|
|
|
261
265
|
// src/config-writers.ts
|
|
266
|
+
var HERMES_API_KEY_ENV = "MCP_HAVEN_API_KEY";
|
|
267
|
+
var HermesConfigRecoveryError = class extends Error {
|
|
268
|
+
constructor() {
|
|
269
|
+
super("Hermes configuration recovery did not complete");
|
|
270
|
+
this.name = "HermesConfigRecoveryError";
|
|
271
|
+
}
|
|
272
|
+
};
|
|
262
273
|
var InvalidCodexTomlError = class extends Error {
|
|
263
274
|
constructor(message) {
|
|
264
275
|
super(message);
|
|
265
276
|
this.name = "InvalidCodexTomlError";
|
|
266
277
|
}
|
|
267
278
|
};
|
|
268
|
-
async function writeRuntimeConfig(input) {
|
|
279
|
+
async function writeRuntimeConfig(input, deps = {}) {
|
|
269
280
|
switch (input.runtime) {
|
|
270
281
|
case "codex-cli":
|
|
271
282
|
case "codex-desktop":
|
|
@@ -278,6 +289,8 @@ async function writeRuntimeConfig(input) {
|
|
|
278
289
|
return writeJsonRuntimeConfig(input, vscodeInsidersConfigPath(input.homeDir), "servers");
|
|
279
290
|
case "claude-desktop":
|
|
280
291
|
return writeJsonRuntimeConfig(input, claudeDesktopConfigPath(input.homeDir), "mcpServers");
|
|
292
|
+
case "hermes":
|
|
293
|
+
return writeHermesConfig(input, deps);
|
|
281
294
|
default:
|
|
282
295
|
return {
|
|
283
296
|
hostedConfigured: false,
|
|
@@ -331,6 +344,115 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
331
344
|
return `${JSON.stringify(config, null, 2)}
|
|
332
345
|
`;
|
|
333
346
|
}
|
|
347
|
+
function mergeHermesYaml(existingYaml, hostedServer, signerServer) {
|
|
348
|
+
if (!existingYaml?.trim()) return renderHermesYaml({ haven: hostedServer, "haven-signer": signerServer });
|
|
349
|
+
const doc = yaml.parseDocument(existingYaml, { keepSourceTokens: true });
|
|
350
|
+
if (doc.errors.length > 0 || !yaml.isMap(doc.contents)) {
|
|
351
|
+
throw new Error("Hermes config must be a YAML object");
|
|
352
|
+
}
|
|
353
|
+
const mcpPair = doc.contents.items.find((item) => item.key?.toString() === "mcp_servers");
|
|
354
|
+
const existingServers = mcpPair && yaml.isMap(mcpPair.value) ? mcpPair.value.toJSON() : {};
|
|
355
|
+
const servers = isRecord(existingServers) ? existingServers : {};
|
|
356
|
+
const mergedServers = {
|
|
357
|
+
...servers,
|
|
358
|
+
haven: hostedServer,
|
|
359
|
+
"haven-signer": signerServer
|
|
360
|
+
};
|
|
361
|
+
if (!mcpPair) {
|
|
362
|
+
return appendHermesMcpServers(existingYaml, mergedServers);
|
|
363
|
+
}
|
|
364
|
+
if (!mcpPair.value?.range) return replaceEmptyHermesMcpServers(existingYaml, mcpPair.key?.range, mergedServers);
|
|
365
|
+
return replaceHermesMcpServers(existingYaml, mcpPair.key?.range, mcpPair.value.range, mergedServers);
|
|
366
|
+
}
|
|
367
|
+
function mergeHermesEnv(existingEnv, apiKey) {
|
|
368
|
+
if (/[\r\n]/.test(apiKey)) throw new Error("Hermes API key must be a single line");
|
|
369
|
+
const assignment = `${HERMES_API_KEY_ENV}=${apiKey}`;
|
|
370
|
+
if (!existingEnv) return `${assignment}
|
|
371
|
+
`;
|
|
372
|
+
const lineEnding = existingEnv.includes("\r\n") ? "\r\n" : "\n";
|
|
373
|
+
const hasTrailingNewline = /\r?\n$/.test(existingEnv);
|
|
374
|
+
const lines = existingEnv.split(/\r?\n/);
|
|
375
|
+
if (hasTrailingNewline) lines.pop();
|
|
376
|
+
let found = false;
|
|
377
|
+
const merged = lines.flatMap((line) => {
|
|
378
|
+
if (isHermesEnvAssignment(line)) {
|
|
379
|
+
if (found) return [];
|
|
380
|
+
found = true;
|
|
381
|
+
return [assignment];
|
|
382
|
+
}
|
|
383
|
+
if (isAmbiguousHermesEnvLine(line)) {
|
|
384
|
+
throw new Error("Hermes environment contains an ambiguous managed key");
|
|
385
|
+
}
|
|
386
|
+
return [line];
|
|
387
|
+
});
|
|
388
|
+
if (!found) {
|
|
389
|
+
return `${existingEnv}${hasTrailingNewline ? "" : lineEnding}${assignment}${lineEnding}`;
|
|
390
|
+
}
|
|
391
|
+
return `${merged.join(lineEnding)}${hasTrailingNewline ? lineEnding : ""}`;
|
|
392
|
+
}
|
|
393
|
+
function isHermesEnvAssignment(line) {
|
|
394
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY[ \t]*=/.test(line);
|
|
395
|
+
}
|
|
396
|
+
function isAmbiguousHermesEnvLine(line) {
|
|
397
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY\b/.test(line);
|
|
398
|
+
}
|
|
399
|
+
function appendHermesMcpServers(source, servers) {
|
|
400
|
+
const documentEnd = /(?:^|\n)[ \t]*\.\.\.[ \t]*(?:#[^\n]*)?\r?\n?$/.exec(source);
|
|
401
|
+
if (documentEnd) {
|
|
402
|
+
const markerStart = documentEnd.index + (documentEnd[0].startsWith("\n") ? 1 : 0);
|
|
403
|
+
const beforeMarker = source.slice(0, markerStart);
|
|
404
|
+
return `${beforeMarker}${beforeMarker.endsWith("\n") ? "" : "\n"}${renderHermesMcpServers(servers, "")}
|
|
405
|
+
${source.slice(markerStart)}`;
|
|
406
|
+
}
|
|
407
|
+
const separator = source.endsWith("\n") ? "" : "\n";
|
|
408
|
+
return `${source}${separator}${renderHermesMcpServers(servers, "")}
|
|
409
|
+
`;
|
|
410
|
+
}
|
|
411
|
+
function replaceHermesMcpServers(source, keyRange, valueRange, servers) {
|
|
412
|
+
const valueStart = valueRange[0];
|
|
413
|
+
const valueEnd = valueRange[1];
|
|
414
|
+
const valueLineStart = source.lastIndexOf("\n", valueStart - 1) + 1;
|
|
415
|
+
const keyStartsOnValueLine = keyRange?.[0] !== void 0 && keyRange[0] >= valueLineStart;
|
|
416
|
+
const nestedIndent = `${source.slice(valueLineStart, keyRange?.[0] ?? valueLineStart)} `;
|
|
417
|
+
if (keyStartsOnValueLine) {
|
|
418
|
+
const keyStart = keyRange[0];
|
|
419
|
+
const keyEnd = keyRange[1];
|
|
420
|
+
const replacement2 = `${source.slice(keyStart, keyEnd)}:
|
|
421
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
422
|
+
const needsTrailingNewline2 = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
423
|
+
return `${source.slice(0, keyStart)}${replacement2}${needsTrailingNewline2 ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
424
|
+
}
|
|
425
|
+
const indent = source.slice(valueLineStart, valueStart);
|
|
426
|
+
const rendered = yaml.stringify(servers, { lineWidth: 120 }).trimEnd();
|
|
427
|
+
const replacement = rendered.split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`).join("\n");
|
|
428
|
+
const needsTrailingNewline = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
429
|
+
return `${source.slice(0, valueStart)}${replacement}${needsTrailingNewline ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
430
|
+
}
|
|
431
|
+
function replaceEmptyHermesMcpServers(source, keyRange, servers) {
|
|
432
|
+
if (!keyRange) throw new Error("Hermes config mcp_servers key is invalid");
|
|
433
|
+
const keyStart = keyRange[0];
|
|
434
|
+
const keyEnd = keyRange[1];
|
|
435
|
+
const lineStart = source.lastIndexOf("\n", keyStart - 1) + 1;
|
|
436
|
+
const lineEnd = source.indexOf("\n", keyEnd);
|
|
437
|
+
const nestedIndent = `${source.slice(lineStart, keyStart)} `;
|
|
438
|
+
const replacement = `${source.slice(keyStart, keyEnd)}:
|
|
439
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
440
|
+
return `${source.slice(0, keyStart)}${replacement}${lineEnd === -1 ? "\n" : source.slice(lineEnd)}`;
|
|
441
|
+
}
|
|
442
|
+
function renderHermesYaml(servers) {
|
|
443
|
+
return `${renderHermesMcpServers(servers, "")}
|
|
444
|
+
`;
|
|
445
|
+
}
|
|
446
|
+
function renderHermesMcpServers(servers, indent) {
|
|
447
|
+
return `mcp_servers:
|
|
448
|
+
${renderHermesMcpServerEntries(servers, `${indent} `)}`;
|
|
449
|
+
}
|
|
450
|
+
function renderHermesMcpServerEntries(servers, indent) {
|
|
451
|
+
return yaml.stringify(servers, { lineWidth: 120 }).trimEnd().split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
452
|
+
}
|
|
453
|
+
function isRecord(value) {
|
|
454
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
455
|
+
}
|
|
334
456
|
function mergeCodexToml(existingToml, localMcpCommand) {
|
|
335
457
|
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
336
458
|
next = next.trimEnd();
|
|
@@ -401,6 +523,82 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
401
523
|
};
|
|
402
524
|
}
|
|
403
525
|
}
|
|
526
|
+
async function writeHermesConfig(input, deps) {
|
|
527
|
+
const target = hermesConfigPath(input.homeDir);
|
|
528
|
+
const envTarget = hermesEnvPath(input.homeDir);
|
|
529
|
+
try {
|
|
530
|
+
const [existing, existingEnv] = await Promise.all([readOptional(target), readOptional(envTarget)]);
|
|
531
|
+
const hostedServer = {
|
|
532
|
+
...buildHostedServer(input.hostedMcpUrl, `\${${HERMES_API_KEY_ENV}}`, input.runtime),
|
|
533
|
+
enabled: true
|
|
534
|
+
};
|
|
535
|
+
const signerServer = {
|
|
536
|
+
...buildSignerServer(resolveSignerLaunchSpec(input), input.runtime),
|
|
537
|
+
enabled: true
|
|
538
|
+
};
|
|
539
|
+
const merged = mergeHermesYaml(
|
|
540
|
+
existing,
|
|
541
|
+
hostedServer,
|
|
542
|
+
signerServer
|
|
543
|
+
);
|
|
544
|
+
const mergedEnv = mergeHermesEnv(existingEnv, input.apiKey);
|
|
545
|
+
const writeText = deps.writeOwnerOnlyText ?? writeOwnerOnlyText;
|
|
546
|
+
await writeText(envTarget, mergedEnv);
|
|
547
|
+
try {
|
|
548
|
+
await writeText(target, merged);
|
|
549
|
+
} catch (err) {
|
|
550
|
+
const recovered = await restoreHermesFiles(target, existing, envTarget, existingEnv, writeText);
|
|
551
|
+
if (!recovered) throw new HermesConfigRecoveryError();
|
|
552
|
+
throw err;
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
hostedConfigured: true,
|
|
556
|
+
signerConfigured: true,
|
|
557
|
+
localMcpConfigured: false,
|
|
558
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
559
|
+
target: "Hermes Agent config",
|
|
560
|
+
changed: existing !== merged || existingEnv !== mergedEnv,
|
|
561
|
+
restartRequired: true,
|
|
562
|
+
messages: [
|
|
563
|
+
`Updated Haven MCP entries in ${target}; stored the hosted MCP identity in ${envTarget}.`,
|
|
564
|
+
"Restart Hermes (start a new session; gateway users: /restart), then verify with `hermes mcp list`, `hermes mcp test haven`, and `hermes mcp test haven-signer`.",
|
|
565
|
+
"If no mcp_* tools appear after restart, ensure the MCP SDK is installed in Hermes: pip install mcp"
|
|
566
|
+
]
|
|
567
|
+
};
|
|
568
|
+
} catch (err) {
|
|
569
|
+
const recoveryIncomplete = err instanceof HermesConfigRecoveryError;
|
|
570
|
+
return {
|
|
571
|
+
hostedConfigured: false,
|
|
572
|
+
signerConfigured: false,
|
|
573
|
+
localMcpConfigured: false,
|
|
574
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
575
|
+
target: "Hermes Agent config",
|
|
576
|
+
changed: false,
|
|
577
|
+
restartRequired: true,
|
|
578
|
+
messages: [recoveryIncomplete ? "Could not update Hermes Agent config. Recovery did not complete; inspect the Hermes configuration before retrying." : "Could not update Hermes Agent config. Existing configuration was left unchanged."],
|
|
579
|
+
errorCode: "runtime_config_write_failed"
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function restoreHermesFiles(configPath, existingConfig, envPath, existingEnv, writeText) {
|
|
584
|
+
const [envResult, configResult] = await Promise.allSettled([
|
|
585
|
+
restoreOptionalText(envPath, existingEnv, writeText),
|
|
586
|
+
restoreOptionalText(configPath, existingConfig, writeText)
|
|
587
|
+
]);
|
|
588
|
+
return envResult.status === "fulfilled" && configResult.status === "fulfilled";
|
|
589
|
+
}
|
|
590
|
+
async function restoreOptionalText(path, existing, writeText) {
|
|
591
|
+
if (existing !== null) {
|
|
592
|
+
await writeText(path, existing);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
await promises.unlink(path);
|
|
597
|
+
} catch (err) {
|
|
598
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
|
|
599
|
+
throw err;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
404
602
|
async function writeCodexConfig(input) {
|
|
405
603
|
const target = codexConfigPath(input.homeDir);
|
|
406
604
|
const local = input.mode === "local";
|
|
@@ -691,6 +889,15 @@ function claudeDesktopConfigPath(homeDir = os.homedir()) {
|
|
|
691
889
|
}
|
|
692
890
|
return path.resolve(homeDir, ".config", "Claude", "claude_desktop_config.json");
|
|
693
891
|
}
|
|
892
|
+
function hermesConfigPath(homeDir) {
|
|
893
|
+
return path.join(hermesHomePath(homeDir), "config.yaml");
|
|
894
|
+
}
|
|
895
|
+
function hermesEnvPath(homeDir) {
|
|
896
|
+
return path.join(hermesHomePath(homeDir), ".env");
|
|
897
|
+
}
|
|
898
|
+
function hermesHomePath(homeDir) {
|
|
899
|
+
return process.env.HERMES_HOME ?? path.join(homeDir ?? os.homedir(), ".hermes");
|
|
900
|
+
}
|
|
694
901
|
function configTargetLabel(runtime) {
|
|
695
902
|
switch (runtime) {
|
|
696
903
|
case "codex-cli":
|
|
@@ -1196,49 +1403,64 @@ var RUNTIME_PROFILES = {
|
|
|
1196
1403
|
id: "claude-code",
|
|
1197
1404
|
label: "Claude Code",
|
|
1198
1405
|
restartMode: "restart-session",
|
|
1199
|
-
canWriteRuntimeConfig: true
|
|
1406
|
+
canWriteRuntimeConfig: true,
|
|
1407
|
+
activationInstruction: "Start a new Claude Code session so it loads the Haven MCP entries."
|
|
1200
1408
|
},
|
|
1201
1409
|
"codex-cli": {
|
|
1202
1410
|
id: "codex-cli",
|
|
1203
1411
|
label: "Codex CLI",
|
|
1204
1412
|
restartMode: "restart-session",
|
|
1205
|
-
canWriteRuntimeConfig: true
|
|
1413
|
+
canWriteRuntimeConfig: true,
|
|
1414
|
+
activationInstruction: "Start a fresh Codex CLI session (for example, run `codex resume --last`)."
|
|
1206
1415
|
},
|
|
1207
1416
|
"codex-desktop": {
|
|
1208
1417
|
id: "codex-desktop",
|
|
1209
1418
|
label: "Codex Desktop",
|
|
1210
1419
|
restartMode: "restart-session",
|
|
1211
|
-
canWriteRuntimeConfig: true
|
|
1420
|
+
canWriteRuntimeConfig: true,
|
|
1421
|
+
activationInstruction: "Quit and reopen Codex Desktop so it loads the Haven MCP entries."
|
|
1212
1422
|
},
|
|
1213
1423
|
cursor: {
|
|
1214
1424
|
id: "cursor",
|
|
1215
1425
|
label: "Cursor",
|
|
1216
1426
|
restartMode: "hot-reload",
|
|
1217
|
-
canWriteRuntimeConfig: true
|
|
1427
|
+
canWriteRuntimeConfig: true,
|
|
1428
|
+
activationInstruction: "Wait for Cursor to hot-reload the Haven MCP entries; no app restart is required."
|
|
1218
1429
|
},
|
|
1219
1430
|
vscode: {
|
|
1220
1431
|
id: "vscode",
|
|
1221
1432
|
label: "VS Code",
|
|
1222
1433
|
restartMode: "hot-reload",
|
|
1223
|
-
canWriteRuntimeConfig: true
|
|
1434
|
+
canWriteRuntimeConfig: true,
|
|
1435
|
+
activationInstruction: "Wait for VS Code to hot-reload the Haven MCP entries; no app restart is required."
|
|
1224
1436
|
},
|
|
1225
1437
|
"vscode-insiders": {
|
|
1226
1438
|
id: "vscode-insiders",
|
|
1227
1439
|
label: "VS Code Insiders",
|
|
1228
1440
|
restartMode: "hot-reload",
|
|
1229
|
-
canWriteRuntimeConfig: true
|
|
1441
|
+
canWriteRuntimeConfig: true,
|
|
1442
|
+
activationInstruction: "Wait for VS Code Insiders to hot-reload the Haven MCP entries; no app restart is required."
|
|
1230
1443
|
},
|
|
1231
1444
|
"claude-desktop": {
|
|
1232
1445
|
id: "claude-desktop",
|
|
1233
1446
|
label: "Claude Desktop",
|
|
1234
1447
|
restartMode: "restart-app",
|
|
1235
|
-
canWriteRuntimeConfig: true
|
|
1448
|
+
canWriteRuntimeConfig: true,
|
|
1449
|
+
activationInstruction: "Quit and reopen Claude Desktop so it loads the Haven MCP entries."
|
|
1450
|
+
},
|
|
1451
|
+
hermes: {
|
|
1452
|
+
id: "hermes",
|
|
1453
|
+
label: "Hermes Agent",
|
|
1454
|
+
restartMode: "restart-session",
|
|
1455
|
+
canWriteRuntimeConfig: true,
|
|
1456
|
+
activationInstruction: "Start a new Hermes session; in Hermes Gateway, run `/restart` instead."
|
|
1236
1457
|
},
|
|
1237
1458
|
other: {
|
|
1238
1459
|
id: "other",
|
|
1239
1460
|
label: "Other agent runtime",
|
|
1240
1461
|
restartMode: "manual",
|
|
1241
|
-
canWriteRuntimeConfig: false
|
|
1462
|
+
canWriteRuntimeConfig: false,
|
|
1463
|
+
activationInstruction: "Finish the manual MCP setup shown above, then start a fresh session in that runtime."
|
|
1242
1464
|
}
|
|
1243
1465
|
};
|
|
1244
1466
|
var RUNTIME_ALIASES = {
|
|
@@ -1271,6 +1493,10 @@ var RUNTIME_ALIASES = {
|
|
|
1271
1493
|
"claude_desktop": "claude-desktop",
|
|
1272
1494
|
claudesktop: "claude-desktop",
|
|
1273
1495
|
desktop: "claude-desktop",
|
|
1496
|
+
hermes: "hermes",
|
|
1497
|
+
"hermes-agent": "hermes",
|
|
1498
|
+
hermes_agent: "hermes",
|
|
1499
|
+
hermesagent: "hermes",
|
|
1274
1500
|
other: "other",
|
|
1275
1501
|
manual: "other"
|
|
1276
1502
|
};
|
|
@@ -1286,8 +1512,9 @@ function restartRequiredForRuntime(runtime, env = process.env) {
|
|
|
1286
1512
|
const mode = runtimeProfile(runtime, env).restartMode;
|
|
1287
1513
|
return mode === "restart-session" || mode === "restart-app";
|
|
1288
1514
|
}
|
|
1289
|
-
function
|
|
1290
|
-
|
|
1515
|
+
function runtimeVerificationInstruction(runtime) {
|
|
1516
|
+
const label = RUNTIME_PROFILES[runtime].label;
|
|
1517
|
+
return `In ${label}, run the read-only \`haven_get_agent\` and \`haven_get_allowances\` tools to confirm the Haven wallet and live budget. Do not sign, fund, or create a payment to verify setup.`;
|
|
1291
1518
|
}
|
|
1292
1519
|
function normalizeRuntimeName(runtime) {
|
|
1293
1520
|
const key = runtime?.trim().toLowerCase();
|
|
@@ -1298,6 +1525,7 @@ function detectRuntime(env) {
|
|
|
1298
1525
|
if (env.CLAUDECODE || env.CLAUDE_CODE || env.CLAUDECODE_CWD) return "claude-code";
|
|
1299
1526
|
if (env.CODEX_SANDBOX || env.CODEX_HOME || env.CODEX_CWD) return "codex-cli";
|
|
1300
1527
|
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
1528
|
+
if (env.HERMES_HOME || env.HERMES_AGENT) return "hermes";
|
|
1301
1529
|
return null;
|
|
1302
1530
|
}
|
|
1303
1531
|
async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
@@ -1472,11 +1700,12 @@ async function installRuntime(input, deps = {}) {
|
|
|
1472
1700
|
probeLocalSignerCredential(input.signerPath),
|
|
1473
1701
|
localProbePromise
|
|
1474
1702
|
]);
|
|
1475
|
-
const hostedOk = configResult.hostedConfigured && hostedProbe.status
|
|
1703
|
+
const hostedOk = configResult.hostedConfigured && hostedProbe.status === "ok";
|
|
1476
1704
|
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
|
|
1477
1705
|
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
1478
1706
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1479
|
-
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1707
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : hostedMcpErrorCode(configResult.hostedConfigured, hostedProbe.status) ?? signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1708
|
+
const hostedProbeMessages = configResult.hostedConfigured && hostedProbe.status !== "ok" ? [`Hosted Haven MCP probe failed: ${hostedProbe.status}.`] : configResult.hostedConfigured ? ["Verified hosted Haven MCP tools with a read-only handshake."] : [];
|
|
1480
1709
|
const localProbeMessages = localMcpProbe && localMcpProbe.status !== "ok" ? [`Local Haven MCP handshake failed: ${localMcpProbe.status}.`] : localMcpProbe?.status === "ok" ? ["Verified local Haven MCP tools with a stdio handshake."] : [];
|
|
1481
1710
|
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1482
1711
|
return {
|
|
@@ -1495,7 +1724,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1495
1724
|
activationCommand: configResult.activationCommand,
|
|
1496
1725
|
skillInstalled: skillInstall?.installed,
|
|
1497
1726
|
signerRuntimePrepared,
|
|
1498
|
-
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1727
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...hostedProbeMessages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1499
1728
|
};
|
|
1500
1729
|
}
|
|
1501
1730
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1656,6 +1885,10 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
|
1656
1885
|
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
1657
1886
|
return void 0;
|
|
1658
1887
|
}
|
|
1888
|
+
function hostedMcpErrorCode(hostedConfigured, hostedProbeStatus) {
|
|
1889
|
+
if (!hostedConfigured || hostedProbeStatus === "ok") return void 0;
|
|
1890
|
+
return `hosted_mcp_probe_${hostedProbeStatus}`;
|
|
1891
|
+
}
|
|
1659
1892
|
function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
|
|
1660
1893
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
1661
1894
|
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
@@ -1707,13 +1940,17 @@ function localRuntimePrepareErrorCode(err) {
|
|
|
1707
1940
|
}
|
|
1708
1941
|
|
|
1709
1942
|
// src/runtime.ts
|
|
1710
|
-
var CONNECTOR_VERSION = "0.1.
|
|
1943
|
+
var CONNECTOR_VERSION = "0.1.23-alpha.0";
|
|
1944
|
+
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
1711
1945
|
async function runConnect(options, deps = {}) {
|
|
1712
1946
|
assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
|
|
1713
1947
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
1714
1948
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
1715
|
-
const log = secureLogger(
|
|
1716
|
-
|
|
1949
|
+
const log = secureLogger(
|
|
1950
|
+
deps.log ?? ((message) => process.stdout.write(`${message}
|
|
1951
|
+
`)),
|
|
1952
|
+
deps.redactPaths === true
|
|
1953
|
+
);
|
|
1717
1954
|
const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
|
|
1718
1955
|
const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
|
|
1719
1956
|
const runRuntimeInstall = deps.installRuntime ?? installRuntime;
|
|
@@ -1734,6 +1971,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1734
1971
|
connectorVersion,
|
|
1735
1972
|
runtime: options.runtime
|
|
1736
1973
|
});
|
|
1974
|
+
assertSetupChallengeIsUsable(setup.challenge.expires_at);
|
|
1737
1975
|
printSetupSummary(setup, log);
|
|
1738
1976
|
await preflightStorage({ baseDir: options.credentialsDir, warn: log });
|
|
1739
1977
|
log("Checked local credential storage \u2014 all clear.");
|
|
@@ -1742,21 +1980,31 @@ async function runConnect(options, deps = {}) {
|
|
|
1742
1980
|
log("Minting a fresh signing key and API key \u2014 both stay on this machine.");
|
|
1743
1981
|
const proofSignature = await localKey.signChallenge(setup.challenge.message);
|
|
1744
1982
|
log("Introducing your agent to Haven\u2026");
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1983
|
+
let registration;
|
|
1984
|
+
try {
|
|
1985
|
+
registration = await api.registerSetup({
|
|
1986
|
+
setupToken: options.setupToken,
|
|
1987
|
+
connectorVersion,
|
|
1988
|
+
runtime: options.runtime,
|
|
1989
|
+
challengeId: setup.challenge.id,
|
|
1990
|
+
delegateAddress: localKey.address,
|
|
1991
|
+
proofSignature,
|
|
1992
|
+
apiKeyHash: hashAgentApiKey(localApiKey),
|
|
1993
|
+
apiKeyPrefix: agentApiKeyPrefix(localApiKey),
|
|
1994
|
+
connectorContext: {
|
|
1995
|
+
environment_label: options.environmentLabel ?? "Local workspace",
|
|
1996
|
+
config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
|
|
1997
|
+
},
|
|
1998
|
+
installCapabilities
|
|
1999
|
+
});
|
|
2000
|
+
} catch (err) {
|
|
2001
|
+
if (isExpiredSetupChallenge(err)) {
|
|
2002
|
+
throw new Error(
|
|
2003
|
+
"The Haven setup challenge expired while connecting. Return to Haven, start a fresh connection, and run its new Connect command. Do not reuse or paste credentials."
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
throw err;
|
|
2007
|
+
}
|
|
1760
2008
|
log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
|
|
1761
2009
|
log("Tucking your credentials away safely on disk\u2026");
|
|
1762
2010
|
const credentialPaths = await writeCredentials({
|
|
@@ -1833,7 +2081,69 @@ async function runConnect(options, deps = {}) {
|
|
|
1833
2081
|
setupId: registration.setup_id,
|
|
1834
2082
|
agentId: registration.agent_id,
|
|
1835
2083
|
delegateAddress: registration.delegate_address,
|
|
1836
|
-
credentialPaths
|
|
2084
|
+
credentialPaths,
|
|
2085
|
+
outcome: completionOutcome({
|
|
2086
|
+
runtimeInstall,
|
|
2087
|
+
delegateAddress: registration.delegate_address,
|
|
2088
|
+
setupChallengeExpiresAt: setup.challenge.expires_at,
|
|
2089
|
+
approvalRequired: registration.agent_status === "pending_approval"
|
|
2090
|
+
})
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function completionOutcome(input) {
|
|
2094
|
+
const { runtimeInstall } = input;
|
|
2095
|
+
const manualSetup = runtimeInstall.errorCode === "manual_runtime_setup_required";
|
|
2096
|
+
const nextAction2 = runtimeInstall.nextUserAction;
|
|
2097
|
+
const outcome = {
|
|
2098
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2099
|
+
outcome: runtimeInstall.errorCode ? "action_required" : "complete",
|
|
2100
|
+
runtime: runtimeInstall.runtime,
|
|
2101
|
+
topology: runtimeInstall.runtimeMcpMode,
|
|
2102
|
+
configuration: {
|
|
2103
|
+
hosted_mcp: runtimeInstall.hostedMcpConfigured,
|
|
2104
|
+
local_signer: runtimeInstall.localSignerConfigured,
|
|
2105
|
+
local_mcp: runtimeInstall.localMcpConfigured
|
|
2106
|
+
},
|
|
2107
|
+
probe: { result: runtimeInstall.probeResult },
|
|
2108
|
+
activation: {
|
|
2109
|
+
restart_required: runtimeInstall.restartRequired,
|
|
2110
|
+
instruction: manualSetup ? "Finish the manual MCP setup using the secret-free references shown in normal Connect output, then start a fresh session." : runtimeProfile(runtimeInstall.runtime).activationInstruction
|
|
2111
|
+
},
|
|
2112
|
+
next_action: nextAction2,
|
|
2113
|
+
approval: { required: input.approvalRequired, expires_at: null },
|
|
2114
|
+
verification: {
|
|
2115
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2116
|
+
instruction: runtimeVerificationInstruction(runtimeInstall.runtime)
|
|
2117
|
+
},
|
|
2118
|
+
// The backend contract supplies a 20-byte address. If an unexpected
|
|
2119
|
+
// malformed value arrives, do not echo it into an automation-facing
|
|
2120
|
+
// record; the human log has already been redacted separately.
|
|
2121
|
+
delegate_address: /^0x[0-9a-fA-F]{40}$/.test(input.delegateAddress) ? shortAddress(input.delegateAddress) : "[delegate-address-redacted]",
|
|
2122
|
+
...input.setupChallengeExpiresAt ? { setup_challenge_expires_at: input.setupChallengeExpiresAt } : {},
|
|
2123
|
+
...runtimeInstall.errorCode ? { error: { code: runtimeInstall.errorCode, next_action: nextAction2 } } : {}
|
|
2124
|
+
};
|
|
2125
|
+
return outcome;
|
|
2126
|
+
}
|
|
2127
|
+
function failedConnectOutcome(runtimeHint, error) {
|
|
2128
|
+
const message = error instanceof Error ? error.message : "";
|
|
2129
|
+
const code = /Node\.js >=/i.test(message) ? "unsupported_node_version" : /setup challenge.*expired|expired or invalid/i.test(message) ? "setup_challenge_expired_or_invalid" : /only available for Claude Code and Codex/i.test(message) ? "local_mcp_unsupported_runtime" : "connect_failed";
|
|
2130
|
+
const runtime = normalizeRuntime(runtimeHint);
|
|
2131
|
+
const nextAction2 = code === "setup_challenge_expired_or_invalid" ? "return_to_haven_for_fresh_setup" : code === "unsupported_node_version" ? "install_supported_node_and_rerun_connect" : code === "local_mcp_unsupported_runtime" ? "rerun_without_local_mcp" : "review_the_safe_error_output_and_start_a_fresh_haven_setup_if_needed";
|
|
2132
|
+
return {
|
|
2133
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2134
|
+
outcome: "failed",
|
|
2135
|
+
runtime,
|
|
2136
|
+
topology: "unknown",
|
|
2137
|
+
configuration: { hosted_mcp: false, local_signer: false, local_mcp: false },
|
|
2138
|
+
probe: { result: "not_run" },
|
|
2139
|
+
activation: { restart_required: false, instruction: "Resolve the reported problem before activating Haven tools." },
|
|
2140
|
+
next_action: nextAction2,
|
|
2141
|
+
approval: { required: false, expires_at: null },
|
|
2142
|
+
verification: {
|
|
2143
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2144
|
+
instruction: "After a successful setup and activation, verify only with haven_get_agent and haven_get_allowances."
|
|
2145
|
+
},
|
|
2146
|
+
error: { code, next_action: nextAction2 }
|
|
1837
2147
|
};
|
|
1838
2148
|
}
|
|
1839
2149
|
function printSetupSummary(setup, log) {
|
|
@@ -1846,10 +2156,26 @@ function printSetupSummary(setup, log) {
|
|
|
1846
2156
|
);
|
|
1847
2157
|
}
|
|
1848
2158
|
}
|
|
1849
|
-
log(`Setup challenge expires at ${setup.challenge.expires_at}.`);
|
|
2159
|
+
log(`Setup challenge expires at ${setup.challenge.expires_at}. If it expires, return to Haven for a fresh setup and rerun Connect \u2014 do not reuse or paste credentials.`);
|
|
2160
|
+
}
|
|
2161
|
+
function assertSetupChallengeIsUsable(expiresAt) {
|
|
2162
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
2163
|
+
if (!Number.isNaN(expiresAtMs) && expiresAtMs > Date.now()) return;
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
"This Haven setup challenge is expired or invalid. Return to Haven, start a fresh connection, and rerun Connect. No local credentials were written."
|
|
2166
|
+
);
|
|
1850
2167
|
}
|
|
1851
|
-
function
|
|
1852
|
-
return (
|
|
2168
|
+
function isExpiredSetupChallenge(err) {
|
|
2169
|
+
return err instanceof Error && /(?:setup )?challenge.*expir|expir.*(?:setup )?challenge/i.test(err.message);
|
|
2170
|
+
}
|
|
2171
|
+
function secureLogger(log, redactPaths = false) {
|
|
2172
|
+
return (message) => {
|
|
2173
|
+
let safe = redactSecrets(message);
|
|
2174
|
+
if (redactPaths) {
|
|
2175
|
+
safe = safe.replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
|
|
2176
|
+
}
|
|
2177
|
+
log(safe);
|
|
2178
|
+
};
|
|
1853
2179
|
}
|
|
1854
2180
|
function printRuntimeInstall(result, log) {
|
|
1855
2181
|
for (const message of result.messages) log(message);
|
|
@@ -1866,17 +2192,30 @@ function printRuntimeInstall(result, log) {
|
|
|
1866
2192
|
log("Local Haven signer still needs runtime setup.");
|
|
1867
2193
|
}
|
|
1868
2194
|
}
|
|
1869
|
-
function
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
}
|
|
1878
|
-
|
|
2195
|
+
function completionHandoffLines(result) {
|
|
2196
|
+
if (result.errorCode === "manual_runtime_setup_required") {
|
|
2197
|
+
return [
|
|
2198
|
+
"Next steps:",
|
|
2199
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2200
|
+
"2. Finish the manual MCP setup using the secret-free file references printed above, then start a fresh session in your runtime.",
|
|
2201
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2202
|
+
];
|
|
2203
|
+
}
|
|
2204
|
+
if (result.errorCode) {
|
|
2205
|
+
return [
|
|
2206
|
+
"Recovery: runtime setup is not complete. Resolve the reported problem, then return to Haven for a fresh connection and run its new Connect command. Do not manually edit runtime config or paste credentials into prompts, logs, or config."
|
|
2207
|
+
];
|
|
1879
2208
|
}
|
|
2209
|
+
const profile = runtimeProfile(result.runtime);
|
|
2210
|
+
return [
|
|
2211
|
+
"Next steps:",
|
|
2212
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2213
|
+
`2. ${profile.activationInstruction}`,
|
|
2214
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2215
|
+
];
|
|
2216
|
+
}
|
|
2217
|
+
function printNextSteps(result, log) {
|
|
2218
|
+
for (const line of completionHandoffLines(result)) log(line);
|
|
1880
2219
|
}
|
|
1881
2220
|
|
|
1882
2221
|
// src/args.ts
|
|
@@ -1886,10 +2225,13 @@ function parseArgs(argv, env = process.env) {
|
|
|
1886
2225
|
connectorVersion: CONNECTOR_VERSION
|
|
1887
2226
|
};
|
|
1888
2227
|
let help = false;
|
|
2228
|
+
let json = false;
|
|
1889
2229
|
for (let i = 0; i < argv.length; i += 1) {
|
|
1890
2230
|
const arg = argv[i];
|
|
1891
2231
|
if (arg === "--help" || arg === "-h") {
|
|
1892
2232
|
help = true;
|
|
2233
|
+
} else if (arg === "--json") {
|
|
2234
|
+
json = true;
|
|
1893
2235
|
} else if (arg === "--setup" || arg === "--setup-token") {
|
|
1894
2236
|
options.setupToken = requireValue(argv, ++i, arg);
|
|
1895
2237
|
} else if (arg === "--api" || arg === "--api-url") {
|
|
@@ -1916,7 +2258,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
1916
2258
|
}
|
|
1917
2259
|
}
|
|
1918
2260
|
if (help) {
|
|
1919
|
-
return { options, help };
|
|
2261
|
+
return { options, help, json };
|
|
1920
2262
|
}
|
|
1921
2263
|
if (!options.setupToken) {
|
|
1922
2264
|
throw new Error("Missing --setup <hv_setup_...> setup token.");
|
|
@@ -1925,7 +2267,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
1925
2267
|
throw new Error("Missing --api <Haven API URL>.");
|
|
1926
2268
|
}
|
|
1927
2269
|
options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
1928
|
-
return { options, help };
|
|
2270
|
+
return { options, help, json };
|
|
1929
2271
|
}
|
|
1930
2272
|
function helpText() {
|
|
1931
2273
|
return [
|
|
@@ -1940,16 +2282,17 @@ function helpText() {
|
|
|
1940
2282
|
"Options:",
|
|
1941
2283
|
" --setup <token> Short-lived setup token from Haven.",
|
|
1942
2284
|
" --api <url> Haven backend API URL. Defaults to HAVEN_API_URL or http://localhost:3001.",
|
|
1943
|
-
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode,
|
|
2285
|
+
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode, claude-desktop, or hermes.",
|
|
1944
2286
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
1945
2287
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1946
2288
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1947
2289
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
1948
2290
|
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
1949
2291
|
" Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
|
|
2292
|
+
" --json Emit one versioned, secret-free result object on stdout; progress stays on stderr.",
|
|
1950
2293
|
" --help Show this help.",
|
|
1951
2294
|
"",
|
|
1952
|
-
"The connector never prints the private key and never sends it to Haven."
|
|
2295
|
+
"The connector never prints the private key and never sends it to Haven. JSON output never includes credential contents or full credential paths."
|
|
1953
2296
|
].join("\n");
|
|
1954
2297
|
}
|
|
1955
2298
|
function requireValue(argv, index, option) {
|
|
@@ -1959,12 +2302,63 @@ function requireValue(argv, index, option) {
|
|
|
1959
2302
|
}
|
|
1960
2303
|
return value;
|
|
1961
2304
|
}
|
|
2305
|
+
async function runCli(argv, io = {
|
|
2306
|
+
stdout: (message) => process.stdout.write(message),
|
|
2307
|
+
stderr: (message) => process.stderr.write(message)
|
|
2308
|
+
}) {
|
|
2309
|
+
const wantsJson = argv.includes("--json");
|
|
2310
|
+
let parsed;
|
|
2311
|
+
try {
|
|
2312
|
+
parsed = parseArgs(argv);
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
if (wantsJson) {
|
|
2315
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(void 0, err))}
|
|
2316
|
+
`);
|
|
2317
|
+
} else {
|
|
2318
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2319
|
+
`);
|
|
2320
|
+
}
|
|
2321
|
+
return 1;
|
|
2322
|
+
}
|
|
2323
|
+
if (parsed.help) {
|
|
2324
|
+
io.stdout(`${helpText()}
|
|
2325
|
+
`);
|
|
2326
|
+
return 0;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
const result = await runConnect(parsed.options, {
|
|
2330
|
+
log: (message) => (parsed.json ? io.stderr : io.stdout)(`${message}
|
|
2331
|
+
`),
|
|
2332
|
+
redactPaths: parsed.json
|
|
2333
|
+
});
|
|
2334
|
+
if (parsed.json) io.stdout(`${JSON.stringify(result.outcome)}
|
|
2335
|
+
`);
|
|
2336
|
+
return 0;
|
|
2337
|
+
} catch (err) {
|
|
2338
|
+
if (parsed.json) {
|
|
2339
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(parsed.options.runtime, err))}
|
|
2340
|
+
`);
|
|
2341
|
+
} else {
|
|
2342
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2343
|
+
`);
|
|
2344
|
+
}
|
|
2345
|
+
return 1;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async function main() {
|
|
2349
|
+
const exitCode = await runCli(process.argv.slice(2));
|
|
2350
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2351
|
+
}
|
|
2352
|
+
if (process.argv[1] && url.pathToFileURL(process.argv[1]).href === (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) void main();
|
|
1962
2353
|
|
|
1963
2354
|
exports.CONNECTOR_VERSION = CONNECTOR_VERSION;
|
|
2355
|
+
exports.CONNECT_OUTCOME_SCHEMA_VERSION = CONNECT_OUTCOME_SCHEMA_VERSION;
|
|
1964
2356
|
exports.MCP_RUNTIME_MANIFEST = MCP_RUNTIME_MANIFEST;
|
|
2357
|
+
exports.completionOutcome = completionOutcome;
|
|
1965
2358
|
exports.createConnectApiClient = createConnectApiClient;
|
|
1966
2359
|
exports.defaultAgentDirectory = defaultAgentDirectory;
|
|
1967
2360
|
exports.delegateKeyFromPrivateKey = delegateKeyFromPrivateKey;
|
|
2361
|
+
exports.failedConnectOutcome = failedConnectOutcome;
|
|
1968
2362
|
exports.generateDelegateKey = generateDelegateKey;
|
|
1969
2363
|
exports.helpText = helpText;
|
|
1970
2364
|
exports.installRuntime = installRuntime;
|
|
@@ -1973,6 +2367,7 @@ exports.normalizeRuntime = normalizeRuntime;
|
|
|
1973
2367
|
exports.parseArgs = parseArgs;
|
|
1974
2368
|
exports.prepareSignerRuntime = prepareSignerRuntime;
|
|
1975
2369
|
exports.redactSecrets = redactSecrets;
|
|
2370
|
+
exports.runCli = runCli;
|
|
1976
2371
|
exports.runConnect = runConnect;
|
|
1977
2372
|
exports.runtimeInstallCapabilities = runtimeInstallCapabilities;
|
|
1978
2373
|
exports.runtimeProfile = runtimeProfile;
|