@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 +5 -1
- package/dist/cli.cjs +159 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +159 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +159 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +159 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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",
|
|
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",
|
|
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:
|
|
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.
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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 &&
|
|
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,
|
|
605
|
-
errorCode
|
|
704
|
+
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
705
|
+
errorCode,
|
|
606
706
|
configTarget: configResult.target,
|
|
607
|
-
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
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.
|
|
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."
|