@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.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var sdk = require('@haven_ai/sdk');
3
4
  var crypto = require('crypto');
4
5
  var ethers = require('ethers');
5
6
  var promises = require('fs/promises');
@@ -9,9 +10,11 @@ var child_process = require('child_process');
9
10
  var util = require('util');
10
11
  var yaml = require('yaml');
11
12
  var mcp = require('@haven_ai/mcp');
12
- var sdk = require('@haven_ai/sdk');
13
13
  var signer = require('@haven_ai/signer');
14
+ var fs = require('fs');
15
+ var url = require('url');
14
16
 
17
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
19
 
17
20
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
@@ -46,6 +49,10 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
46
49
  }
47
50
  })
48
51
  }),
52
+ getConnectorStatus: (setupId, apiKey) => request(fetchImpl, `${root}/agent-connection-setups/${encodeURIComponent(setupId)}/connector-status`, {
53
+ method: "GET",
54
+ headers: { Authorization: `Bearer ${apiKey}` }
55
+ }),
49
56
  updateInstallStatus: async (setupId, apiKey, input) => {
50
57
  await request(fetchImpl, `${root}/agent-connection-setups/${encodeURIComponent(setupId)}/install-status`, {
51
58
  method: "POST",
@@ -72,6 +79,14 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
72
79
  }
73
80
  };
74
81
  }
