@haven_ai/connect 0.1.22-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 +47 -7
- package/dist/cli.cjs +226 -62
- 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 +225 -62
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +232 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -6
- package/dist/index.d.ts +62 -6
- package/dist/index.js +230 -55
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -33,14 +33,54 @@ identity) plus a separate local signer. The API key identifies the agent; the
|
|
|
33
33
|
locally held signer key and the user's approved Haven wallet rules remain the
|
|
34
34
|
spending authority.
|
|
35
35
|
|
|
36
|
-
| Runtime | Configuration written by setup |
|
|
36
|
+
| Runtime | Configuration written by setup | Activate the new entry |
|
|
37
37
|
| --- | --- | --- |
|
|
38
|
-
| Claude Code | User MCP registry | Start a new session |
|
|
39
|
-
| Codex CLI
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
38
|
+
| Claude Code | User MCP registry | Start a new Claude Code session. |
|
|
39
|
+
| Codex CLI | `~/.codex/config.toml` | Start a fresh session, for example `codex resume --last`. |
|
|
40
|
+
| Codex Desktop | `~/.codex/config.toml` | Quit and reopen the app. |
|
|
41
|
+
| Cursor | Cursor MCP configuration | Wait for hot reload; no app restart is required. |
|
|
42
|
+
| VS Code / VS Code Insiders | VS Code MCP configuration | Wait for hot reload; no app restart is required. |
|
|
43
|
+
| Claude Desktop | Claude Desktop MCP configuration | Quit and reopen the app. |
|
|
44
|
+
| Hermes Agent | `$HERMES_HOME/config.yaml` + `.env`, or `~/.hermes/config.yaml` + `.env` | Start a new session; gateway users run `/restart`. |
|
|
45
|
+
|
|
46
|
+
## After setup
|
|
47
|
+
|
|
48
|
+
1. Return to Haven and approve the agent rules. Approval — not restarting —
|
|
49
|
+
unlocks the Haven tools.
|
|
50
|
+
2. Activate the runtime using the table above.
|
|
51
|
+
3. In the activated runtime, run the read-only `haven_get_agent` and
|
|
52
|
+
`haven_get_allowances` tools to confirm the Haven wallet and live budget.
|
|
53
|
+
Do not sign, fund, or create a payment to verify setup.
|
|
54
|
+
|
|
55
|
+
### Structured output for automation
|
|
56
|
+
|
|
57
|
+
Pass `--json` when a launcher needs a machine-readable completion record. Connect
|
|
58
|
+
writes progress and human recovery notes to stderr and exactly one JSON object
|
|
59
|
+
to stdout, with `schema_version: 1` and `outcome` set to `complete`,
|
|
60
|
+
`action_required`, or `failed`. The object includes runtime/topology status,
|
|
61
|
+
probe result, activation and next-action guidance, approval state/expiry (null
|
|
62
|
+
when the backend does not provide an approval expiry), and the two
|
|
63
|
+
read-only verification tools. It contains no API key, private key, credential
|
|
64
|
+
contents, full credential paths, or full delegate address. The same redacted
|
|
65
|
+
object is available to library callers as `runConnect(...).outcome`; the older
|
|
66
|
+
fields remain for additive compatibility.
|
|
67
|
+
|
|
68
|
+
For a recoverable install, configuration, probe, consent, or manual-runtime
|
|
69
|
+
condition, inspect `error.code` and `error.next_action`, then follow the safe
|
|
70
|
+
next action. A failed setup emits `outcome: "failed"` with a stable error code;
|
|
71
|
+
it never presents credential material as a recovery diagnostic.
|
|
72
|
+
|
|
73
|
+
If the setup challenge expires, return to Haven to start a fresh connection and
|
|
74
|
+
rerun Connect. If a runtime write, installation, or probe fails, follow the
|
|
75
|
+
structured `error.next_action` (or its human equivalent). The `other` runtime
|
|
76
|
+
is the manual exception: finish the secret-free file-reference setup it prints,
|
|
77
|
+
then start a fresh runtime session. Do not manually edit managed runtime
|
|
78
|
+
configuration or paste credentials into prompts, logs, or configuration files.
|
|
79
|
+
|
|
80
|
+
Connect abbreviates the public delegate address in normal output. Operators who
|
|
81
|
+
need its full public identifier can inspect the owner-only, non-secret
|
|
82
|
+
`agent.json` orientation file that Connect reports; do not inspect or share
|
|
83
|
+
`identity.json` or `signer.json` for diagnostics because they contain secrets.
|
|
44
84
|
|
|
45
85
|
For Hermes, Connect stores the hosted-MCP API key in the matching owner-only
|
|
46
86
|
`.env` file and keeps only `Bearer ${MCP_HAVEN_API_KEY}` in `config.yaml`.
|
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');
|
|
@@ -13,6 +14,7 @@ var mcp = require('@haven_ai/mcp');
|
|
|
13
14
|
var sdk = require('@haven_ai/sdk');
|
|
14
15
|
var signer = require('@haven_ai/signer');
|
|
15
16
|
|
|
17
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
16
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
19
|
|
|
18
20
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
@@ -181,11 +183,12 @@ async function writeCredentialFiles(input) {
|
|
|
181
183
|
agentPath,
|
|
182
184
|
{
|
|
183
185
|
agent_id: input.agentId,
|
|
186
|
+
delegate_address: input.delegateAddress,
|
|
184
187
|
safe_address: input.safeAddress,
|
|
185
188
|
chain_id: input.chainId,
|
|
186
189
|
network: input.network,
|
|
187
190
|
agent_budget: input.agentBudget,
|
|
188
|
-
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."
|
|
189
192
|
},
|
|
190
193
|
input.warn
|
|
191
194
|
);
|
|
@@ -237,9 +240,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
237
240
|
mcpPackage: "@haven_ai/mcp",
|
|
238
241
|
mcpVersion: mcp.MCP_VERSION,
|
|
239
242
|
sdkPackage: "@haven_ai/sdk",
|
|
240
|
-
sdkVersion: "0.1.
|
|
243
|
+
sdkVersion: "0.1.23-alpha.0",
|
|
241
244
|
signerPackage: "@haven_ai/signer",
|
|
242
|
-
signerVersion: "0.1.
|
|
245
|
+
signerVersion: "0.1.23-alpha.0",
|
|
243
246
|
// Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
|
|
244
247
|
// while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
|
|
245
248
|
// so the guard that was supposed to enforce the floor waved Node v23 through
|
|
@@ -1401,55 +1404,64 @@ var RUNTIME_PROFILES = {
|
|
|
1401
1404
|
id: "claude-code",
|
|
1402
1405
|
label: "Claude Code",
|
|
1403
1406
|
restartMode: "restart-session",
|
|
1404
|
-
canWriteRuntimeConfig: true
|
|
1407
|
+
canWriteRuntimeConfig: true,
|
|
1408
|
+
activationInstruction: "Start a new Claude Code session so it loads the Haven MCP entries."
|
|
1405
1409
|
},
|
|
1406
1410
|
"codex-cli": {
|
|
1407
1411
|
id: "codex-cli",
|
|
1408
1412
|
label: "Codex CLI",
|
|
1409
1413
|
restartMode: "restart-session",
|
|
1410
|
-
canWriteRuntimeConfig: true
|
|
1414
|
+
canWriteRuntimeConfig: true,
|
|
1415
|
+
activationInstruction: "Start a fresh Codex CLI session (for example, run `codex resume --last`)."
|
|
1411
1416
|
},
|
|
1412
1417
|
"codex-desktop": {
|
|
1413
1418
|
id: "codex-desktop",
|
|
1414
1419
|
label: "Codex Desktop",
|
|
1415
1420
|
restartMode: "restart-session",
|
|
1416
|
-
canWriteRuntimeConfig: true
|
|
1421
|
+
canWriteRuntimeConfig: true,
|
|
1422
|
+
activationInstruction: "Quit and reopen Codex Desktop so it loads the Haven MCP entries."
|
|
1417
1423
|
},
|
|
1418
1424
|
cursor: {
|
|
1419
1425
|
id: "cursor",
|
|
1420
1426
|
label: "Cursor",
|
|
1421
1427
|
restartMode: "hot-reload",
|
|
1422
|
-
canWriteRuntimeConfig: true
|
|
1428
|
+
canWriteRuntimeConfig: true,
|
|
1429
|
+
activationInstruction: "Wait for Cursor to hot-reload the Haven MCP entries; no app restart is required."
|
|
1423
1430
|
},
|
|
1424
1431
|
vscode: {
|
|
1425
1432
|
id: "vscode",
|
|
1426
1433
|
label: "VS Code",
|
|
1427
1434
|
restartMode: "hot-reload",
|
|
1428
|
-
canWriteRuntimeConfig: true
|
|
1435
|
+
canWriteRuntimeConfig: true,
|
|
1436
|
+
activationInstruction: "Wait for VS Code to hot-reload the Haven MCP entries; no app restart is required."
|
|
1429
1437
|
},
|
|
1430
1438
|
"vscode-insiders": {
|
|
1431
1439
|
id: "vscode-insiders",
|
|
1432
1440
|
label: "VS Code Insiders",
|
|
1433
1441
|
restartMode: "hot-reload",
|
|
1434
|
-
canWriteRuntimeConfig: true
|
|
1442
|
+
canWriteRuntimeConfig: true,
|
|
1443
|
+
activationInstruction: "Wait for VS Code Insiders to hot-reload the Haven MCP entries; no app restart is required."
|
|
1435
1444
|
},
|
|
1436
1445
|
"claude-desktop": {
|
|
1437
1446
|
id: "claude-desktop",
|
|
1438
1447
|
label: "Claude Desktop",
|
|
1439
1448
|
restartMode: "restart-app",
|
|
1440
|
-
canWriteRuntimeConfig: true
|
|
1449
|
+
canWriteRuntimeConfig: true,
|
|
1450
|
+
activationInstruction: "Quit and reopen Claude Desktop so it loads the Haven MCP entries."
|
|
1441
1451
|
},
|
|
1442
1452
|
hermes: {
|
|
1443
1453
|
id: "hermes",
|
|
1444
1454
|
label: "Hermes Agent",
|
|
1445
1455
|
restartMode: "restart-session",
|
|
1446
|
-
canWriteRuntimeConfig: true
|
|
1456
|
+
canWriteRuntimeConfig: true,
|
|
1457
|
+
activationInstruction: "Start a new Hermes session; in Hermes Gateway, run `/restart` instead."
|
|
1447
1458
|
},
|
|
1448
1459
|
other: {
|
|
1449
1460
|
id: "other",
|
|
1450
1461
|
label: "Other agent runtime",
|
|
1451
1462
|
restartMode: "manual",
|
|
1452
|
-
canWriteRuntimeConfig: false
|
|
1463
|
+
canWriteRuntimeConfig: false,
|
|
1464
|
+
activationInstruction: "Finish the manual MCP setup shown above, then start a fresh session in that runtime."
|
|
1453
1465
|
}
|
|
1454
1466
|
};
|
|
1455
1467
|
var RUNTIME_ALIASES = {
|
|
@@ -1501,8 +1513,9 @@ function restartRequiredForRuntime(runtime, env = process.env) {
|
|
|
1501
1513
|
const mode = runtimeProfile(runtime, env).restartMode;
|
|
1502
1514
|
return mode === "restart-session" || mode === "restart-app";
|
|
1503
1515
|
}
|
|
1504
|
-
function
|
|
1505
|
-
|
|
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.`;
|
|
1506
1519
|
}
|
|
1507
1520
|
function normalizeRuntimeName(runtime) {
|
|
1508
1521
|
const key = runtime?.trim().toLowerCase();
|
|
@@ -1688,11 +1701,12 @@ async function installRuntime(input, deps = {}) {
|
|
|
1688
1701
|
probeLocalSignerCredential(input.signerPath),
|
|
1689
1702
|
localProbePromise
|
|
1690
1703
|
]);
|
|
1691
|
-
const hostedOk = configResult.hostedConfigured && hostedProbe.status
|
|
1704
|
+
const hostedOk = configResult.hostedConfigured && hostedProbe.status === "ok";
|
|
1692
1705
|
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
|
|
1693
1706
|
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
1694
1707
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1695
|
-
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."] : [];
|
|
1696
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."] : [];
|
|
1697
1711
|
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1698
1712
|
return {
|
|
@@ -1711,7 +1725,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1711
1725
|
activationCommand: configResult.activationCommand,
|
|
1712
1726
|
skillInstalled: skillInstall?.installed,
|
|
1713
1727
|
signerRuntimePrepared,
|
|
1714
|
-
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1728
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...hostedProbeMessages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1715
1729
|
};
|
|
1716
1730
|
}
|
|
1717
1731
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1872,6 +1886,10 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
|
1872
1886
|
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
1873
1887
|
return void 0;
|
|
1874
1888
|
}
|
|
1889
|
+
function hostedMcpErrorCode(hostedConfigured, hostedProbeStatus) {
|
|
1890
|
+
if (!hostedConfigured || hostedProbeStatus === "ok") return void 0;
|
|
1891
|
+
return `hosted_mcp_probe_${hostedProbeStatus}`;
|
|
1892
|
+
}
|
|
1875
1893
|
function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
|
|
1876
1894
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
1877
1895
|
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
@@ -1923,13 +1941,17 @@ function localRuntimePrepareErrorCode(err) {
|
|
|
1923
1941
|
}
|
|
1924
1942
|
|
|
1925
1943
|
// src/runtime.ts
|
|
1926
|
-
var CONNECTOR_VERSION = "0.1.
|
|
1944
|
+
var CONNECTOR_VERSION = "0.1.23-alpha.0";
|
|
1945
|
+
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
1927
1946
|
async function runConnect(options, deps = {}) {
|
|
1928
1947
|
assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
|
|
1929
1948
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
1930
1949
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
1931
|
-
const log = secureLogger(
|
|
1932
|
-
|
|
1950
|
+
const log = secureLogger(
|
|
1951
|
+
deps.log ?? ((message) => process.stdout.write(`${message}
|
|
1952
|
+
`)),
|
|
1953
|
+
deps.redactPaths === true
|
|
1954
|
+
);
|
|
1933
1955
|
const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
|
|
1934
1956
|
const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
|
|
1935
1957
|
const runRuntimeInstall = deps.installRuntime ?? installRuntime;
|
|
@@ -1950,6 +1972,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1950
1972
|
connectorVersion,
|
|
1951
1973
|
runtime: options.runtime
|
|
1952
1974
|
});
|
|
1975
|
+
assertSetupChallengeIsUsable(setup.challenge.expires_at);
|
|
1953
1976
|
printSetupSummary(setup, log);
|
|
1954
1977
|
await preflightStorage({ baseDir: options.credentialsDir, warn: log });
|
|
1955
1978
|
log("Checked local credential storage \u2014 all clear.");
|
|
@@ -1958,21 +1981,31 @@ async function runConnect(options, deps = {}) {
|
|
|
1958
1981
|
log("Minting a fresh signing key and API key \u2014 both stay on this machine.");
|
|
1959
1982
|
const proofSignature = await localKey.signChallenge(setup.challenge.message);
|
|
1960
1983
|
log("Introducing your agent to Haven\u2026");
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
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
|
+
}
|
|
1976
2009
|
log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
|
|
1977
2010
|
log("Tucking your credentials away safely on disk\u2026");
|
|
1978
2011
|
const credentialPaths = await writeCredentials({
|
|
@@ -2049,7 +2082,69 @@ async function runConnect(options, deps = {}) {
|
|
|
2049
2082
|
setupId: registration.setup_id,
|
|
2050
2083
|
agentId: registration.agent_id,
|
|
2051
2084
|
delegateAddress: registration.delegate_address,
|
|
2052
|
-
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 }
|
|
2053
2148
|
};
|
|
2054
2149
|
}
|
|
2055
2150
|
function printSetupSummary(setup, log) {
|
|
@@ -2062,10 +2157,26 @@ function printSetupSummary(setup, log) {
|
|
|
2062
2157
|
);
|
|
2063
2158
|
}
|
|
2064
2159
|
}
|
|
2065
|
-
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.`);
|
|
2066
2161
|
}
|
|
2067
|
-
function
|
|
2068
|
-
|
|
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
|
+
);
|
|
2168
|
+
}
|
|
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
|
+
};
|
|
2069
2180
|
}
|
|
2070
2181
|
function printRuntimeInstall(result, log) {
|
|
2071
2182
|
for (const message of result.messages) log(message);
|
|
@@ -2082,17 +2193,30 @@ function printRuntimeInstall(result, log) {
|
|
|
2082
2193
|
log("Local Haven signer still needs runtime setup.");
|
|
2083
2194
|
}
|
|
2084
2195
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
}
|
|
2094
|
-
|
|
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
|
+
];
|
|
2095
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);
|
|
2096
2220
|
}
|
|
2097
2221
|
|
|
2098
2222
|
// src/args.ts
|
|
@@ -2102,10 +2226,13 @@ function parseArgs(argv, env = process.env) {
|
|
|
2102
2226
|
connectorVersion: CONNECTOR_VERSION
|
|
2103
2227
|
};
|
|
2104
2228
|
let help = false;
|
|
2229
|
+
let json = false;
|
|
2105
2230
|
for (let i = 0; i < argv.length; i += 1) {
|
|
2106
2231
|
const arg = argv[i];
|
|
2107
2232
|
if (arg === "--help" || arg === "-h") {
|
|
2108
2233
|
help = true;
|
|
2234
|
+
} else if (arg === "--json") {
|
|
2235
|
+
json = true;
|
|
2109
2236
|
} else if (arg === "--setup" || arg === "--setup-token") {
|
|
2110
2237
|
options.setupToken = requireValue(argv, ++i, arg);
|
|
2111
2238
|
} else if (arg === "--api" || arg === "--api-url") {
|
|
@@ -2132,7 +2259,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
2132
2259
|
}
|
|
2133
2260
|
}
|
|
2134
2261
|
if (help) {
|
|
2135
|
-
return { options, help };
|
|
2262
|
+
return { options, help, json };
|
|
2136
2263
|
}
|
|
2137
2264
|
if (!options.setupToken) {
|
|
2138
2265
|
throw new Error("Missing --setup <hv_setup_...> setup token.");
|
|
@@ -2141,7 +2268,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
2141
2268
|
throw new Error("Missing --api <Haven API URL>.");
|
|
2142
2269
|
}
|
|
2143
2270
|
options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
2144
|
-
return { options, help };
|
|
2271
|
+
return { options, help, json };
|
|
2145
2272
|
}
|
|
2146
2273
|
function helpText() {
|
|
2147
2274
|
return [
|
|
@@ -2163,9 +2290,10 @@ function helpText() {
|
|
|
2163
2290
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
2164
2291
|
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
2165
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.",
|
|
2166
2294
|
" --help Show this help.",
|
|
2167
2295
|
"",
|
|
2168
|
-
"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."
|
|
2169
2297
|
].join("\n");
|
|
2170
2298
|
}
|
|
2171
2299
|
function requireValue(argv, index, option) {
|
|
@@ -2177,19 +2305,55 @@ function requireValue(argv, index, option) {
|
|
|
2177
2305
|
}
|
|
2178
2306
|
|
|
2179
2307
|
// src/cli.ts
|
|
2180
|
-
async function
|
|
2181
|
-
|
|
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
|
+
}
|
|
2182
2326
|
if (parsed.help) {
|
|
2183
|
-
|
|
2327
|
+
io.stdout(`${helpText()}
|
|
2184
2328
|
`);
|
|
2185
|
-
return;
|
|
2329
|
+
return 0;
|
|
2186
2330
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
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))}
|
|
2191
2343
|
`);
|
|
2192
|
-
|
|
2193
|
-
}
|
|
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;
|
|
2194
2358
|
//# sourceMappingURL=cli.cjs.map
|
|
2195
2359
|
//# sourceMappingURL=cli.cjs.map
|