@haven_ai/connect 0.1.11-alpha.0 → 0.1.13-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -9,6 +9,7 @@ var path = require('path');
9
9
  var child_process = require('child_process');
10
10
  var util = require('util');
11
11
  var mcp = require('@haven_ai/mcp');
12
+ var sdk = require('@haven_ai/sdk');
12
13
  var signer = require('@haven_ai/signer');
13
14
 
14
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -147,6 +148,7 @@ async function writeCredentialFiles(input) {
147
148
  safe_address: input.safeAddress,
148
149
  chain_id: input.chainId,
149
150
  network: input.network,
151
+ x402_binding_signer: input.x402BindingSigner,
150
152
  note: "Local signer credential. Haven backend never receives this private key."
151
153
  },
152
154
  input.warn
@@ -213,9 +215,9 @@ var MCP_RUNTIME_MANIFEST = {
213
215
  mcpPackage: "@haven_ai/mcp",
214
216
  mcpVersion: mcp.MCP_VERSION,
215
217
  sdkPackage: "@haven_ai/sdk",
216
- sdkVersion: "0.1.11-alpha.0",
218
+ sdkVersion: "0.1.13-alpha.0",
217
219
  signerPackage: "@haven_ai/signer",
218
- signerVersion: "0.1.11-alpha.0",
220
+ signerVersion: "0.1.13-alpha.0",
219
221
  minimumNodeVersion: "20.0.0",
220
222
  supportedClients: ["codex-cli", "codex-desktop", "claude-code"],
221
223
  requiredTools: mcp.registeredToolNames()
@@ -388,10 +390,16 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
388
390
  headers: { Authorization: `Bearer ${apiKey}` }
389
391
  };
390
392
  }
