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