@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.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
2
3
|
import crypto from 'crypto';
|
|
3
4
|
import { Wallet } from 'ethers';
|
|
4
|
-
import { mkdir, rm, chmod, access, writeFile, readFile } from 'fs/promises';
|
|
5
|
+
import { mkdir, rm, chmod, access, writeFile, readFile, unlink } from 'fs/promises';
|
|
5
6
|
import { homedir, platform } from 'os';
|
|
6
7
|
import { join, resolve, dirname } from 'path';
|
|
7
8
|
import { execFile, spawn } from 'child_process';
|
|
8
9
|
import { promisify } from 'util';
|
|
10
|
+
import { parseDocument, isMap, stringify } from 'yaml';
|
|
9
11
|
import { registeredToolNames, MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient } from '@haven_ai/mcp';
|
|
10
12
|
import { HAVEN_MINIMUM_NODE_VERSION, isSupportedNodeVersion, unsupportedNodeVersionMessage, SKILL_FOLDER_NAME, HAVEN_SKILL_MD } from '@haven_ai/sdk';
|
|
11
13
|
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
|
|
@@ -174,11 +176,12 @@ async function writeCredentialFiles(input) {
|
|
|
174
176
|
agentPath,
|
|
175
177
|
{
|
|
176
178
|
agent_id: input.agentId,
|
|
179
|
+
delegate_address: input.delegateAddress,
|
|
177
180
|
safe_address: input.safeAddress,
|
|
178
181
|
chain_id: input.chainId,
|
|
179
182
|
network: input.network,
|
|
180
183
|
agent_budget: input.agentBudget,
|
|
181
|
-
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."
|
|
184
|
+
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."
|
|
182
185
|
},
|
|
183
186
|
input.warn
|
|
184
187
|
);
|
|
@@ -230,9 +233,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
230
233
|
mcpPackage: "@haven_ai/mcp",
|
|
231
234
|
mcpVersion: MCP_VERSION,
|
|
232
235
|
sdkPackage: "@haven_ai/sdk",
|
|
233
|
-
sdkVersion: "0.1.
|
|
236
|
+
sdkVersion: "0.1.23-alpha.0",
|
|
234
237
|
signerPackage: "@haven_ai/signer",
|
|
235
|
-
signerVersion: "0.1.
|
|
238
|
+
signerVersion: "0.1.23-alpha.0",
|
|
236
239
|
// Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
|
|
237
240
|
// while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
|
|
238
241
|
// so the guard that was supposed to enforce the floor waved Node v23 through
|
|
@@ -254,13 +257,20 @@ function signerPackageSpec() {
|
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
// src/config-writers.ts
|
|
260
|
+
var HERMES_API_KEY_ENV = "MCP_HAVEN_API_KEY";
|
|
261
|
+
var HermesConfigRecoveryError = class extends Error {
|
|
262
|
+
constructor() {
|
|
263
|
+
super("Hermes configuration recovery did not complete");
|
|
264
|
+
this.name = "HermesConfigRecoveryError";
|
|
265
|
+
}
|
|
266
|
+
};
|
|
257
267
|
var InvalidCodexTomlError = class extends Error {
|
|
258
268
|
constructor(message) {
|
|
259
269
|
super(message);
|
|
260
270
|
this.name = "InvalidCodexTomlError";
|
|
261
271
|
}
|
|
262
272
|
};
|
|
263
|
-
async function writeRuntimeConfig(input) {
|
|
273
|
+
async function writeRuntimeConfig(input, deps = {}) {
|
|
264
274
|
switch (input.runtime) {
|
|
265
275
|
case "codex-cli":
|
|
266
276
|
case "codex-desktop":
|
|
@@ -273,6 +283,8 @@ async function writeRuntimeConfig(input) {
|
|
|
273
283
|
return writeJsonRuntimeConfig(input, vscodeInsidersConfigPath(input.homeDir), "servers");
|
|
274
284
|
case "claude-desktop":
|
|
275
285
|
return writeJsonRuntimeConfig(input, claudeDesktopConfigPath(input.homeDir), "mcpServers");
|
|
286
|
+
case "hermes":
|
|
287
|
+
return writeHermesConfig(input, deps);
|
|
276
288
|
default:
|
|
277
289
|
return {
|
|
278
290
|
hostedConfigured: false,
|
|
@@ -326,6 +338,115 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
326
338
|
return `${JSON.stringify(config, null, 2)}
|
|
327
339
|
`;
|
|
328
340
|
}
|
|
341
|
+
function mergeHermesYaml(existingYaml, hostedServer, signerServer) {
|
|
342
|
+
if (!existingYaml?.trim()) return renderHermesYaml({ haven: hostedServer, "haven-signer": signerServer });
|
|
343
|
+
const doc = parseDocument(existingYaml, { keepSourceTokens: true });
|
|
344
|
+
if (doc.errors.length > 0 || !isMap(doc.contents)) {
|
|
345
|
+
throw new Error("Hermes config must be a YAML object");
|
|
346
|
+
}
|
|
347
|
+
const mcpPair = doc.contents.items.find((item) => item.key?.toString() === "mcp_servers");
|
|
348
|
+
const existingServers = mcpPair && isMap(mcpPair.value) ? mcpPair.value.toJSON() : {};
|
|
349
|
+
const servers = isRecord(existingServers) ? existingServers : {};
|
|
350
|
+
const mergedServers = {
|
|
351
|
+
...servers,
|
|
352
|
+
haven: hostedServer,
|
|
353
|
+
"haven-signer": signerServer
|
|
354
|
+
};
|
|
355
|
+
if (!mcpPair) {
|
|
356
|
+
return appendHermesMcpServers(existingYaml, mergedServers);
|
|
357
|
+
}
|
|
358
|
+
if (!mcpPair.value?.range) return replaceEmptyHermesMcpServers(existingYaml, mcpPair.key?.range, mergedServers);
|
|
359
|
+
return replaceHermesMcpServers(existingYaml, mcpPair.key?.range, mcpPair.value.range, mergedServers);
|
|
360
|
+
}
|
|
361
|
+
function mergeHermesEnv(existingEnv, apiKey) {
|
|
362
|
+
if (/[\r\n]/.test(apiKey)) throw new Error("Hermes API key must be a single line");
|
|
363
|
+
const assignment = `${HERMES_API_KEY_ENV}=${apiKey}`;
|
|
364
|
+
if (!existingEnv) return `${assignment}
|
|
365
|
+
`;
|
|
366
|
+
const lineEnding = existingEnv.includes("\r\n") ? "\r\n" : "\n";
|
|
367
|
+
const hasTrailingNewline = /\r?\n$/.test(existingEnv);
|
|
368
|
+
const lines = existingEnv.split(/\r?\n/);
|
|
369
|
+
if (hasTrailingNewline) lines.pop();
|
|
370
|
+
let found = false;
|
|
371
|
+
const merged = lines.flatMap((line) => {
|
|
372
|
+
if (isHermesEnvAssignment(line)) {
|
|
373
|
+
if (found) return [];
|
|
374
|
+
found = true;
|
|
375
|
+
return [assignment];
|
|
376
|
+
}
|
|
377
|
+
if (isAmbiguousHermesEnvLine(line)) {
|
|
378
|
+
throw new Error("Hermes environment contains an ambiguous managed key");
|
|
379
|
+
}
|
|
380
|
+
return [line];
|
|
381
|
+
});
|
|
382
|
+
if (!found) {
|
|
383
|
+
return `${existingEnv}${hasTrailingNewline ? "" : lineEnding}${assignment}${lineEnding}`;
|
|
384
|
+
}
|
|
385
|
+
return `${merged.join(lineEnding)}${hasTrailingNewline ? lineEnding : ""}`;
|
|
386
|
+
}
|
|
387
|
+
function isHermesEnvAssignment(line) {
|
|
388
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY[ \t]*=/.test(line);
|
|
389
|
+
}
|
|
390
|
+
function isAmbiguousHermesEnvLine(line) {
|
|
391
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY\b/.test(line);
|
|
392
|
+
}
|
|
393
|
+
function appendHermesMcpServers(source, servers) {
|
|
394
|
+
const documentEnd = /(?:^|\n)[ \t]*\.\.\.[ \t]*(?:#[^\n]*)?\r?\n?$/.exec(source);
|
|
395
|
+
if (documentEnd) {
|
|
396
|
+
const markerStart = documentEnd.index + (documentEnd[0].startsWith("\n") ? 1 : 0);
|
|
397
|
+
const beforeMarker = source.slice(0, markerStart);
|
|
398
|
+
return `${beforeMarker}${beforeMarker.endsWith("\n") ? "" : "\n"}${renderHermesMcpServers(servers, "")}
|
|
399
|
+
${source.slice(markerStart)}`;
|
|
400
|
+
}
|
|
401
|
+
const separator = source.endsWith("\n") ? "" : "\n";
|
|
402
|
+
return `${source}${separator}${renderHermesMcpServers(servers, "")}
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
function replaceHermesMcpServers(source, keyRange, valueRange, servers) {
|
|
406
|
+
const valueStart = valueRange[0];
|
|
407
|
+
const valueEnd = valueRange[1];
|
|
408
|
+
const valueLineStart = source.lastIndexOf("\n", valueStart - 1) + 1;
|
|
409
|
+
const keyStartsOnValueLine = keyRange?.[0] !== void 0 && keyRange[0] >= valueLineStart;
|
|
410
|
+
const nestedIndent = `${source.slice(valueLineStart, keyRange?.[0] ?? valueLineStart)} `;
|
|
411
|
+
if (keyStartsOnValueLine) {
|
|
412
|
+
const keyStart = keyRange[0];
|
|
413
|
+
const keyEnd = keyRange[1];
|
|
414
|
+
const replacement2 = `${source.slice(keyStart, keyEnd)}:
|
|
415
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
416
|
+
const needsTrailingNewline2 = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
417
|
+
return `${source.slice(0, keyStart)}${replacement2}${needsTrailingNewline2 ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
418
|
+
}
|
|
419
|
+
const indent = source.slice(valueLineStart, valueStart);
|
|
420
|
+
const rendered = stringify(servers, { lineWidth: 120 }).trimEnd();
|
|
421
|
+
const replacement = rendered.split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`).join("\n");
|
|
422
|
+
const needsTrailingNewline = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
423
|
+
return `${source.slice(0, valueStart)}${replacement}${needsTrailingNewline ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
424
|
+
}
|
|
425
|
+
function replaceEmptyHermesMcpServers(source, keyRange, servers) {
|
|
426
|
+
if (!keyRange) throw new Error("Hermes config mcp_servers key is invalid");
|
|
427
|
+
const keyStart = keyRange[0];
|
|
428
|
+
const keyEnd = keyRange[1];
|
|
429
|
+
const lineStart = source.lastIndexOf("\n", keyStart - 1) + 1;
|
|
430
|
+
const lineEnd = source.indexOf("\n", keyEnd);
|
|
431
|
+
const nestedIndent = `${source.slice(lineStart, keyStart)} `;
|
|
432
|
+
const replacement = `${source.slice(keyStart, keyEnd)}:
|
|
433
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
434
|
+
return `${source.slice(0, keyStart)}${replacement}${lineEnd === -1 ? "\n" : source.slice(lineEnd)}`;
|
|
435
|
+
}
|
|
436
|
+
function renderHermesYaml(servers) {
|
|
437
|
+
return `${renderHermesMcpServers(servers, "")}
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
function renderHermesMcpServers(servers, indent) {
|
|
441
|
+
return `mcp_servers:
|
|
442
|
+
${renderHermesMcpServerEntries(servers, `${indent} `)}`;
|
|
443
|
+
}
|
|
444
|
+
function renderHermesMcpServerEntries(servers, indent) {
|
|
445
|
+
return stringify(servers, { lineWidth: 120 }).trimEnd().split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
446
|
+
}
|
|
447
|
+
function isRecord(value) {
|
|
448
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
449
|
+
}
|
|
329
450
|
function mergeCodexToml(existingToml, localMcpCommand) {
|
|
330
451
|
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
331
452
|
next = next.trimEnd();
|
|
@@ -396,6 +517,82 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
396
517
|
};
|
|
397
518
|
}
|
|
398
519
|
}
|
|
520
|
+
async function writeHermesConfig(input, deps) {
|
|
521
|
+
const target = hermesConfigPath(input.homeDir);
|
|
522
|
+
const envTarget = hermesEnvPath(input.homeDir);
|
|
523
|
+
try {
|
|
524
|
+
const [existing, existingEnv] = await Promise.all([readOptional(target), readOptional(envTarget)]);
|
|
525
|
+
const hostedServer = {
|
|
526
|
+
...buildHostedServer(input.hostedMcpUrl, `\${${HERMES_API_KEY_ENV}}`, input.runtime),
|
|
527
|
+
enabled: true
|
|
528
|
+
};
|
|
529
|
+
const signerServer = {
|
|
530
|
+
...buildSignerServer(resolveSignerLaunchSpec(input), input.runtime),
|
|
531
|
+
enabled: true
|
|
532
|
+
};
|
|
533
|
+
const merged = mergeHermesYaml(
|
|
534
|
+
existing,
|
|
535
|
+
hostedServer,
|
|
536
|
+
signerServer
|
|
537
|
+
);
|
|
538
|
+
const mergedEnv = mergeHermesEnv(existingEnv, input.apiKey);
|
|
539
|
+
const writeText = deps.writeOwnerOnlyText ?? writeOwnerOnlyText;
|
|
540
|
+
await writeText(envTarget, mergedEnv);
|
|
541
|
+
try {
|
|
542
|
+
await writeText(target, merged);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
const recovered = await restoreHermesFiles(target, existing, envTarget, existingEnv, writeText);
|
|
545
|
+
if (!recovered) throw new HermesConfigRecoveryError();
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
hostedConfigured: true,
|
|
550
|
+
signerConfigured: true,
|
|
551
|
+
localMcpConfigured: false,
|
|
552
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
553
|
+
target: "Hermes Agent config",
|
|
554
|
+
changed: existing !== merged || existingEnv !== mergedEnv,
|
|
555
|
+
restartRequired: true,
|
|
556
|
+
messages: [
|
|
557
|
+
`Updated Haven MCP entries in ${target}; stored the hosted MCP identity in ${envTarget}.`,
|
|
558
|
+
"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`.",
|
|
559
|
+
"If no mcp_* tools appear after restart, ensure the MCP SDK is installed in Hermes: pip install mcp"
|
|
560
|
+
]
|
|
561
|
+
};
|
|
562
|
+
} catch (err) {
|
|
563
|
+
const recoveryIncomplete = err instanceof HermesConfigRecoveryError;
|
|
564
|
+
return {
|
|
565
|
+
hostedConfigured: false,
|
|
566
|
+
signerConfigured: false,
|
|
567
|
+
localMcpConfigured: false,
|
|
568
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
569
|
+
target: "Hermes Agent config",
|
|
570
|
+
changed: false,
|
|
571
|
+
restartRequired: true,
|
|
572
|
+
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."],
|
|
573
|
+
errorCode: "runtime_config_write_failed"
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function restoreHermesFiles(configPath, existingConfig, envPath, existingEnv, writeText) {
|
|
578
|
+
const [envResult, configResult] = await Promise.allSettled([
|
|
579
|
+
restoreOptionalText(envPath, existingEnv, writeText),
|
|
580
|
+
restoreOptionalText(configPath, existingConfig, writeText)
|
|
581
|
+
]);
|
|
582
|
+
return envResult.status === "fulfilled" && configResult.status === "fulfilled";
|
|
583
|
+
}
|
|
584
|
+
async function restoreOptionalText(path, existing, writeText) {
|
|
585
|
+
if (existing !== null) {
|
|
586
|
+
await writeText(path, existing);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
await unlink(path);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
|
|
593
|
+
throw err;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
399
596
|
async function writeCodexConfig(input) {
|
|
400
597
|
const target = codexConfigPath(input.homeDir);
|
|
401
598
|
const local = input.mode === "local";
|
|
@@ -686,6 +883,15 @@ function claudeDesktopConfigPath(homeDir = homedir()) {
|
|
|
686
883
|
}
|
|
687
884
|
return resolve(homeDir, ".config", "Claude", "claude_desktop_config.json");
|
|
688
885
|
}
|
|
886
|
+
function hermesConfigPath(homeDir) {
|
|
887
|
+
return join(hermesHomePath(homeDir), "config.yaml");
|
|
888
|
+
}
|
|
889
|
+
function hermesEnvPath(homeDir) {
|
|
890
|
+
return join(hermesHomePath(homeDir), ".env");
|
|
891
|
+
}
|
|
892
|
+
function hermesHomePath(homeDir) {
|
|
893
|
+
return process.env.HERMES_HOME ?? join(homeDir ?? homedir(), ".hermes");
|
|
894
|
+
}
|
|
689
895
|
function configTargetLabel(runtime) {
|
|
690
896
|
switch (runtime) {
|
|
691
897
|
case "codex-cli":
|
|
@@ -1191,49 +1397,64 @@ var RUNTIME_PROFILES = {
|
|
|
1191
1397
|
id: "claude-code",
|
|
1192
1398
|
label: "Claude Code",
|
|
1193
1399
|
restartMode: "restart-session",
|
|
1194
|
-
canWriteRuntimeConfig: true
|
|
1400
|
+
canWriteRuntimeConfig: true,
|
|
1401
|
+
activationInstruction: "Start a new Claude Code session so it loads the Haven MCP entries."
|
|
1195
1402
|
},
|
|
1196
1403
|
"codex-cli": {
|
|
1197
1404
|
id: "codex-cli",
|
|
1198
1405
|
label: "Codex CLI",
|
|
1199
1406
|
restartMode: "restart-session",
|
|
1200
|
-
canWriteRuntimeConfig: true
|
|
1407
|
+
canWriteRuntimeConfig: true,
|
|
1408
|
+
activationInstruction: "Start a fresh Codex CLI session (for example, run `codex resume --last`)."
|
|
1201
1409
|
},
|
|
1202
1410
|
"codex-desktop": {
|
|
1203
1411
|
id: "codex-desktop",
|
|
1204
1412
|
label: "Codex Desktop",
|
|
1205
1413
|
restartMode: "restart-session",
|
|
1206
|
-
canWriteRuntimeConfig: true
|
|
1414
|
+
canWriteRuntimeConfig: true,
|
|
1415
|
+
activationInstruction: "Quit and reopen Codex Desktop so it loads the Haven MCP entries."
|
|
1207
1416
|
},
|
|
1208
1417
|
cursor: {
|
|
1209
1418
|
id: "cursor",
|
|
1210
1419
|
label: "Cursor",
|
|
1211
1420
|
restartMode: "hot-reload",
|
|
1212
|
-
canWriteRuntimeConfig: true
|
|
1421
|
+
canWriteRuntimeConfig: true,
|
|
1422
|
+
activationInstruction: "Wait for Cursor to hot-reload the Haven MCP entries; no app restart is required."
|
|
1213
1423
|
},
|
|
1214
1424
|
vscode: {
|
|
1215
1425
|
id: "vscode",
|
|
1216
1426
|
label: "VS Code",
|
|
1217
1427
|
restartMode: "hot-reload",
|
|
1218
|
-
canWriteRuntimeConfig: true
|
|
1428
|
+
canWriteRuntimeConfig: true,
|
|
1429
|
+
activationInstruction: "Wait for VS Code to hot-reload the Haven MCP entries; no app restart is required."
|
|
1219
1430
|
},
|
|
1220
1431
|
"vscode-insiders": {
|
|
1221
1432
|
id: "vscode-insiders",
|
|
1222
1433
|
label: "VS Code Insiders",
|
|
1223
1434
|
restartMode: "hot-reload",
|
|
1224
|
-
canWriteRuntimeConfig: true
|
|
1435
|
+
canWriteRuntimeConfig: true,
|
|
1436
|
+
activationInstruction: "Wait for VS Code Insiders to hot-reload the Haven MCP entries; no app restart is required."
|
|
1225
1437
|
},
|
|
1226
1438
|
"claude-desktop": {
|
|
1227
1439
|
id: "claude-desktop",
|
|
1228
1440
|
label: "Claude Desktop",
|
|
1229
1441
|
restartMode: "restart-app",
|
|
1230
|
-
canWriteRuntimeConfig: true
|
|
1442
|
+
canWriteRuntimeConfig: true,
|
|
1443
|
+
activationInstruction: "Quit and reopen Claude Desktop so it loads the Haven MCP entries."
|
|
1444
|
+
},
|
|
1445
|
+
hermes: {
|
|
1446
|
+
id: "hermes",
|
|
1447
|
+
label: "Hermes Agent",
|
|
1448
|
+
restartMode: "restart-session",
|
|
1449
|
+
canWriteRuntimeConfig: true,
|
|
1450
|
+
activationInstruction: "Start a new Hermes session; in Hermes Gateway, run `/restart` instead."
|
|
1231
1451
|
},
|
|
1232
1452
|
other: {
|
|
1233
1453
|
id: "other",
|
|
1234
1454
|
label: "Other agent runtime",
|
|
1235
1455
|
restartMode: "manual",
|
|
1236
|
-
canWriteRuntimeConfig: false
|
|
1456
|
+
canWriteRuntimeConfig: false,
|
|
1457
|
+
activationInstruction: "Finish the manual MCP setup shown above, then start a fresh session in that runtime."
|
|
1237
1458
|
}
|
|
1238
1459
|
};
|
|
1239
1460
|
var RUNTIME_ALIASES = {
|
|
@@ -1266,6 +1487,10 @@ var RUNTIME_ALIASES = {
|
|
|
1266
1487
|
"claude_desktop": "claude-desktop",
|
|
1267
1488
|
claudesktop: "claude-desktop",
|
|
1268
1489
|
desktop: "claude-desktop",
|
|
1490
|
+
hermes: "hermes",
|
|
1491
|
+
"hermes-agent": "hermes",
|
|
1492
|
+
hermes_agent: "hermes",
|
|
1493
|
+
hermesagent: "hermes",
|
|
1269
1494
|
other: "other",
|
|
1270
1495
|
manual: "other"
|
|
1271
1496
|
};
|
|
@@ -1281,8 +1506,9 @@ function restartRequiredForRuntime(runtime, env = process.env) {
|
|
|
1281
1506
|
const mode = runtimeProfile(runtime, env).restartMode;
|
|
1282
1507
|
return mode === "restart-session" || mode === "restart-app";
|
|
1283
1508
|
}
|
|
1284
|
-
function
|
|
1285
|
-
|
|
1509
|
+
function runtimeVerificationInstruction(runtime) {
|
|
1510
|
+
const label = RUNTIME_PROFILES[runtime].label;
|
|
1511
|
+
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.`;
|
|
1286
1512
|
}
|
|
1287
1513
|
function normalizeRuntimeName(runtime) {
|
|
1288
1514
|
const key = runtime?.trim().toLowerCase();
|
|
@@ -1293,6 +1519,7 @@ function detectRuntime(env) {
|
|
|
1293
1519
|
if (env.CLAUDECODE || env.CLAUDE_CODE || env.CLAUDECODE_CWD) return "claude-code";
|
|
1294
1520
|
if (env.CODEX_SANDBOX || env.CODEX_HOME || env.CODEX_CWD) return "codex-cli";
|
|
1295
1521
|
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
1522
|
+
if (env.HERMES_HOME || env.HERMES_AGENT) return "hermes";
|
|
1296
1523
|
return null;
|
|
1297
1524
|
}
|
|
1298
1525
|
async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
@@ -1467,11 +1694,12 @@ async function installRuntime(input, deps = {}) {
|
|
|
1467
1694
|
probeLocalSignerCredential(input.signerPath),
|
|
1468
1695
|
localProbePromise
|
|
1469
1696
|
]);
|
|
1470
|
-
const hostedOk = configResult.hostedConfigured && hostedProbe.status
|
|
1697
|
+
const hostedOk = configResult.hostedConfigured && hostedProbe.status === "ok";
|
|
1471
1698
|
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
|
|
1472
1699
|
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
1473
1700
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1474
|
-
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1701
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : hostedMcpErrorCode(configResult.hostedConfigured, hostedProbe.status) ?? signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1702
|
+
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."] : [];
|
|
1475
1703
|
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."] : [];
|
|
1476
1704
|
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1477
1705
|
return {
|
|
@@ -1490,7 +1718,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1490
1718
|
activationCommand: configResult.activationCommand,
|
|
1491
1719
|
skillInstalled: skillInstall?.installed,
|
|
1492
1720
|
signerRuntimePrepared,
|
|
1493
|
-
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1721
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...hostedProbeMessages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1494
1722
|
};
|
|
1495
1723
|
}
|
|
1496
1724
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1651,6 +1879,10 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
|
1651
1879
|
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
1652
1880
|
return void 0;
|
|
1653
1881
|
}
|
|
1882
|
+
function hostedMcpErrorCode(hostedConfigured, hostedProbeStatus) {
|
|
1883
|
+
if (!hostedConfigured || hostedProbeStatus === "ok") return void 0;
|
|
1884
|
+
return `hosted_mcp_probe_${hostedProbeStatus}`;
|
|
1885
|
+
}
|
|
1654
1886
|
function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
|
|
1655
1887
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
1656
1888
|
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
@@ -1702,13 +1934,17 @@ function localRuntimePrepareErrorCode(err) {
|
|
|
1702
1934
|
}
|
|
1703
1935
|
|
|
1704
1936
|
// src/runtime.ts
|
|
1705
|
-
var CONNECTOR_VERSION = "0.1.
|
|
1937
|
+
var CONNECTOR_VERSION = "0.1.23-alpha.0";
|
|
1938
|
+
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
1706
1939
|
async function runConnect(options, deps = {}) {
|
|
1707
1940
|
assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
|
|
1708
1941
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
1709
1942
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
1710
|
-
const log = secureLogger(
|
|
1711
|
-
|
|
1943
|
+
const log = secureLogger(
|
|
1944
|
+
deps.log ?? ((message) => process.stdout.write(`${message}
|
|
1945
|
+
`)),
|
|
1946
|
+
deps.redactPaths === true
|
|
1947
|
+
);
|
|
1712
1948
|
const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
|
|
1713
1949
|
const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
|
|
1714
1950
|
const runRuntimeInstall = deps.installRuntime ?? installRuntime;
|
|
@@ -1729,6 +1965,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1729
1965
|
connectorVersion,
|
|
1730
1966
|
runtime: options.runtime
|
|
1731
1967
|
});
|
|
1968
|
+
assertSetupChallengeIsUsable(setup.challenge.expires_at);
|
|
1732
1969
|
printSetupSummary(setup, log);
|
|
1733
1970
|
await preflightStorage({ baseDir: options.credentialsDir, warn: log });
|
|
1734
1971
|
log("Checked local credential storage \u2014 all clear.");
|
|
@@ -1737,21 +1974,31 @@ async function runConnect(options, deps = {}) {
|
|
|
1737
1974
|
log("Minting a fresh signing key and API key \u2014 both stay on this machine.");
|
|
1738
1975
|
const proofSignature = await localKey.signChallenge(setup.challenge.message);
|
|
1739
1976
|
log("Introducing your agent to Haven\u2026");
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1977
|
+
let registration;
|
|
1978
|
+
try {
|
|
1979
|
+
registration = await api.registerSetup({
|
|
1980
|
+
setupToken: options.setupToken,
|
|
1981
|
+
connectorVersion,
|
|
1982
|
+
runtime: options.runtime,
|
|
1983
|
+
challengeId: setup.challenge.id,
|
|
1984
|
+
delegateAddress: localKey.address,
|
|
1985
|
+
proofSignature,
|
|
1986
|
+
apiKeyHash: hashAgentApiKey(localApiKey),
|
|
1987
|
+
apiKeyPrefix: agentApiKeyPrefix(localApiKey),
|
|
1988
|
+
connectorContext: {
|
|
1989
|
+
environment_label: options.environmentLabel ?? "Local workspace",
|
|
1990
|
+
config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
|
|
1991
|
+
},
|
|
1992
|
+
installCapabilities
|
|
1993
|
+
});
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
if (isExpiredSetupChallenge(err)) {
|
|
1996
|
+
throw new Error(
|
|
1997
|
+
"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."
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
throw err;
|
|
2001
|
+
}
|
|
1755
2002
|
log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
|
|
1756
2003
|
log("Tucking your credentials away safely on disk\u2026");
|
|
1757
2004
|
const credentialPaths = await writeCredentials({
|
|
@@ -1828,7 +2075,69 @@ async function runConnect(options, deps = {}) {
|
|
|
1828
2075
|
setupId: registration.setup_id,
|
|
1829
2076
|
agentId: registration.agent_id,
|
|
1830
2077
|
delegateAddress: registration.delegate_address,
|
|
1831
|
-
credentialPaths
|
|
2078
|
+
credentialPaths,
|
|
2079
|
+
outcome: completionOutcome({
|
|
2080
|
+
runtimeInstall,
|
|
2081
|
+
delegateAddress: registration.delegate_address,
|
|
2082
|
+
setupChallengeExpiresAt: setup.challenge.expires_at,
|
|
2083
|
+
approvalRequired: registration.agent_status === "pending_approval"
|
|
2084
|
+
})
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function completionOutcome(input) {
|
|
2088
|
+
const { runtimeInstall } = input;
|
|
2089
|
+
const manualSetup = runtimeInstall.errorCode === "manual_runtime_setup_required";
|
|
2090
|
+
const nextAction2 = runtimeInstall.nextUserAction;
|
|
2091
|
+
const outcome = {
|
|
2092
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2093
|
+
outcome: runtimeInstall.errorCode ? "action_required" : "complete",
|
|
2094
|
+
runtime: runtimeInstall.runtime,
|
|
2095
|
+
topology: runtimeInstall.runtimeMcpMode,
|
|
2096
|
+
configuration: {
|
|
2097
|
+
hosted_mcp: runtimeInstall.hostedMcpConfigured,
|
|
2098
|
+
local_signer: runtimeInstall.localSignerConfigured,
|
|
2099
|
+
local_mcp: runtimeInstall.localMcpConfigured
|
|
2100
|
+
},
|
|
2101
|
+
probe: { result: runtimeInstall.probeResult },
|
|
2102
|
+
activation: {
|
|
2103
|
+
restart_required: runtimeInstall.restartRequired,
|
|
2104
|
+
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
|
|
2105
|
+
},
|
|
2106
|
+
next_action: nextAction2,
|
|
2107
|
+
approval: { required: input.approvalRequired, expires_at: null },
|
|
2108
|
+
verification: {
|
|
2109
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2110
|
+
instruction: runtimeVerificationInstruction(runtimeInstall.runtime)
|
|
2111
|
+
},
|
|
2112
|
+
// The backend contract supplies a 20-byte address. If an unexpected
|
|
2113
|
+
// malformed value arrives, do not echo it into an automation-facing
|
|
2114
|
+
// record; the human log has already been redacted separately.
|
|
2115
|
+
delegate_address: /^0x[0-9a-fA-F]{40}$/.test(input.delegateAddress) ? shortAddress(input.delegateAddress) : "[delegate-address-redacted]",
|
|
2116
|
+
...input.setupChallengeExpiresAt ? { setup_challenge_expires_at: input.setupChallengeExpiresAt } : {},
|
|
2117
|
+
...runtimeInstall.errorCode ? { error: { code: runtimeInstall.errorCode, next_action: nextAction2 } } : {}
|
|
2118
|
+
};
|
|
2119
|
+
return outcome;
|
|
2120
|
+
}
|
|
2121
|
+
function failedConnectOutcome(runtimeHint, error) {
|
|
2122
|
+
const message = error instanceof Error ? error.message : "";
|
|
2123
|
+
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";
|
|
2124
|
+
const runtime = normalizeRuntime(runtimeHint);
|
|
2125
|
+
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";
|
|
2126
|
+
return {
|
|
2127
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2128
|
+
outcome: "failed",
|
|
2129
|
+
runtime,
|
|
2130
|
+
topology: "unknown",
|
|
2131
|
+
configuration: { hosted_mcp: false, local_signer: false, local_mcp: false },
|
|
2132
|
+
probe: { result: "not_run" },
|
|
2133
|
+
activation: { restart_required: false, instruction: "Resolve the reported problem before activating Haven tools." },
|
|
2134
|
+
next_action: nextAction2,
|
|
2135
|
+
approval: { required: false, expires_at: null },
|
|
2136
|
+
verification: {
|
|
2137
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2138
|
+
instruction: "After a successful setup and activation, verify only with haven_get_agent and haven_get_allowances."
|
|
2139
|
+
},
|
|
2140
|
+
error: { code, next_action: nextAction2 }
|
|
1832
2141
|
};
|
|
1833
2142
|
}
|
|
1834
2143
|
function printSetupSummary(setup, log) {
|
|
@@ -1841,10 +2150,26 @@ function printSetupSummary(setup, log) {
|
|
|
1841
2150
|
);
|
|
1842
2151
|
}
|
|
1843
2152
|
}
|
|
1844
|
-
log(`Setup challenge expires at ${setup.challenge.expires_at}.`);
|
|
2153
|
+
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.`);
|
|
2154
|
+
}
|
|
2155
|
+
function assertSetupChallengeIsUsable(expiresAt) {
|
|
2156
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
2157
|
+
if (!Number.isNaN(expiresAtMs) && expiresAtMs > Date.now()) return;
|
|
2158
|
+
throw new Error(
|
|
2159
|
+
"This Haven setup challenge is expired or invalid. Return to Haven, start a fresh connection, and rerun Connect. No local credentials were written."
|
|
2160
|
+
);
|
|
1845
2161
|
}
|
|
1846
|
-
function
|
|
1847
|
-
return (
|
|
2162
|
+
function isExpiredSetupChallenge(err) {
|
|
2163
|
+
return err instanceof Error && /(?:setup )?challenge.*expir|expir.*(?:setup )?challenge/i.test(err.message);
|
|
2164
|
+
}
|
|
2165
|
+
function secureLogger(log, redactPaths = false) {
|
|
2166
|
+
return (message) => {
|
|
2167
|
+
let safe = redactSecrets(message);
|
|
2168
|
+
if (redactPaths) {
|
|
2169
|
+
safe = safe.replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
|
|
2170
|
+
}
|
|
2171
|
+
log(safe);
|
|
2172
|
+
};
|
|
1848
2173
|
}
|
|
1849
2174
|
function printRuntimeInstall(result, log) {
|
|
1850
2175
|
for (const message of result.messages) log(message);
|
|
@@ -1861,17 +2186,30 @@ function printRuntimeInstall(result, log) {
|
|
|
1861
2186
|
log("Local Haven signer still needs runtime setup.");
|
|
1862
2187
|
}
|
|
1863
2188
|
}
|
|
1864
|
-
function
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
}
|
|
1873
|
-
|
|
2189
|
+
function completionHandoffLines(result) {
|
|
2190
|
+
if (result.errorCode === "manual_runtime_setup_required") {
|
|
2191
|
+
return [
|
|
2192
|
+
"Next steps:",
|
|
2193
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2194
|
+
"2. Finish the manual MCP setup using the secret-free file references printed above, then start a fresh session in your runtime.",
|
|
2195
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2196
|
+
];
|
|
2197
|
+
}
|
|
2198
|
+
if (result.errorCode) {
|
|
2199
|
+
return [
|
|
2200
|
+
"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."
|
|
2201
|
+
];
|
|
1874
2202
|
}
|
|
2203
|
+
const profile = runtimeProfile(result.runtime);
|
|
2204
|
+
return [
|
|
2205
|
+
"Next steps:",
|
|
2206
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2207
|
+
`2. ${profile.activationInstruction}`,
|
|
2208
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2209
|
+
];
|
|
2210
|
+
}
|
|
2211
|
+
function printNextSteps(result, log) {
|
|
2212
|
+
for (const line of completionHandoffLines(result)) log(line);
|
|
1875
2213
|
}
|
|
1876
2214
|
|
|
1877
2215
|
// src/args.ts
|
|
@@ -1881,10 +2219,13 @@ function parseArgs(argv, env = process.env) {
|
|
|
1881
2219
|
connectorVersion: CONNECTOR_VERSION
|
|
1882
2220
|
};
|
|
1883
2221
|
let help = false;
|
|
2222
|
+
let json = false;
|
|
1884
2223
|
for (let i = 0; i < argv.length; i += 1) {
|
|
1885
2224
|
const arg = argv[i];
|
|
1886
2225
|
if (arg === "--help" || arg === "-h") {
|
|
1887
2226
|
help = true;
|
|
2227
|
+
} else if (arg === "--json") {
|
|
2228
|
+
json = true;
|
|
1888
2229
|
} else if (arg === "--setup" || arg === "--setup-token") {
|
|
1889
2230
|
options.setupToken = requireValue(argv, ++i, arg);
|
|
1890
2231
|
} else if (arg === "--api" || arg === "--api-url") {
|
|
@@ -1911,7 +2252,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
1911
2252
|
}
|
|
1912
2253
|
}
|
|
1913
2254
|
if (help) {
|
|
1914
|
-
return { options, help };
|
|
2255
|
+
return { options, help, json };
|
|
1915
2256
|
}
|
|
1916
2257
|
if (!options.setupToken) {
|
|
1917
2258
|
throw new Error("Missing --setup <hv_setup_...> setup token.");
|
|
@@ -1920,7 +2261,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
1920
2261
|
throw new Error("Missing --api <Haven API URL>.");
|
|
1921
2262
|
}
|
|
1922
2263
|
options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
1923
|
-
return { options, help };
|
|
2264
|
+
return { options, help, json };
|
|
1924
2265
|
}
|
|
1925
2266
|
function helpText() {
|
|
1926
2267
|
return [
|
|
@@ -1935,16 +2276,17 @@ function helpText() {
|
|
|
1935
2276
|
"Options:",
|
|
1936
2277
|
" --setup <token> Short-lived setup token from Haven.",
|
|
1937
2278
|
" --api <url> Haven backend API URL. Defaults to HAVEN_API_URL or http://localhost:3001.",
|
|
1938
|
-
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode,
|
|
2279
|
+
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode, claude-desktop, or hermes.",
|
|
1939
2280
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
1940
2281
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1941
2282
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1942
2283
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
1943
2284
|
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
1944
2285
|
" Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
|
|
2286
|
+
" --json Emit one versioned, secret-free result object on stdout; progress stays on stderr.",
|
|
1945
2287
|
" --help Show this help.",
|
|
1946
2288
|
"",
|
|
1947
|
-
"The connector never prints the private key and never sends it to Haven."
|
|
2289
|
+
"The connector never prints the private key and never sends it to Haven. JSON output never includes credential contents or full credential paths."
|
|
1948
2290
|
].join("\n");
|
|
1949
2291
|
}
|
|
1950
2292
|
function requireValue(argv, index, option) {
|
|
@@ -1956,19 +2298,55 @@ function requireValue(argv, index, option) {
|
|
|
1956
2298
|
}
|
|
1957
2299
|
|
|
1958
2300
|
// src/cli.ts
|
|
1959
|
-
async function
|
|
1960
|
-
|
|
2301
|
+
async function runCli(argv, io = {
|
|
2302
|
+
stdout: (message) => process.stdout.write(message),
|
|
2303
|
+
stderr: (message) => process.stderr.write(message)
|
|
2304
|
+
}) {
|
|
2305
|
+
const wantsJson = argv.includes("--json");
|
|
2306
|
+
let parsed;
|
|
2307
|
+
try {
|
|
2308
|
+
parsed = parseArgs(argv);
|
|
2309
|
+
} catch (err) {
|
|
2310
|
+
if (wantsJson) {
|
|
2311
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(void 0, err))}
|
|
2312
|
+
`);
|
|
2313
|
+
} else {
|
|
2314
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2315
|
+
`);
|
|
2316
|
+
}
|
|
2317
|
+
return 1;
|
|
2318
|
+
}
|
|
1961
2319
|
if (parsed.help) {
|
|
1962
|
-
|
|
2320
|
+
io.stdout(`${helpText()}
|
|
1963
2321
|
`);
|
|
1964
|
-
return;
|
|
2322
|
+
return 0;
|
|
1965
2323
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2324
|
+
try {
|
|
2325
|
+
const result = await runConnect(parsed.options, {
|
|
2326
|
+
log: (message) => (parsed.json ? io.stderr : io.stdout)(`${message}
|
|
2327
|
+
`),
|
|
2328
|
+
redactPaths: parsed.json
|
|
2329
|
+
});
|
|
2330
|
+
if (parsed.json) io.stdout(`${JSON.stringify(result.outcome)}
|
|
2331
|
+
`);
|
|
2332
|
+
return 0;
|
|
2333
|
+
} catch (err) {
|
|
2334
|
+
if (parsed.json) {
|
|
2335
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(parsed.options.runtime, err))}
|
|
1970
2336
|
`);
|
|
1971
|
-
|
|
1972
|
-
}
|
|
2337
|
+
} else {
|
|
2338
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2339
|
+
`);
|
|
2340
|
+
}
|
|
2341
|
+
return 1;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
async function main() {
|
|
2345
|
+
const exitCode = await runCli(process.argv.slice(2));
|
|
2346
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2347
|
+
}
|
|
2348
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) void main();
|
|
2349
|
+
|
|
2350
|
+
export { runCli };
|
|
1973
2351
|
//# sourceMappingURL=cli.js.map
|
|
1974
2352
|
//# sourceMappingURL=cli.js.map
|