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