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