391
- function buildSignerServer(signerPath, runtime) {
392
- const server = {
393
+ function resolveSignerLaunchSpec(input) {
394
+ return input.signerCommand ?? {
393
395
  command: "npx",
394
- args: ["-y", signerPackageName(), "--credentials", signerPath]
396
+ args: ["-y", signerPackageSpec(), "--credentials", input.signerPath]
397
+ };
398
+ }
399
+ function buildSignerServer(spec, runtime) {
400
+ const server = {
401
+ command: spec.command,
402
+ args: spec.args
395
403
  };
396
404
  if (runtime === "vscode" || runtime === "vscode-insiders") return { type: "stdio", ...server };
397
405
  return server;
@@ -424,7 +432,7 @@ function mergeCodexToml(existingToml, localMcpCommand) {
424
432
  `;
425
433
  return merged;
426
434
  }
427
- function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
435
+ function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerSpec) {
428
436
  let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
429
437
  next = next.trimEnd();
430
438
  const block = [
@@ -433,8 +441,8 @@ function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
433
441
  `http_headers = { "Authorization" = ${tomlString(`Bearer ${apiKey}`)} }`,
434
442
  "",
435
443
  "[mcp_servers.haven_signer]",
436
- 'command = "npx"',
437
- `args = ["-y", ${tomlString(signerPackageSpec())}, "--credentials", ${tomlString(signerPath)}]`,
444
+ `command = ${tomlString(signerSpec.command)}`,
445
+ `args = [${signerSpec.args.map((arg) => tomlString(arg)).join(", ")}]`,
438
446
  "startup_timeout_sec = 120"
439
447
  ].join("\n");
440
448
  validateCodexToml(block, "Generated Codex Haven config");
@@ -451,7 +459,7 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
451
459
  existing,
452
460
  serverRoot,
453
461
  buildHostedServer(input.hostedMcpUrl, input.apiKey, input.runtime),
454
- buildSignerServer(input.signerPath, input.runtime)
462
+ buildSignerServer(resolveSignerLaunchSpec(input), input.runtime)
455
463
  );
456
464
  await writeOwnerOnlyText(target, merged);
457
465
  return {
@@ -507,7 +515,7 @@ async function writeCodexConfig(input) {
507
515
  ]
508
516
  };
509
517
  }
510
- const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, input.signerPath);
518
+ const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, resolveSignerLaunchSpec(input));
511
519
  await writeOwnerOnlyText(target, merged);
512
520
  return {
513
521
  hostedConfigured: true,
@@ -792,9 +800,6 @@ function configTargetLabel(runtime) {
792
800
  return "runtime MCP config";
793
801
  }
794
802
  }
795
- function signerPackageName() {
796
- return signerPackageSpec();
797
- }
798
803
  async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
799
804
  try {
800
805
  const input = await buildLocalMcpConsentInput(identityPath, signerPath);
@@ -908,7 +913,7 @@ async function probeLocalSignerCredential(signerPath) {
908
913
  }
909
914
  }
910
915
  async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
911
- return new Promise((resolve7) => {
916
+ return new Promise((resolve8) => {
912
917
  const child = child_process.spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
913
918
  let stdout = "";
914
919
  let settled = false;
@@ -918,7 +923,7 @@ async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4)
918
923
  settled = true;
919
924
  clearTimeout(timeout);
920
925
  child.kill();
921
- resolve7(result);
926
+ resolve8(result);
922
927
  };
923
928
  const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
924
929
  child.on("error", () => finish({ status: "process_error" }));
@@ -1156,75 +1161,126 @@ async function assertFileExists(path, label) {
1156
1161
  throw new Error(`Missing ${label}: ${path}`);
1157
1162
  }
1158
1163
  }
1159
-
1160
- // src/skill-content.ts
1161
- var HAVEN_SKILL_MD = `---
1162
- name: haven-pay
1163
- description: Pay for things from the user's Haven wallet within their agent rules. Use when the user asks to send, pay, tip, or transfer crypto \u2014 or when a request hits an HTTP 402 (x402) paywall.
1164
- ---
1165
-
1166
- # Haven: pay from a Haven wallet
1167
-
1168
- This skill lets the agent make payments from the user's Haven wallet through
1169
- the Haven MCP tools. Every payment is checked against the agent's on-chain
1170
- budget before money moves; payments above the remaining budget wait for the
1171
- user's approval in Haven.
1172
-
1173
- ## When to use this skill
1174
-
1175
- - The user asks to send money, pay someone, tip, donate, or transfer tokens.
1176
- - A request returns HTTP 402 (x402): use the Haven pay tools to settle it,
1177
- then retry the original request.
1178
-
1179
- ## Identity and budget come from the tools \u2014 never assume them
1180
-
1181
- Do not guess the wallet address, network, or budget. Read them live:
1182
-
1183
- - \`haven_get_agent\` \u2014 agent identity, Haven wallet address, network.
1184
- - \`haven_get_allowances\` \u2014 current per-token budgets and what remains.
1185
-
1186
- Budgets reset on a period the user chose. If a payment exceeds the remaining
1187
- budget it is queued for the user to approve in the Haven dashboard \u2014 this is
1188
- normal, not an error.
1189
-
1190
- ## Paying
1191
-
1192
- - **Direct transfer:** \`haven_pay\` with recipient, amount, and token.
1193
- - **x402 paywall:** \`haven_quote_x402\` to get a quote, then
1194
- \`haven_pay_x402_quote\`. In the hosted setup the signing step happens in
1195
- the local Haven signer; follow the tool results \u2014 they tell you the next
1196
- action at every step. Retry the original request only when the result says
1197
- \`retry_original_x402_request\`.
1198
- - **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
1199
- queued or in-flight payments. Do not poll in a tight loop.
1200
-
1201
- ## Approval semantics
1202
-
1203
- - A result with \`pending_approval\` means the payment exceeded the remaining
1204
- budget and is waiting for the user in Haven. Tell the user, then check
1205
- status later.
1206
- - Never ask the user for private keys and never try to sign anything
1207
- yourself \u2014 Haven signs. If a tool reports a missing or invalid credential,
1208
- tell the user to re-run the Haven setup command.
1209
-
1210
- ## Failure handling
1211
-
1212
- Haven errors are shaped \`{ error, status, details? }\` and written for
1213
- humans \u2014 surface the message verbatim. Common cases:
1214
-
1215
- - \`pending_approval\`: queued for the user's approval (see above).
1216
- - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
1217
- Suggest the user add funds in the Haven dashboard.
1218
- - Budget exceeded: tell the user how much remains (from
1219
- \`haven_get_allowances\`) and that they can raise the budget in Haven.
1220
-
1221
- ## Revoke
1222
-
1223
- If this agent's credential may have leaked, tell the user to pause or revoke
1224
- the agent in the Haven dashboard under Agents. New requests stop immediately
1225
- for that credential.
1226
- `;
1227
- var SKILL_FOLDER_NAME = "haven-pay";
1164
+ var execFileAsync2 = util.promisify(child_process.execFile);
1165
+ async function prepareSignerRuntime(input, deps = {}) {
1166
+ const homeDir = input.homeDir ?? os.homedir();
1167
+ const runtimeDirectory = path.resolve(homeDir, ".haven", "signer-runtime", MCP_RUNTIME_MANIFEST.signerVersion);
1168
+ const npmCacheDirectory = path.resolve(homeDir, ".haven", "npm-cache");
1169
+ const cliPath = path.join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "dist", "cli.js");
1170
+ const messages = [];
1171
+ await promises.mkdir(runtimeDirectory, { recursive: true, mode: 448 });
1172
+ await promises.chmod(runtimeDirectory, 448).catch(() => void 0);
1173
+ await promises.mkdir(npmCacheDirectory, { recursive: true, mode: 448 });
1174
+ await promises.chmod(npmCacheDirectory, 448).catch(() => void 0);
1175
+ if (await installedRuntimeMatches2(runtimeDirectory, cliPath)) {
1176
+ messages.push(`Using existing local Haven signer runtime ${signerPackageSpec()}.`);
1177
+ } else {
1178
+ await installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps.runCommand);
1179
+ messages.push(`Installed local Haven signer runtime ${signerPackageSpec()}.`);
1180
+ }
1181
+ await assertFileExists2(cliPath, "local Haven signer CLI");
1182
+ const wrapperPath = path.join(input.credentialDirectory, "bin", "haven-signer.mjs");
1183
+ await writeWrapper2({ wrapperPath, cliPath, signerPath: input.signerPath });
1184
+ await writeRuntimeSidecar2({
1185
+ path: path.join(input.credentialDirectory, "signer-runtime.json"),
1186
+ wrapperPath,
1187
+ runtimeDirectory,
1188
+ npmCacheDirectory,
1189
+ cliPath
1190
+ });
1191
+ messages.push(`Prepared stable local Haven signer wrapper: ${wrapperPath}`);
1192
+ return {
1193
+ command: wrapperPath,
1194
+ args: [],
1195
+ wrapperPath,
1196
+ runtimeDirectory,
1197
+ npmCacheDirectory,
1198
+ cliPath,
1199
+ messages
1200
+ };
1201
+ }
1202
+ async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, runCommand) {
1203
+ const args = [
1204
+ "install",
1205
+ "--prefix",
1206
+ runtimeDirectory,
1207
+ "--cache",
1208
+ npmCacheDirectory,
1209
+ "--no-audit",
1210
+ "--no-fund",
1211
+ "--omit=dev",
1212
+ signerPackageSpec(),
1213
+ sdkPackageSpec()
1214
+ ];
1215
+ try {
1216
+ if (runCommand) await runCommand("npm", args);
1217
+ else await execFileAsync2("npm", args, { timeout: 12e4, maxBuffer: 1024 * 1024 });
1218
+ } catch (err) {
1219
+ throw new Error(
1220
+ `Could not install local Haven signer runtime ${signerPackageSpec()}: ${err instanceof Error ? err.message : String(err)}`
1221
+ );
1222
+ }
1223
+ }
1224
+ async function installedRuntimeMatches2(runtimeDirectory, cliPath) {
1225
+ try {
1226
+ await assertFileExists2(cliPath, "local Haven signer CLI");
1227
+ const [signerPackage, sdkPackage] = await Promise.all([
1228
+ readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "package.json")),
1229
+ readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
1230
+ ]);
1231
+ return signerPackage.version === MCP_RUNTIME_MANIFEST.signerVersion && sdkPackage.version === MCP_RUNTIME_MANIFEST.sdkVersion;
1232
+ } catch {
1233
+ return false;
1234
+ }
1235
+ }
1236
+ async function readPackageJson2(path) {
1237
+ return JSON.parse(await promises.readFile(path, "utf8"));
1238
+ }
1239
+ async function writeWrapper2(input) {
1240
+ await promises.mkdir(path.dirname(input.wrapperPath), { recursive: true, mode: 448 });
1241
+ await promises.chmod(path.dirname(input.wrapperPath), 448).catch(() => void 0);
1242
+ const source = [
1243
+ "#!/usr/bin/env node",
1244
+ "import { spawn } from 'node:child_process'",
1245
+ "",
1246
+ `const cliPath = ${JSON.stringify(input.cliPath)}`,
1247
+ `const signerPath = ${JSON.stringify(input.signerPath)}`,
1248
+ "",
1249
+ "const child = spawn(process.execPath, [cliPath, '--credentials', signerPath, ...process.argv.slice(2)], {",
1250
+ " stdio: 'inherit',",
1251
+ "})",
1252
+ "",
1253
+ "child.on('exit', (code, signal) => {",
1254
+ " if (signal) process.kill(process.pid, signal)",
1255
+ " else process.exit(code ?? 1)",
1256
+ "})",
1257
+ ""
1258
+ ].join("\n");
1259
+ await promises.writeFile(input.wrapperPath, source, { mode: 448 });
1260
+ await promises.chmod(input.wrapperPath, 448).catch(() => void 0);
1261
+ }
1262
+ async function writeRuntimeSidecar2(input) {
1263
+ const value = {
1264
+ signer_package: MCP_RUNTIME_MANIFEST.signerPackage,
1265
+ signer_version: MCP_RUNTIME_MANIFEST.signerVersion,
1266
+ sdk_package: MCP_RUNTIME_MANIFEST.sdkPackage,
1267
+ sdk_version: MCP_RUNTIME_MANIFEST.sdkVersion,
1268
+ wrapper_path: input.wrapperPath,
1269
+ runtime_directory: input.runtimeDirectory,
1270
+ npm_cache_directory: input.npmCacheDirectory,
1271
+ cli_path: input.cliPath
1272
+ };
1273
+ await promises.writeFile(input.path, `${JSON.stringify(value, null, 2)}
1274
+ `, { mode: 384 });
1275
+ await promises.chmod(input.path, 384).catch(() => void 0);
1276
+ }
1277
+ async function assertFileExists2(path, label) {
1278
+ try {
1279
+ await promises.access(path);
1280
+ } catch {
1281
+ throw new Error(`Missing ${label}: ${path}`);
1282
+ }
1283
+ }
1228
1284
  async function acknowledgeLocalSignerConsent(signerPath, log) {
1229
1285
  try {
1230
1286
  const input = await buildSignerConsentInput(signerPath);
@@ -1296,7 +1352,7 @@ function writeLogChunk2(log, chunk) {
1296
1352
  }
1297
1353
 
1298
1354
  // src/runtime-install.ts
1299
- var execFileAsync2 = util.promisify(child_process.execFile);
1355
+ var execFileAsync3 = util.promisify(child_process.execFile);
1300
1356
  async function installRuntime(input, deps = {}) {
1301
1357
  const runtime = normalizeRuntime(input.runtime, deps.env);
1302
1358
  const profile = runtimeProfile(runtime, deps.env);
@@ -1357,7 +1413,20 @@ async function installRuntime(input, deps = {}) {
1357
1413
  ]
1358
1414
  };
1359
1415
  }
1360
- const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input) : await writeRuntimeConfig({
1416
+ let signerCommand;
1417
+ if (!localRuntime) {
1418
+ try {
1419
+ const signerRuntime = await prepareSignerForRuntime(input, deps);
1420
+ signerCommand = { command: signerRuntime.command, args: signerRuntime.args };
1421
+ consentMessages.push(...signerRuntime.messages);
1422
+ } catch (err) {
1423
+ consentMessages.push(
1424
+ `Could not pre-install the local Haven signer; falling back to npx launch: ${err instanceof Error ? err.message : String(err)}`
1425
+ );
1426
+ }
1427
+ }
1428
+ const signerRuntimePrepared = localRuntime ? void 0 : signerCommand !== void 0;
1429
+ const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input, signerCommand) : await writeRuntimeConfig({
1361
1430
  runtime,
1362
1431
  hostedMcpUrl: input.hostedMcpUrl,
1363
1432
  apiKey: input.apiKey,
@@ -1365,6 +1434,7 @@ async function installRuntime(input, deps = {}) {
1365
1434
  signerPath: input.signerPath,
1366
1435
  credentialDirectory: input.credentialDirectory,
1367
1436
  localMcpCommand: localRuntimeInstall?.command,
1437
+ signerCommand,
1368
1438
  homeDir: deps.homeDir,
1369
1439
  mode: localRuntime ? "local" : "hosted"
1370
1440
  });
@@ -1396,6 +1466,7 @@ async function installRuntime(input, deps = {}) {
1396
1466
  localMcpAcknowledged: localMcpConsent?.acknowledged,
1397
1467
  activationCommand: configResult.activationCommand,
1398
1468
  skillInstalled: skillInstall?.installed,
1469
+ signerRuntimePrepared,
1399
1470
  messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
1400
1471
  };
1401
1472
  }
@@ -1452,7 +1523,7 @@ async function configureClaudeCode(deps, localMcpCommand) {
1452
1523
  };
1453
1524
  }
