@haven_ai/connect 0.1.0-alpha → 0.1.2-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 +300 -50
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +300 -50
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +300 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +300 -50
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ 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 { MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient, registeredToolNames } from '@haven_ai/mcp';
|
|
9
|
+
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas, SIGNER_VERSION } from '@haven_ai/signer';
|
|
8
10
|
|
|
9
11
|
// src/api.ts
|
|
10
12
|
function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
@@ -43,9 +45,14 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
43
45
|
body: JSON.stringify({
|
|
44
46
|
runtime: input.runtime,
|
|
45
47
|
connector_version: input.connectorVersion,
|
|
48
|
+
runtime_mcp_mode: input.runtimeMcpMode,
|
|
46
49
|
hosted_mcp_configured: input.hostedMcpConfigured,
|
|
47
50
|
local_signer_configured: input.localSignerConfigured,
|
|
51
|
+
local_mcp_configured: input.localMcpConfigured,
|
|
48
52
|
credential_files_written: input.credentialFilesWritten,
|
|
53
|
+
signer_acknowledged: input.signerAcknowledged,
|
|
54
|
+
local_mcp_acknowledged: input.localMcpAcknowledged,
|
|
55
|
+
activation_command_available: input.activationCommandAvailable,
|
|
49
56
|
probe_result: input.probeResult,
|
|
50
57
|
restart_required: input.restartRequired,
|
|
51
58
|
next_user_action: input.nextUserAction,
|
|
@@ -127,6 +134,7 @@ async function writeCredentialFiles(input) {
|
|
|
127
134
|
signerPath,
|
|
128
135
|
{
|
|
129
136
|
delegate_key: input.delegateKey,
|
|
137
|
+
delegate_address: input.delegateAddress,
|
|
130
138
|
agent_id: input.agentId,
|
|
131
139
|
safe_address: input.safeAddress,
|
|
132
140
|
chain_id: input.chainId,
|
|
@@ -146,6 +154,7 @@ async function writeCredentialFiles(input) {
|
|
|
146
154
|
network: input.network,
|
|
147
155
|
api_url: input.apiUrl,
|
|
148
156
|
hosted_mcp_url: input.hostedMcpUrl,
|
|
157
|
+
agent_budget: input.agentBudget,
|
|
149
158
|
note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
|
|
150
159
|
},
|
|
151
160
|
input.warn
|
|
@@ -206,6 +215,8 @@ async function writeRuntimeConfig(input) {
|
|
|
206
215
|
return {
|
|
207
216
|
hostedConfigured: false,
|
|
208
217
|
signerConfigured: false,
|
|
218
|
+
localMcpConfigured: false,
|
|
219
|
+
runtimeMcpMode: "manual",
|
|
209
220
|
target: "manual runtime setup",
|
|
210
221
|
changed: false,
|
|
211
222
|
restartRequired: true,
|
|
@@ -230,7 +241,7 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
|
|
|
230
241
|
function buildSignerServer(signerPath, runtime) {
|
|
231
242
|
const server = {
|
|
232
243
|
command: "npx",
|
|
233
|
-
args: ["-y",
|
|
244
|
+
args: ["-y", signerPackageName(), "--credentials", signerPath]
|
|
234
245
|
};
|
|
235
246
|
if (runtime === "vscode") return { type: "stdio", ...server };
|
|
236
247
|
return server;
|
|
@@ -247,17 +258,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
247
258
|
return `${JSON.stringify(config, null, 2)}
|
|
248
259
|
`;
|
|
249
260
|
}
|
|
250
|
-
function mergeCodexToml(existingToml,
|
|
261
|
+
function mergeCodexToml(existingToml, identityPath, signerPath) {
|
|
251
262
|
let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
252
263
|
next = next.trimEnd();
|
|
253
264
|
const block = [
|
|
254
265
|
"[mcp_servers.haven]",
|
|
255
|
-
`url = ${tomlString(hostedMcpUrl)}`,
|
|
256
|
-
'bearer_token_env_var = "HAVEN_TOKEN"',
|
|
257
|
-
"",
|
|
258
|
-
"[mcp_servers.haven_signer]",
|
|
259
266
|
'command = "npx"',
|
|
260
|
-
`args = ["-y", "
|
|
267
|
+
`args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
|
|
261
268
|
].join("\n");
|
|
262
269
|
return `${next ? `${next}
|
|
263
270
|
|
|
@@ -277,6 +284,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
277
284
|
return {
|
|
278
285
|
hostedConfigured: true,
|
|
279
286
|
signerConfigured: true,
|
|
287
|
+
localMcpConfigured: false,
|
|
288
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
280
289
|
target: configTargetLabel(input.runtime),
|
|
281
290
|
changed: existing !== merged,
|
|
282
291
|
restartRequired: input.runtime === "claude-desktop",
|
|
@@ -286,6 +295,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
286
295
|
return {
|
|
287
296
|
hostedConfigured: false,
|
|
288
297
|
signerConfigured: false,
|
|
298
|
+
localMcpConfigured: false,
|
|
299
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
289
300
|
target: configTargetLabel(input.runtime),
|
|
290
301
|
changed: false,
|
|
291
302
|
restartRequired: true,
|
|
@@ -296,29 +307,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
296
307
|
}
|
|
297
308
|
async function writeCodexConfig(input) {
|
|
298
309
|
const target = codexConfigPath(input.homeDir);
|
|
299
|
-
const envTarget = join(input.credentialDirectory, "identity.env");
|
|
300
310
|
try {
|
|
301
311
|
const existing = await readOptional(target);
|
|
302
|
-
const merged = mergeCodexToml(existing ?? "", input.
|
|
312
|
+
const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
|
|
303
313
|
await writeOwnerOnlyText(target, merged);
|
|
304
|
-
await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
|
|
305
|
-
`);
|
|
306
314
|
return {
|
|
307
315
|
hostedConfigured: false,
|
|
308
316
|
signerConfigured: true,
|
|
317
|
+
localMcpConfigured: true,
|
|
318
|
+
runtimeMcpMode: "local_stdio",
|
|
309
319
|
target: "Codex CLI config",
|
|
310
320
|
changed: existing !== merged,
|
|
311
321
|
restartRequired: true,
|
|
312
322
|
messages: [
|
|
313
|
-
"Updated Haven MCP
|
|
314
|
-
"
|
|
315
|
-
]
|
|
316
|
-
errorCode: "codex_env_activation_required"
|
|
323
|
+
"Updated local Haven MCP entry in Codex CLI config.",
|
|
324
|
+
"After Haven approval, restart Codex normally so it can load Haven tools."
|
|
325
|
+
]
|
|
317
326
|
};
|
|
318
327
|
} catch (err) {
|
|
319
328
|
return {
|
|
320
329
|
hostedConfigured: false,
|
|
321
330
|
signerConfigured: false,
|
|
331
|
+
localMcpConfigured: false,
|
|
332
|
+
runtimeMcpMode: "local_stdio",
|
|
322
333
|
target: "Codex CLI config",
|
|
323
334
|
changed: false,
|
|
324
335
|
restartRequired: true,
|
|
@@ -368,9 +379,6 @@ function removeTomlTable(toml, table) {
|
|
|
368
379
|
function tomlString(value) {
|
|
369
380
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
370
381
|
}
|
|
371
|
-
function shellToken(value) {
|
|
372
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
373
|
-
}
|
|
374
382
|
function cursorConfigPath(homeDir = homedir()) {
|
|
375
383
|
return resolve(homeDir, ".cursor", "mcp.json");
|
|
376
384
|
}
|
|
@@ -405,6 +413,88 @@ function configTargetLabel(runtime) {
|
|
|
405
413
|
return "runtime MCP config";
|
|
406
414
|
}
|
|
407
415
|
}
|
|
416
|
+
function signerPackageName() {
|
|
417
|
+
return `@haven_ai/signer@${SIGNER_VERSION}`;
|
|
418
|
+
}
|
|
419
|
+
function localMcpPackageName() {
|
|
420
|
+
return `@haven_ai/mcp@${MCP_VERSION}`;
|
|
421
|
+
}
|
|
422
|
+
async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
|
|
423
|
+
try {
|
|
424
|
+
const input = await buildLocalMcpConsentInput(identityPath, signerPath);
|
|
425
|
+
const decision = await ensureConsent(input, {
|
|
426
|
+
credentialsPath: identityPath,
|
|
427
|
+
writeAck: true,
|
|
428
|
+
out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
acknowledged: decision.ok,
|
|
432
|
+
hash: decision.hash,
|
|
433
|
+
reason: decision.reason
|
|
434
|
+
};
|
|
435
|
+
} catch (err) {
|
|
436
|
+
return {
|
|
437
|
+
acknowledged: false,
|
|
438
|
+
error: err instanceof Error ? err.message : String(err)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function getLocalMcpConsentStatus(identityPath, signerPath) {
|
|
443
|
+
try {
|
|
444
|
+
const input = await buildLocalMcpConsentInput(identityPath, signerPath);
|
|
445
|
+
const hash = computeConsentHash(input);
|
|
446
|
+
const stored = await readLocalMcpAckFile(localMcpAckPath(identityPath));
|
|
447
|
+
if (stored === hash) {
|
|
448
|
+
return { acknowledged: true, hash, reason: "ack_file_match" };
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
acknowledged: false,
|
|
452
|
+
hash,
|
|
453
|
+
reason: stored ? "ack_file_mismatch" : "ack_file_missing"
|
|
454
|
+
};
|
|
455
|
+
} catch (err) {
|
|
456
|
+
return {
|
|
457
|
+
acknowledged: false,
|
|
458
|
+
error: err instanceof Error ? err.message : String(err)
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function localMcpAckPath(identityPath) {
|
|
463
|
+
return resolve(`${identityPath}.ack.json`);
|
|
464
|
+
}
|
|
465
|
+
async function buildLocalMcpConsentInput(identityPath, signerPath) {
|
|
466
|
+
const credentials = await loadCredentials({ identityPath, signerPath });
|
|
467
|
+
const unavailableDuringSetup = {
|
|
468
|
+
getAllowances: async () => {
|
|
469
|
+
throw new Error("Haven approval is not complete yet.");
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
return consentInputFromClient(
|
|
473
|
+
unavailableDuringSetup,
|
|
474
|
+
{
|
|
475
|
+
apiKey: credentials.apiKey,
|
|
476
|
+
apiUrl: credentials.apiUrl,
|
|
477
|
+
agentId: credentials.agentId,
|
|
478
|
+
safeAddress: credentials.safeAddress,
|
|
479
|
+
delegateAddress: credentials.delegateAddress,
|
|
480
|
+
chainId: credentials.chainId,
|
|
481
|
+
allowanceSummary: credentials.allowanceSummary
|
|
482
|
+
},
|
|
483
|
+
registeredToolNames()
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
async function readLocalMcpAckFile(path) {
|
|
487
|
+
try {
|
|
488
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
489
|
+
return typeof parsed.ack === "string" ? parsed.ack : null;
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function writeLogChunk(log, chunk) {
|
|
495
|
+
const message = String(chunk).trimEnd();
|
|
496
|
+
if (message) log(message);
|
|
497
|
+
}
|
|
408
498
|
async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
|
|
409
499
|
let response;
|
|
410
500
|
try {
|
|
@@ -553,51 +643,140 @@ function detectRuntime(env) {
|
|
|
553
643
|
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
554
644
|
return null;
|
|
555
645
|
}
|
|
646
|
+
async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
647
|
+
try {
|
|
648
|
+
const input = await buildSignerConsentInput(signerPath);
|
|
649
|
+
const decision = await ensureSignerConsent(input, {
|
|
650
|
+
credentialsPath: signerPath,
|
|
651
|
+
writeAck: true,
|
|
652
|
+
out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
|
|
653
|
+
});
|
|
654
|
+
return {
|
|
655
|
+
acknowledged: decision.ok,
|
|
656
|
+
hash: decision.hash,
|
|
657
|
+
reason: decision.reason
|
|
658
|
+
};
|
|
659
|
+
} catch (err) {
|
|
660
|
+
return {
|
|
661
|
+
acknowledged: false,
|
|
662
|
+
error: err instanceof Error ? err.message : String(err)
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async function getLocalSignerConsentStatus(signerPath) {
|
|
667
|
+
try {
|
|
668
|
+
const input = await buildSignerConsentInput(signerPath);
|
|
669
|
+
const hash = computeSignerConsentHash(input);
|
|
670
|
+
const stored = await readSignerAckFile(signerAckPath(signerPath));
|
|
671
|
+
if (stored === hash) {
|
|
672
|
+
return { acknowledged: true, hash, reason: "ack_file_match" };
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
acknowledged: false,
|
|
676
|
+
hash,
|
|
677
|
+
reason: stored ? "ack_file_mismatch" : "ack_file_missing"
|
|
678
|
+
};
|
|
679
|
+
} catch (err) {
|
|
680
|
+
return {
|
|
681
|
+
acknowledged: false,
|
|
682
|
+
error: err instanceof Error ? err.message : String(err)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function signerAckPath(signerPath) {
|
|
687
|
+
return resolve(`${signerPath}.signer-ack.json`);
|
|
688
|
+
}
|
|
689
|
+
async function buildSignerConsentInput(signerPath) {
|
|
690
|
+
const credentials = await loadSignerCredentials(signerPath);
|
|
691
|
+
const signer = createEdgeSigner(credentials.delegateKey, {
|
|
692
|
+
x402BindingSigner: credentials.x402BindingSigner
|
|
693
|
+
});
|
|
694
|
+
return {
|
|
695
|
+
delegateAddress: signer.delegateAddress,
|
|
696
|
+
safeAddress: credentials.safeAddress,
|
|
697
|
+
agentId: credentials.agentId,
|
|
698
|
+
chainId: credentials.chainId,
|
|
699
|
+
network: credentials.network,
|
|
700
|
+
toolNames: Object.keys(toolSchemas)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
async function readSignerAckFile(path) {
|
|
704
|
+
try {
|
|
705
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
706
|
+
return typeof parsed.ack === "string" ? parsed.ack : null;
|
|
707
|
+
} catch {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function writeLogChunk2(log, chunk) {
|
|
712
|
+
const message = String(chunk).trimEnd();
|
|
713
|
+
if (message) log(message);
|
|
714
|
+
}
|
|
556
715
|
|
|
557
716
|
// src/runtime-install.ts
|
|
558
717
|
var execFileAsync = promisify(execFile);
|
|
559
718
|
async function installRuntime(input, deps = {}) {
|
|
560
719
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
561
720
|
const profile = runtimeProfile(runtime, deps.env);
|
|
721
|
+
const localRuntime = usesLocalMcp(runtime);
|
|
722
|
+
const consentMessages = [];
|
|
723
|
+
const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
|
|
724
|
+
const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
|
|
562
725
|
if (runtime === "other") {
|
|
563
|
-
const
|
|
726
|
+
const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
|
|
727
|
+
const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
|
|
564
728
|
return {
|
|
565
729
|
runtime,
|
|
730
|
+
runtimeMcpMode: "manual",
|
|
566
731
|
hostedMcpConfigured: false,
|
|
567
732
|
localSignerConfigured: false,
|
|
568
|
-
|
|
733
|
+
localMcpConfigured: false,
|
|
734
|
+
probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
|
|
569
735
|
restartRequired: true,
|
|
570
736
|
nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
|
|
571
737
|
errorCode: "manual_runtime_setup_required",
|
|
572
738
|
configTarget: "manual runtime setup",
|
|
573
|
-
|
|
739
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
740
|
+
localMcpAcknowledged: false,
|
|
741
|
+
messages: [
|
|
742
|
+
...consentMessages,
|
|
743
|
+
"Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
|
|
744
|
+
]
|
|
574
745
|
};
|
|
575
746
|
}
|
|
576
747
|
const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
|
|
577
748
|
runtime,
|
|
578
749
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
579
750
|
apiKey: input.apiKey,
|
|
751
|
+
identityPath: input.identityPath,
|
|
580
752
|
signerPath: input.signerPath,
|
|
581
753
|
credentialDirectory: input.credentialDirectory,
|
|
582
754
|
homeDir: deps.homeDir
|
|
583
755
|
});
|
|
584
|
-
const [hostedProbe,
|
|
756
|
+
const [hostedProbe, signerCredentialReady] = await Promise.all([
|
|
585
757
|
configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
|
|
586
758
|
probeLocalSignerCredential(input.signerPath)
|
|
587
759
|
]);
|
|
588
760
|
const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
|
|
589
|
-
const
|
|
761
|
+
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
|
|
762
|
+
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
590
763
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
764
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
591
765
|
return {
|
|
592
766
|
runtime,
|
|
767
|
+
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
593
768
|
hostedMcpConfigured: hostedOk,
|
|
594
769
|
localSignerConfigured: signerOk,
|
|
595
|
-
|
|
770
|
+
localMcpConfigured: localMcpOk,
|
|
771
|
+
probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
|
|
596
772
|
restartRequired,
|
|
597
|
-
nextUserAction: nextAction(profile.restartMode,
|
|
598
|
-
errorCode
|
|
773
|
+
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
774
|
+
errorCode,
|
|
599
775
|
configTarget: configResult.target,
|
|
600
|
-
|
|
776
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
777
|
+
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
778
|
+
activationCommand: configResult.activationCommand,
|
|
779
|
+
messages: [...consentMessages, ...configResult.messages]
|
|
601
780
|
};
|
|
602
781
|
}
|
|
603
782
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -613,39 +792,43 @@ async function configureClaudeCode(input, deps) {
|
|
|
613
792
|
await runCommand("claude", [
|
|
614
793
|
"mcp",
|
|
615
794
|
"add",
|
|
616
|
-
"--transport",
|
|
617
|
-
"http",
|
|
618
795
|
"haven",
|
|
619
|
-
|
|
620
|
-
"--header",
|
|
621
|
-
`Authorization: Bearer ${input.apiKey}`
|
|
622
|
-
]);
|
|
623
|
-
await runCommand("claude", [
|
|
624
|
-
"mcp",
|
|
625
|
-
"add",
|
|
626
|
-
"haven-signer",
|
|
796
|
+
"--",
|
|
627
797
|
"npx",
|
|
628
798
|
"-y",
|
|
629
|
-
|
|
630
|
-
"--
|
|
799
|
+
localMcpPackageName2(),
|
|
800
|
+
"--identity",
|
|
801
|
+
input.identityPath,
|
|
802
|
+
"--signer",
|
|
631
803
|
input.signerPath
|
|
632
804
|
]);
|
|
805
|
+
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
633
806
|
return {
|
|
634
|
-
hostedConfigured:
|
|
807
|
+
hostedConfigured: false,
|
|
635
808
|
signerConfigured: true,
|
|
809
|
+
localMcpConfigured: true,
|
|
810
|
+
runtimeMcpMode: "local_stdio",
|
|
636
811
|
target: "Claude Code MCP config",
|
|
637
812
|
changed: true,
|
|
638
813
|
restartRequired: true,
|
|
639
|
-
messages: [
|
|
814
|
+
messages: [
|
|
815
|
+
"Updated local Haven MCP entry with Claude Code.",
|
|
816
|
+
"After Haven approval, restart Claude Code normally so it can load Haven tools."
|
|
817
|
+
]
|
|
640
818
|
};
|
|
641
819
|
} catch (err) {
|
|
642
820
|
return {
|
|
643
821
|
hostedConfigured: false,
|
|
644
822
|
signerConfigured: false,
|
|
823
|
+
localMcpConfigured: false,
|
|
824
|
+
runtimeMcpMode: "local_stdio",
|
|
645
825
|
target: "Claude Code MCP config",
|
|
646
826
|
changed: false,
|
|
647
827
|
restartRequired: true,
|
|
648
|
-
messages: [
|
|
828
|
+
messages: [
|
|
829
|
+
`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
|
|
830
|
+
"Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
|
|
831
|
+
],
|
|
649
832
|
errorCode: "claude_code_config_failed"
|
|
650
833
|
};
|
|
651
834
|
}
|
|
@@ -653,21 +836,66 @@ async function configureClaudeCode(input, deps) {
|
|
|
653
836
|
async function defaultRunCommand(command, args) {
|
|
654
837
|
await execFileAsync(command, args, { timeout: 1e4 });
|
|
655
838
|
}
|
|
656
|
-
function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
|
|
839
|
+
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
|
|
840
|
+
if (mode === "local_stdio") {
|
|
841
|
+
return localMcpReady ? "local_stdio_mcp_ready" : "local_stdio_mcp_unavailable";
|
|
842
|
+
}
|
|
657
843
|
const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
|
|
658
844
|
const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
|
|
659
845
|
return `${hostedPart}_${signerPart}`.slice(0, 120);
|
|
660
846
|
}
|
|
661
|
-
function
|
|
847
|
+
async function resolveLocalMcpConsent(input, messages) {
|
|
848
|
+
if (input.ackLocalTools || input.ackSigner) {
|
|
849
|
+
const status = await acknowledgeLocalMcpConsent(input.identityPath, input.signerPath, (message) => messages.push(message));
|
|
850
|
+
if (status.acknowledged) {
|
|
851
|
+
messages.push("Prepared the local Haven tools acknowledgement.");
|
|
852
|
+
} else {
|
|
853
|
+
messages.push("Local Haven tools acknowledgement still needs attention.");
|
|
854
|
+
}
|
|
855
|
+
return status;
|
|
856
|
+
}
|
|
857
|
+
return getLocalMcpConsentStatus(input.identityPath, input.signerPath);
|
|
858
|
+
}
|
|
859
|
+
async function resolveSignerConsent(input, messages) {
|
|
860
|
+
if (input.ackSigner || input.ackLocalTools) {
|
|
861
|
+
const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
|
|
862
|
+
if (status.acknowledged) {
|
|
863
|
+
messages.push("Prepared the local Haven signer acknowledgement.");
|
|
864
|
+
} else {
|
|
865
|
+
messages.push("Local Haven signer acknowledgement still needs attention.");
|
|
866
|
+
}
|
|
867
|
+
return status;
|
|
868
|
+
}
|
|
869
|
+
return getLocalSignerConsentStatus(input.signerPath);
|
|
870
|
+
}
|
|
871
|
+
function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
872
|
+
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
873
|
+
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
function localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) {
|
|
877
|
+
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
878
|
+
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
879
|
+
return void 0;
|
|
880
|
+
}
|
|
881
|
+
function nextAction(runtime, restartMode, errorCode) {
|
|
662
882
|
if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
|
|
663
883
|
if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
|
|
884
|
+
if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
|
|
885
|
+
if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
|
|
664
886
|
if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
|
|
665
887
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
666
888
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
667
889
|
}
|
|
890
|
+
function localMcpPackageName2() {
|
|
891
|
+
return `@haven_ai/mcp@${MCP_VERSION}`;
|
|
892
|
+
}
|
|
893
|
+
function usesLocalMcp(runtime) {
|
|
894
|
+
return runtime === "codex-cli" || runtime === "claude-code";
|
|
895
|
+
}
|
|
668
896
|
|
|
669
897
|
// src/runtime.ts
|
|
670
|
-
var CONNECTOR_VERSION = "0.1.
|
|
898
|
+
var CONNECTOR_VERSION = "0.1.2";
|
|
671
899
|
async function runConnect(options, deps = {}) {
|
|
672
900
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
673
901
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
@@ -713,9 +941,15 @@ async function runConnect(options, deps = {}) {
|
|
|
713
941
|
agentId: registration.agent_id,
|
|
714
942
|
apiKey: localApiKey,
|
|
715
943
|
delegateKey: localKey.privateKey,
|
|
944
|
+
delegateAddress: localKey.address,
|
|
716
945
|
safeAddress: setup.haven_wallet.address,
|
|
717
946
|
chainId: setup.haven_wallet.chain_id,
|
|
718
947
|
network: setup.haven_wallet.network,
|
|
948
|
+
agentBudget: setup.agent_budget.map((budget) => ({
|
|
949
|
+
token_symbol: budget.token_symbol,
|
|
950
|
+
allowance_amount: budget.allowance_amount,
|
|
951
|
+
reset_period_min: budget.reset_period_min
|
|
952
|
+
})),
|
|
719
953
|
apiUrl: options.apiBaseUrl,
|
|
720
954
|
hostedMcpUrl: registration.hosted_mcp_url,
|
|
721
955
|
warn: log
|
|
@@ -729,16 +963,23 @@ async function runConnect(options, deps = {}) {
|
|
|
729
963
|
signerPath: credentialPaths.signerPath,
|
|
730
964
|
identityPath: credentialPaths.identityPath,
|
|
731
965
|
credentialDirectory: credentialPaths.directory,
|
|
732
|
-
environmentLabel: options.environmentLabel ?? "Local workspace"
|
|
966
|
+
environmentLabel: options.environmentLabel ?? "Local workspace",
|
|
967
|
+
ackSigner: options.ackSigner,
|
|
968
|
+
ackLocalTools: options.ackLocalTools
|
|
733
969
|
});
|
|
734
970
|
printRuntimeInstall(runtimeInstall, log);
|
|
735
971
|
try {
|
|
736
972
|
await api.updateInstallStatus(registration.setup_id, localApiKey, {
|
|
737
973
|
runtime: runtimeInstall.runtime,
|
|
738
974
|
connectorVersion,
|
|
975
|
+
runtimeMcpMode: runtimeInstall.runtimeMcpMode,
|
|
739
976
|
hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
|
|
740
977
|
localSignerConfigured: runtimeInstall.localSignerConfigured,
|
|
978
|
+
localMcpConfigured: runtimeInstall.localMcpConfigured,
|
|
741
979
|
credentialFilesWritten: true,
|
|
980
|
+
signerAcknowledged: runtimeInstall.signerAcknowledged,
|
|
981
|
+
localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
|
|
982
|
+
activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
|
|
742
983
|
probeResult: runtimeInstall.probeResult,
|
|
743
984
|
restartRequired: runtimeInstall.restartRequired,
|
|
744
985
|
nextUserAction: runtimeInstall.nextUserAction,
|
|
@@ -750,7 +991,7 @@ async function runConnect(options, deps = {}) {
|
|
|
750
991
|
}
|
|
751
992
|
log("Return to Haven to approve the agent rules.");
|
|
752
993
|
if (runtimeInstall.restartRequired) {
|
|
753
|
-
log("
|
|
994
|
+
log("After approval, restart this agent normally so it can load Haven tools.");
|
|
754
995
|
}
|
|
755
996
|
return {
|
|
756
997
|
setupId: registration.setup_id,
|
|
@@ -776,10 +1017,12 @@ function secureLogger(log) {
|
|
|
776
1017
|
}
|
|
777
1018
|
function printRuntimeInstall(result, log) {
|
|
778
1019
|
for (const message of result.messages) log(message);
|
|
779
|
-
if (result.
|
|
1020
|
+
if (result.localMcpConfigured) {
|
|
1021
|
+
log("Configured local Haven MCP tools.");
|
|
1022
|
+
} else if (result.hostedMcpConfigured) {
|
|
780
1023
|
log("Configured hosted Haven MCP identity.");
|
|
781
1024
|
} else {
|
|
782
|
-
log("
|
|
1025
|
+
log("Haven MCP tools still need runtime setup.");
|
|
783
1026
|
}
|
|
784
1027
|
if (result.localSignerConfigured) {
|
|
785
1028
|
log("Configured local Haven signer.");
|
|
@@ -809,6 +1052,11 @@ function parseArgs(argv, env = process.env) {
|
|
|
809
1052
|
options.credentialsDir = requireValue(argv, ++i, arg);
|
|
810
1053
|
} else if (arg === "--environment-label") {
|
|
811
1054
|
options.environmentLabel = requireValue(argv, ++i, arg);
|
|
1055
|
+
} else if (arg === "--ack-local-tools") {
|
|
1056
|
+
options.ackLocalTools = true;
|
|
1057
|
+
} else if (arg === "--ack-signer") {
|
|
1058
|
+
options.ackSigner = true;
|
|
1059
|
+
options.ackLocalTools = true;
|
|
812
1060
|
} else if (arg === "--version") {
|
|
813
1061
|
process.stdout.write(`${CONNECTOR_VERSION}
|
|
814
1062
|
`);
|
|
@@ -837,7 +1085,7 @@ function helpText() {
|
|
|
837
1085
|
"sends Haven only the public signing address plus a proof signature.",
|
|
838
1086
|
"",
|
|
839
1087
|
"Usage:",
|
|
840
|
-
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
|
|
1088
|
+
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
|
|
841
1089
|
"",
|
|
842
1090
|
"Options:",
|
|
843
1091
|
" --setup <token> Short-lived setup token from Haven.",
|
|
@@ -845,6 +1093,8 @@ function helpText() {
|
|
|
845
1093
|
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
|
|
846
1094
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
847
1095
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1096
|
+
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1097
|
+
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
848
1098
|
" --help Show this help.",
|
|
849
1099
|
"",
|
|
850
1100
|
"The connector never prints the private key and never sends it to Haven."
|