@haven_ai/connect 0.1.1-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 +4 -4
- package/dist/cli.cjs +187 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +188 -75
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +187 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +188 -75
- package/dist/index.js.map +1 -1
- package/package.json +5 -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@0.1.
|
|
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:
|
|
@@ -22,6 +22,6 @@ the locally held signer key and the user-approved on-chain Haven wallet rules.
|
|
|
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
24
|
|
|
25
|
-
Use `--ack-
|
|
26
|
-
|
|
27
|
-
|
|
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,7 @@ 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');
|
|
11
12
|
var signer = require('@haven_ai/signer');
|
|
12
13
|
|
|
13
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -51,10 +52,13 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
51
52
|
body: JSON.stringify({
|
|
52
53
|
runtime: input.runtime,
|
|
53
54
|
connector_version: input.connectorVersion,
|
|
55
|
+
runtime_mcp_mode: input.runtimeMcpMode,
|
|
54
56
|
hosted_mcp_configured: input.hostedMcpConfigured,
|
|
55
57
|
local_signer_configured: input.localSignerConfigured,
|
|
58
|
+
local_mcp_configured: input.localMcpConfigured,
|
|
56
59
|
credential_files_written: input.credentialFilesWritten,
|
|
57
60
|
signer_acknowledged: input.signerAcknowledged,
|
|
61
|
+
local_mcp_acknowledged: input.localMcpAcknowledged,
|
|
58
62
|
activation_command_available: input.activationCommandAvailable,
|
|
59
63
|
probe_result: input.probeResult,
|
|
60
64
|
restart_required: input.restartRequired,
|
|
@@ -137,6 +141,7 @@ async function writeCredentialFiles(input) {
|
|
|
137
141
|
signerPath,
|
|
138
142
|
{
|
|
139
143
|
delegate_key: input.delegateKey,
|
|
144
|
+
delegate_address: input.delegateAddress,
|
|
140
145
|
agent_id: input.agentId,
|
|
141
146
|
safe_address: input.safeAddress,
|
|
142
147
|
chain_id: input.chainId,
|
|
@@ -156,6 +161,7 @@ async function writeCredentialFiles(input) {
|
|
|
156
161
|
network: input.network,
|
|
157
162
|
api_url: input.apiUrl,
|
|
158
163
|
hosted_mcp_url: input.hostedMcpUrl,
|
|
164
|
+
agent_budget: input.agentBudget,
|
|
159
165
|
note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
|
|
160
166
|
},
|
|
161
167
|
input.warn
|
|
@@ -216,6 +222,8 @@ async function writeRuntimeConfig(input) {
|
|
|
216
222
|
return {
|
|
217
223
|
hostedConfigured: false,
|
|
218
224
|
signerConfigured: false,
|
|
225
|
+
localMcpConfigured: false,
|
|
226
|
+
runtimeMcpMode: "manual",
|
|
219
227
|
target: "manual runtime setup",
|
|
220
228
|
changed: false,
|
|
221
229
|
restartRequired: true,
|
|
@@ -257,17 +265,13 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
257
265
|
return `${JSON.stringify(config, null, 2)}
|
|
258
266
|
`;
|
|
259
267
|
}
|
|
260
|
-
function mergeCodexToml(existingToml,
|
|
268
|
+
function mergeCodexToml(existingToml, identityPath, signerPath) {
|
|
261
269
|
let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
262
270
|
next = next.trimEnd();
|
|
263
271
|
const block = [
|
|
264
272
|
"[mcp_servers.haven]",
|
|
265
|
-
`url = ${tomlString(hostedMcpUrl)}`,
|
|
266
|
-
'bearer_token_env_var = "HAVEN_TOKEN"',
|
|
267
|
-
"",
|
|
268
|
-
"[mcp_servers.haven_signer]",
|
|
269
273
|
'command = "npx"',
|
|
270
|
-
`args = ["-y", ${tomlString(
|
|
274
|
+
`args = ["-y", ${tomlString(localMcpPackageName())}, "--identity", ${tomlString(identityPath)}, "--signer", ${tomlString(signerPath)}]`
|
|
271
275
|
].join("\n");
|
|
272
276
|
return `${next ? `${next}
|
|
273
277
|
|
|
@@ -287,6 +291,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
287
291
|
return {
|
|
288
292
|
hostedConfigured: true,
|
|
289
293
|
signerConfigured: true,
|
|
294
|
+
localMcpConfigured: false,
|
|
295
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
290
296
|
target: configTargetLabel(input.runtime),
|
|
291
297
|
changed: existing !== merged,
|
|
292
298
|
restartRequired: input.runtime === "claude-desktop",
|
|
@@ -296,6 +302,8 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
296
302
|
return {
|
|
297
303
|
hostedConfigured: false,
|
|
298
304
|
signerConfigured: false,
|
|
305
|
+
localMcpConfigured: false,
|
|
306
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
299
307
|
target: configTargetLabel(input.runtime),
|
|
300
308
|
changed: false,
|
|
301
309
|
restartRequired: true,
|
|
@@ -306,41 +314,29 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
306
314
|
}
|
|
307
315
|
async function writeCodexConfig(input) {
|
|
308
316
|
const target = codexConfigPath(input.homeDir);
|
|
309
|
-
const envTarget = path.join(input.credentialDirectory, "identity.env");
|
|
310
|
-
const launchTarget = path.join(input.credentialDirectory, "start-codex.sh");
|
|
311
317
|
try {
|
|
312
318
|
const existing = await readOptional(target);
|
|
313
|
-
const merged = mergeCodexToml(existing ?? "", input.
|
|
319
|
+
const merged = mergeCodexToml(existing ?? "", input.identityPath, input.signerPath);
|
|
314
320
|
await writeOwnerOnlyText(target, merged);
|
|
315
|
-
await writeOwnerOnlyText(envTarget, `export HAVEN_TOKEN=${shellToken(input.apiKey)}
|
|
316
|
-
`);
|
|
317
|
-
await writeOwnerExecutableText(
|
|
318
|
-
launchTarget,
|
|
319
|
-
[
|
|
320
|
-
"#!/bin/sh",
|
|
321
|
-
"set -eu",
|
|
322
|
-
`. ${shellToken(envTarget)}`,
|
|
323
|
-
'exec codex "$@"',
|
|
324
|
-
""
|
|
325
|
-
].join("\n")
|
|
326
|
-
);
|
|
327
321
|
return {
|
|
328
|
-
hostedConfigured:
|
|
322
|
+
hostedConfigured: false,
|
|
329
323
|
signerConfigured: true,
|
|
324
|
+
localMcpConfigured: true,
|
|
325
|
+
runtimeMcpMode: "local_stdio",
|
|
330
326
|
target: "Codex CLI config",
|
|
331
327
|
changed: existing !== merged,
|
|
332
328
|
restartRequired: true,
|
|
333
|
-
activationCommand: shellToken(launchTarget),
|
|
334
329
|
messages: [
|
|
335
|
-
"Updated Haven MCP
|
|
336
|
-
"
|
|
337
|
-
`Restart Codex with: ${shellToken(launchTarget)}`
|
|
330
|
+
"Updated local Haven MCP entry in Codex CLI config.",
|
|
331
|
+
"After Haven approval, restart Codex normally so it can load Haven tools."
|
|
338
332
|
]
|
|
339
333
|
};
|
|
340
334
|
} catch (err) {
|
|
341
335
|
return {
|
|
342
336
|
hostedConfigured: false,
|
|
343
337
|
signerConfigured: false,
|
|
338
|
+
localMcpConfigured: false,
|
|
339
|
+
runtimeMcpMode: "local_stdio",
|
|
344
340
|
target: "Codex CLI config",
|
|
345
341
|
changed: false,
|
|
346
342
|
restartRequired: true,
|
|
@@ -362,11 +358,6 @@ async function writeOwnerOnlyText(path$1, value) {
|
|
|
362
358
|
await promises.writeFile(path$1, value, { mode: 384 });
|
|
363
359
|
await promises.chmod(path$1, 384).catch(() => void 0);
|
|
364
360
|
}
|
|
365
|
-
async function writeOwnerExecutableText(path$1, value) {
|
|
366
|
-
await promises.mkdir(path.dirname(path$1), { recursive: true, mode: 448 });
|
|
367
|
-
await promises.writeFile(path$1, value, { mode: 448 });
|
|
368
|
-
await promises.chmod(path$1, 448).catch(() => void 0);
|
|
369
|
-
}
|
|
370
361
|
function parseJsonObject(value) {
|
|
371
362
|
const parsed = JSON.parse(value);
|
|
372
363
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -395,9 +386,6 @@ function removeTomlTable(toml, table) {
|
|
|
395
386
|
function tomlString(value) {
|
|
396
387
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
397
388
|
}
|
|
398
|
-
function shellToken(value) {
|
|
399
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
400
|
-
}
|
|
401
389
|
function cursorConfigPath(homeDir = os.homedir()) {
|
|
402
390
|
return path.resolve(homeDir, ".cursor", "mcp.json");
|
|
403
391
|
}
|
|
@@ -435,6 +423,85 @@ function configTargetLabel(runtime) {
|
|
|
435
423
|
function signerPackageName() {
|
|
436
424
|
return `@haven_ai/signer@${signer.SIGNER_VERSION}`;
|
|
437
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
|
+
}
|
|
438
505
|
async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
|
|
439
506
|
let response;
|
|
440
507
|
try {
|
|
@@ -589,7 +656,7 @@ async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
|
589
656
|
const decision = await signer.ensureSignerConsent(input, {
|
|
590
657
|
credentialsPath: signerPath,
|
|
591
658
|
writeAck: true,
|
|
592
|
-
out: log ? { write: (chunk) =>
|
|
659
|
+
out: log ? { write: (chunk) => writeLogChunk2(log, chunk) } : void 0
|
|
593
660
|
});
|
|
594
661
|
return {
|
|
595
662
|
acknowledged: decision.ok,
|
|
@@ -648,7 +715,7 @@ async function readSignerAckFile(path) {
|
|
|
648
715
|
return null;
|
|
649
716
|
}
|
|
650
717
|
}
|
|
651
|
-
function
|
|
718
|
+
function writeLogChunk2(log, chunk) {
|
|
652
719
|
const message = String(chunk).trimEnd();
|
|
653
720
|
if (message) log(message);
|
|
654
721
|
}
|
|
@@ -658,23 +725,28 @@ var execFileAsync = util.promisify(child_process.execFile);
|
|
|
658
725
|
async function installRuntime(input, deps = {}) {
|
|
659
726
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
660
727
|
const profile = runtimeProfile(runtime, deps.env);
|
|
661
|
-
const
|
|
662
|
-
const
|
|
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);
|
|
663
732
|
if (runtime === "other") {
|
|
664
733
|
const signerCredentialReady2 = await probeLocalSignerCredential(input.signerPath);
|
|
665
|
-
const signerReady = signerCredentialReady2 && signerConsent
|
|
734
|
+
const signerReady = signerCredentialReady2 && signerConsent?.acknowledged;
|
|
666
735
|
return {
|
|
667
736
|
runtime,
|
|
737
|
+
runtimeMcpMode: "manual",
|
|
668
738
|
hostedMcpConfigured: false,
|
|
669
739
|
localSignerConfigured: false,
|
|
740
|
+
localMcpConfigured: false,
|
|
670
741
|
probeResult: signerReady ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
|
|
671
742
|
restartRequired: true,
|
|
672
743
|
nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
|
|
673
744
|
errorCode: "manual_runtime_setup_required",
|
|
674
745
|
configTarget: "manual runtime setup",
|
|
675
|
-
signerAcknowledged: signerConsent
|
|
746
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
747
|
+
localMcpAcknowledged: false,
|
|
676
748
|
messages: [
|
|
677
|
-
...
|
|
749
|
+
...consentMessages,
|
|
678
750
|
"Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."
|
|
679
751
|
]
|
|
680
752
|
};
|
|
@@ -683,6 +755,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
683
755
|
runtime,
|
|
684
756
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
685
757
|
apiKey: input.apiKey,
|
|
758
|
+
identityPath: input.identityPath,
|
|
686
759
|
signerPath: input.signerPath,
|
|
687
760
|
credentialDirectory: input.credentialDirectory,
|
|
688
761
|
homeDir: deps.homeDir
|
|
@@ -692,21 +765,25 @@ async function installRuntime(input, deps = {}) {
|
|
|
692
765
|
probeLocalSignerCredential(input.signerPath)
|
|
693
766
|
]);
|
|
694
767
|
const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
|
|
695
|
-
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);
|
|
696
770
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
697
|
-
const errorCode = configResult.errorCode ?? signerConsentErrorCode(signerCredentialReady, signerConsent);
|
|
771
|
+
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpConsentErrorCode(signerCredentialReady, localMcpConsent) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
698
772
|
return {
|
|
699
773
|
runtime,
|
|
774
|
+
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
700
775
|
hostedMcpConfigured: hostedOk,
|
|
701
776
|
localSignerConfigured: signerOk,
|
|
702
|
-
|
|
777
|
+
localMcpConfigured: localMcpOk,
|
|
778
|
+
probeResult: buildProbeResult(configResult.runtimeMcpMode, configResult.hostedConfigured, hostedProbe.status, signerOk, localMcpOk),
|
|
703
779
|
restartRequired,
|
|
704
780
|
nextUserAction: nextAction(runtime, profile.restartMode, errorCode),
|
|
705
781
|
errorCode,
|
|
706
782
|
configTarget: configResult.target,
|
|
707
|
-
signerAcknowledged: signerConsent
|
|
783
|
+
signerAcknowledged: signerConsent?.acknowledged,
|
|
784
|
+
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
708
785
|
activationCommand: configResult.activationCommand,
|
|
709
|
-
messages: [...
|
|
786
|
+
messages: [...consentMessages, ...configResult.messages]
|
|
710
787
|
};
|
|
711
788
|
}
|
|
712
789
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -722,39 +799,36 @@ async function configureClaudeCode(input, deps) {
|
|
|
722
799
|
await runCommand("claude", [
|
|
723
800
|
"mcp",
|
|
724
801
|
"add",
|
|
725
|
-
"--transport",
|
|
726
|
-
"http",
|
|
727
802
|
"haven",
|
|
728
|
-
input.hostedMcpUrl,
|
|
729
|
-
"--header",
|
|
730
|
-
`Authorization: Bearer ${input.apiKey}`
|
|
731
|
-
]);
|
|
732
|
-
await runCommand("claude", [
|
|
733
|
-
"mcp",
|
|
734
|
-
"add",
|
|
735
|
-
"haven-signer",
|
|
736
803
|
"--",
|
|
737
804
|
"npx",
|
|
738
805
|
"-y",
|
|
739
|
-
|
|
740
|
-
"--
|
|
806
|
+
localMcpPackageName2(),
|
|
807
|
+
"--identity",
|
|
808
|
+
input.identityPath,
|
|
809
|
+
"--signer",
|
|
741
810
|
input.signerPath
|
|
742
811
|
]);
|
|
812
|
+
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
743
813
|
return {
|
|
744
|
-
hostedConfigured:
|
|
814
|
+
hostedConfigured: false,
|
|
745
815
|
signerConfigured: true,
|
|
816
|
+
localMcpConfigured: true,
|
|
817
|
+
runtimeMcpMode: "local_stdio",
|
|
746
818
|
target: "Claude Code MCP config",
|
|
747
819
|
changed: true,
|
|
748
820
|
restartRequired: true,
|
|
749
821
|
messages: [
|
|
750
|
-
"Updated Haven MCP
|
|
751
|
-
"
|
|
822
|
+
"Updated local Haven MCP entry with Claude Code.",
|
|
823
|
+
"After Haven approval, restart Claude Code normally so it can load Haven tools."
|
|
752
824
|
]
|
|
753
825
|
};
|
|
754
826
|
} catch (err) {
|
|
755
827
|
return {
|
|
756
828
|
hostedConfigured: false,
|
|
757
829
|
signerConfigured: false,
|
|
830
|
+
localMcpConfigured: false,
|
|
831
|
+
runtimeMcpMode: "local_stdio",
|
|
758
832
|
target: "Claude Code MCP config",
|
|
759
833
|
changed: false,
|
|
760
834
|
restartRequired: true,
|
|
@@ -769,13 +843,28 @@ async function configureClaudeCode(input, deps) {
|
|
|
769
843
|
async function defaultRunCommand(command, args) {
|
|
770
844
|
await execFileAsync(command, args, { timeout: 1e4 });
|
|
771
845
|
}
|
|
772
|
-
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
|
+
}
|
|
773
850
|
const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
|
|
774
851
|
const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
|
|
775
852
|
return `${hostedPart}_${signerPart}`.slice(0, 120);
|
|
776
853
|
}
|
|
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
|
+
}
|
|
777
866
|
async function resolveSignerConsent(input, messages) {
|
|
778
|
-
if (input.ackSigner) {
|
|
867
|
+
if (input.ackSigner || input.ackLocalTools) {
|
|
779
868
|
const status = await acknowledgeLocalSignerConsent(input.signerPath, (message) => messages.push(message));
|
|
780
869
|
if (status.acknowledged) {
|
|
781
870
|
messages.push("Prepared the local Haven signer acknowledgement.");
|
|
@@ -788,24 +877,32 @@ async function resolveSignerConsent(input, messages) {
|
|
|
788
877
|
}
|
|
789
878
|
function signerConsentErrorCode(signerCredentialReady, signerConsent) {
|
|
790
879
|
if (!signerCredentialReady) return "local_signer_credential_unavailable";
|
|
791
|
-
if (!signerConsent
|
|
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";
|
|
792
886
|
return void 0;
|
|
793
887
|
}
|
|
794
888
|
function nextAction(runtime, restartMode, errorCode) {
|
|
795
889
|
if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
|
|
796
890
|
if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
|
|
797
|
-
if (runtime === "codex-cli") return "
|
|
891
|
+
if (runtime === "codex-cli") return "return_to_haven_for_wallet_approval_then_restart_codex";
|
|
798
892
|
if (runtime === "claude-code") return "return_to_haven_for_wallet_approval_then_restart_claude_code";
|
|
799
893
|
if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
|
|
800
894
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
801
895
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
802
896
|
}
|
|
803
|
-
function
|
|
804
|
-
return `@haven_ai/
|
|
897
|
+
function localMcpPackageName2() {
|
|
898
|
+
return `@haven_ai/mcp@${mcp.MCP_VERSION}`;
|
|
899
|
+
}
|
|
900
|
+
function usesLocalMcp(runtime) {
|
|
901
|
+
return runtime === "codex-cli" || runtime === "claude-code";
|
|
805
902
|
}
|
|
806
903
|
|
|
807
904
|
// src/runtime.ts
|
|
808
|
-
var CONNECTOR_VERSION = "0.1.
|
|
905
|
+
var CONNECTOR_VERSION = "0.1.2";
|
|
809
906
|
async function runConnect(options, deps = {}) {
|
|
810
907
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
811
908
|
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
@@ -851,9 +948,15 @@ async function runConnect(options, deps = {}) {
|
|
|
851
948
|
agentId: registration.agent_id,
|
|
852
949
|
apiKey: localApiKey,
|
|
853
950
|
delegateKey: localKey.privateKey,
|
|
951
|
+
delegateAddress: localKey.address,
|
|
854
952
|
safeAddress: setup.haven_wallet.address,
|
|
855
953
|
chainId: setup.haven_wallet.chain_id,
|
|
856
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
|
+
})),
|
|
857
960
|
apiUrl: options.apiBaseUrl,
|
|
858
961
|
hostedMcpUrl: registration.hosted_mcp_url,
|
|
859
962
|
warn: log
|
|
@@ -868,17 +971,21 @@ async function runConnect(options, deps = {}) {
|
|
|
868
971
|
identityPath: credentialPaths.identityPath,
|
|
869
972
|
credentialDirectory: credentialPaths.directory,
|
|
870
973
|
environmentLabel: options.environmentLabel ?? "Local workspace",
|
|
871
|
-
ackSigner: options.ackSigner
|
|
974
|
+
ackSigner: options.ackSigner,
|
|
975
|
+
ackLocalTools: options.ackLocalTools
|
|
872
976
|
});
|
|
873
977
|
printRuntimeInstall(runtimeInstall, log);
|
|
874
978
|
try {
|
|
875
979
|
await api.updateInstallStatus(registration.setup_id, localApiKey, {
|
|
876
980
|
runtime: runtimeInstall.runtime,
|
|
877
981
|
connectorVersion,
|
|
982
|
+
runtimeMcpMode: runtimeInstall.runtimeMcpMode,
|
|
878
983
|
hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
|
|
879
984
|
localSignerConfigured: runtimeInstall.localSignerConfigured,
|
|
985
|
+
localMcpConfigured: runtimeInstall.localMcpConfigured,
|
|
880
986
|
credentialFilesWritten: true,
|
|
881
987
|
signerAcknowledged: runtimeInstall.signerAcknowledged,
|
|
988
|
+
localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
|
|
882
989
|
activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
|
|
883
990
|
probeResult: runtimeInstall.probeResult,
|
|
884
991
|
restartRequired: runtimeInstall.restartRequired,
|
|
@@ -891,7 +998,7 @@ async function runConnect(options, deps = {}) {
|
|
|
891
998
|
}
|
|
892
999
|
log("Return to Haven to approve the agent rules.");
|
|
893
1000
|
if (runtimeInstall.restartRequired) {
|
|
894
|
-
log("
|
|
1001
|
+
log("After approval, restart this agent normally so it can load Haven tools.");
|
|
895
1002
|
}
|
|
896
1003
|
return {
|
|
897
1004
|
setupId: registration.setup_id,
|
|
@@ -917,10 +1024,12 @@ function secureLogger(log) {
|
|
|
917
1024
|
}
|
|
918
1025
|
function printRuntimeInstall(result, log) {
|
|
919
1026
|
for (const message of result.messages) log(message);
|
|
920
|
-
if (result.
|
|
1027
|
+
if (result.localMcpConfigured) {
|
|
1028
|
+
log("Configured local Haven MCP tools.");
|
|
1029
|
+
} else if (result.hostedMcpConfigured) {
|
|
921
1030
|
log("Configured hosted Haven MCP identity.");
|
|
922
1031
|
} else {
|
|
923
|
-
log("
|
|
1032
|
+
log("Haven MCP tools still need runtime setup.");
|
|
924
1033
|
}
|
|
925
1034
|
if (result.localSignerConfigured) {
|
|
926
1035
|
log("Configured local Haven signer.");
|
|
@@ -950,8 +1059,11 @@ function parseArgs(argv, env = process.env) {
|
|
|
950
1059
|
options.credentialsDir = requireValue(argv, ++i, arg);
|
|
951
1060
|
} else if (arg === "--environment-label") {
|
|
952
1061
|
options.environmentLabel = requireValue(argv, ++i, arg);
|
|
1062
|
+
} else if (arg === "--ack-local-tools") {
|
|
1063
|
+
options.ackLocalTools = true;
|
|
953
1064
|
} else if (arg === "--ack-signer") {
|
|
954
1065
|
options.ackSigner = true;
|
|
1066
|
+
options.ackLocalTools = true;
|
|
955
1067
|
} else if (arg === "--version") {
|
|
956
1068
|
process.stdout.write(`${CONNECTOR_VERSION}
|
|
957
1069
|
`);
|
|
@@ -980,7 +1092,7 @@ function helpText() {
|
|
|
980
1092
|
"sends Haven only the public signing address plus a proof signature.",
|
|
981
1093
|
"",
|
|
982
1094
|
"Usage:",
|
|
983
|
-
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-
|
|
1095
|
+
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --ack-local-tools --runtime claude-code",
|
|
984
1096
|
"",
|
|
985
1097
|
"Options:",
|
|
986
1098
|
" --setup <token> Short-lived setup token from Haven.",
|
|
@@ -988,7 +1100,8 @@ function helpText() {
|
|
|
988
1100
|
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
|
|
989
1101
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
990
1102
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
991
|
-
" --ack-
|
|
1103
|
+
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1104
|
+
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
992
1105
|
" --help Show this help.",
|
|
993
1106
|
"",
|
|
994
1107
|
"The connector never prints the private key and never sends it to Haven."
|