@haven_ai/connect 0.1.6-alpha → 0.1.10-alpha.0
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 +1 -1
- package/dist/cli.cjs +146 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +146 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +150 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +147 -23
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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@
|
|
11
|
+
npx -y @haven_ai/connect@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:
|
package/dist/cli.cjs
CHANGED
|
@@ -9,6 +9,7 @@ var path = require('path');
|
|
|
9
9
|
var child_process = require('child_process');
|
|
10
10
|
var util = require('util');
|
|
11
11
|
var mcp = require('@haven_ai/mcp');
|
|
12
|
+
var sdk = require('@haven_ai/sdk');
|
|
12
13
|
var signer = require('@haven_ai/signer');
|
|
13
14
|
|
|
14
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -60,6 +61,7 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
60
61
|
signer_acknowledged: input.signerAcknowledged,
|
|
61
62
|
local_mcp_acknowledged: input.localMcpAcknowledged,
|
|
62
63
|
activation_command_available: input.activationCommandAvailable,
|
|
64
|
+
skill_installed: input.skillInstalled,
|
|
63
65
|
probe_result: input.probeResult,
|
|
64
66
|
restart_required: input.restartRequired,
|
|
65
67
|
next_user_action: input.nextUserAction,
|
|
@@ -212,9 +214,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
212
214
|
mcpPackage: "@haven_ai/mcp",
|
|
213
215
|
mcpVersion: mcp.MCP_VERSION,
|
|
214
216
|
sdkPackage: "@haven_ai/sdk",
|
|
215
|
-
sdkVersion: "0.1.
|
|
217
|
+
sdkVersion: "0.1.10-alpha.0",
|
|
216
218
|
signerPackage: "@haven_ai/signer",
|
|
217
|
-
signerVersion: "0.1.
|
|
219
|
+
signerVersion: "0.1.10-alpha.0",
|
|
218
220
|
minimumNodeVersion: "20.0.0",
|
|
219
221
|
supportedClients: ["codex-cli", "codex-desktop", "claude-code"],
|
|
220
222
|
requiredTools: mcp.registeredToolNames()
|
|
@@ -419,6 +421,26 @@ function mergeCodexToml(existingToml, localMcpCommand) {
|
|
|
419
421
|
validateCodexToml(block, "Generated Codex Haven config");
|
|
420
422
|
const merged = `${next ? `${next}
|
|
421
423
|
|
|
424
|
+
` : ""}${block}
|
|
425
|
+
`;
|
|
426
|
+
return merged;
|
|
427
|
+
}
|
|
428
|
+
function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
|
|
429
|
+
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
430
|
+
next = next.trimEnd();
|
|
431
|
+
const block = [
|
|
432
|
+
"[mcp_servers.haven]",
|
|
433
|
+
`url = ${tomlString(hostedMcpUrl)}`,
|
|
434
|
+
`http_headers = { "Authorization" = ${tomlString(`Bearer ${apiKey}`)} }`,
|
|
435
|
+
"",
|
|
436
|
+
"[mcp_servers.haven_signer]",
|
|
437
|
+
'command = "npx"',
|
|
438
|
+
`args = ["-y", ${tomlString(signerPackageSpec())}, "--credentials", ${tomlString(signerPath)}]`,
|
|
439
|
+
"startup_timeout_sec = 120"
|
|
440
|
+
].join("\n");
|
|
441
|
+
validateCodexToml(block, "Generated Codex Haven config");
|
|
442
|
+
const merged = `${next ? `${next}
|
|
443
|
+
|
|
422
444
|
` : ""}${block}
|
|
423
445
|
`;
|
|
424
446
|
return merged;
|
|
@@ -459,27 +481,46 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
459
481
|
}
|
|
460
482
|
async function writeCodexConfig(input) {
|
|
461
483
|
const target = codexConfigPath(input.homeDir);
|
|
484
|
+
const local = input.mode === "local";
|
|
485
|
+
const restartMessage = runtimeRequiresHardRestart(input.runtime) ? "After Haven approval, restart Codex Desktop so it can load Haven tools." : "After Haven approval, Haven tools should appear in your next Codex message. If they don't, restart Codex to load them.";
|
|
462
486
|
try {
|
|
463
487
|
const existing = await readOptional(target);
|
|
464
|
-
if (
|
|
465
|
-
|
|
488
|
+
if (local) {
|
|
489
|
+
if (!input.localMcpCommand) {
|
|
490
|
+
throw new Error("local MCP wrapper command is required");
|
|
491
|
+
}
|
|
492
|
+
const merged2 = mergeCodexToml(existing ?? "", input.localMcpCommand);
|
|
493
|
+
await writeOwnerOnlyText(target, merged2);
|
|
494
|
+
return {
|
|
495
|
+
hostedConfigured: false,
|
|
496
|
+
signerConfigured: true,
|
|
497
|
+
localMcpConfigured: true,
|
|
498
|
+
runtimeMcpMode: "local_stdio",
|
|
499
|
+
target: configTargetLabel(input.runtime),
|
|
500
|
+
changed: existing !== merged2,
|
|
501
|
+
restartRequired: true,
|
|
502
|
+
messages: [
|
|
503
|
+
`Updated local Haven MCP entry in ${configTargetLabel(input.runtime)}.`,
|
|
504
|
+
// Codex Desktop loads MCP servers at app launch; Codex CLI typically
|
|
505
|
+
// picks them up in the next session. Branch the copy so desktop users
|
|
506
|
+
// get the unambiguous instruction.
|
|
507
|
+
restartMessage
|
|
508
|
+
]
|
|
509
|
+
};
|
|
466
510
|
}
|
|
467
|
-
const merged =
|
|
511
|
+
const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, input.signerPath);
|
|
468
512
|
await writeOwnerOnlyText(target, merged);
|
|
469
513
|
return {
|
|
470
|
-
hostedConfigured:
|
|
514
|
+
hostedConfigured: true,
|
|
471
515
|
signerConfigured: true,
|
|
472
|
-
localMcpConfigured:
|
|
473
|
-
runtimeMcpMode: "
|
|
516
|
+
localMcpConfigured: false,
|
|
517
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
474
518
|
target: configTargetLabel(input.runtime),
|
|
475
519
|
changed: existing !== merged,
|
|
476
520
|
restartRequired: true,
|
|
477
521
|
messages: [
|
|
478
|
-
`Updated
|
|
479
|
-
|
|
480
|
-
// picks them up in the next session. Branch the copy so desktop users
|
|
481
|
-
// get the unambiguous instruction.
|
|
482
|
-
runtimeRequiresHardRestart(input.runtime) ? "After Haven approval, restart Codex Desktop so it can load Haven tools." : "After Haven approval, Haven tools should appear in your next Codex message. If they don't, restart Codex to load them."
|
|
522
|
+
`Updated Haven MCP entries in ${configTargetLabel(input.runtime)}.`,
|
|
523
|
+
restartMessage
|
|
483
524
|
]
|
|
484
525
|
};
|
|
485
526
|
} catch (err) {
|
|
@@ -488,7 +529,7 @@ async function writeCodexConfig(input) {
|
|
|
488
529
|
hostedConfigured: false,
|
|
489
530
|
signerConfigured: false,
|
|
490
531
|
localMcpConfigured: false,
|
|
491
|
-
runtimeMcpMode: "local_stdio",
|
|
532
|
+
runtimeMcpMode: local ? "local_stdio" : "hosted_plus_signer",
|
|
492
533
|
target: configTargetLabel(input.runtime),
|
|
493
534
|
changed: false,
|
|
494
535
|
restartRequired: true,
|
|
@@ -868,7 +909,7 @@ async function probeLocalSignerCredential(signerPath) {
|
|
|
868
909
|
}
|
|
869
910
|
}
|
|
870
911
|
async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
|
|
871
|
-
return new Promise((
|
|
912
|
+
return new Promise((resolve7) => {
|
|
872
913
|
const child = child_process.spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
873
914
|
let stdout = "";
|
|
874
915
|
let settled = false;
|
|
@@ -878,7 +919,7 @@ async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4)
|
|
|
878
919
|
settled = true;
|
|
879
920
|
clearTimeout(timeout);
|
|
880
921
|
child.kill();
|
|
881
|
-
|
|
922
|
+
resolve7(result);
|
|
882
923
|
};
|
|
883
924
|
const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
|
|
884
925
|
child.on("error", () => finish({ status: "process_error" }));
|
|
@@ -1191,7 +1232,7 @@ var execFileAsync2 = util.promisify(child_process.execFile);
|
|
|
1191
1232
|
async function installRuntime(input, deps = {}) {
|
|
1192
1233
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
1193
1234
|
const profile = runtimeProfile(runtime, deps.env);
|
|
1194
|
-
const localRuntime =
|
|
1235
|
+
const localRuntime = input.localMcp === true && supportsLocalMcp(runtime);
|
|
1195
1236
|
const consentMessages = [];
|
|
1196
1237
|
const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
|
|
1197
1238
|
const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
|
|
@@ -1248,7 +1289,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1248
1289
|
]
|
|
1249
1290
|
};
|
|
1250
1291
|
}
|
|
1251
|
-
const configResult = runtime === "claude-code" ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await writeRuntimeConfig({
|
|
1292
|
+
const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input) : await writeRuntimeConfig({
|
|
1252
1293
|
runtime,
|
|
1253
1294
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
1254
1295
|
apiKey: input.apiKey,
|
|
@@ -1256,7 +1297,8 @@ async function installRuntime(input, deps = {}) {
|
|
|
1256
1297
|
signerPath: input.signerPath,
|
|
1257
1298
|
credentialDirectory: input.credentialDirectory,
|
|
1258
1299
|
localMcpCommand: localRuntimeInstall?.command,
|
|
1259
|
-
homeDir: deps.homeDir
|
|
1300
|
+
homeDir: deps.homeDir,
|
|
1301
|
+
mode: localRuntime ? "local" : "hosted"
|
|
1260
1302
|
});
|
|
1261
1303
|
const localProbePromise = configResult.runtimeMcpMode === "local_stdio" && localRuntimeInstall ? runLocalMcpProbe(localRuntimeInstall, deps) : Promise.resolve(void 0);
|
|
1262
1304
|
const [hostedProbe, signerCredentialReady, localMcpProbe] = await Promise.all([
|
|
@@ -1270,6 +1312,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1270
1312
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1271
1313
|
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1272
1314
|
const localProbeMessages = localMcpProbe && localMcpProbe.status !== "ok" ? [`Local Haven MCP handshake failed: ${localMcpProbe.status}.`] : localMcpProbe?.status === "ok" ? ["Verified local Haven MCP tools with a stdio handshake."] : [];
|
|
1315
|
+
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1273
1316
|
return {
|
|
1274
1317
|
runtime,
|
|
1275
1318
|
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
@@ -1284,7 +1327,8 @@ async function installRuntime(input, deps = {}) {
|
|
|
1284
1327
|
signerAcknowledged: signerConsent?.acknowledged,
|
|
1285
1328
|
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
1286
1329
|
activationCommand: configResult.activationCommand,
|
|
1287
|
-
|
|
1330
|
+
skillInstalled: skillInstall?.installed,
|
|
1331
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1288
1332
|
};
|
|
1289
1333
|
}
|
|
1290
1334
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1340,9 +1384,75 @@ async function configureClaudeCode(deps, localMcpCommand) {
|
|
|
1340
1384
|
};
|
|
1341
1385
|
}
|
|
1342
1386
|
}
|
|
1387
|
+
async function configureClaudeCodeHosted(deps, input) {
|
|
1388
|
+
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
1389
|
+
const hostedJson = JSON.stringify({
|
|
1390
|
+
type: "http",
|
|
1391
|
+
url: input.hostedMcpUrl,
|
|
1392
|
+
headers: { Authorization: `Bearer ${input.apiKey}` }
|
|
1393
|
+
});
|
|
1394
|
+
const signerJson = JSON.stringify({
|
|
1395
|
+
type: "stdio",
|
|
1396
|
+
command: "npx",
|
|
1397
|
+
args: ["-y", signerPackageSpec(), "--credentials", input.signerPath],
|
|
1398
|
+
env: {}
|
|
1399
|
+
});
|
|
1400
|
+
try {
|
|
1401
|
+
await runCommand("claude", ["mcp", "remove", "haven"]).catch(() => void 0);
|
|
1402
|
+
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
1403
|
+
await runCommand("claude", ["mcp", "add-json", "haven", hostedJson, "--scope", "user"]);
|
|
1404
|
+
await runCommand("claude", ["mcp", "add-json", "haven-signer", signerJson, "--scope", "user"]);
|
|
1405
|
+
const verified = await runCommand("claude", ["mcp", "get", "haven"]).then(() => true).catch(() => false);
|
|
1406
|
+
return {
|
|
1407
|
+
hostedConfigured: true,
|
|
1408
|
+
signerConfigured: true,
|
|
1409
|
+
localMcpConfigured: false,
|
|
1410
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
1411
|
+
target: "Claude Code MCP config",
|
|
1412
|
+
changed: true,
|
|
1413
|
+
restartRequired: true,
|
|
1414
|
+
messages: [
|
|
1415
|
+
"Updated hosted Haven MCP and local signer entries with Claude Code.",
|
|
1416
|
+
...verified ? ["Verified Claude Code MCP entry."] : [],
|
|
1417
|
+
"After Haven approval, Haven tools should appear in your next Claude Code message. If they don't, restart the session to load them."
|
|
1418
|
+
]
|
|
1419
|
+
};
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
return {
|
|
1422
|
+
hostedConfigured: false,
|
|
1423
|
+
signerConfigured: false,
|
|
1424
|
+
localMcpConfigured: false,
|
|
1425
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
1426
|
+
target: "Claude Code MCP config",
|
|
1427
|
+
changed: false,
|
|
1428
|
+
restartRequired: true,
|
|
1429
|
+
messages: [
|
|
1430
|
+
`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
|
|
1431
|
+
"Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
|
|
1432
|
+
],
|
|
1433
|
+
errorCode: "claude_code_config_failed"
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1343
1437
|
async function defaultRunCommand(command, args) {
|
|
1344
1438
|
await execFileAsync2(command, args, { timeout: 1e4 });
|
|
1345
1439
|
}
|
|
1440
|
+
async function installClaudeSkill(homeDir) {
|
|
1441
|
+
try {
|
|
1442
|
+
const skillDir = path.resolve(homeDir ?? os.homedir(), ".claude", "skills", sdk.SKILL_FOLDER_NAME);
|
|
1443
|
+
await promises.mkdir(skillDir, { recursive: true });
|
|
1444
|
+
await promises.writeFile(path.join(skillDir, "SKILL.md"), sdk.HAVEN_SKILL_MD, "utf8");
|
|
1445
|
+
return {
|
|
1446
|
+
installed: true,
|
|
1447
|
+
messages: ["Installed the generic Haven payment skill (~/.claude/skills/haven-pay). It contains no secrets."]
|
|
1448
|
+
};
|
|
1449
|
+
} catch (err) {
|
|
1450
|
+
return {
|
|
1451
|
+
installed: false,
|
|
1452
|
+
messages: [`Could not install the Haven payment skill: ${err instanceof Error ? err.message : String(err)}. Download it from the Haven dashboard instead.`]
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1346
1456
|
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady, localMcpProbeStatus) {
|
|
1347
1457
|
if (mode === "local_stdio") {
|
|
1348
1458
|
if (localMcpReady) return "local_stdio_mcp_ready";
|
|
@@ -1396,7 +1506,7 @@ function nextAction(runtime, restartMode, errorCode) {
|
|
|
1396
1506
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
1397
1507
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
1398
1508
|
}
|
|
1399
|
-
function
|
|
1509
|
+
function supportsLocalMcp(runtime) {
|
|
1400
1510
|
return runtime === "codex-cli" || runtime === "codex-desktop" || runtime === "claude-code";
|
|
1401
1511
|
}
|
|
1402
1512
|
async function prepareRuntimeForLocalMcp(input, deps) {
|
|
@@ -1436,6 +1546,14 @@ async function runConnect(options, deps = {}) {
|
|
|
1436
1546
|
const generateKey = deps.generateKey ?? generateDelegateKey;
|
|
1437
1547
|
const generateLocalApiKey = deps.generateApiKey ?? generateAgentApiKey;
|
|
1438
1548
|
const installCapabilities = runtimeInstallCapabilities(options.runtime);
|
|
1549
|
+
if (options.localMcp) {
|
|
1550
|
+
const resolvedRuntime = normalizeRuntime(options.runtime);
|
|
1551
|
+
if (!supportsLocalMcp(resolvedRuntime)) {
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
`--local (fully-local Haven MCP) is only available for Claude Code and Codex. The detected runtime is ${runtimeProfile(resolvedRuntime).label}. Re-run without --local to use the default hosted MCP + local signer setup.`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1439
1557
|
const setup = await api.resolveSetup({
|
|
1440
1558
|
setupToken: options.setupToken,
|
|
1441
1559
|
connectorVersion,
|
|
@@ -1494,7 +1612,8 @@ async function runConnect(options, deps = {}) {
|
|
|
1494
1612
|
credentialDirectory: credentialPaths.directory,
|
|
1495
1613
|
environmentLabel: options.environmentLabel ?? "Local workspace",
|
|
1496
1614
|
ackSigner: options.ackSigner,
|
|
1497
|
-
ackLocalTools: options.ackLocalTools
|
|
1615
|
+
ackLocalTools: options.ackLocalTools,
|
|
1616
|
+
localMcp: options.localMcp
|
|
1498
1617
|
});
|
|
1499
1618
|
printRuntimeInstall(runtimeInstall, log);
|
|
1500
1619
|
try {
|
|
@@ -1509,6 +1628,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1509
1628
|
signerAcknowledged: runtimeInstall.signerAcknowledged,
|
|
1510
1629
|
localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
|
|
1511
1630
|
activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
|
|
1631
|
+
skillInstalled: runtimeInstall.skillInstalled,
|
|
1512
1632
|
probeResult: runtimeInstall.probeResult,
|
|
1513
1633
|
restartRequired: runtimeInstall.restartRequired,
|
|
1514
1634
|
nextUserAction: runtimeInstall.nextUserAction,
|
|
@@ -1590,6 +1710,8 @@ function parseArgs(argv, env = process.env) {
|
|
|
1590
1710
|
} else if (arg === "--ack-signer") {
|
|
1591
1711
|
options.ackSigner = true;
|
|
1592
1712
|
options.ackLocalTools = true;
|
|
1713
|
+
} else if (arg === "--local" || arg === "--local-mcp") {
|
|
1714
|
+
options.localMcp = true;
|
|
1593
1715
|
} else if (arg === "--version") {
|
|
1594
1716
|
process.stdout.write(`${CONNECTOR_VERSION}
|
|
1595
1717
|
`);
|
|
@@ -1628,6 +1750,8 @@ function helpText() {
|
|
|
1628
1750
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1629
1751
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1630
1752
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
1753
|
+
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
1754
|
+
" Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
|
|
1631
1755
|
" --help Show this help.",
|
|
1632
1756
|
"",
|
|
1633
1757
|
"The connector never prints the private key and never sends it to Haven."
|