1454
1525
  }
1455
- async function configureClaudeCodeHosted(deps, input) {
1526
+ async function configureClaudeCodeHosted(deps, input, signerCommand) {
1456
1527
  const runCommand = deps.runCommand ?? defaultRunCommand;
1457
1528
  const hostedJson = JSON.stringify({
1458
1529
  type: "http",
@@ -1461,8 +1532,8 @@ async function configureClaudeCodeHosted(deps, input) {
1461
1532
  });
1462
1533
  const signerJson = JSON.stringify({
1463
1534
  type: "stdio",
1464
- command: "npx",
1465
- args: ["-y", signerPackageSpec(), "--credentials", input.signerPath],
1535
+ command: signerCommand?.command ?? "npx",
1536
+ args: signerCommand?.args ?? ["-y", signerPackageSpec(), "--credentials", input.signerPath],
1466
1537
  env: {}
1467
1538
  });
1468
1539
  try {
@@ -1503,13 +1574,13 @@ async function configureClaudeCodeHosted(deps, input) {
1503
1574
  }
1504
1575
  }
1505
1576
  async function defaultRunCommand(command, args) {
1506
- await execFileAsync2(command, args, { timeout: 1e4 });
1577
+ await execFileAsync3(command, args, { timeout: 1e4 });
1507
1578
  }
1508
1579
  async function installClaudeSkill(homeDir) {
1509
1580
  try {
1510
- const skillDir = path.resolve(homeDir ?? os.homedir(), ".claude", "skills", SKILL_FOLDER_NAME);
1581
+ const skillDir = path.resolve(homeDir ?? os.homedir(), ".claude", "skills", sdk.SKILL_FOLDER_NAME);
1511
1582
  await promises.mkdir(skillDir, { recursive: true });
1512
- await promises.writeFile(path.join(skillDir, "SKILL.md"), HAVEN_SKILL_MD, "utf8");
1583
+ await promises.writeFile(path.join(skillDir, "SKILL.md"), sdk.HAVEN_SKILL_MD, "utf8");
1513
1584
  return {
1514
1585
  installed: true,
1515
1586
  messages: ["Installed the generic Haven payment skill (~/.claude/skills/haven-pay). It contains no secrets."]
@@ -1586,6 +1657,14 @@ async function prepareRuntimeForLocalMcp(input, deps) {
1586
1657
  homeDir: deps.homeDir
1587
1658
  });
1588
1659
  }
1660
+ async function prepareSignerForRuntime(input, deps) {
1661
+ const prepare = deps.prepareSignerRuntime ?? ((runtimeInput) => prepareSignerRuntime(runtimeInput, { runCommand: deps.runCommand }));
1662
+ return prepare({
1663
+ credentialDirectory: input.credentialDirectory,
1664
+ signerPath: input.signerPath,
1665
+ homeDir: deps.homeDir
1666
+ });
1667
+ }
1589
1668
  async function runLocalMcpProbe(runtimeInstall, deps) {
1590
1669
  const probe = deps.probeLocalMcpTools ?? probeLocalMcpTools;
1591
1670
  try {
@@ -1602,7 +1681,7 @@ function localRuntimePrepareErrorCode(err) {
1602
1681
  }
1603
1682
 
1604
1683
  // src/runtime.ts
1605
- var CONNECTOR_VERSION = "0.1.11-alpha.0";
1684
+ var CONNECTOR_VERSION = "0.1.13-alpha.0";
1606
1685
  async function runConnect(options, deps = {}) {
1607
1686
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
1608
1687
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -1667,10 +1746,18 @@ async function runConnect(options, deps = {}) {
1667
1746
  })),
1668
1747
  apiUrl: options.apiBaseUrl,
1669
1748
  hostedMcpUrl: registration.hosted_mcp_url,
1749
+ x402BindingSigner: setup.x402_binding_signer ?? void 0,
1670
1750
  warn: log
1671
1751
  });
1672
1752
  log(`Stored Haven identity credential locally: ${credentialPaths.identityPath}`);
1673
1753
  log(`Stored local signer credential locally: ${credentialPaths.signerPath}`);
1754
+ if (setup.x402_binding_signer) {
1755
+ log("Configured x402 binding signer for the local signer.");
1756
+ } else {
1757
+ log(
1758
+ "Warning: Haven did not provide an x402 binding signer, so x402 payments will not sign until HAVEN_X402_BINDING_SIGNER is set for the signer. Non-x402 payments are unaffected."
1759
+ );
1760
+ }
1674
1761
  const runtimeInstall = await runRuntimeInstall({
1675
1762
  runtime: options.runtime,
1676
1763
  hostedMcpUrl: registration.hosted_mcp_url,