@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.js CHANGED
@@ -5,6 +5,7 @@ import { homedir, platform } from 'os';
5
5
  import { join, resolve, dirname } from 'path';
6
6
  import { execFile } from 'child_process';
7
7
  import { promisify } from 'util';
8
+ import { SIGNER_VERSION, ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
8
9
 
9
10
  // src/api.ts
10
11
  function createConnectApiClient(baseUrl, fetchImpl = fetch) {
@@ -46,6 +47,8 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
46
47
  hosted_mcp_configured: input.hostedMcpConfigured,
47
48
  local_signer_configured: input.localSignerConfigured,
48
49
  credential_files_written: input.credentialFilesWritten,
50
+ signer_acknowledged: input.signerAcknowledged,
51
+ activation_command_available: input.activationCommandAvailable,
49
52
  probe_result: input.probeResult,
50
53
  restart_required: input.restartRequired,
51
54
  next_user_action: input.nextUserAction,
@@ -230,7 +233,7 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
230
233
  function buildSignerServer(signerPath, runtime) {
231
234
  const server = {
232
235
  command: "npx",
233
- args: ["-y", "@haven_ai/signer", "--credentials", signerPath]
236
+ args: ["-y", signerPackageName(), "--credentials", signerPath]
234
237
  };
235
238
  if (runtime === "vscode") return { type: "stdio", ...server };
236
239
  return server;
@@ -257,7 +260,7 @@ function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
257
260
  "",
258
261
  "[mcp_servers.haven_signer]",
259
262
  'command = "npx"',
260
- `args = ["-y", "@haven_ai/signer", "--credentials", ${tomlString(signerPath)}]`
263
+ `args = ["-y", ${tomlString(signerPackageName())}, "--credentials", ${tomlString(signerPath)}]`
261
264
  ].join("\n");
262
265
  return `${next ? `${next}
263
266
 
@@ -297,23 +300,35 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
297
300
  async function writeCodexConfig(input) {
298
301
  const target = codexConfigPath(input.homeDir);
299
302
  const envTarget = join(input.credentialDirectory, "identity.env");
303
+ const launchTarget = join(input.credentialDirectory, "start-codex.sh");
300
304
  try {
301
305
  const existing = await readOptional(target);
302
306
  const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
303
307
  await writeOwnerOnlyText(target, merged);
304
- await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
308
+ await writeOwnerOnlyText(envTarget, `export HAVEN_TOKEN=${shellToken(input.apiKey)}
305
309
  `);
310
+ await writeOwnerExecutableText(
311
+ launchTarget,
312
+ [
313
+ "#!/bin/sh",
314
+ "set -eu",
315
+ `. ${shellToken(envTarget)}`,
316
+ 'exec codex "$@"',
317
+ ""
318
+ ].join("\n")
319
+ );
306
320
  return {
307
- hostedConfigured: false,
321
+ hostedConfigured: true,
308
322
  signerConfigured: true,
309
323
  target: "Codex CLI config",
310
324
  changed: existing !== merged,
311
325
  restartRequired: true,
326
+ activationCommand: shellToken(launchTarget),
312
327
  messages: [
313
328
  "Updated Haven MCP entries in Codex CLI config.",
314
- "Wrote the hosted MCP token to a private env file. Launch Codex with that env file before using Haven tools."
315
- ],
316
- errorCode: "codex_env_activation_required"
329
+ "Wrote the hosted MCP token to a private env file.",
330
+ `Restart Codex with: ${shellToken(launchTarget)}`
331
+ ]
317
332
  };
318
333
  } catch (err) {
319
334
  return {
@@ -340,6 +355,11 @@ async function writeOwnerOnlyText(path, value) {
340
355
  await writeFile(path, value, { mode: 384 });
341
356
  await chmod(path, 384).catch(() => void 0);
342
357
  }
358
+ async function writeOwnerExecutableText(path, value) {
359
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
360
+ await writeFile(path, value, { mode: 448 });
361
+ await chmod(path, 448).catch(() => void 0);
362
+ }
343
363
  function parseJsonObject(value) {
344
364
  const parsed = JSON.parse(value);
345
365
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -405,6 +425,9 @@ function configTargetLabel(runtime) {
405
425
  return "runtime MCP config";
406
426
  }
407
427
  }
428
+ function signerPackageName() {
429
+ return `@haven_ai/signer@${SIGNER_VERSION}`;
430
+ }
408
431
  async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
409
432
  let response;
410
433
  try {
@@ -553,24 +576,100 @@ function detectRuntime(env) {
553
576
  if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
554
577
  return null;
555
578
  }
579
+ async function acknowledgeLocalSignerConsent(signerPath, log) {
580
+ try {
581
+ const input = await buildSignerConsentInput(signerPath);
582
+ const decision = await ensureSignerConsent(input, {
583
+ credentialsPath: signerPath,
584
+ writeAck: true,
585
+ out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
586
+ });
587
+ return {
588
+ acknowledged: decision.ok,
589
+ hash: decision.hash,
590
+ reason: decision.reason
591
+ };
592
+ } catch (err) {
593
+ return {
594
+ acknowledged: false,
595
+ error: err instanceof Error ? err.message : String(err)
596
+ };
597
+ }
598
+ }
599
+ async function getLocalSignerConsentStatus(signerPath) {
600
+ try {
601
+ const input = await buildSignerConsentInput(signerPath);
602
+ const hash = computeSignerConsentHash(input);
603
+ const stored = await readSignerAckFile(signerAckPath(signerPath));
604
+ if (stored === hash) {
605
+ return { acknowledged: true, hash, reason: "ack_file_match" };
606
+ }
607
+ return {
608
+ acknowledged: false,
609
+ hash,
610
+ reason: stored ? "ack_file_mismatch" : "ack_file_missing"
611
+ };
612
+ } catch (err) {
613
+ return {
614
+ acknowledged: false,
615
+ error: err instanceof Error ? err.message : String(err)
616
+ };
617
+ }
618
+ }
619
+ function signerAckPath(signerPath) {
620
+ return resolve(`${signerPath}.signer-ack.json`);
621
+ }
622
+ async function buildSignerConsentInput(signerPath) {
623
+ const credentials = await loadSignerCredentials(signerPath);
624
+ const signer = createEdgeSigner(credentials.delegateKey, {
625
+ x402BindingSigner: credentials.x402BindingSigner
626
+ });
627
+ return {
628
+ delegateAddress: signer.delegateAddress,
629
+ safeAddress: credentials.safeAddress,
630
+ agentId: credentials.agentId,
631
+ chainId: credentials.chainId,
632
+ network: credentials.network,
633
+ toolNames: Object.keys(toolSchemas)
634
+ };
635
+ }
636
+ async function readSignerAckFile(path) {
637
+ try {
638
+ const parsed = JSON.parse(await readFile(path, "utf8"));
639
+ return typeof parsed.ack === "string" ? parsed.ack : null;
640
+ } catch {
641
+ return null;
642
+ }
643
+ }
644
+ function writeLogChunk(log, chunk) {
645
+ const message = String(chunk).trimEnd();
646
+ if (message) log(message);
647
+ }
556
648
 
557
649
  // src/runtime-install.ts
558
650
  var execFileAsync = promisify(execFile);
559
651
  async function installRuntime(input, deps = {}) {
560
652
  const runtime = normalizeRuntime(input.runtime, deps.env);
561
653
  const profile = runtimeProfile(runtime, deps.env);
654
+ const signerConsentMessages = [];
655
+ const signerConsent = await resolveSignerConsent(input, signerConsentMessages);
562
656
  if (runtime === "other") {
563
- const signerReady2 = await probeLocalSignerCredential(input.signerPath);
657
+ const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
658
+ const signerReady = signerCredentialReady2 && signerConsent.acknowledged;
564
659
  return {
565
660
  runtime,
566
661
  hostedMcpConfigured: false,
567
662
  localSignerConfigured: false,
568
- probeResult: signerReady2 ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
663
+ probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
569
664
  restartRequired: true,
570
665
  nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
571
666
  errorCode: "manual_runtime_setup_required",
572
667
  configTarget: "manual runtime setup",
573
- messages: ["Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."]
668
+ signerAcknowledged: signerConsent.acknowledged,
669
+ messages: [
670
+ ...signerConsentMessages,
671
+ "Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
672
+ ]
574
673
  };
575
674
  }
576
675
  const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
@@ -581,23 +680,26 @@ async function installRuntime(input, deps = {}) {
581
680
  credentialDirectory: input.credentialDirectory,
582
681
  homeDir: deps.homeDir
583
682
  });
584
- const [hostedProbe, signerReady] = await Promise.all([
683
+ const [hostedProbe, signerCredentialReady] = await Promise.all([
585
684
  configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
586
685
  probeLocalSignerCredential(input.signerPath)
587
686
  ]);
588
687
  const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
589
- const signerOk = configResult.signerConfigured && signerReady;
688
+ const signerOk = configResult.signerConfigured && signerCredentialReady && signerConsent.acknowledged;
590
689
  const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
690
+ const errorCode = configResult.errorCode ?? signerConsentErrorCode(signerCredentialReady, signerConsent);
591
691
  return {
592
692
  runtime,
593
693
  hostedMcpConfigured: hostedOk,
594
694
  localSignerConfigured: signerOk,
595
695
  probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
596
696
  restartRequired,
597
- nextUserAction: nextAction(profile.restartMode, configResult.errorCode),
598
- errorCode: configResult.errorCode,
697
+ nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
698
+ errorCode,
599
699
  configTarget: configResult.target,
600
- messages: configResult.messages
700
+ signerAcknowledged: signerConsent.acknowledged,
701
+ activationCommand: configResult.activationCommand,
702
+ messages: [...signerConsentMessages, ...configResult.messages]
601
703
  };
602
704
  }
603
705
  function runtimeInstallCapabilities(runtime, env = process.env) {
@@ -624,9 +726,10 @@ async function configureClaudeCode(input, deps) {
624
726
  "mcp",
625
727
  "add",
626
728
  "haven-signer",
729
+ "--",
627
730
  "npx",
628
731
  "-y",
629
- "@haven_ai/signer",
732
+ signerPackageName2(),
630
733
  "--credentials",
631
734
  input.signerPath
632
735
  ]);
@@ -636,7 +739,10 @@ async function configureClaudeCode(input, deps) {
636
739
  target: "Claude Code MCP config",
637
740
  changed: true,
638
741
  restartRequired: true,
639
- messages: ["Updated Haven MCP entries with Claude Code."]
742
+ messages: [
743
+ "Updated Haven MCP entries with Claude Code.",
744
+ "Restart Claude Code after Haven approval so it can load the Haven tools."
745
+ ]
640
746
  };
641
747
  } catch (err) {
642
748
  return {
@@ -645,7 +751,10 @@ async function configureClaudeCode(input, deps) {
645
751
  target: "Claude Code MCP config",
646
752
  changed: false,
647
753
  restartRequired: true,
648
- messages: [`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`],
754
+ messages: [
755
+ `Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
756
+ "Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
757
+ ],
649
758
  errorCode: "claude_code_config_failed"
650
759
  };
651
760
  }
@@ -658,16 +767,38 @@ function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
658
767
  const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
659
768
  return `${hostedPart}_${signerPart}`.slice(0, 120);
660
769
  }
661
- function nextAction(restartMode, errorCode) {
770
+ async function resolveSignerConsent(input, messages) {
771
+ if (input.ackSigner) {
772
+ const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
773
+ if (status.acknowledged) {
774
+ messages.push("Prepared the local Haven signer acknowledgement.");
775
+ } else {
776
+ messages.push("Local Haven signer acknowledgement still needs attention.");
777
+ }
778
+ return status;
779
+ }
780
+ return getLocalSignerConsentStatus(input.signerPath);
781
+ }
782
+ function signerConsentErrorCode(signerCredentialReady, signerConsent) {
783
+ if (!signerCredentialReady) return "local_signer_credential_unavailable";
784
+ if (!signerConsent.acknowledged) return "local_signer_ack_required";
785
+ return void 0;
786
+ }
787
+ function nextAction(runtime, restartMode, errorCode) {
662
788
  if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
663
789
  if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
790
+ if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex_with_haven_env";
791
+ if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
664
792
  if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
665
793
  if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
666
794
  return "return_to_haven_for_wallet_approval_then_configure_runtime";
667
795
  }
796
+ function signerPackageName2() {
797
+ return `@haven_ai/signer@${SIGNER_VERSION}`;
798
+ }
668
799
 
669
800
  // src/runtime.ts
670
- var CONNECTOR_VERSION = "0.1.0";
801
+ var CONNECTOR_VERSION = "0.1.1-alpha";
671
802
  async function runConnect(options, deps = {}) {
672
803
  const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
673
804
  const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
@@ -729,7 +860,8 @@ async function runConnect(options, deps = {}) {
729
860
  signerPath: credentialPaths.signerPath,
730
861
  identityPath: credentialPaths.identityPath,
731
862
  credentialDirectory: credentialPaths.directory,
732
- environmentLabel: options.environmentLabel ?? "Local workspace"
863
+ environmentLabel: options.environmentLabel ?? "Local workspace",
864
+ ackSigner: options.ackSigner
733
865
  });
734
866
  printRuntimeInstall(runtimeInstall, log);
735
867
  try {
@@ -739,6 +871,8 @@ async function runConnect(options, deps = {}) {
739
871
  hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
740
872
  localSignerConfigured: runtimeInstall.localSignerConfigured,
741
873
  credentialFilesWritten: true,
874
+ signerAcknowledged: runtimeInstall.signerAcknowledged,
875
+ activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
742
876
  probeResult: runtimeInstall.probeResult,
743
877
  restartRequired: runtimeInstall.restartRequired,
744
878
  nextUserAction: runtimeInstall.nextUserAction,
@@ -809,6 +943,8 @@ function parseArgs(argv, env = process.env) {
809
943
  options.credentialsDir = requireValue(argv, ++i, arg);
810
944
  } else if (arg === "--environment-label") {
811
945
  options.environmentLabel = requireValue(argv, ++i, arg);
946
+ } else if (arg === "--ack-signer") {
947
+ options.ackSigner = true;
812
948
  } else if (arg === "--version") {
813
949
  process.stdout.write(`${CONNECTOR_VERSION}
814
950
  `);
@@ -837,7 +973,7 @@ function helpText() {
837
973
  "sends Haven only the public signing address plus a proof signature.",
838
974
  "",
839
975
  "Usage:",
840
- " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
976
+ " npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-signer --runtime claude-code",
841
977
  "",
842
978
  "Options:",
843
979
  " --setup <token> Short-lived setup token from Haven.",
@@ -845,6 +981,7 @@ function helpText() {
845
981
  " --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
846
982
  " --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
847
983
  " --environment-label <text> Non-sensitive label shown in Haven setup review.",
984
+ " --ack-signer Write the one-time local signer acknowledgement during setup.",
848
985
  " --help Show this help.",
849
986
  "",
850
987
  "The connector never prints the private key and never sends it to Haven."