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