82
+ var ConnectRequestError = class extends Error {
83
+ constructor(message, status) {
84
+ super(message);
85
+ this.status = status;
86
+ this.name = "ConnectRequestError";
87
+ }
88
+ status;
89
+ };
75
90
  async function request(fetchImpl, url, init) {
76
91
  const response = await fetchImpl(url, {
77
92
  ...init,
@@ -84,7 +99,7 @@ async function request(fetchImpl, url, init) {
84
99
  const body = text ? JSON.parse(text) : null;
85
100
  if (!response.ok) {
86
101
  const message = body?.error ?? body?.message ?? `${response.status} ${response.statusText}`;
87
- throw new Error(`Haven setup request failed: ${message}`);
102
+ throw new ConnectRequestError(`Haven setup request failed: ${message}`, response.status);
88
103
  }
89
104
  return body;
90
105
  }
@@ -180,11 +195,12 @@ async function writeCredentialFiles(input) {
180
195
  agentPath,
181
196
  {
182
197
  agent_id: input.agentId,
198
+ delegate_address: input.delegateAddress,
183
199
  safe_address: input.safeAddress,
184
200
  chain_id: input.chainId,
185
201
  network: input.network,
186
202
  agent_budget: input.agentBudget,
187
- 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."
203
+ 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."
188
204
  },
189
205
  input.warn
190
206
  );
@@ -236,9 +252,9 @@ var MCP_RUNTIME_MANIFEST = {
236
252
  mcpPackage: "@haven_ai/mcp",
237
253
  mcpVersion: mcp.MCP_VERSION,
238
254
  sdkPackage: "@haven_ai/sdk",
239
- sdkVersion: "0.1.22-alpha.0",
255
+ sdkVersion: "0.1.23-alpha.1",
240
256
  signerPackage: "@haven_ai/signer",
241
- signerVersion: "0.1.22-alpha.0",
257
+ signerVersion: "0.1.23-alpha.1",
242
258
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
243
259
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
244
260
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -1400,55 +1416,64 @@ var RUNTIME_PROFILES = {
1400
1416
  id: "claude-code",
1401
1417
  label: "Claude Code",
1402
1418
  restartMode: "restart-session",
1403
- canWriteRuntimeConfig: true
1419
+ canWriteRuntimeConfig: true,
1420
+ activationInstruction: "Start a new Claude Code session so it loads the Haven MCP entries."
1404
1421
  },
1405
1422
  "codex-cli": {
1406
1423
  id: "codex-cli",
1407
1424
  label: "Codex CLI",
1408
1425
  restartMode: "restart-session",
1409
- canWriteRuntimeConfig: true
1426
+ canWriteRuntimeConfig: true,
1427
+ activationInstruction: "Start a fresh Codex CLI session (for example, run `codex resume --last`)."
1410
1428
  },
1411
1429
  "codex-desktop": {
1412
1430
  id: "codex-desktop",
1413
1431
  label: "Codex Desktop",
1414
1432
  restartMode: "restart-session",
1415
- canWriteRuntimeConfig: true
1433
+ canWriteRuntimeConfig: true,
1434
+ activationInstruction: "Quit and reopen Codex Desktop so it loads the Haven MCP entries."
1416
1435
  },
1417
1436
  cursor: {
1418
1437
  id: "cursor",
1419
1438
  label: "Cursor",
1420
1439
  restartMode: "hot-reload",
1421
- canWriteRuntimeConfig: true
1440
+ canWriteRuntimeConfig: true,
1441
+ activationInstruction: "Wait for Cursor to hot-reload the Haven MCP entries; no app restart is required."
1422
1442
  },
1423
1443
  vscode: {
1424
1444
  id: "vscode",
1425
1445
  label: "VS Code",
1426
1446
  restartMode: "hot-reload",
1427
- canWriteRuntimeConfig: true
1447
+ canWriteRuntimeConfig: true,
1448
+ activationInstruction: "Wait for VS Code to hot-reload the Haven MCP entries; no app restart is required."
1428
1449
  },
1429
1450
  "vscode-insiders": {
1430
1451
  id: "vscode-insiders",
1431
1452
  label: "VS Code Insiders",
1432
1453
  restartMode: "hot-reload",
1433
- canWriteRuntimeConfig: true
1454
+ canWriteRuntimeConfig: true,
1455
+ activationInstruction: "Wait for VS Code Insiders to hot-reload the Haven MCP entries; no app restart is required."
1434
1456
  },
1435
1457
  "claude-desktop": {
1436
1458
  id: "claude-desktop",
1437
1459
  label: "Claude Desktop",
1438
1460
  restartMode: "restart-app",
1439
- canWriteRuntimeConfig: true
1461
+ canWriteRuntimeConfig: true,
1462
+ activationInstruction: "Quit and reopen Claude Desktop so it loads the Haven MCP entries."
1440
1463
  },
1441
1464
  hermes: {
1442
1465
  id: "hermes",
1443
1466
  label: "Hermes Agent",
1444
1467
  restartMode: "restart-session",
1445
- canWriteRuntimeConfig: true
1468
+ canWriteRuntimeConfig: true,
1469
+ activationInstruction: "Start a new Hermes session; in Hermes Gateway, run `/restart` instead."
1446
1470
  },
1447
1471
  other: {
1448
1472
  id: "other",
1449
1473
  label: "Other agent runtime",
1450
1474
  restartMode: "manual",
1451
- canWriteRuntimeConfig: false
1475
+ canWriteRuntimeConfig: false,
1476
+ activationInstruction: "Finish the manual MCP setup shown above, then start a fresh session in that runtime."
1452
1477
  }
1453
1478
  };
1454
1479
  var RUNTIME_ALIASES = {
@@ -1500,8 +1525,9 @@ function restartRequiredForRuntime(runtime, env = process.env) {
1500
1525
  const mode = runtimeProfile(runtime, env).restartMode;
1501
1526
  return mode === "restart-session" || mode === "restart-app";
1502
1527
  }
1503
- function runtimeRequiresHardRestart(runtime) {
1504
- return runtime === "claude-desktop" || runtime === "codex-desktop";
1528
+ function runtimeVerificationInstruction(runtime) {
1529
+ const label = RUNTIME_PROFILES[runtime].label;
1530
+ 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.`;
1505
1531
  }
1506
1532
  function normalizeRuntimeName(runtime) {
1507
1533
  const key = runtime?.trim().toLowerCase();
@@ -1687,11 +1713,12 @@ async function installRuntime(input, deps = {}) {
1687
1713
  probeLocalSignerCredential(input.signerPath),
1688
1714
  localProbePromise
1689
1715
  ]);
1690
- const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
1716
+ const hostedOk = configResult.hostedConfigured && hostedProbe.status === "ok";
1691
1717
  const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged) && localMcpProbe?.status === "ok";
1692
1718
  const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
1693
1719
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
1694
- const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
1720
+ const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : hostedMcpErrorCode(configResult.hostedConfigured, hostedProbe.status) ?? signerConsentErrorCode(signerCredentialReady, signerConsent));
1721
+ 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."] : [];
1695
1722
  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."] : [];
1696
1723
  const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
1697
1724
  return {
@@ -1710,7 +1737,7 @@ async function installRuntime(input, deps = {}) {
1710
1737
  activationCommand: configResult.activationCommand,
1711
1738
  skillInstalled: skillInstall?.installed,
1712
1739
  signerRuntimePrepared,
1713
- messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
1740
+ messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...hostedProbeMessages, ...localProbeMessages, ...skillInstall?.messages ?? []]
1714
1741
  };
1715
1742
  }
1716
1743
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -1871,6 +1898,10 @@ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
1871
1898
  if (!signerConsent?.acknowledged) return "local_signer_ack_required";
1872
1899
  return void 0;
1873
1900
  }
1901
+ function hostedMcpErrorCode(hostedConfigured, hostedProbeStatus) {
1902
+ if (!hostedConfigured || hostedProbeStatus === "ok") return void 0;
1903
+ return `hosted_mcp_probe_${hostedProbeStatus}`;
1904
+ }
1874
1905
  function localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbeStatus) {
1875
1906
  if (!signerCredentialReady) return "local_signer_credential_unavailable";
1876
1907
  if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
@@ -1922,13 +1953,17 @@ function localRuntimePrepareErrorCode(err) {
1922
1953
  }
1923
1954
 
1924
1955
  // src/runtime.ts
1925
- var CONNECTOR_VERSION = "0.1.22-alpha.0";
1956
+ var CONNECTOR_VERSION = "0.1.23-alpha.1";
1957
+ var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
1926
1958
  async function runConnect(options, deps = {}) {
1927
1959
  assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
1928
1960
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
1929
1961
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
1930
- const log = secureLogger(deps.log ?? ((message) => process.stdout.write(`${message}
1931
- `)));
1962
+ const log = secureLogger(
1963
+ deps.log ?? ((message) => process.stdout.write(`${message}
1964
+ `)),
1965
+ deps.redactPaths === true
1966
+ );
1932
1967
  const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
1933
1968
  const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
1934
1969
  const runRuntimeInstall = deps.installRuntime ?? installRuntime;
@@ -1949,6 +1984,7 @@ async function runConnect(options, deps = {}) {
1949
1984
  connectorVersion,
1950
1985
  runtime: options.runtime
1951
1986
  });
1987
+ assertSetupChallengeIsUsable(setup.challenge.expires_at);
1952
1988
  printSetupSummary(setup, log);
1953
1989
  await preflightStorage({ baseDir: options.credentialsDir, warn: log });
1954
1990
  log("Checked local credential storage \u2014 all clear.");
@@ -1957,21 +1993,31 @@ async function runConnect(options, deps = {}) {
1957
1993
  log("Minting a fresh signing key and API key \u2014 both stay on this machine.");
1958
1994
  const proofSignature = await localKey.signChallenge(setup.challenge.message);
1959
1995
  log("Introducing your agent to Haven\u2026");
1960
- const registration = await api.registerSetup({
1961
- setupToken: options.setupToken,
1962
- connectorVersion,
1963
- runtime: options.runtime,
1964
- challengeId: setup.challenge.id,
1965
- delegateAddress: localKey.address,
1966
- proofSignature,
1967
- apiKeyHash: hashAgentApiKey(localApiKey),
1968
- apiKeyPrefix: agentApiKeyPrefix(localApiKey),
1969
- connectorContext: {
1970
- environment_label: options.environmentLabel ?? "Local workspace",
1971
- config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
1972
- },
1973
- installCapabilities
1974
- });
1996
+ let registration;
1997
+ try {
1998
+ registration = await api.registerSetup({
1999
+ setupToken: options.setupToken,
2000
+ connectorVersion,
2001
+ runtime: options.runtime,
2002
+ challengeId: setup.challenge.id,
2003
+ delegateAddress: localKey.address,
2004
+ proofSignature,
2005
+ apiKeyHash: hashAgentApiKey(localApiKey),
2006
+ apiKeyPrefix: agentApiKeyPrefix(localApiKey),
2007
+ connectorContext: {
2008
+ environment_label: options.environmentLabel ?? "Local workspace",
2009
+ config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
2010
+ },
2011
+ installCapabilities
2012
+ });
2013
+ } catch (err) {
2014
+ if (isExpiredSetupChallenge(err)) {
2015
+ throw new Error(
2016
+ "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."
2017
+ );
2018
+ }
2019
+ throw err;
2020
+ }
1975
2021
  log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
1976
2022
  log("Tucking your credentials away safely on disk\u2026");
1977
2023
  const credentialPaths = await writeCredentials({
@@ -2021,6 +2067,9 @@ async function runConnect(options, deps = {}) {
2021
2067
  } else {
2022
2068
  log("Haven setup on this machine is complete.");
2023
2069
  }
2070
+ if (options.waitForApproval !== false && !runtimeInstall.errorCode) {
2071
+ await waitForBudgetApproval(api, registration.setup_id, localApiKey, log, options.approvalWait);
2072
+ }
2024
2073
  printNextSteps(runtimeInstall, log);
2025
2074
  try {
2026
2075
  await api.updateInstallStatus(registration.setup_id, localApiKey, {
@@ -2048,7 +2097,69 @@ async function runConnect(options, deps = {}) {
2048
2097
  setupId: registration.setup_id,
2049
2098
  agentId: registration.agent_id,
2050
2099
  delegateAddress: registration.delegate_address,
2051
- credentialPaths
2100
+ credentialPaths,
2101
+ outcome: completionOutcome({
2102
+ runtimeInstall,
2103
+ delegateAddress: registration.delegate_address,
2104
+ setupChallengeExpiresAt: setup.challenge.expires_at,
2105
+ approvalRequired: registration.agent_status === "pending_approval"
2106
+ })
2107
+ };
2108
+ }
2109
+ function completionOutcome(input) {
2110
+ const { runtimeInstall } = input;
2111
+ const manualSetup = runtimeInstall.errorCode === "manual_runtime_setup_required";
2112
+ const nextAction2 = runtimeInstall.nextUserAction;
2113
+ const outcome = {
2114
+ schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
2115
+ outcome: runtimeInstall.errorCode ? "action_required" : "complete",
2116
+ runtime: runtimeInstall.runtime,
2117
+ topology: runtimeInstall.runtimeMcpMode,
2118
+ configuration: {
2119
+ hosted_mcp: runtimeInstall.hostedMcpConfigured,
2120
+ local_signer: runtimeInstall.localSignerConfigured,
2121
+ local_mcp: runtimeInstall.localMcpConfigured
2122
+ },
2123
+ probe: { result: runtimeInstall.probeResult },
2124
+ activation: {
2125
+ restart_required: runtimeInstall.restartRequired,
2126
+ 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
2127
+ },
2128
+ next_action: nextAction2,
2129
+ approval: { required: input.approvalRequired, expires_at: null },
2130
+ verification: {
2131
+ tools: ["haven_get_agent", "haven_get_allowances"],
2132
+ instruction: runtimeVerificationInstruction(runtimeInstall.runtime)
2133
+ },
2134
+ // The backend contract supplies a 20-byte address. If an unexpected
2135
+ // malformed value arrives, do not echo it into an automation-facing
2136
+ // record; the human log has already been redacted separately.
2137
+ delegate_address: /^0x[0-9a-fA-F]{40}$/.test(input.delegateAddress) ? shortAddress(input.delegateAddress) : "[delegate-address-redacted]",
2138
+ ...input.setupChallengeExpiresAt ? { setup_challenge_expires_at: input.setupChallengeExpiresAt } : {},
2139
+ ...runtimeInstall.errorCode ? { error: { code: runtimeInstall.errorCode, next_action: nextAction2 } } : {}
2140
+ };
2141
+ return outcome;
2142
+ }
2143
+ function failedConnectOutcome(runtimeHint, error) {
2144
+ const message = error instanceof Error ? error.message : "";
2145
+ 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";
2146
+ const runtime = normalizeRuntime(runtimeHint);
2147
+ 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";
2148
+ return {
2149
+ schema_version: CONNECT_OUTCOME_SCHEMA_VERSION,
2150
+ outcome: "failed",
2151
+ runtime,
2152
+ topology: "unknown",
2153
+ configuration: { hosted_mcp: false, local_signer: false, local_mcp: false },
2154
+ probe: { result: "not_run" },
2155
+ activation: { restart_required: false, instruction: "Resolve the reported problem before activating Haven tools." },
2156
+ next_action: nextAction2,
2157
+ approval: { required: false, expires_at: null },
2158
+ verification: {
2159
+ tools: ["haven_get_agent", "haven_get_allowances"],
2160
+ instruction: "After a successful setup and activation, verify only with haven_get_agent and haven_get_allowances."
2161
+ },
2162
+ error: { code, next_action: nextAction2 }
2052
2163
  };
2053
2164
  }
2054
2165
  function printSetupSummary(setup, log) {
@@ -2061,10 +2172,26 @@ function printSetupSummary(setup, log) {
2061
2172
  );
2062
2173
  }
2063
2174
  }
2064
- log(`Setup challenge expires at ${setup.challenge.expires_at}.`);
2175
+ 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.`);
2176
+ }
2177
+ function assertSetupChallengeIsUsable(expiresAt) {
2178
+ const expiresAtMs = Date.parse(expiresAt);
2179
+ if (!Number.isNaN(expiresAtMs) && expiresAtMs > Date.now()) return;
2180
+ throw new Error(
2181
+ "This Haven setup challenge is expired or invalid. Return to Haven, start a fresh connection, and rerun Connect. No local credentials were written."
2182
+ );
2065
2183
  }
2066
- function secureLogger(log) {
2067
- return (message) => log(redactSecrets(message));
2184
+ function isExpiredSetupChallenge(err) {
2185
+ return err instanceof Error && /(?:setup )?challenge.*expir|expir.*(?:setup )?challenge/i.test(err.message);
2186
+ }
2187
+ function secureLogger(log, redactPaths = false) {
2188
+ return (message) => {
2189
+ let safe = redactSecrets(message);
2190
+ if (redactPaths) {
2191
+ safe = safe.replace(/(?:~|\/)[^\s`"']*\/(?:identity|signer|agent)\.json\b/g, "[credential-file-redacted]").replace(/(?:~|\/)[^\s`"']*\/\.env\b/g, "[credential-env-redacted]");
2192
+ }
2193
+ log(safe);
2194
+ };
2068
2195
  }
2069
2196
  function printRuntimeInstall(result, log) {
2070
2197
  for (const message of result.messages) log(message);
@@ -2081,17 +2208,85 @@ function printRuntimeInstall(result, log) {
2081
2208
  log("Local Haven signer still needs runtime setup.");
2082
2209
  }
2083
2210
  }
2084
- function printNextSteps(result, log) {
2085
- log("Next: approve the agent rules in Haven. No Haven tools appear until you approve \u2014 restart or not.");
2086
- if (result.restartRequired) {
2087
- if (runtimeRequiresHardRestart(result.runtime)) {
2088
- log("After you approve, restart this agent so it can load Haven tools.");
2089
- } else {
2090
- log("After you approve, restart this agent \u2014 your current session won't load the Haven tools until you do.");
2211
+ function formatAtomicAmount(atomic, decimals) {
2212
+ const s = atomic.toString().padStart(decimals + 1, "0");
2213
+ const intPart = s.slice(0, s.length - decimals) || "0";
2214
+ const fracPart = s.slice(s.length - decimals).replace(/0+$/, "");
2215
+ return fracPart ? `${intPart}.${fracPart}` : intPart;
2216
+ }
2217
+ function describeResetPeriod(resetPeriodMin) {
2218
+ if (resetPeriodMin === 1440) return "per day";
2219
+ if (resetPeriodMin === 60) return "per hour";
2220
+ if (resetPeriodMin === 0) return "with no automatic reset";
2221
+ return `per ${resetPeriodMin} minutes`;
2222
+ }
2223
+ function describeApprovedBudget(budget) {
2224
+ const token = sdk.resolveTokenFromAddress(budget.token_address);
2225
+ const amount = token ? `${formatAtomicAmount(BigInt(budget.amount), token.decimals)} ${budget.token_symbol}` : `${budget.amount} ${budget.token_symbol} (atomic units)`;
2226
+ return `${amount} ${describeResetPeriod(budget.reset_period_min)}`;
2227
+ }
2228
+ async function waitForBudgetApproval(api, setupId, apiKey, log, options = {}) {
2229
+ const intervalMs = options.intervalMs ?? 5e3;
2230
+ const timeoutMs = options.timeoutMs ?? 18e4;
2231
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
2232
+ const maxPolls = Math.max(1, Math.floor(timeoutMs / intervalMs));
2233
+ const remindEvery = Math.max(1, Math.floor(3e4 / intervalMs));
2234
+ log("Registered with Haven \u2014 waiting for you to approve the budget in the dashboard\u2026");
2235
+ for (let i = 0; i < maxPolls; i++) {
2236
+ await sleep(intervalMs);
2237
+ let status;
2238
+ try {
2239
+ status = await api.getConnectorStatus(setupId, apiKey);
2240
+ } catch (err) {
2241
+ if (err instanceof ConnectRequestError && (err.status === 401 || err.status === 404)) {
2242
+ log("This setup ended in Haven \u2014 start a fresh connection from the dashboard when ready.");
2243
+ return "ended";
2244
+ }
2245
+ continue;
2246
+ }
2247
+ if (status.status === "active") {
2248
+ log(
2249
+ 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."
2250
+ );
2251
+ return "approved";
2252
+ }
2253
+ if (status.status === "cancelled" || status.status === "expired" || status.status === "failed") {
2254
+ log(`This setup ended in Haven (${status.status}) \u2014 start a fresh connection from the dashboard when ready.`);
2255
+ return "ended";
2256
+ }
2257
+ if ((i + 1) % remindEvery === 0) {
2258
+ log("Still waiting for budget approval in Haven\u2026");
2091
2259
  }
2092
- } else {
2093
- log("After you approve, the Haven tools should appear in this runtime shortly.");
2094
2260
  }
2261
+ log(
2262
+ "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."
2263
+ );
2264
+ return "pending";
2265
+ }
2266
+ function completionHandoffLines(result) {
2267
+ if (result.errorCode === "manual_runtime_setup_required") {
2268
+ return [
2269
+ "Next steps:",
2270
+ "1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
2271
+ "2. Finish the manual MCP setup using the secret-free file references printed above, then start a fresh session in your runtime.",
2272
+ `3. ${runtimeVerificationInstruction(result.runtime)}`
2273
+ ];
2274
+ }
2275
+ if (result.errorCode) {
2276
+ return [
2277
+ "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."
2278
+ ];
2279
+ }
2280
+ const profile = runtimeProfile(result.runtime);
2281
+ return [
2282
+ "Next steps:",
2283
+ "1. Return to Haven and approve the agent rules. Approval \u2014 not restarting \u2014 unlocks Haven tools.",
2284
+ `2. ${profile.activationInstruction}`,
2285
+ `3. ${runtimeVerificationInstruction(result.runtime)}`
2286
+ ];
2287
+ }
2288
+ function printNextSteps(result, log) {
2289
+ for (const line of completionHandoffLines(result)) log(line);
2095
2290
  }
2096
2291
 
2097
2292
  // src/args.ts
@@ -2101,10 +2296,13 @@ function parseArgs(argv, env = process.env) {
2101
2296
  connectorVersion: CONNECTOR_VERSION
2102
2297
  };
2103
2298
  let help = false;
2299
+ let json = false;
2104
2300
  for (let i = 0; i < argv.length; i += 1) {
2105
2301
  const arg = argv[i];
2106
2302
  if (arg === "--help" || arg === "-h") {
2107
2303
  help = true;
2304
+ } else if (arg === "--json") {
2305
+ json = true;
2108
2306
  } else if (arg === "--setup" || arg === "--setup-token") {
2109
2307
  options.setupToken = requireValue(argv, ++i, arg);
2110
2308
  } else if (arg === "--api" || arg === "--api-url") {
@@ -2131,7 +2329,7 @@ function parseArgs(argv, env = process.env) {
2131
2329
  }
2132
2330
  }
2133
2331
  if (help) {
2134
- return { options, help };
2332
+ return { options, help, json };
2135
2333
  }
2136
2334
  if (!options.setupToken) {
2137
2335
  throw new Error("Missing --setup <hv_setup_...> setup token.");
@@ -2140,7 +2338,7 @@ function parseArgs(argv, env = process.env) {
2140
2338
  throw new Error("Missing --api <Haven API URL>.");
2141
2339
  }
2142
2340
  options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
2143
- return { options, help };
2341
+ return { options, help, json };
2144
2342
  }
2145
2343
  function helpText() {
2146
2344
  return [
@@ -2162,9 +2360,10 @@ function helpText() {
2162
2360
  " --ack-signer Backward-compatible alias for --ack-local-tools.",
2163
2361
  " --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
2164
2362
  " Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
2363
+ " --json Emit one versioned, secret-free result object on stdout; progress stays on stderr.",
2165
2364
  " --help Show this help.",
2166
2365
  "",
2167
- "The connector never prints the private key and never sends it to Haven."
2366
+ "The connector never prints the private key and never sends it to Haven. JSON output never includes credential contents or full credential paths."
2168
2367
  ].join("\n");
2169
2368
  }
2170
2369
  function requireValue(argv, index, option) {
@@ -2174,12 +2373,74 @@ function requireValue(argv, index, option) {
2174
2373
  }
2175
2374
  return value;
2176
2375
  }
2376
+ async function runCli(argv, io = {
2377
+ stdout: (message) => process.stdout.write(message),
2378
+ stderr: (message) => process.stderr.write(message)
2379
+ }) {
2380
+ const wantsJson = argv.includes("--json");
2381
+ let parsed;
2382
+ try {
2383
+ parsed = parseArgs(argv);
2384
+ } catch (err) {
2385
+ if (wantsJson) {
2386
+ io.stdout(`${JSON.stringify(failedConnectOutcome(void 0, err))}
2387
+ `);
2388
+ } else {
2389
+ io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
2390
+ `);
2391
+ }
2392
+ return 1;
2393
+ }
2394
+ if (parsed.help) {
2395
+ io.stdout(`${helpText()}
2396
+ `);
2397
+ return 0;
2398
+ }
2399
+ try {
2400
+ const result = await runConnect(
2401
+ { ...parsed.options, waitForApproval: !parsed.json },
2402
+ {
2403
+ log: (message) => (parsed.json ? io.stderr : io.stdout)(`${message}
2404
+ `),
2405
+ redactPaths: parsed.json
2406
+ }
2407
+ );
2408
+ if (parsed.json) io.stdout(`${JSON.stringify(result.outcome)}
2409
+ `);
2410
+ return 0;
2411
+ } catch (err) {
2412
+ if (parsed.json) {
2413
+ io.stdout(`${JSON.stringify(failedConnectOutcome(parsed.options.runtime, err))}
2414
+ `);
2415
+ } else {
2416
+ io.stderr(`${redactSecrets(err instanceof Error ? err.message : String(err))}
2417
+ `);
2418
+ }
2419
+ return 1;
2420
+ }
2421
+ }
2422
+ async function main() {
2423
+ const exitCode = await runCli(process.argv.slice(2));
2424
+ if (exitCode !== 0) process.exitCode = exitCode;
2425
+ }
2426
+ function isCliEntrypoint(argvPath = process.argv[1], moduleUrl = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) {
2427
+ if (!argvPath) return false;
2428
+ try {
2429
+ return fs.realpathSync(argvPath) === fs.realpathSync(url.fileURLToPath(moduleUrl));
2430
+ } catch {
2431
+ return url.pathToFileURL(argvPath).href === moduleUrl;
2432
+ }
2433
+ }
2434
+ if (isCliEntrypoint()) void main();
2177
2435
 
2178
2436
  exports.CONNECTOR_VERSION = CONNECTOR_VERSION;
2437
+ exports.CONNECT_OUTCOME_SCHEMA_VERSION = CONNECT_OUTCOME_SCHEMA_VERSION;
2179
2438
  exports.MCP_RUNTIME_MANIFEST = MCP_RUNTIME_MANIFEST;
2439
+ exports.completionOutcome = completionOutcome;
2180
2440
  exports.createConnectApiClient = createConnectApiClient;
2181
2441
  exports.defaultAgentDirectory = defaultAgentDirectory;
2182
2442
  exports.delegateKeyFromPrivateKey = delegateKeyFromPrivateKey;
2443
+ exports.failedConnectOutcome = failedConnectOutcome;
2183
2444
  exports.generateDelegateKey = generateDelegateKey;
2184
2445
  exports.helpText = helpText;
2185
2446
  exports.installRuntime = installRuntime;
@@ -2188,6 +2449,7 @@ exports.normalizeRuntime = normalizeRuntime;
2188
2449
  exports.parseArgs = parseArgs;
2189
2450
  exports.prepareSignerRuntime = prepareSignerRuntime;
2190
2451
  exports.redactSecrets = redactSecrets;
2452
+ exports.runCli = runCli;
2191
2453
  exports.runConnect = runConnect;
2192
2454
  exports.runtimeInstallCapabilities = runtimeInstallCapabilities;
2193
2455
  exports.runtimeProfile = runtimeProfile;