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