@haven_ai/connect 0.1.22-alpha.0 → 0.1.23-alpha.1
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 +310 -63
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +14 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +308 -63
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +315 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -6
- package/dist/index.d.ts +101 -6
- package/dist/index.js +312 -55
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { HAVEN_MINIMUM_NODE_VERSION, isSupportedNodeVersion, unsupportedNodeVersionMessage, SKILL_FOLDER_NAME, HAVEN_SKILL_MD, resolveTokenFromAddress } from '@haven_ai/sdk';
|
|
1
2
|
import crypto from 'crypto';
|
|
2
3
|
import { Wallet } from 'ethers';
|
|
3
|
-
import { mkdir, rm,
|
|
4
|
+
import { mkdir, rm, chmod, access, writeFile, readFile, unlink } from 'fs/promises';
|
|
4
5
|
import { homedir, platform } from 'os';
|
|
5
6
|
import { join, resolve, dirname } from 'path';
|
|
6
7
|
import { execFile, spawn } from 'child_process';
|
|
7
8
|
import { promisify } from 'util';
|
|
8
9
|
import { parseDocument, isMap, stringify } from 'yaml';
|
|
9
10
|
import { registeredToolNames, MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient } from '@haven_ai/mcp';
|
|
10
|
-
import { HAVEN_MINIMUM_NODE_VERSION, SKILL_FOLDER_NAME, HAVEN_SKILL_MD, isSupportedNodeVersion, unsupportedNodeVersionMessage } from '@haven_ai/sdk';
|
|
11
11
|
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
|
|
12
|
+
import { realpathSync } from 'fs';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
12
14
|
|
|
13
15
|
// src/api.ts
|
|
14
16
|
function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
@@ -40,6 +42,10 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
40
42
|
}
|
|
41
43
|
})
|
|
42
44
|
}),
|
|
45
|
+
getConnectorStatus: (setupId, apiKey) => request(fetchImpl, `${root}/agent-connection-setups/${encodeURIComponent(setupId)}/connector-status`, {
|
|
46
|
+
method: "GET",
|
|
47
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
48
|
+
}),
|
|
43
49
|
updateInstallStatus: async (setupId, apiKey, input) => {
|
|
44
50
|
await request(fetchImpl, `${root}/agent-connection-setups/${encodeURIComponent(setupId)}/install-status`, {
|
|
45
51
|
method: "POST",
|
|
@@ -66,6 +72,14 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
66
72
|
}
|
|
67
73
|
};
|
|
68
74
|
}
|
|
75
|
+
var ConnectRequestError = class extends Error {
|
|
76
|
+
constructor(message, status) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.status = status;
|
|
79
|
+
this.name = "ConnectRequestError";
|
|
80
|
+
}
|
|
81
|
+
status;
|
|
82
|
+
};
|
|
69
83
|
async function request(fetchImpl, url, init) {
|
|
70
84
|
const response = await fetchImpl(url, {
|
|
71
85
|
...init,
|
|
@@ -78,7 +92,7 @@ async function request(fetchImpl, url, init) {
|
|
|
78
92
|
const body = text ? JSON.parse(text) : null;
|
|
79
93
|
if (!response.ok) {
|
|
80
94
|
const message = body?.error ?? body?.message ?? `${response.status} ${response.statusText}`;
|
|
81
|
-
throw new
|
|
95
|
+
throw new ConnectRequestError(`Haven setup request failed: ${message}`, response.status);
|
|
82
96
|
}
|
|
83
97
|
return body;
|
|
84
98
|
}
|
|
@@ -174,11 +188,12 @@ async function writeCredentialFiles(input) {
|
|
|
174
188
|
agentPath,
|
|
175
189
|
{
|
|
176
190
|
agent_id: input.agentId,
|
|
191
|
+
delegate_address: input.delegateAddress,
|
|
177
192
|
safe_address: input.safeAddress,
|
|
178
193
|
chain_id: input.chainId,
|
|
179
194
|
network: input.network,
|
|
180
195
|
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."
|
|
196
|
+
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
197
|
},
|
|
183
198
|
input.warn
|
|
184
199
|
);
|
|
@@ -230,9 +245,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
230
245
|
mcpPackage: "@haven_ai/mcp",
|
|
231
246
|
mcpVersion: MCP_VERSION,
|
|
232
247
|
sdkPackage: "@haven_ai/sdk",
|
|
233
|
-
sdkVersion: "0.1.
|
|
248
|
+
sdkVersion: "0.1.23-alpha.1",
|
|
234
249
|
signerPackage: "@haven_ai/signer",
|
|
235
|
-
signerVersion: "0.1.
|
|
250
|
+
signerVersion: "0.1.23-alpha.1",
|
|
236
251
|
// Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
|
|
237
252
|
// while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
|
|
238
253
|
// so the guard that was supposed to enforce the floor waved Node v23 through
|
|
@@ -1394,55 +1409,64 @@ var RUNTIME_PROFILES = {
|
|
|
1394
1409
|
id: "claude-code",
|
|
1395
1410
|
label: "Claude Code",
|
|
1396
1411
|
restartMode: "restart-session",
|
|
1397
|
-
canWriteRuntimeConfig: true
|
|
1412
|
+
canWriteRuntimeConfig: true,
|
|
1413
|
+
activationInstruction: "Start a new Claude Code session so it loads the Haven MCP entries."
|
|
1398
1414
|
},
|
|
1399
1415
|
"codex-cli": {
|
|
1400
1416
|
id: "codex-cli",
|
|
1401
1417
|
label: "Codex CLI",
|
|
1402
1418
|
restartMode: "restart-session",
|
|
1403
|
-
canWriteRuntimeConfig: true
|
|
1419
|
+
canWriteRuntimeConfig: true,
|
|
1420
|
+
activationInstruction: "Start a fresh Codex CLI session (for example, run `codex resume --last`)."
|
|
1404
1421
|
},
|
|
1405
1422
|
"codex-desktop": {
|
|
1406
1423
|
id: "codex-desktop",
|
|
1407
1424
|
label: "Codex Desktop",
|
|
1408
1425
|
restartMode: "restart-session",
|
|
1409
|
-
canWriteRuntimeConfig: true
|
|
1426
|
+
canWriteRuntimeConfig: true,
|
|
1427
|
+
activationInstruction: "Quit and reopen Codex Desktop so it loads the Haven MCP entries."
|
|
1410
1428
|
},
|
|
1411
1429
|
cursor: {
|
|
1412
1430
|
id: "cursor",
|
|
1413
1431
|
label: "Cursor",
|
|
1414
1432
|
restartMode: "hot-reload",
|
|
1415
|
-
canWriteRuntimeConfig: true
|
|
1433
|
+
canWriteRuntimeConfig: true,
|
|
1434
|
+
activationInstruction: "Wait for Cursor to hot-reload the Haven MCP entries; no app restart is required."
|
|
1416
1435
|
},
|
|
1417
1436
|
vscode: {
|
|
1418
1437
|
id: "vscode",
|
|
1419
1438
|
label: "VS Code",
|
|
1420
1439
|
restartMode: "hot-reload",
|
|
1421
|
-
canWriteRuntimeConfig: true
|
|
1440
|
+
canWriteRuntimeConfig: true,
|
|
1441
|
+
activationInstruction: "Wait for VS Code to hot-reload the Haven MCP entries; no app restart is required."
|
|
1422
1442
|
},
|
|
1423
1443
|
"vscode-insiders": {
|
|
1424
1444
|
id: "vscode-insiders",
|
|
1425
1445
|
label: "VS Code Insiders",
|
|
1426
1446
|
restartMode: "hot-reload",
|
|
1427
|
-
canWriteRuntimeConfig: true
|
|
1447
|
+
canWriteRuntimeConfig: true,
|
|
1448
|
+
activationInstruction: "Wait for VS Code Insiders to hot-reload the Haven MCP entries; no app restart is required."
|
|
1428
1449
|
},
|
|
1429
1450
|
"claude-desktop": {
|
|
1430
1451
|
id: "claude-desktop",
|
|
1431
1452
|
label: "Claude Desktop",
|
|
1432
1453
|
restartMode: "restart-app",
|
|
1433
|
-
canWriteRuntimeConfig: true
|
|
1454
|
+
canWriteRuntimeConfig: true,
|
|
1455
|
+
activationInstruction: "Quit and reopen Claude Desktop so it loads the Haven MCP entries."
|
|
1434
1456
|
},
|
|
1435
1457
|
hermes: {
|
|
1436
1458
|
id: "hermes",
|
|
1437
1459
|
label: "Hermes Agent",
|
|
1438
1460
|
restartMode: "restart-session",
|
|
1439
|
-
canWriteRuntimeConfig: true
|
|
1461
|
+
canWriteRuntimeConfig: true,
|
|
1462
|
+
activationInstruction: "Start a new Hermes session; in Hermes Gateway, run `/restart` instead."
|
|
1440
1463
|
},
|
|
1441
1464
|
other: {
|
|
1442
1465
|
id: "other",
|
|
1443
1466
|
label: "Other agent runtime",
|
|
1444
1467
|
restartMode: "manual",
|
|
1445
|
-
canWriteRuntimeConfig: false
|
|
1468
|
+
canWriteRuntimeConfig: false,
|
|
1469
|
+
activationInstruction: "Finish the manual MCP setup shown above, then start a fresh session in that runtime."
|
|
1446
1470
|
}
|
|
1447
1471
|
};
|
|
1448
1472
|
var RUNTIME_ALIASES = {
|
|
@@ -1494,8 +1518,9 @@ function restartRequiredForRuntime(runtime, env = process.env) {
|
|
|
1494
1518
|
const mode = runtimeProfile(runtime, env).restartMode;
|
|
1495
1519
|
return mode === "restart-session" || mode === "restart-app";
|
|
1496
1520
|
}
|
|
1497
|
-
function
|
|
1498
|
-
|
|
1521
|
+
function runtimeVerificationInstruction(runtime) {
|
|
1522
|
+
const label = RUNTIME_PROFILES[runtime].label;
|
|
1523
|
+
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.`;
|
|
1499
1524
|
}
|
|
1500
1525
|
function normalizeRuntimeName(runtime) {
|
|
1501
1526
|
const key = runtime?.trim().toLowerCase();
|
|
@@ -1681,11 +1706,12 @@ async function installRuntime(input, deps = {}) {
|
|
|
1681
1706
|
probeLocalSignerCredential(input.signerPath),
|
|
1682
1707
|
localProbePromise
|
|
1683
1708
|
]);
|
|
1684
|
-
const hostedOk = configResult.hostedConfigured && hostedProbe.status
|
|
1709
|
+
const hostedOk = configResult.hostedConfigured && hostedProbe.status === "ok";
|
|
1685
1710
|
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
|
|
1686
1711
|
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
1687
1712
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1688
|
-
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1713
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : hostedMcpErrorCode(configResult.hostedConfigured, hostedProbe.status) ?? signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1714
|
+
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."] : [];
|
|
1689
1715
|
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."] : [];
|
|
1690
1716
|
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1691
1717
|
return {
|
|
@@ -1704,7 +1730,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1704
1730
|
activationCommand: configResult.activationCommand,
|
|
1705
1731
|
skillInstalled: skillInstall?.installed,
|
|
1706
1732
|
signerRuntimePrepared,
|
|
1707
|
-
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1733
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...hostedProbeMessages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1708
1734
|
};
|
|
1709
1735
|
}
|
|
1710
1736
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1865,6 +1891,10 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
|
1865
1891
|
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
1866
1892
|
return void 0;
|
|
1867
1893
|
}
|
|
1894
|
+
function hostedMcpErrorCode(hostedConfigured, hostedProbeStatus) {
|
|
1895
|
+
if (!hostedConfigured || hostedProbeStatus === "ok") return void 0;
|
|
1896
|
+
return `hosted_mcp_probe_${hostedProbeStatus}`;
|
|
1897
|
+
}
|
|
1868
1898
|
function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
|
|
1869
1899
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
1870
1900
|
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
@@ -1916,13 +1946,17 @@ function localRuntimePrepareErrorCode(err) {
|
|
|
1916
1946
|
}
|
|
1917
1947
|
|
|
1918
1948
|
// src/runtime.ts
|
|
1919
|
-
var CONNECTOR_VERSION = "0.1.
|
|
1949
|
+
var CONNECTOR_VERSION = "0.1.23-alpha.1";
|
|
1950
|
+
var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
|
|
1920
1951
|
async function runConnect(options, deps = {}) {
|
|
1921
1952
|
assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
|
|
1922
1953
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
1923
1954
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
1924
|
-
const log = secureLogger(
|
|
1925
|
-
|
|
1955
|
+
const log = secureLogger(
|
|
1956
|
+
deps.log ?? ((message) => process.stdout.write(`${message}
|
|
1957
|
+
`)),
|
|
1958
|
+
deps.redactPaths === true
|
|
1959
|
+
);
|
|
1926
1960
|
const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
|
|
1927
1961
|
const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
|
|
1928
1962
|
const runRuntimeInstall = deps.installRuntime ?? installRuntime;
|
|
@@ -1943,6 +1977,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1943
1977
|
connectorVersion,
|
|
1944
1978
|
runtime: options.runtime
|
|
1945
1979
|
});
|
|
1980
|
+
assertSetupChallengeIsUsable(setup.challenge.expires_at);
|
|
1946
1981
|
printSetupSummary(setup, log);
|
|
1947
1982
|
await preflightStorage({ baseDir: options.credentialsDir, warn: log });
|
|
1948
1983
|
log("Checked local credential storage \u2014 all clear.");
|
|
@@ -1951,21 +1986,31 @@ async function runConnect(options, deps = {}) {
|
|
|
1951
1986
|
log("Minting a fresh signing key and API key \u2014 both stay on this machine.");
|
|
1952
1987
|
const proofSignature = await localKey.signChallenge(setup.challenge.message);
|
|
1953
1988
|
log("Introducing your agent to Haven\u2026");
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1989
|
+
let registration;
|
|
1990
|
+
try {
|
|
1991
|
+
registration = await api.registerSetup({
|
|
1992
|
+
setupToken: options.setupToken,
|
|
1993
|
+
connectorVersion,
|
|
1994
|
+
runtime: options.runtime,
|
|
1995
|
+
challengeId: setup.challenge.id,
|
|
1996
|
+
delegateAddress: localKey.address,
|
|
1997
|
+
proofSignature,
|
|
1998
|
+
apiKeyHash: hashAgentApiKey(localApiKey),
|
|
1999
|
+
apiKeyPrefix: agentApiKeyPrefix(localApiKey),
|
|
2000
|
+
connectorContext: {
|
|
2001
|
+
environment_label: options.environmentLabel ?? "Local workspace",
|
|
2002
|
+
config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
|
|
2003
|
+
},
|
|
2004
|
+
installCapabilities
|
|
2005
|
+
});
|
|
2006
|
+
} catch (err) {
|
|
2007
|
+
if (isExpiredSetupChallenge(err)) {
|
|
2008
|
+
throw new Error(
|
|
2009
|
+
"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."
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
throw err;
|
|
2013
|
+
}
|
|
1969
2014
|
log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
|
|
1970
2015
|
log("Tucking your credentials away safely on disk\u2026");
|
|
1971
2016
|
const credentialPaths = await writeCredentials({
|
|
@@ -2015,6 +2060,9 @@ async function runConnect(options, deps = {}) {
|
|
|
2015
2060
|
} else {
|
|
2016
2061
|
log("Haven setup on this machine is complete.");
|
|
2017
2062
|
}
|
|
2063
|
+
if (options.waitForApproval !== false && !runtimeInstall.errorCode) {
|
|
2064
|
+
await waitForBudgetApproval(api, registration.setup_id, localApiKey, log, options.approvalWait);
|
|
2065
|
+
}
|
|
2018
2066
|
printNextSteps(runtimeInstall, log);
|
|
2019
2067
|
try {
|
|
2020
2068
|
await api.updateInstallStatus(registration.setup_id, localApiKey, {
|
|
@@ -2042,7 +2090,69 @@ async function runConnect(options, deps = {}) {
|
|
|
2042
2090
|
setupId: registration.setup_id,
|
|
2043
2091
|
agentId: registration.agent_id,
|
|
2044
2092
|
delegateAddress: registration.delegate_address,
|
|
2045
|
-
credentialPaths
|
|
2093
|
+
credentialPaths,
|
|
2094
|
+
outcome: completionOutcome({
|
|
2095
|
+
runtimeInstall,
|
|
2096
|
+
delegateAddress: registration.delegate_address,
|
|
2097
|
+
setupChallengeExpiresAt: setup.challenge.expires_at,
|
|
2098
|
+
approvalRequired: registration.agent_status === "pending_approval"
|
|
2099
|
+
})
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
function completionOutcome(input) {
|
|
2103
|
+
const { runtimeInstall } = input;
|
|
2104
|
+
const manualSetup = runtimeInstall.errorCode === "manual_runtime_setup_required";
|
|
2105
|
+
const nextAction2 = runtimeInstall.nextUserAction;
|
|
2106
|
+
const outcome = {
|
|
2107
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2108
|
+
outcome: runtimeInstall.errorCode ? "action_required" : "complete",
|
|
2109
|
+
runtime: runtimeInstall.runtime,
|
|
2110
|
+
topology: runtimeInstall.runtimeMcpMode,
|
|
2111
|
+
configuration: {
|
|
2112
|
+
hosted_mcp: runtimeInstall.hostedMcpConfigured,
|
|
2113
|
+
local_signer: runtimeInstall.localSignerConfigured,
|
|
2114
|
+
local_mcp: runtimeInstall.localMcpConfigured
|
|
2115
|
+
},
|
|
2116
|
+
probe: { result: runtimeInstall.probeResult },
|
|
2117
|
+
activation: {
|
|
2118
|
+
restart_required: runtimeInstall.restartRequired,
|
|
2119
|
+
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
|
|
2120
|
+
},
|
|
2121
|
+
next_action: nextAction2,
|
|
2122
|
+
approval: { required: input.approvalRequired, expires_at: null },
|
|
2123
|
+
verification: {
|
|
2124
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2125
|
+
instruction: runtimeVerificationInstruction(runtimeInstall.runtime)
|
|
2126
|
+
},
|
|
2127
|
+
// The backend contract supplies a 20-byte address. If an unexpected
|
|
2128
|
+
// malformed value arrives, do not echo it into an automation-facing
|
|
2129
|
+
// record; the human log has already been redacted separately.
|
|
2130
|
+
delegate_address: /^0x[0-9a-fA-F]{40}$/.test(input.delegateAddress) ? shortAddress(input.delegateAddress) : "[delegate-address-redacted]",
|
|
2131
|
+
...input.setupChallengeExpiresAt ? { setup_challenge_expires_at: input.setupChallengeExpiresAt } : {},
|
|
2132
|
+
...runtimeInstall.errorCode ? { error: { code: runtimeInstall.errorCode, next_action: nextAction2 } } : {}
|
|
2133
|
+
};
|
|
2134
|
+
return outcome;
|
|
2135
|
+
}
|
|
2136
|
+
function failedConnectOutcome(runtimeHint, error) {
|
|
2137
|
+
const message = error instanceof Error ? error.message : "";
|
|
2138
|
+
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";
|
|
2139
|
+
const runtime = normalizeRuntime(runtimeHint);
|
|
2140
|
+
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";
|
|
2141
|
+
return {
|
|
2142
|
+
schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
|
|
2143
|
+
outcome: "failed",
|
|
2144
|
+
runtime,
|
|
2145
|
+
topology: "unknown",
|
|
2146
|
+
configuration: { hosted_mcp: false, local_signer: false, local_mcp: false },
|
|
2147
|
+
probe: { result: "not_run" },
|
|
2148
|
+
activation: { restart_required: false, instruction: "Resolve the reported problem before activating Haven tools." },
|
|
2149
|
+
next_action: nextAction2,
|
|
2150
|
+
approval: { required: false, expires_at: null },
|
|
2151
|
+
verification: {
|
|
2152
|
+
tools: ["haven_get_agent", "haven_get_allowances"],
|
|
2153
|
+
instruction: "After a successful setup and activation, verify only with haven_get_agent and haven_get_allowances."
|
|
2154
|
+
},
|
|
2155
|
+
error: { code, next_action: nextAction2 }
|
|
2046
2156
|
};
|
|
2047
2157
|
}
|
|
2048
2158
|
function printSetupSummary(setup, log) {
|
|
@@ -2055,10 +2165,26 @@ function printSetupSummary(setup, log) {
|
|
|
2055
2165
|
);
|
|
2056
2166
|
}
|
|
2057
2167
|
}
|
|
2058
|
-
log(`Setup challenge expires at ${setup.challenge.expires_at}.`);
|
|
2168
|
+
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.`);
|
|
2169
|
+
}
|
|
2170
|
+
function assertSetupChallengeIsUsable(expiresAt) {
|
|
2171
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
2172
|
+
if (!Number.isNaN(expiresAtMs) && expiresAtMs > Date.now()) return;
|
|
2173
|
+
throw new Error(
|
|
2174
|
+
"This Haven setup challenge is expired or invalid. Return to Haven, start a fresh connection, and rerun Connect. No local credentials were written."
|
|
2175
|
+
);
|
|
2059
2176
|
}
|
|
2060
|
-
function
|
|
2061
|
-
return (
|
|
2177
|
+
function isExpiredSetupChallenge(err) {
|
|
2178
|
+
return err instanceof Error && /(?:setup )?challenge.*expir|expir.*(?:setup )?challenge/i.test(err.message);
|
|
2179
|
+
}
|
|
2180
|
+
function secureLogger(log, redactPaths = false) {
|
|
2181
|
+
return (message) => {
|
|
2182
|
+
let safe = redactSecrets(message);
|
|
2183
|
+
if (redactPaths) {
|
|
2184
|
+
safe = safe.replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
|
|
2185
|
+
}
|
|
2186
|
+
log(safe);
|
|
2187
|
+
};
|
|
2062
2188
|
}
|
|
2063
2189
|
function printRuntimeInstall(result, log) {
|
|
2064
2190
|
for (const message of result.messages) log(message);
|
|
@@ -2075,17 +2201,85 @@ function printRuntimeInstall(result, log) {
|
|
|
2075
2201
|
log("Local Haven signer still needs runtime setup.");
|
|
2076
2202
|
}
|
|
2077
2203
|
}
|
|
2078
|
-
function
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2204
|
+
function formatAtomicAmount(atomic, decimals) {
|
|
2205
|
+
const s = atomic.toString().padStart(decimals + 1, "0");
|
|
2206
|
+
const intPart = s.slice(0, s.length - decimals) || "0";
|
|
2207
|
+
const fracPart = s.slice(s.length - decimals).replace(/0+$/, "");
|
|
2208
|
+
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
2209
|
+
}
|
|
2210
|
+
function describeResetPeriod(resetPeriodMin) {
|
|
2211
|
+
if (resetPeriodMin === 1440) return "per day";
|
|
2212
|
+
if (resetPeriodMin === 60) return "per hour";
|
|
2213
|
+
if (resetPeriodMin === 0) return "with no automatic reset";
|
|
2214
|
+
return `per ${resetPeriodMin} minutes`;
|
|
2215
|
+
}
|
|
2216
|
+
function describeApprovedBudget(budget) {
|
|
2217
|
+
const token = resolveTokenFromAddress(budget.token_address);
|
|
2218
|
+
const amount = token ? `${formatAtomicAmount(BigInt(budget.amount), token.decimals)} ${budget.token_symbol}` : `${budget.amount} ${budget.token_symbol} (atomic units)`;
|
|
2219
|
+
return `${amount} ${describeResetPeriod(budget.reset_period_min)}`;
|
|
2220
|
+
}
|
|
2221
|
+
async function waitForBudgetApproval(api, setupId, apiKey, log, options = {}) {
|
|
2222
|
+
const intervalMs = options.intervalMs ?? 5e3;
|
|
2223
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
2224
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
2225
|
+
const maxPolls = Math.max(1, Math.floor(timeoutMs / intervalMs));
|
|
2226
|
+
const remindEvery = Math.max(1, Math.floor(3e4 / intervalMs));
|
|
2227
|
+
log("Registered with Haven \u2014 waiting for you to approve the budget in the dashboard\u2026");
|
|
2228
|
+
for (let i = 0; i < maxPolls; i++) {
|
|
2229
|
+
await sleep(intervalMs);
|
|
2230
|
+
let status;
|
|
2231
|
+
try {
|
|
2232
|
+
status = await api.getConnectorStatus(setupId, apiKey);
|
|
2233
|
+
} catch (err) {
|
|
2234
|
+
if (err instanceof ConnectRequestError && (err.status === 401 || err.status === 404)) {
|
|
2235
|
+
log("This setup ended in Haven \u2014 start a fresh connection from the dashboard when ready.");
|
|
2236
|
+
return "ended";
|
|
2237
|
+
}
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (status.status === "active") {
|
|
2241
|
+
log(
|
|
2242
|
+
status.approved_budget ? `Budget approved \u{1F389} \u2014 I can now spend up to ${describeApprovedBudget(status.approved_budget)} from your Haven wallet.` : "Budget approved \u{1F389} \u2014 the agent can now spend within its Haven rules."
|
|
2243
|
+
);
|
|
2244
|
+
return "approved";
|
|
2245
|
+
}
|
|
2246
|
+
if (status.status === "cancelled" || status.status === "expired" || status.status === "failed") {
|
|
2247
|
+
log(`This setup ended in Haven (${status.status}) \u2014 start a fresh connection from the dashboard when ready.`);
|
|
2248
|
+
return "ended";
|
|
2249
|
+
}
|
|
2250
|
+
if ((i + 1) % remindEvery === 0) {
|
|
2251
|
+
log("Still waiting for budget approval in Haven\u2026");
|
|
2085
2252
|
}
|
|
2086
|
-
} else {
|
|
2087
|
-
log("After you approve, the Haven tools should appear in this runtime shortly.");
|
|
2088
2253
|
}
|
|
2254
|
+
log(
|
|
2255
|
+
"Budget approval is still pending in Haven. Approve it in the dashboard whenever you are ready \u2014 the agent tools unlock the moment you do. Verify later with the read-only haven_get_agent tool."
|
|
2256
|
+
);
|
|
2257
|
+
return "pending";
|
|
2258
|
+
}
|
|
2259
|
+
function completionHandoffLines(result) {
|
|
2260
|
+
if (result.errorCode === "manual_runtime_setup_required") {
|
|
2261
|
+
return [
|
|
2262
|
+
"Next steps:",
|
|
2263
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2264
|
+
"2. Finish the manual MCP setup using the secret-free file references printed above, then start a fresh session in your runtime.",
|
|
2265
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2266
|
+
];
|
|
2267
|
+
}
|
|
2268
|
+
if (result.errorCode) {
|
|
2269
|
+
return [
|
|
2270
|
+
"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."
|
|
2271
|
+
];
|
|
2272
|
+
}
|
|
2273
|
+
const profile = runtimeProfile(result.runtime);
|
|
2274
|
+
return [
|
|
2275
|
+
"Next steps:",
|
|
2276
|
+
"1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
|
|
2277
|
+
`2. ${profile.activationInstruction}`,
|
|
2278
|
+
`3. ${runtimeVerificationInstruction(result.runtime)}`
|
|
2279
|
+
];
|
|
2280
|
+
}
|
|
2281
|
+
function printNextSteps(result, log) {
|
|
2282
|
+
for (const line of completionHandoffLines(result)) log(line);
|
|
2089
2283
|
}
|
|
2090
2284
|
|
|
2091
2285
|
// src/args.ts
|
|
@@ -2095,10 +2289,13 @@ function parseArgs(argv, env = process.env) {
|
|
|
2095
2289
|
connectorVersion: CONNECTOR_VERSION
|
|
2096
2290
|
};
|
|
2097
2291
|
let help = false;
|
|
2292
|
+
let json = false;
|
|
2098
2293
|
for (let i = 0; i < argv.length; i += 1) {
|
|
2099
2294
|
const arg = argv[i];
|
|
2100
2295
|
if (arg === "--help" || arg === "-h") {
|
|
2101
2296
|
help = true;
|
|
2297
|
+
} else if (arg === "--json") {
|
|
2298
|
+
json = true;
|
|
2102
2299
|
} else if (arg === "--setup" || arg === "--setup-token") {
|
|
2103
2300
|
options.setupToken = requireValue(argv, ++i, arg);
|
|
2104
2301
|
} else if (arg === "--api" || arg === "--api-url") {
|
|
@@ -2125,7 +2322,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
2125
2322
|
}
|
|
2126
2323
|
}
|
|
2127
2324
|
if (help) {
|
|
2128
|
-
return { options, help };
|
|
2325
|
+
return { options, help, json };
|
|
2129
2326
|
}
|
|
2130
2327
|
if (!options.setupToken) {
|
|
2131
2328
|
throw new Error("Missing --setup <hv_setup_...> setup token.");
|
|
@@ -2134,7 +2331,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
2134
2331
|
throw new Error("Missing --api <Haven API URL>.");
|
|
2135
2332
|
}
|
|
2136
2333
|
options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
2137
|
-
return { options, help };
|
|
2334
|
+
return { options, help, json };
|
|
2138
2335
|
}
|
|
2139
2336
|
function helpText() {
|
|
2140
2337
|
return [
|
|
@@ -2156,9 +2353,10 @@ function helpText() {
|
|
|
2156
2353
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
2157
2354
|
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
2158
2355
|
" Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
|
|
2356
|
+
" --json Emit one versioned, secret-free result object on stdout; progress stays on stderr.",
|
|
2159
2357
|
" --help Show this help.",
|
|
2160
2358
|
"",
|
|
2161
|
-
"The connector never prints the private key and never sends it to Haven."
|
|
2359
|
+
"The connector never prints the private key and never sends it to Haven. JSON output never includes credential contents or full credential paths."
|
|
2162
2360
|
].join("\n");
|
|
2163
2361
|
}
|
|
2164
2362
|
function requireValue(argv, index, option) {
|
|
@@ -2168,7 +2366,66 @@ function requireValue(argv, index, option) {
|
|
|
2168
2366
|
}
|
|
2169
2367
|
return value;
|
|
2170
2368
|
}
|
|
2369
|
+
async function runCli(argv, io = {
|
|
2370
|
+
stdout: (message) => process.stdout.write(message),
|
|
2371
|
+
stderr: (message) => process.stderr.write(message)
|
|
2372
|
+
}) {
|
|
2373
|
+
const wantsJson = argv.includes("--json");
|
|
2374
|
+
let parsed;
|
|
2375
|
+
try {
|
|
2376
|
+
parsed = parseArgs(argv);
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
if (wantsJson) {
|
|
2379
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(void 0, err))}
|
|
2380
|
+
`);
|
|
2381
|
+
} else {
|
|
2382
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2383
|
+
`);
|
|
2384
|
+
}
|
|
2385
|
+
return 1;
|
|
2386
|
+
}
|
|
2387
|
+
if (parsed.help) {
|
|
2388
|
+
io.stdout(`${helpText()}
|
|
2389
|
+
`);
|
|
2390
|
+
return 0;
|
|
2391
|
+
}
|
|
2392
|
+
try {
|
|
2393
|
+
const result = await runConnect(
|
|
2394
|
+
{ ...parsed.options, waitForApproval: !parsed.json },
|
|
2395
|
+
{
|
|
2396
|
+
log: (message) => (parsed.json ? io.stderr : io.stdout)(`${message}
|
|
2397
|
+
`),
|
|
2398
|
+
redactPaths: parsed.json
|
|
2399
|
+
}
|
|
2400
|
+
);
|
|
2401
|
+
if (parsed.json) io.stdout(`${JSON.stringify(result.outcome)}
|
|
2402
|
+
`);
|
|
2403
|
+
return 0;
|
|
2404
|
+
} catch (err) {
|
|
2405
|
+
if (parsed.json) {
|
|
2406
|
+
io.stdout(`${JSON.stringify(failedConnectOutcome(parsed.options.runtime, err))}
|
|
2407
|
+
`);
|
|
2408
|
+
} else {
|
|
2409
|
+
io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
|
|
2410
|
+
`);
|
|
2411
|
+
}
|
|
2412
|
+
return 1;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
async function main() {
|
|
2416
|
+
const exitCode = await runCli(process.argv.slice(2));
|
|
2417
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
2418
|
+
}
|
|
2419
|
+
function isCliEntrypoint(argvPath = process.argv[1], moduleUrl = import.meta.url) {
|
|
2420
|
+
if (!argvPath) return false;
|
|
2421
|
+
try {
|
|
2422
|
+
return realpathSync(argvPath) === realpathSync(fileURLToPath(moduleUrl));
|
|
2423
|
+
} catch {
|
|
2424
|
+
return pathToFileURL(argvPath).href === moduleUrl;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
if (isCliEntrypoint()) void main();
|
|
2171
2428
|
|
|
2172
|
-
export { CONNECTOR_VERSION, MCP_RUNTIME_MANIFEST, createConnectApiClient, defaultAgentDirectory, delegateKeyFromPrivateKey, generateDelegateKey, helpText, installRuntime, mcpPackageSpec, normalizeRuntime, parseArgs, prepareSignerRuntime, redactSecrets, runConnect, runtimeInstallCapabilities, runtimeProfile, sdkPackageSpec, shortAddress, signerPackageSpec, writeCredentialFiles };
|
|
2429
|
+
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 };
|
|
2173
2430
|
//# sourceMappingURL=index.js.map
|
|
2174
2431
|
//# sourceMappingURL=index.js.map
|