@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/README.md
CHANGED
|
@@ -8,7 +8,7 @@ public signing address, proof signature, and API-key hash. Haven never receives
|
|
|
8
8
|
the private key or plaintext API key.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code
|
|
11
|
+
npx -y @haven_ai/connect@0.1.2-alpha --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The connector writes owner-only credential files outside the project by default:
|
|
@@ -21,3 +21,7 @@ the locally held signer key and the user-approved on-chain Haven wallet rules.
|
|
|
21
21
|
|
|
22
22
|
Use `--credentials-dir <path>` to choose a different local credential directory.
|
|
23
23
|
Do not point it at a project repository, shared folder, or cloud-synced folder.
|
|
24
|
+
|
|
25
|
+
Use `--ack-local-tools` with Haven-generated setup prompts. It prepares the
|
|
26
|
+
local Haven tools acknowledgement during setup so Codex and Claude Code can load
|
|
27
|
+
Haven after a normal restart.
|
package/dist/cli.cjs
CHANGED
|
@@ -8,6 +8,8 @@ var os = require('os');
|
|
|
8
8
|
var path = require('path');
|
|
9
9
|
var child_process = require('child_process');
|
|
10
10
|
var util = require('util');
|
|
11
|
+
var mcp = require('@haven_ai/mcp');
|
|
12
|
+
var signer = require('@haven_ai/signer');
|
|
11
13
|
|
|
12
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
15
|
|
|
@@ -50,9 +52,14 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
50
52
|
body: JSON.stringify({
|
|
51
53
|
runtime: input.runtime,
|
|
52
54
|
connector_version: input.connectorVersion,
|
|
55
|
+
runtime_mcp_mode: input.runtimeMcpMode,
|
|
53
56
|
hosted_mcp_configured: input.hostedMcpConfigured,
|
|
54
57
|
local_signer_configured: input.localSignerConfigured,
|
|
58
|
+
local_mcp_configured: input.localMcpConfigured,
|
|
55
59
|
credential_files_written: input.credentialFilesWritten,
|
|
60
|
+
signer_acknowledged: input.signerAcknowledged,
|
|
61
|
+
local_mcp_acknowledged: input.localMcpAcknowledged,
|
|
62
|
+
activation_command_available: input.activationCommandAvailable,
|
|
56
63
|
probe_result: input.probeResult,
|
|
57
64
|
restart_required: input.restartRequired,
|
|
58
65
|
next_user_action: input.nextUserAction,
|
|
@@ -134,6 +141,7 @@ async function writeCredentialFiles(input) {
|
|
|
134
141
|
signerPath,
|
|
135
142
|
{
|
|
136
143
|
delegate_key: input.delegateKey,
|
|
144
|
+
delegate_address: input.delegateAddress,
|
|
137
145
|
agent_id: input.agentId,
|
|
138
146
|
safe_address: input.safeAddress,
|
|
139
147
|
chain_id: input.chainId,
|
|
@@ -153,6 +161,7 @@ async function writeCredentialFiles(input) {
|
|
|
153
161
|
network: input.network,
|
|
154
162
|
api_url: input.apiUrl,
|
|
155
163
|
hosted_mcp_url: input.hostedMcpUrl,
|
|
164
|
+
agent_budget: input.agentBudget,
|
|
156
165
|
note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
|
|
157
166
|
},
|
|
158
167
|
input.warn
|
|
@@ -213,6 +222,8 @@ async function writeRuntimeConfig(input) {
|
|
|
213
222
|
return {
|
|
214
223
|
hostedConfigured: false,
|
|
215
224
|
signerConfigured: false,
|
|
225
|
+
localMcpConfigured: false,
|
|
226
|
+
runtimeMcpMode: "manual",
|
|
216
227
|
target: "manual runtime setup",
|
|
217
228
|
changed: false,
|
|
218
229
|
restartRequired: true,
|
|
@@ -237,7 +248,7 @@ function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
|
|
|
237
248
|
function buildSignerServer(signerPath, runtime) {
|
|
238
249
|
const server = {
|
|
239
250
|
command: "npx",
|
|
240
|
-
args: ["-y",
|
|
251
|
+
args: ["-y", signerPackageName(), "--credentials", signerPath]
|
|
241
252
|
};
|
|
242
253
|
if (runtime === "vscode") return { type: "stdio", ...server };
|
|
243
254
|
return server;
|
|
@@ -254,17 +265,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
254
265
|
return `${JSON.stringify(config, null, 2)}
|
|
255
266
|
`;
|
|
256
267
|
}
|
|
257
|
-
function mergeCodexToml(existingToml,
|
|
268
|
+
function mergeCodexToml(existingToml, identityPath, signerPath) {
|
|
258
269
|
let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
259
270
|
next = next.trimEnd();
|
|
260
271
|
const block = [
|
|
261
272
|
"[mcp_servers.haven]",
|
|
262
|
-
`url = ${tomlString(hostedMcpUrl)}`,
|
|
263
|
-
'bearer_token_env_var = "HAVEN_TOKEN"',
|
|
264
|
-
"",
|
|
265
|
-
"[mcp_servers.haven_signer]",
|
|
266
273
|
'command = "npx"',
|
|
267
|
-
`args = ["-y", "
|
|
274
|
+
`args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
|
|
268
275
|
].join("\n");
|
|
269
276
|
return `${next ? `${next}
|
|
270
277
|
|
|
@@ -284,6 +291,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
284
291
|
return {
|
|
285
292
|
hostedConfigured: true,
|
|
286
293
|
signerConfigured: true,
|
|
294
|
+
localMcpConfigured: false,
|
|
295
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
287
296
|
target: configTargetLabel(input.runtime),
|
|
288
297
|
changed: existing !== merged,
|
|
289
298
|
restartRequired: input.runtime === "claude-desktop",
|
|
@@ -293,6 +302,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
293
302
|
return {
|
|
294
303
|
hostedConfigured: false,
|
|
295
304
|
signerConfigured: false,
|
|
305
|
+
localMcpConfigured: false,
|
|
306
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
296
307
|
target: configTargetLabel(input.runtime),
|
|
297
308
|
changed: false,
|
|
298
309
|
restartRequired: true,
|
|
@@ -303,29 +314,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
303
314
|
}
|
|
304
315
|
async function writeCodexConfig(input) {
|
|
305
316
|
const target = codexConfigPath(input.homeDir);
|
|
306
|
-
const envTarget = path.join(input.credentialDirectory, "identity.env");
|
|
307
317
|
try {
|
|
308
318
|
const existing = await readOptional(target);
|
|
309
|
-
const merged = mergeCodexToml(existing ?? "", input.
|
|
319
|
+
const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
|
|
310
320
|
await writeOwnerOnlyText(target, merged);
|
|
311
|
-
await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
|
|
312
|
-
`);
|
|
313
321
|
return {
|
|
314
322
|
hostedConfigured: false,
|
|
315
323
|
signerConfigured: true,
|
|
324
|
+
localMcpConfigured: true,
|
|
325
|
+
runtimeMcpMode: "local_stdio",
|
|
316
326
|
target: "Codex CLI config",
|
|
317
327
|
changed: existing !== merged,
|
|
318
328
|
restartRequired: true,
|
|
319
329
|
messages: [
|
|
320
|
-
"Updated Haven MCP
|
|
321
|
-
"
|
|
322
|
-
]
|
|
323
|
-
errorCode: "codex_env_activation_required"
|
|
330
|
+
"Updated local Haven MCP entry in Codex CLI config.",
|
|
331
|
+
"After Haven approval, restart Codex normally so it can load Haven tools."
|
|
332
|
+
]
|
|
324
333
|
};
|
|
325
334
|
} catch (err) {
|
|
326
335
|
return {
|
|
327
336
|
hostedConfigured: false,
|
|
328
337
|
signerConfigured: false,
|
|
338
|
+
localMcpConfigured: false,
|
|
339
|
+
runtimeMcpMode: "local_stdio",
|
|
329
340
|
target: "Codex CLI config",
|
|
330
341
|
changed: false,
|
|
331
342
|
restartRequired: true,
|
|
@@ -375,9 +386,6 @@ function removeTomlTable(toml, table) {
|
|
|
375
386
|
function tomlString(value) {
|
|
376
387
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
377
388
|
}
|
|
378
|
-
function shellToken(value) {
|
|
379
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
380
|
-
}
|
|
381
389
|
function cursorConfigPath(homeDir = os.homedir()) {
|
|
382
390
|
return path.resolve(homeDir, ".cursor", "mcp.json");
|
|
383
391
|
}
|
|
@@ -412,6 +420,88 @@ function configTargetLabel(runtime) {
|
|
|
412
420
|
return "runtime MCP config";
|
|
413
421
|
}
|
|
414
422
|
}
|
|
423
|
+
function signerPackageName() {
|
|
424
|
+
return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
|
|
425
|
+
}
|
|
426
|
+
function localMcpPackageName() {
|
|
427
|
+
return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
|
|
428
|
+
}
|
|
429
|
+
async function acknowledgeLocalMcpConsent(identityPath, signerPath, log) {
|
|
430
|
+
try {
|
|
431
|
+
const input = await buildLocalMcpConsentInput(identityPath, signerPath);
|
|
432
|
+
const decision = await mcp.ensureConsent(input, {
|
|
433
|
+
credentialsPath: identityPath,
|
|
434
|
+
writeAck: true,
|
|
435
|
+
out: log ? { write: (chunk) => writeLogChunk(log, chunk) } : void 0
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
acknowledged: decision.ok,
|
|
439
|
+
hash: decision.hash,
|
|
440
|
+
reason: decision.reason
|
|
441
|
+
};
|
|
442
|
+
} catch (err) {
|
|
443
|
+
return {
|
|
444
|
+
acknowledged: false,
|
|
445
|
+
error: err instanceof Error ? err.message : String(err)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function getLocalMcpConsentStatus(identityPath, signerPath) {
|
|
450
|
+
try {
|
|
451
|
+
const input = await buildLocalMcpConsentInput(identityPath, signerPath);
|
|
452
|
+
const hash = mcp.computeConsentHash(input);
|
|
453
|
+
const stored = await readLocalMcpAckFile(localMcpAckPath(identityPath));
|
|
454
|
+
if (stored === hash) {
|
|
455
|
+
return { acknowledged: true, hash, reason: "ack_file_match" };
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
acknowledged: false,
|
|
459
|
+
hash,
|
|
460
|
+
reason: stored ? "ack_file_mismatch" : "ack_file_missing"
|
|
461
|
+
};
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return {
|
|
464
|
+
acknowledged: false,
|
|
465
|
+
error: err instanceof Error ? err.message : String(err)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function localMcpAckPath(identityPath) {
|
|
470
|
+
return path.resolve(`${identityPath}.ack.json`);
|
|
471
|
+
}
|
|
472
|
+
async function buildLocalMcpConsentInput(identityPath, signerPath) {
|
|
473
|
+
const credentials = await mcp.loadCredentials({ identityPath, signerPath });
|
|
474
|
+
const unavailableDuringSetup = {
|
|
475
|
+
getAllowances: async () => {
|
|
476
|
+
throw new Error("Haven approval is not complete yet.");
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
return mcp.consentInputFromClient(
|
|
480
|
+
unavailableDuringSetup,
|
|
481
|
+
{
|
|
482
|
+
apiKey: credentials.apiKey,
|
|
483
|
+
apiUrl: credentials.apiUrl,
|
|
484
|
+
agentId: credentials.agentId,
|
|
485
|
+
safeAddress: credentials.safeAddress,
|
|
486
|
+
delegateAddress: credentials.delegateAddress,
|
|
487
|
+
chainId: credentials.chainId,
|
|
488
|
+
allowanceSummary: credentials.allowanceSummary
|
|
489
|
+
},
|
|
490
|
+
mcp.registeredToolNames()
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
async function readLocalMcpAckFile(path) {
|
|
494
|
+
try {
|
|
495
|
+
const parsed = JSON.parse(await promises.readFile(path, "utf8"));
|
|
496
|
+
return typeof parsed.ack === "string" ? parsed.ack : null;
|
|
497
|
+
} catch {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function writeLogChunk(log, chunk) {
|
|
502
|
+
const message = String(chunk).trimEnd();
|
|
503
|
+
if (message) log(message);
|
|
504
|
+
}
|
|
415
505
|
async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
|
|
416
506
|
let response;
|
|
417
507
|
try {
|
|
@@ -560,51 +650,140 @@ function detectRuntime(env) {
|
|
|
560
650
|
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
561
651
|
return null;
|
|
562
652
|
}
|
|
653
|
+
async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
654
|
+
try {
|
|
655
|
+
const input = await buildSignerConsentInput(signerPath);
|
|
656
|
+
const decision = await signer.ensureSignerConsent(input, {
|
|
657
|
+
credentialsPath: signerPath,
|
|
658
|
+
writeAck: true,
|
|
659
|
+
out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
|
|
660
|
+
});
|
|
661
|
+
return {
|
|
662
|
+
acknowledged: decision.ok,
|
|
663
|
+
hash: decision.hash,
|
|
664
|
+
reason: decision.reason
|
|
665
|
+
};
|
|
666
|
+
} catch (err) {
|
|
667
|
+
return {
|
|
668
|
+
acknowledged: false,
|
|
669
|
+
error: err instanceof Error ? err.message : String(err)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async function getLocalSignerConsentStatus(signerPath) {
|
|
674
|
+
try {
|
|
675
|
+
const input = await buildSignerConsentInput(signerPath);
|
|
676
|
+
const hash = signer.computeSignerConsentHash(input);
|
|
677
|
+
const stored = await readSignerAckFile(signerAckPath(signerPath));
|
|
678
|
+
if (stored === hash) {
|
|
679
|
+
return { acknowledged: true, hash, reason: "ack_file_match" };
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
acknowledged: false,
|
|
683
|
+
hash,
|
|
684
|
+
reason: stored ? "ack_file_mismatch" : "ack_file_missing"
|
|
685
|
+
};
|
|
686
|
+
} catch (err) {
|
|
687
|
+
return {
|
|
688
|
+
acknowledged: false,
|
|
689
|
+
error: err instanceof Error ? err.message : String(err)
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function signerAckPath(signerPath) {
|
|
694
|
+
return path.resolve(`${signerPath}.signer-ack.json`);
|
|
695
|
+
}
|
|
696
|
+
async function buildSignerConsentInput(signerPath) {
|
|
697
|
+
const credentials = await signer.loadSignerCredentials(signerPath);
|
|
698
|
+
const signer$1 = signer.createEdgeSigner(credentials.delegateKey, {
|
|
699
|
+
x402BindingSigner: credentials.x402BindingSigner
|
|
700
|
+
});
|
|
701
|
+
return {
|
|
702
|
+
delegateAddress: signer$1.delegateAddress,
|
|
703
|
+
safeAddress: credentials.safeAddress,
|
|
704
|
+
agentId: credentials.agentId,
|
|
705
|
+
chainId: credentials.chainId,
|
|
706
|
+
network: credentials.network,
|
|
707
|
+
toolNames: Object.keys(signer.toolSchemas)
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
async function readSignerAckFile(path) {
|
|
711
|
+
try {
|
|
712
|
+
const parsed = JSON.parse(await promises.readFile(path, "utf8"));
|
|
713
|
+
return typeof parsed.ack === "string" ? parsed.ack : null;
|
|
714
|
+
} catch {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function writeLogChunk2(log, chunk) {
|
|
719
|
+
const message = String(chunk).trimEnd();
|
|
720
|
+
if (message) log(message);
|
|
721
|
+
}
|
|
563
722
|
|
|
564
723
|
// src/runtime-install.ts
|
|
565
724
|
var execFileAsync = util.promisify(child_process.execFile);
|
|
566
725
|
async function installRuntime(input, deps = {}) {
|
|
567
726
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
568
727
|
const profile = runtimeProfile(runtime, deps.env);
|
|
728
|
+
const localRuntime = usesLocalMcp(runtime);
|
|
729
|
+
const consentMessages = [];
|
|
730
|
+
const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
|
|
731
|
+
const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
|
|
569
732
|
if (runtime === "other") {
|
|
570
|
-
const
|
|
733
|
+
const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
|
|
734
|
+
const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
|
|
571
735
|
return {
|
|
572
736
|
runtime,
|
|
737
|
+
runtimeMcpMode: "manual",
|
|
573
738
|
hostedMcpConfigured: false,
|
|
574
739
|
localSignerConfigured: false,
|
|
575
|
-
|
|
740
|
+
localMcpConfigured: false,
|
|
741
|
+
probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
|
|
576
742
|
restartRequired: true,
|
|
577
743
|
nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
|
|
578
744
|
errorCode: "manual_runtime_setup_required",
|
|
579
745
|
configTarget: "manual runtime setup",
|
|
580
|
-
|
|
746
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
747
|
+
localMcpAcknowledged: false,
|
|
748
|
+
messages: [
|
|
749
|
+
...consentMessages,
|
|
750
|
+
"Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
|
|
751
|
+
]
|
|
581
752
|
};
|
|
582
753
|
}
|
|
583
754
|
const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
|
|
584
755
|
runtime,
|
|
585
756
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
586
757
|
apiKey: input.apiKey,
|
|
758
|
+
identityPath: input.identityPath,
|
|
587
759
|
signerPath: input.signerPath,
|
|
588
760
|
credentialDirectory: input.credentialDirectory,
|
|
589
761
|
homeDir: deps.homeDir
|
|
590
762
|
});
|
|
591
|
-
const [hostedProbe,
|
|
763
|
+
const [hostedProbe, signerCredentialReady] = await Promise.all([
|
|
592
764
|
configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
|
|
593
765
|
probeLocalSignerCredential(input.signerPath)
|
|
594
766
|
]);
|
|
595
767
|
const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
|
|
596
|
-
const
|
|
768
|
+
const localMcpOk = configResult.runtimeMcpMode === "local_stdio" && configResult.localMcpConfigured && signerCredentialReady && Boolean(localMcpConsent?.acknowledged);
|
|
769
|
+
const signerOk = configResult.runtimeMcpMode === "local_stdio" ? localMcpOk : configResult.signerConfigured && signerCredentialReady && Boolean(signerConsent?.acknowledged);
|
|
597
770
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
771
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
598
772
|
return {
|
|
599
773
|
runtime,
|
|
774
|
+
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
600
775
|
hostedMcpConfigured: hostedOk,
|
|
601
776
|
localSignerConfigured: signerOk,
|
|
602
|
-
|
|
777
|
+
localMcpConfigured: localMcpOk,
|
|
778
|
+
probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
|
|
603
779
|
restartRequired,
|
|
604
|
-
nextUserAction: nextAction(profile.restartMode,
|
|
605
|
-
errorCode
|
|
780
|
+
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
781
|
+
errorCode,
|
|
606
782
|
configTarget: configResult.target,
|
|
607
|
-
|
|
783
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
784
|
+
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
785
|
+
activationCommand: configResult.activationCommand,
|
|
786
|
+
messages: [...consentMessages, ...configResult.messages]
|
|
608
787
|
};
|
|
609
788
|
}
|
|
610
789
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -620,39 +799,43 @@ async function configureClaudeCode(input, deps) {
|
|
|
620
799
|
await runCommand("claude", [
|
|
621
800
|
"mcp",
|
|
622
801
|
"add",
|
|
623
|
-
"--transport",
|
|
624
|
-
"http",
|
|
625
802
|
"haven",
|
|
626
|
-
|
|
627
|
-
"--header",
|
|
628
|
-
`Authorization: Bearer ${input.apiKey}`
|
|
629
|
-
]);
|
|
630
|
-
await runCommand("claude", [
|
|
631
|
-
"mcp",
|
|
632
|
-
"add",
|
|
633
|
-
"haven-signer",
|
|
803
|
+
"--",
|
|
634
804
|
"npx",
|
|
635
805
|
"-y",
|
|
636
|
-
|
|
637
|
-
"--
|
|
806
|
+
localMcpPackageName2(),
|
|
807
|
+
"--identity",
|
|
808
|
+
input.identityPath,
|
|
809
|
+
"--signer",
|
|
638
810
|
input.signerPath
|
|
639
811
|
]);
|
|
812
|
+
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
640
813
|
return {
|
|
641
|
-
hostedConfigured:
|
|
814
|
+
hostedConfigured: false,
|
|
642
815
|
signerConfigured: true,
|
|
816
|
+
localMcpConfigured: true,
|
|
817
|
+
runtimeMcpMode: "local_stdio",
|
|
643
818
|
target: "Claude Code MCP config",
|
|
644
819
|
changed: true,
|
|
645
820
|
restartRequired: true,
|
|
646
|
-
messages: [
|
|
821
|
+
messages: [
|
|
822
|
+
"Updated local Haven MCP entry with Claude Code.",
|
|
823
|
+
"After Haven approval, restart Claude Code normally so it can load Haven tools."
|
|
824
|
+
]
|
|
647
825
|
};
|
|
648
826
|
} catch (err) {
|
|
649
827
|
return {
|
|
650
828
|
hostedConfigured: false,
|
|
651
829
|
signerConfigured: false,
|
|
830
|
+
localMcpConfigured: false,
|
|
831
|
+
runtimeMcpMode: "local_stdio",
|
|
652
832
|
target: "Claude Code MCP config",
|
|
653
833
|
changed: false,
|
|
654
834
|
restartRequired: true,
|
|
655
|
-
messages: [
|
|
835
|
+
messages: [
|
|
836
|
+
`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
|
|
837
|
+
"Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
|
|
838
|
+
],
|
|
656
839
|
errorCode: "claude_code_config_failed"
|
|
657
840
|
};
|
|
658
841
|
}
|
|
@@ -660,21 +843,66 @@ async function configureClaudeCode(input, deps) {
|
|
|
660
843
|
async function defaultRunCommand(command, args) {
|
|
661
844
|
await execFileAsync(command, args, { timeout: 1e4 });
|
|
662
845
|
}
|
|
663
|
-
function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
|
|
846
|
+
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady) {
|
|
847
|
+
if (mode === "local_stdio") {
|
|
848
|
+
return localMcpReady ? "local_stdio_mcp_ready" : "local_stdio_mcp_unavailable";
|
|
849
|
+
}
|
|
664
850
|
const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
|
|
665
851
|
const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
|
|
666
852
|
return `${hostedPart}_${signerPart}`.slice(0, 120);
|
|
667
853
|
}
|
|
668
|
-
function
|
|
854
|
+
async function resolveLocalMcpConsent(input, messages) {
|
|
855
|
+
if (input.ackLocalTools || input.ackSigner) {
|
|
856
|
+
const status = await acknowledgeLocalMcpConsent(input.identityPath, input.signerPath, (message) => messages.push(message));
|
|
857
|
+
if (status.acknowledged) {
|
|
858
|
+
messages.push("Prepared the local Haven tools acknowledgement.");
|
|
859
|
+
} else {
|
|
860
|
+
messages.push("Local Haven tools acknowledgement still needs attention.");
|
|
861
|
+
}
|
|
862
|
+
return status;
|
|
863
|
+
}
|
|
864
|
+
return getLocalMcpConsentStatus(input.identityPath, input.signerPath);
|
|
865
|
+
}
|
|
866
|
+
async function resolveSignerConsent(input, messages) {
|
|
867
|
+
if (input.ackSigner || input.ackLocalTools) {
|
|
868
|
+
const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
|
|
869
|
+
if (status.acknowledged) {
|
|
870
|
+
messages.push("Prepared the local Haven signer acknowledgement.");
|
|
871
|
+
} else {
|
|
872
|
+
messages.push("Local Haven signer acknowledgement still needs attention.");
|
|
873
|
+
}
|
|
874
|
+
return status;
|
|
875
|
+
}
|
|
876
|
+
return getLocalSignerConsentStatus(input.signerPath);
|
|
877
|
+
}
|
|
878
|
+
function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
879
|
+
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
880
|
+
if (!signerConsent?.acknowledged) return "local_signer_ack_required";
|
|
881
|
+
return void 0;
|
|
882
|
+
}
|
|
883
|
+
function localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) {
|
|
884
|
+
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
885
|
+
if (!localMcpConsent?.acknowledged) return "local_mcp_ack_required";
|
|
886
|
+
return void 0;
|
|
887
|
+
}
|
|
888
|
+
function nextAction(runtime, restartMode, errorCode) {
|
|
669
889
|
if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
|
|
670
890
|
if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
|
|
891
|
+
if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
|
|
892
|
+
if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
|
|
671
893
|
if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
|
|
672
894
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
673
895
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
674
896
|
}
|
|
897
|
+
function localMcpPackageName2() {
|
|
898
|
+
return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
|
|
899
|
+
}
|
|
900
|
+
function usesLocalMcp(runtime) {
|
|
901
|
+
return runtime === "codex-cli" || runtime === "claude-code";
|
|
902
|
+
}
|
|
675
903
|
|
|
676
904
|
// src/runtime.ts
|
|
677
|
-
var CONNECTOR_VERSION = "0.1.
|
|
905
|
+
var CONNECTOR_VERSION = "0.1.2";
|
|
678
906
|
async function runConnect(options, deps = {}) {
|
|
679
907
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
680
908
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
@@ -720,9 +948,15 @@ async function runConnect(options, deps = {}) {
|
|
|
720
948
|
agentId: registration.agent_id,
|
|
721
949
|
apiKey: localApiKey,
|
|
722
950
|
delegateKey: localKey.privateKey,
|
|
951
|
+
delegateAddress: localKey.address,
|
|
723
952
|
safeAddress: setup.haven_wallet.address,
|
|
724
953
|
chainId: setup.haven_wallet.chain_id,
|
|
725
954
|
network: setup.haven_wallet.network,
|
|
955
|
+
agentBudget: setup.agent_budget.map((budget) => ({
|
|
956
|
+
token_symbol: budget.token_symbol,
|
|
957
|
+
allowance_amount: budget.allowance_amount,
|
|
958
|
+
reset_period_min: budget.reset_period_min
|
|
959
|
+
})),
|
|
726
960
|
apiUrl: options.apiBaseUrl,
|
|
727
961
|
hostedMcpUrl: registration.hosted_mcp_url,
|
|
728
962
|
warn: log
|
|
@@ -736,16 +970,23 @@ async function runConnect(options, deps = {}) {
|
|
|
736
970
|
signerPath: credentialPaths.signerPath,
|
|
737
971
|
identityPath: credentialPaths.identityPath,
|
|
738
972
|
credentialDirectory: credentialPaths.directory,
|
|
739
|
-
environmentLabel: options.environmentLabel ?? "Local workspace"
|
|
973
|
+
environmentLabel: options.environmentLabel ?? "Local workspace",
|
|
974
|
+
ackSigner: options.ackSigner,
|
|
975
|
+
ackLocalTools: options.ackLocalTools
|
|
740
976
|
});
|
|
741
977
|
printRuntimeInstall(runtimeInstall, log);
|
|
742
978
|
try {
|
|
743
979
|
await api.updateInstallStatus(registration.setup_id, localApiKey, {
|
|
744
980
|
runtime: runtimeInstall.runtime,
|
|
745
981
|
connectorVersion,
|
|
982
|
+
runtimeMcpMode: runtimeInstall.runtimeMcpMode,
|
|
746
983
|
hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
|
|
747
984
|
localSignerConfigured: runtimeInstall.localSignerConfigured,
|
|
985
|
+
localMcpConfigured: runtimeInstall.localMcpConfigured,
|
|
748
986
|
credentialFilesWritten: true,
|
|
987
|
+
signerAcknowledged: runtimeInstall.signerAcknowledged,
|
|
988
|
+
localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
|
|
989
|
+
activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
|
|
749
990
|
probeResult: runtimeInstall.probeResult,
|
|
750
991
|
restartRequired: runtimeInstall.restartRequired,
|
|
751
992
|
nextUserAction: runtimeInstall.nextUserAction,
|
|
@@ -757,7 +998,7 @@ async function runConnect(options, deps = {}) {
|
|
|
757
998
|
}
|
|
758
999
|
log("Return to Haven to approve the agent rules.");
|
|
759
1000
|
if (runtimeInstall.restartRequired) {
|
|
760
|
-
log("
|
|
1001
|
+
log("After approval, restart this agent normally so it can load Haven tools.");
|
|
761
1002
|
}
|
|
762
1003
|
return {
|
|
763
1004
|
setupId: registration.setup_id,
|
|
@@ -783,10 +1024,12 @@ function secureLogger(log) {
|
|
|
783
1024
|
}
|
|
784
1025
|
function printRuntimeInstall(result, log) {
|
|
785
1026
|
for (const message of result.messages) log(message);
|
|
786
|
-
if (result.
|
|
1027
|
+
if (result.localMcpConfigured) {
|
|
1028
|
+
log("Configured local Haven MCP tools.");
|
|
1029
|
+
} else if (result.hostedMcpConfigured) {
|
|
787
1030
|
log("Configured hosted Haven MCP identity.");
|
|
788
1031
|
} else {
|
|
789
|
-
log("
|
|
1032
|
+
log("Haven MCP tools still need runtime setup.");
|
|
790
1033
|
}
|
|
791
1034
|
if (result.localSignerConfigured) {
|
|
792
1035
|
log("Configured local Haven signer.");
|
|
@@ -816,6 +1059,11 @@ function parseArgs(argv, env = process.env) {
|
|
|
816
1059
|
options.credentialsDir = requireValue(argv, ++i, arg);
|
|
817
1060
|
} else if (arg === "--environment-label") {
|
|
818
1061
|
options.environmentLabel = requireValue(argv, ++i, arg);
|
|
1062
|
+
} else if (arg === "--ack-local-tools") {
|
|
1063
|
+
options.ackLocalTools = true;
|
|
1064
|
+
} else if (arg === "--ack-signer") {
|
|
1065
|
+
options.ackSigner = true;
|
|
1066
|
+
options.ackLocalTools = true;
|
|
819
1067
|
} else if (arg === "--version") {
|
|
820
1068
|
process.stdout.write(`${CONNECTOR_VERSION}
|
|
821
1069
|
`);
|
|
@@ -844,7 +1092,7 @@ function helpText() {
|
|
|
844
1092
|
"sends Haven only the public signing address plus a proof signature.",
|
|
845
1093
|
"",
|
|
846
1094
|
"Usage:",
|
|
847
|
-
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
|
|
1095
|
+
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
|
|
848
1096
|
"",
|
|
849
1097
|
"Options:",
|
|
850
1098
|
" --setup <token> Short-lived setup token from Haven.",
|
|
@@ -852,6 +1100,8 @@ function helpText() {
|
|
|
852
1100
|
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
|
|
853
1101
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
854
1102
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1103
|
+
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1104
|
+
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
855
1105
|
" --help Show this help.",
|
|
856
1106
|
"",
|
|
857
1107
|
"The connector never prints the private key and never sends it to Haven."
|