@haven_ai/connect 0.1.0-alpha → 0.1.1-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ public signing address, proof signature, and API-key hash. Haven never receives
8
8
  the private key or plaintext API key.
9
9
 
10
10
  ```sh
11
- npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code
11
+ npx -y @haven_ai/connect@0.1.1-alpha --setup hv_setup_... --api https://api.haven.example --ack-signer --runtime claude-code
12
12
  ```
13
13
 
14
14
  The connector writes owner-only credential files outside the project by default:
@@ -21,3 +21,7 @@ the locally held signer key and the user-approved on-chain Haven wallet rules.
21
21
 
22
22
  Use `--credentials-dir <path>` to choose a different local credential directory.
23
23
  Do not point it at a project repository, shared folder, or cloud-synced folder.
24
+
25
+ Use `--ack-signer` with Haven-generated setup prompts. It prepares the local
26
+ signer acknowledgement during setup so the configured agent runtime can start
27
+ the signer after its required restart.
package/dist/cli.cjs CHANGED
@@ -8,6 +8,7 @@ var os = require('os');
8
8
  var path = require('path');
9
9
  var child_process = require('child_process');
10
10
  var util = require('util');
11
+ var signer = require('@haven_ai/signer');
11
12
 
12
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
14
 
@@ -53,6 +54,8 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
53
54
  hosted_mcp_configured: input.hostedMcpConfigured,
54
55
  local_signer_configured: input.localSignerConfigured,
55
56
  credential_files_written: input.credentialFilesWritten,
57
+ signer_acknowledged: input.signerAcknowledged,
58
+ activation_command_available: input.activationCommandAvailable,
56
59
  probe_result: input.probeResult,
57
60
  restart_required: input.restartRequired,
58
61
  next_user_action: input.nextUserAction,
@@ -237,7 +240,7 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
237
240
  function buildSignerServer(signerPath, runtime) {
238
241
  const server = {
239
242
  command: "npx",
240
- args: ["-y", "@haven_ai/signer", "--credentials", signerPath]
243
+ args: ["-y", signerPackageName(), "--credentials", signerPath]
241
244
  };
242
245
  if (runtime === "vscode") return { type: "stdio", ...server };
243
246
  return server;
@@ -264,7 +267,7 @@ function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
264
267
  "",
265
268
  "[mcp_servers.haven_signer]",
266
269
  'command = "npx"',
267
- `args = ["-y", "@haven_ai/signer", "--credentials", ${tomlString(signerPath)}]`
270
+ `args = ["-y", ${tomlString(signerPackageName())}, "--credentials", ${tomlString(signerPath)}]`
268
271
  ].join("\n");
269
272
  return `${next ? `${next}
270
273
 
