@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/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",
|
|
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",
|
|
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:
|
|
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.
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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 &&
|
|
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,
|
|
598
|
-
errorCode
|
|
697
|
+
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
698
|
+
errorCode,
|
|
599
699
|
configTarget: configResult.target,
|
|
600
|
-
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
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.
|
|
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."
|