@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.js CHANGED
@@ -7,6 +7,7 @@ import { join, resolve, dirname } from 'path';
7
7
  import { execFile, spawn } from 'child_process';
8
8
  import { promisify } from 'util';
9
9
  import { registeredToolNames, MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient } from '@haven_ai/mcp';
10
+ import { SKILL_FOLDER_NAME, HAVEN_SKILL_MD } from '@haven_ai/sdk';
10
11
  import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
11
12
 
12
13
  // src/api.ts
@@ -141,6 +142,7 @@ async function writeCredentialFiles(input) {
141
142
  safe_address: input.safeAddress,
142
143
  chain_id: input.chainId,
143
144
  network: input.network,
145
+ x402_binding_signer: input.x402BindingSigner,
144
146
  note: "Local signer credential. Haven backend never receives this private key."
145
147
  },
146
148
  input.warn
@@ -207,9 +209,9 @@ var MCP_RUNTIME_MANIFEST = {
207
209
  mcpPackage: "@haven_ai/mcp",
208
210
  mcpVersion: MCP_VERSION,
209
211
  sdkPackage: "@haven_ai/sdk",
210
- sdkVersion: "0.1.11-alpha.0",
212
+ sdkVersion: "0.1.13-alpha.0",
211
213
  signerPackage: "@haven_ai/signer",
212
- signerVersion: "0.1.11-alpha.0",
214
+ signerVersion: "0.1.13-alpha.0",
213
215
  minimumNodeVersion: "20.0.0",
214
216
  supportedClients: ["codex-cli", "codex-desktop", "claude-code"],
215
217
  requiredTools: registeredToolNames()
@@ -382,10 +384,16 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
382
384
  headers: { Authorization: `Bearer ${apiKey}` }
383
385
  };
384
386
  }