@@ -304,23 +307,35 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
304
307
  async function writeCodexConfig(input) {
305
308
  const target = codexConfigPath(input.homeDir);
306
309
  const envTarget = path.join(input.credentialDirectory, "identity.env");
310
+ const launchTarget = path.join(input.credentialDirectory, "start-codex.sh");
307
311
  try {
308
312
  const existing = await readOptional(target);
309
313
  const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
310
314
  await writeOwnerOnlyText(target, merged);
311
- await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
315
+ await writeOwnerOnlyText(envTarget, `export HAVEN_TOKEN=${shellToken(input.apiKey)}
312
316
  `);
317
+ await writeOwnerExecutableText(
318
+ launchTarget,
319
+ [
320
+ "#!/bin/sh",
321
+ "set -eu",
322
+ `. ${shellToken(envTarget)}`,
323
+ 'exec codex "$@"',
324
+ ""
325
+ ].join("\n")
326
+ );
313
327
  return {
314
- hostedConfigured: false,
328
+ hostedConfigured: true,
315
329
  signerConfigured: true,
316
330
  target: "Codex CLI config",
317
331
  changed: existing !== merged,
318
332
  restartRequired: true,
333
+ activationCommand: shellToken(launchTarget),
319
334
  messages: [
320
335
  "Updated Haven MCP entries in Codex CLI config.",
321
- "Wrote the hosted MCP token to a private env file. Launch Codex with that env file before using Haven tools."
322
- ],
323
- errorCode: "codex_env_activation_required"
336
+ "Wrote the hosted MCP token to a private env file.",
337
+ `Restart Codex with: ${shellToken(launchTarget)}`
338
+ ]
324
339
  };
325
340
  } catch (err) {
326
341
  return {
@@ -347,6 +362,11 @@ async function writeOwnerOnlyText(path$1, value) {
347
362
  await promises.writeFile(path$1, value, { mode: 384 });
348
363
  await promises.chmod(path$1, 384).catch(() => void 0);
349
364
  }
365
+ async function writeOwnerExecutableText(path$1, value) {
366
+ await promises.mkdir(path.dirname(path$1), { recursive: true, mode: 448 });
367
+ await promises.writeFile(path$1, value, { mode: 448 });
368
+ await promises.chmod(path$1, 448).catch(() => void 0);
369
+ }
350
370
  function parseJsonObject(value) {
351
371
  const parsed = JSON.parse(value);
352
372
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -412,6 +432,9 @@ function configTargetLabel(runtime) {
412
432
  return "runtime MCP config";
413
433
  }
414
434
  }
435
+ function signerPackageName() {
436
+ return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
437
+ }
415
438
  async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
416
439
  let response;
417
440
  try {
@@ -560,24 +583,100 @@ function detectRuntime(env) {
560
583
  if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
561
584
  return null;
562
585
  }
586
+ async function acknowledgeLocalSignerConsent(signerPath, log) {
587
+ try {
588
+ const input = await buildSignerConsentInput(signerPath);
589
+ const decision = await signer.ensureSignerConsent(input, {
590
+ credentialsPath: signerPath,
591
+ writeAck: true,
592
+ out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
593
+ });
594
+ return {
595
+ acknowledged: decision.ok,
596
+ hash: decision.hash,
597
+ reason: decision.reason
598
+ };
599
+ } catch (err) {
600
+ return {
601
+ acknowledged: false,
602
+ error: err instanceof Error ? err.message : String(err)
603
+ };
604
+ }
605
+ }
606
+ async function getLocalSignerConsentStatus(signerPath) {
607
+ try {
608
+ const input = await buildSignerConsentInput(signerPath);
609
+ const hash = signer.computeSignerConsentHash(input);
610
+ const stored = await readSignerAckFile(signerAckPath(signerPath));
611
+ if (stored === hash) {
612
+ return { acknowledged: true, hash, reason: "ack_file_match" };
613
+ }
614
+ return {
615
+ acknowledged: false,
616
+ hash,
617
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
618
+ };
619
+ } catch (err) {
620
+ return {
621
+ acknowledged: false,
622
+ error: err instanceof Error ? err.message : String(err)
623
+ };
624
+ }
625
+ }
626
+ function signerAckPath(signerPath) {
627
+ return path.resolve(`${signerPath}.signer-ack.json`);
628
+ }
629
+ async function buildSignerConsentInput(signerPath) {
630
+ const credentials = await signer.loadSignerCredentials(signerPath);
631
+ const signer$1 = signer.createEdgeSigner(credentials.delegateKey, {
632
+ x402BindingSigner: credentials.x402BindingSigner
633
+ });
634
+ return {
635
+ delegateAddress: signer$1.delegateAddress,
636
+ safeAddress: credentials.safeAddress,
637
+ agentId: credentials.agentId,
638
+ chainId: credentials.chainId,
639
+ network: credentials.network,
640
+ toolNames: Object.keys(signer.toolSchemas)
641
+ };
642
+ }
643
+ async function readSignerAckFile(path) {
644
+ try {
645
+ const parsed = JSON.parse(await promises.readFile(path, "utf8"));
646
+ return typeof parsed.ack === "string" ? parsed.ack : null;
647
+ } catch {
648
+ return null;
649
+ }
650
+ }
651
+ function writeLogChunk(log, chunk) {
652
+ const message = String(chunk).trimEnd();
653
+ if (message) log(message);
654
+ }
563
655
 
564
656
  // src/runtime-install.ts
565
657
  var execFileAsync = util.promisify(child_process.execFile);
566
658
  async function installRuntime(input, deps = {}) {
567
659
  const runtime = normalizeRuntime(input.runtime, deps.env);
568
660
  const profile = runtimeProfile(runtime, deps.env);
661
+ const signerConsentMessages = [];
662
+ const signerConsent = await resolveSignerConsent(input, signerConsentMessages);
569
663
  if (runtime === "other") {
570
- const signerReady2 = await probeLocalSignerCredential(input.signerPath);
664
+ const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
665
+ const signerReady = signerCredentialReady2 && signerConsent.acknowledged;
571
666
  return {
572
667
  runtime,
573
668
  hostedMcpConfigured: false,
574
669
  localSignerConfigured: false,
575
- probeResult: signerReady2 ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
670
+ probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
576
671
  restartRequired: true,
577
672
  nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
578
673
  errorCode: "manual_runtime_setup_required",
579
674
  configTarget: "manual runtime setup",
580
- messages: ["Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."]
675
+ signerAcknowledged: signerConsent.acknowledged,
676
+ messages: [
677
+ ...signerConsentMessages,
678
+ "Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
679
+ ]
581
680
  };
582
681
  }
583
682
  const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
@@ -588,23 +687,26 @@ async function installRuntime(input, deps = {}) {
588
687
  credentialDirectory: input.credentialDirectory,
589
688
  homeDir: deps.homeDir
590
689
  });
591
- const [hostedProbe, signerReady] = await Promise.all([
690
+ const [hostedProbe, signerCredentialReady] = await Promise.all([
592
691
  configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
593
692
  probeLocalSignerCredential(input.signerPath)
594
693
  ]);
595
694
  const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
596
- const signerOk = configResult.signerConfigured && signerReady;
695
+ const signerOk = configResult.signerConfigured && signerCredentialReady && signerConsent.acknowledged;
597
696
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
697
+ const errorCode = configResult.errorCode ?? signerConsentErrorCode(signerCredentialReady, signerConsent);
598
698
  return {
599
699
  runtime,
600
700
  hostedMcpConfigured: hostedOk,
601
701
  localSignerConfigured: signerOk,
602
702
  probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
603
703
  restartRequired,
604
- nextUserAction: nextAction(profile.restartMode, configResult.errorCode),
605
- errorCode: configResult.errorCode,
704
+ nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
705
+ errorCode,
606
706
  configTarget: configResult.target,
607
- messages: configResult.messages
707
+ signerAcknowledged: signerConsent.acknowledged,
708
+ activationCommand: configResult.activationCommand,
709
+ messages: [...signerConsentMessages, ...configResult.messages]
608
710
  };
609
711
  }
610
712
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -631,9 +733,10 @@ async function configureClaudeCode(input, deps) {
631
733
  "mcp",
632
734
  "add",
633
735
  "haven-signer",
736
+ "--",
634
737
  "npx",
635
738
  "-y",
636
- "@haven_ai/signer",
739
+ signerPackageName2(),
637
740
  "--credentials",
638
741
  input.signerPath
639
742
  ]);
@@ -643,7 +746,10 @@ async function configureClaudeCode(input, deps) {
643
746
  target: "Claude Code MCP config",
644
747
  changed: true,
645
748
  restartRequired: true,
646
- messages: ["Updated Haven MCP entries with Claude Code."]
749
+ messages: [
750
+ "Updated Haven MCP entries with Claude Code.",
751
+ "Restart Claude Code after Haven approval so it can load the Haven tools."
752
+ ]
647
753
  };
648
754
  } catch (err) {
649
755
  return {
@@ -652,7 +758,10 @@ async function configureClaudeCode(input, deps) {
652
758
  target: "Claude Code MCP config",
653
759
  changed: false,
654
760
  restartRequired: true,
655
- messages: [`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`],
761
+ messages: [
762
+ `Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
763
+ "Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
764
+ ],
656
765
  errorCode: "claude_code_config_failed"
657
766
  };
658
767
  }
@@ -665,16 +774,38 @@ function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
665
774
  const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
666
775
  return `${hostedPart}_${signerPart}`.slice(0, 120);
667
776
  }
668
- function nextAction(restartMode, errorCode) {
777
+ async function resolveSignerConsent(input, messages) {
778
+ if (input.ackSigner) {
779
+ const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
780
+ if (status.acknowledged) {
781
+ messages.push("Prepared the local Haven signer acknowledgement.");
782
+ } else {
783
+ messages.push("Local Haven signer acknowledgement still needs attention.");
784
+ }
785
+ return status;
786
+ }
787
+ return getLocalSignerConsentStatus(input.signerPath);
788
+ }
789
+ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
790
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
791
+ if (!signerConsent.acknowledged) return "local_signer_ack_required";
792
+ return void 0;
793
+ }
794
+ function nextAction(runtime, restartMode, errorCode) {
669
795
  if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
670
796
  if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
797
+ if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex_with_haven_env";
798
+ if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
671
799
  if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
672
800
  if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
673
801
  return "return_to_haven_for_wallet_approval_then_configure_runtime";
674
802
  }
803
+ function signerPackageName2() {
804
+ return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
805
+ }
675
806
 
676
807
  // src/runtime.ts
677
- var CONNECTOR_VERSION = "0.1.0";
808
+ var CONNECTOR_VERSION = "0.1.1-alpha";
678
809
  async function runConnect(options, deps = {}) {
679
810
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
680
811
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -736,7 +867,8 @@ async function runConnect(options, deps = {}) {
736
867
  signerPath: credentialPaths.signerPath,
737
868
  identityPath: credentialPaths.identityPath,
738
869
  credentialDirectory: credentialPaths.directory,
739
- environmentLabel: options.environmentLabel ?? "Local workspace"
870
+ environmentLabel: options.environmentLabel ?? "Local workspace",
871
+ ackSigner: options.ackSigner
740
872
  });
741
873
  printRuntimeInstall(runtimeInstall, log);
742
874
  try {
@@ -746,6 +878,8 @@ async function runConnect(options, deps = {}) {
746
878
  hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
747
879
  localSignerConfigured: runtimeInstall.localSignerConfigured,
748
880
  credentialFilesWritten: true,
881
+ signerAcknowledged: runtimeInstall.signerAcknowledged,
882
+ activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
749
883
  probeResult: runtimeInstall.probeResult,
750
884
  restartRequired: runtimeInstall.restartRequired,
751
885
  nextUserAction: runtimeInstall.nextUserAction,
@@ -816,6 +950,8 @@ function parseArgs(argv, env = process.env) {
816
950
  options.credentialsDir = requireValue(argv, ++i, arg);
817
951
  } else if (arg === "--environment-label") {
818
952
  options.environmentLabel = requireValue(argv, ++i, arg);
953
+ } else if (arg === "--ack-signer") {
954
+ options.ackSigner = true;
819
955
  } else if (arg === "--version") {
820
956
  process.stdout.write(`${CONNECTOR_VERSION}
821
957
  `);
@@ -844,7 +980,7 @@ function helpText() {
844
980
  "sends Haven only the public signing address plus a proof signature.",
845
981
  "",
846
982
  "Usage:",
847
- " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
983
+ " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-signer --runtime claude-code",
848
984
  "",
849
985
  "Options:",
850
986
  " --setup <token> Short-lived setup token from Haven.",
@@ -852,6 +988,7 @@ function helpText() {
852
988
  " --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
853
989
  " --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
854
990
  " --environment-label <text> Non-sensitive label shown in Haven setup review.",
991
+ " --ack-signer Write the one-time local signer acknowledgement during setup.",
855
992
  " --help Show this help.",
856
993
  "",
857
994
  "The connector never prints the private key and never sends it to Haven."