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