@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/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, writeFile, access, chmod, readFile, unlink } from 'fs/promises';
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 Error(`Haven setup request failed: ${message}`);
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.22-alpha.0",
248
+ sdkVersion: "0.1.23-alpha.1",
234
249
  signerPackage: "@haven_ai/signer",
235
- signerVersion: "0.1.22-alpha.0",
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 runtimeRequiresHardRestart(runtime) {
1498
- return runtime === "claude-desktop" || runtime === "codex-desktop";
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 !== "unauthorized";
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.22-alpha.0";
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(deps.log ?? ((message) => process.stdout.write(`${message}
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
- const registration = await api.registerSetup({
1955
- setupToken: options.setupToken,
1956
- connectorVersion,
1957
- runtime: options.runtime,
1958
- challengeId: setup.challenge.id,
1959
- delegateAddress: localKey.address,
1960
- proofSignature,
1961
- apiKeyHash: hashAgentApiKey(localApiKey),
1962
- apiKeyPrefix: agentApiKeyPrefix(localApiKey),
1963
- connectorContext: {
1964
- environment_label: options.environmentLabel ?? "Local workspace",
1965
- config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
1966
- },
1967
- installCapabilities
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 secureLogger(log) {
2061
- return (message) => log(redactSecrets(message));
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 printNextSteps(result, log) {
2079
- log("Next: approve the agent rules in Haven. No Haven tools appear until you approve \u2014 restart or not.");
2080
- if (result.restartRequired) {
2081
- if (runtimeRequiresHardRestart(result.runtime)) {
2082
- log("After you approve, restart this agent so it can load Haven tools.");
2083
- } else {
2084
- log("After you approve, restart this agent \u2014 your current session won't load the Haven tools until you do.");
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