385
- function buildSignerServer(signerPath, runtime) {
386
- const server = {
387
+ function resolveSignerLaunchSpec(input) {
388
+ return input.signerCommand ?? {
387
389
  command: "npx",
388
- args: ["-y", signerPackageName(), "--credentials", signerPath]
390
+ args: ["-y", signerPackageSpec(), "--credentials", input.signerPath]
391
+ };
392
+ }
393
+ function buildSignerServer(spec, runtime) {
394
+ const server = {
395
+ command: spec.command,
396
+ args: spec.args
389
397
  };
390
398
  if (runtime === "vscode" || runtime === "vscode-insiders") return { type: "stdio", ...server };
391
399
  return server;
@@ -418,7 +426,7 @@ function mergeCodexToml(existingToml, localMcpCommand) {
418
426
  `;
419
427
  return merged;
420
428
  }
421
- function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
429
+ function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerSpec) {
422
430
  let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
423
431
  next = next.trimEnd();
424
432
  const block = [
@@ -427,8 +435,8 @@ function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
427
435
  `http_headers = { "Authorization" = ${tomlString(`Bearer ${apiKey}`)} }`,
428
436
  "",
429
437
  "[mcp_servers.haven_signer]",
430
- 'command = "npx"',
431
- `args = ["-y", ${tomlString(signerPackageSpec())}, "--credentials", ${tomlString(signerPath)}]`,
438
+ `command = ${tomlString(signerSpec.command)}`,
439
+ `args = [${signerSpec.args.map((arg) => tomlString(arg)).join(", ")}]`,
432
440
  "startup_timeout_sec = 120"
433
441
  ].join("\n");
434
442
  validateCodexToml(block, "Generated Codex Haven config");
@@ -445,7 +453,7 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
445
453
  existing,
446
454
  serverRoot,
447
455
  buildHostedServer(input.hostedMcpUrl, input.apiKey, input.runtime),
448
- buildSignerServer(input.signerPath, input.runtime)
456
+ buildSignerServer(resolveSignerLaunchSpec(input), input.runtime)
449
457
  );
450
458
  await writeOwnerOnlyText(target, merged);
451
459
  return {
@@ -501,7 +509,7 @@ async function writeCodexConfig(input) {
501
509
  ]
502
510
  };
503
511
  }
504
- const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, input.signerPath);
512
+ const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, resolveSignerLaunchSpec(input));
505
513
  await writeOwnerOnlyText(target, merged);
506
514
  return {
507
515
  hostedConfigured: true,
@@ -786,9 +794,6 @@ function configTargetLabel(runtime) {
786
794
  return "runtime MCP config";
787
795
  }
788
796
  }
789
- function signerPackageName() {
790
- return signerPackageSpec();
791
- }
792
797
  async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
793
798
  try {
794
799
  const input = await buildLocalMcpConsentInput(identityPath, signerPath);
@@ -902,7 +907,7 @@ async function probeLocalSignerCredential(signerPath) {
902
907
  }
903
908
  }
904
909
  async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
905
- return new Promise((resolve7) => {
910
+ return new Promise((resolve8) => {
906
911
  const child = spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
907
912
  let stdout = "";
908
913
  let settled = false;
@@ -912,7 +917,7 @@ async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4)
912
917
  settled = true;
913
918
  clearTimeout(timeout);
914
919
  child.kill();
915
- resolve7(result);
920
+ resolve8(result);
916
921
  };
917
922
  const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
918
923
  child.on("error", () => finish({ status: "process_error" }));
@@ -1150,75 +1155,126 @@ async function assertFileExists(path, label) {
1150
1155
  throw new Error(`Missing ${label}: ${path}`);
1151
1156
  }
1152
1157
  }
1153
-
1154
- // src/skill-content.ts
1155
- var HAVEN_SKILL_MD = `---
1156
- name: haven-pay
1157
- 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.
1158
- ---
1159
-
1160
- # Haven: pay from a Haven wallet
1161
-
1162
- This skill lets the agent make payments from the user's Haven wallet through
1163
- the Haven MCP tools. Every payment is checked against the agent's on-chain
1164
- budget before money moves; payments above the remaining budget wait for the
1165
- user's approval in Haven.
1166
-
1167
- ## When to use this skill
1168
-
1169
- - The user asks to send money, pay someone, tip, donate, or transfer tokens.
1170
- - A request returns HTTP 402 (x402): use the Haven pay tools to settle it,
1171
- then retry the original request.
1172
-
1173
- ## Identity and budget come from the tools \u2014 never assume them
1174
-
1175
- Do not guess the wallet address, network, or budget. Read them live:
1176
-
1177
- - \`haven_get_agent\` \u2014 agent identity, Haven wallet address, network.
1178
- - \`haven_get_allowances\` \u2014 current per-token budgets and what remains.
1179
-
1180
- Budgets reset on a period the user chose. If a payment exceeds the remaining
1181
- budget it is queued for the user to approve in the Haven dashboard \u2014 this is
1182
- normal, not an error.
1183
-
1184
- ## Paying
1185
-
1186
- - **Direct transfer:** \`haven_pay\` with recipient, amount, and token.
1187
- - **x402 paywall:** \`haven_quote_x402\` to get a quote, then
1188
- \`haven_pay_x402_quote\`. In the hosted setup the signing step happens in
1189
- the local Haven signer; follow the tool results \u2014 they tell you the next
1190
- action at every step. Retry the original request only when the result says
1191
- \`retry_original_x402_request\`.
1192
- - **Status:** \`haven_get_payment_status\` with a \`payment_id\` to check on
1193
- queued or in-flight payments. Do not poll in a tight loop.
1194
-
1195
- ## Approval semantics
1196
-
1197
- - A result with \`pending_approval\` means the payment exceeded the remaining
1198
- budget and is waiting for the user in Haven. Tell the user, then check
1199
- status later.
1200
- - Never ask the user for private keys and never try to sign anything
1201
- yourself \u2014 Haven signs. If a tool reports a missing or invalid credential,
1202
- tell the user to re-run the Haven setup command.
1203
-
1204
- ## Failure handling
1205
-
1206
- Haven errors are shaped \`{ error, status, details? }\` and written for
1207
- humans \u2014 surface the message verbatim. Common cases:
1208
-
1209
- - \`pending_approval\`: queued for the user's approval (see above).
1210
- - \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
1211
- Suggest the user add funds in the Haven dashboard.
1212
- - Budget exceeded: tell the user how much remains (from
1213
- \`haven_get_allowances\`) and that they can raise the budget in Haven.
1214
-
1215
- ## Revoke
1216
-
1217
- If this agent's credential may have leaked, tell the user to pause or revoke
1218
- the agent in the Haven dashboard under Agents. New requests stop immediately
1219
- for that credential.
1220
- `;
1221
- var SKILL_FOLDER_NAME = "haven-pay";
1158
+ var execFileAsync2 = promisify(execFile);
1159
+ async function prepareSignerRuntime(input, deps = {}) {
1160
+ const homeDir = input.homeDir ?? homedir();
1161
+ const runtimeDirectory = resolve(homeDir, ".haven", "signer-runtime", MCP_RUNTIME_MANIFEST.signerVersion);
1162
+ const npmCacheDirectory = resolve(homeDir, ".haven", "npm-cache");
1163
+ const cliPath = join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "dist", "cli.js");
1164
+ const messages = [];
1165
+ await mkdir(runtimeDirectory, { recursive: true, mode: 448 });
1166
+ await chmod(runtimeDirectory, 448).catch(() => void 0);
1167
+ await mkdir(npmCacheDirectory, { recursive: true, mode: 448 });
1168
+ await chmod(npmCacheDirectory, 448).catch(() => void 0);
1169
+ if (await installedRuntimeMatches2(runtimeDirectory, cliPath)) {
1170
+ messages.push(`Using existing local Haven signer runtime ${signerPackageSpec()}.`);
1171
+ } else {
1172
+ await installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps.runCommand);
1173
+ messages.push(`Installed local Haven signer runtime ${signerPackageSpec()}.`);
1174
+ }
1175
+ await assertFileExists2(cliPath, "local Haven signer CLI");
1176
+ const wrapperPath = join(input.credentialDirectory, "bin", "haven-signer.mjs");
1177
+ await writeWrapper2({ wrapperPath, cliPath, signerPath: input.signerPath });
1178
+ await writeRuntimeSidecar2({
1179
+ path: join(input.credentialDirectory, "signer-runtime.json"),
1180
+ wrapperPath,
1181
+ runtimeDirectory,
1182
+ npmCacheDirectory,
1183
+ cliPath
1184
+ });
1185
+ messages.push(`Prepared stable local Haven signer wrapper: ${wrapperPath}`);
1186
+ return {
1187
+ command: wrapperPath,
1188
+ args: [],
1189
+ wrapperPath,
1190
+ runtimeDirectory,
1191
+ npmCacheDirectory,
1192
+ cliPath,
1193
+ messages
1194
+ };
1195
+ }
1196
+ async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, runCommand) {
1197
+ const args = [
1198
+ "install",
1199
+ "--prefix",
1200
+ runtimeDirectory,
1201
+ "--cache",
1202
+ npmCacheDirectory,
1203
+ "--no-audit",
1204
+ "--no-fund",
1205
+ "--omit=dev",
1206
+ signerPackageSpec(),
1207
+ sdkPackageSpec()
1208
+ ];
1209
+ try {
1210
+ if (runCommand) await runCommand("npm", args);
1211
+ else await execFileAsync2("npm", args, { timeout: 12e4, maxBuffer: 1024 * 1024 });
1212
+ } catch (err) {
1213
+ throw new Error(
1214
+ `Could not install local Haven signer runtime ${signerPackageSpec()}: ${err instanceof Error ? err.message : String(err)}`
1215
+ );
1216
+ }
1217
+ }
1218
+ async function installedRuntimeMatches2(runtimeDirectory, cliPath) {
1219
+ try {
1220
+ await assertFileExists2(cliPath, "local Haven signer CLI");
1221
+ const [signerPackage, sdkPackage] = await Promise.all([
1222
+ readPackageJson2(join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "package.json")),
1223
+ readPackageJson2(join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
1224
+ ]);
1225
+ return signerPackage.version === MCP_RUNTIME_MANIFEST.signerVersion && sdkPackage.version === MCP_RUNTIME_MANIFEST.sdkVersion;
1226
+ } catch {
1227
+ return false;
1228
+ }
1229
+ }
1230
+ async function readPackageJson2(path) {
1231
+ return JSON.parse(await readFile(path, "utf8"));
1232
+ }
1233
+ async function writeWrapper2(input) {
1234
+ await mkdir(dirname(input.wrapperPath), { recursive: true, mode: 448 });
1235
+ await chmod(dirname(input.wrapperPath), 448).catch(() => void 0);
1236
+ const source = [
1237
+ "#!/usr/bin/env node",
1238
+ "import { spawn } from 'node:child_process'",
1239
+ "",
1240
+ `const cliPath = ${JSON.stringify(input.cliPath)}`,
1241
+ `const signerPath = ${JSON.stringify(input.signerPath)}`,
1242
+ "",
1243
+ "const child = spawn(process.execPath, [cliPath, '--credentials', signerPath, ...process.argv.slice(2)], {",
1244
+ " stdio: 'inherit',",
1245
+ "})",
1246
+ "",
1247
+ "child.on('exit', (code, signal) => {",
1248
+ " if (signal) process.kill(process.pid, signal)",
1249
+ " else process.exit(code ?? 1)",
1250
+ "})",
1251
+ ""
1252
+ ].join("\n");
1253
+ await writeFile(input.wrapperPath, source, { mode: 448 });
1254
+ await chmod(input.wrapperPath, 448).catch(() => void 0);
1255
+ }
1256
+ async function writeRuntimeSidecar2(input) {
1257
+ const value = {
1258
+ signer_package: MCP_RUNTIME_MANIFEST.signerPackage,
1259
+ signer_version: MCP_RUNTIME_MANIFEST.signerVersion,
1260
+ sdk_package: MCP_RUNTIME_MANIFEST.sdkPackage,
1261
+ sdk_version: MCP_RUNTIME_MANIFEST.sdkVersion,
1262
+ wrapper_path: input.wrapperPath,
1263
+ runtime_directory: input.runtimeDirectory,
1264
+ npm_cache_directory: input.npmCacheDirectory,
1265
+ cli_path: input.cliPath
1266
+ };
1267
+ await writeFile(input.path, `${JSON.stringify(value, null, 2)}
1268
+ `, { mode: 384 });
1269
+ await chmod(input.path, 384).catch(() => void 0);
1270
+ }
1271
+ async function assertFileExists2(path, label) {
1272
+ try {
1273
+ await access(path);
1274
+ } catch {
1275
+ throw new Error(`Missing ${label}: ${path}`);
1276
+ }
1277
+ }
1222
1278
  async function acknowledgeLocalSignerConsent(signerPath, log) {
1223
1279
  try {
1224
1280
  const input = await buildSignerConsentInput(signerPath);
@@ -1290,7 +1346,7 @@ function writeLogChunk2(log, chunk) {
1290
1346
  }
1291
1347
 
1292
1348
  // src/runtime-install.ts
1293
- var execFileAsync2 = promisify(execFile);
1349
+ var execFileAsync3 = promisify(execFile);
1294
1350
  async function installRuntime(input, deps = {}) {
1295
1351
  const runtime = normalizeRuntime(input.runtime, deps.env);
1296
1352
  const profile = runtimeProfile(runtime, deps.env);
@@ -1351,7 +1407,20 @@ async function installRuntime(input, deps = {}) {
1351
1407
  ]
1352
1408
  };
1353
1409
  }
1354
- const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input) : await writeRuntimeConfig({
1410
+ let signerCommand;
1411
+ if (!localRuntime) {
1412
+ try {
1413
+ const signerRuntime = await prepareSignerForRuntime(input, deps);
1414
+ signerCommand = { command: signerRuntime.command, args: signerRuntime.args };
1415
+ consentMessages.push(...signerRuntime.messages);
1416
+ } catch (err) {
1417
+ consentMessages.push(
1418
+ `Could not pre-install the local Haven signer; falling back to npx launch: ${err instanceof Error ? err.message : String(err)}`
1419
+ );
1420
+ }
1421
+ }
1422
+ const signerRuntimePrepared = localRuntime ? void 0 : signerCommand !== void 0;
1423
+ const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input, signerCommand) : await writeRuntimeConfig({
1355
1424
  runtime,
1356
1425
  hostedMcpUrl: input.hostedMcpUrl,
1357
1426
  apiKey: input.apiKey,
@@ -1359,6 +1428,7 @@ async function installRuntime(input, deps = {}) {
1359
1428
  signerPath: input.signerPath,
1360
1429
  credentialDirectory: input.credentialDirectory,
1361
1430
  localMcpCommand: localRuntimeInstall?.command,
1431
+ signerCommand,
1362
1432
  homeDir: deps.homeDir,
1363
1433
  mode: localRuntime ? "local" : "hosted"
1364
1434
  });
@@ -1390,6 +1460,7 @@ async function installRuntime(input, deps = {}) {
1390
1460
  localMcpAcknowledged: localMcpConsent?.acknowledged,
1391
1461
  activationCommand: configResult.activationCommand,
1392
1462
  skillInstalled: skillInstall?.installed,
1463
+ signerRuntimePrepared,
1393
1464
  messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
1394
1465
  };
1395
1466
  }
@@ -1446,7 +1517,7 @@ async function configureClaudeCode(deps, localMcpCommand) {
1446
1517
  };
1447
1518
  }
1448
1519
  }
1449
- async function configureClaudeCodeHosted(deps, input) {
1520
+ async function configureClaudeCodeHosted(deps, input, signerCommand) {
1450
1521
  const runCommand = deps.runCommand ?? defaultRunCommand;
1451
1522
  const hostedJson = JSON.stringify({
1452
1523
  type: "http",
@@ -1455,8 +1526,8 @@ async function configureClaudeCodeHosted(deps, input) {
1455
1526
  });
1456
1527
  const signerJson = JSON.stringify({
1457
1528
  type: "stdio",
1458
- command: "npx",
1459
- args: ["-y", signerPackageSpec(), "--credentials", input.signerPath],
1529
+ command: signerCommand?.command ?? "npx",
1530
+ args: signerCommand?.args ?? ["-y", signerPackageSpec(), "--credentials", input.signerPath],
1460
1531
  env: {}
1461
1532
  });
1462
1533
  try {
@@ -1497,7 +1568,7 @@ async function configureClaudeCodeHosted(deps, input) {
1497
1568
  }
1498
1569
  }
1499
1570
  async function defaultRunCommand(command, args) {
1500
- await execFileAsync2(command, args, { timeout: 1e4 });
1571
+ await execFileAsync3(command, args, { timeout: 1e4 });
1501
1572
  }
1502
1573
  async function installClaudeSkill(homeDir) {
1503
1574
  try {
@@ -1580,6 +1651,14 @@ async function prepareRuntimeForLocalMcp(input, deps) {
1580
1651
  homeDir: deps.homeDir
1581
1652
  });
1582
1653
  }
1654
+ async function prepareSignerForRuntime(input, deps) {
1655
+ const prepare = deps.prepareSignerRuntime ?? ((runtimeInput) => prepareSignerRuntime(runtimeInput, { runCommand: deps.runCommand }));
1656
+ return prepare({
1657
+ credentialDirectory: input.credentialDirectory,
1658
+ signerPath: input.signerPath,
1659
+ homeDir: deps.homeDir
1660
+ });
1661
+ }
1583
1662
  async function runLocalMcpProbe(runtimeInstall, deps) {
1584
1663
  const probe = deps.probeLocalMcpTools ?? probeLocalMcpTools;
1585
1664
  try {
@@ -1596,7 +1675,7 @@ function localRuntimePrepareErrorCode(err) {
1596
1675
  }
1597
1676
 
1598
1677
  // src/runtime.ts
1599
- var CONNECTOR_VERSION = "0.1.11-alpha.0";
1678
+ var CONNECTOR_VERSION = "0.1.13-alpha.0";
1600
1679
  async function runConnect(options, deps = {}) {
1601
1680
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
1602
1681
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -1661,10 +1740,18 @@ async function runConnect(options, deps = {}) {
1661
1740
  })),
1662
1741
  apiUrl: options.apiBaseUrl,
1663
1742
  hostedMcpUrl: registration.hosted_mcp_url,
1743
+ x402BindingSigner: setup.x402_binding_signer ?? void 0,
1664
1744
  warn: log
1665
1745
  });
1666
1746
  log(`Stored Haven identity credential locally: ${credentialPaths.identityPath}`);
1667
1747
  log(`Stored local signer credential locally: ${credentialPaths.signerPath}`);
1748
+ if (setup.x402_binding_signer) {
1749
+ log("Configured x402 binding signer for the local signer.");
1750
+ } else {
1751
+ log(
1752
+ "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."
1753
+ );
1754
+ }
1668
1755
  const runtimeInstall = await runRuntimeInstall({
1669
1756
  runtime: options.runtime,
1670
1757
  hostedMcpUrl: registration.hosted_mcp_url,