@haven_ai/connect 0.1.5-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/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { join, resolve, dirname } from 'path';
|
|
|
7
7
|
import { execFile, spawn } from 'child_process';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { registeredToolNames, MCP_VERSION, ensureConsent, computeConsentHash, loadCredentials, consentInputFromClient } from '@haven_ai/mcp';
|
|
10
|
+
import { SKILL_FOLDER_NAME, HAVEN_SKILL_MD } from '@haven_ai/sdk';
|
|
10
11
|
import { ensureSignerConsent, computeSignerConsentHash, loadSignerCredentials, createEdgeSigner, toolSchemas } from '@haven_ai/signer';
|
|
11
12
|
|
|
12
13
|
// src/api.ts
|
|
@@ -54,6 +55,7 @@ function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
|
54
55
|
signer_acknowledged: input.signerAcknowledged,
|
|
55
56
|
local_mcp_acknowledged: input.localMcpAcknowledged,
|
|
56
57
|
activation_command_available: input.activationCommandAvailable,
|
|
58
|
+
skill_installed: input.skillInstalled,
|
|
57
59
|
probe_result: input.probeResult,
|
|
58
60
|
restart_required: input.restartRequired,
|
|
59
61
|
next_user_action: input.nextUserAction,
|
|
@@ -206,9 +208,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
206
208
|
mcpPackage: "@haven_ai/mcp",
|
|
207
209
|
mcpVersion: MCP_VERSION,
|
|
208
210
|
sdkPackage: "@haven_ai/sdk",
|
|
209
|
-
sdkVersion: "0.1.
|
|
211
|
+
sdkVersion: "0.1.10-alpha.0",
|
|
210
212
|
signerPackage: "@haven_ai/signer",
|
|
211
|
-
signerVersion: "0.1.
|
|
213
|
+
signerVersion: "0.1.10-alpha.0",
|
|
212
214
|
minimumNodeVersion: "20.0.0",
|
|
213
215
|
supportedClients: ["codex-cli", "codex-desktop", "claude-code"],
|
|
214
216
|
requiredTools: registeredToolNames()
|
|
@@ -413,6 +415,26 @@ function mergeCodexToml(existingToml, localMcpCommand) {
|
|
|
413
415
|
validateCodexToml(block, "Generated Codex Haven config");
|
|
414
416
|
const merged = `${next ? `${next}
|
|
415
417
|
|
|
418
|
+
` : ""}${block}
|
|
419
|
+
`;
|
|
420
|
+
return merged;
|
|
421
|
+
}
|
|
422
|
+
function mergeCodexTomlHosted(existingToml, hostedMcpUrl, apiKey, signerPath) {
|
|
423
|
+
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
424
|
+
next = next.trimEnd();
|
|
425
|
+
const block = [
|
|
426
|
+
"[mcp_servers.haven]",
|
|
427
|
+
`url = ${tomlString(hostedMcpUrl)}`,
|
|
428
|
+
`http_headers = { "Authorization" = ${tomlString(`Bearer ${apiKey}`)} }`,
|
|
429
|
+
"",
|
|
430
|
+
"[mcp_servers.haven_signer]",
|
|
431
|
+
'command = "npx"',
|
|
432
|
+
`args = ["-y", ${tomlString(signerPackageSpec())}, "--credentials", ${tomlString(signerPath)}]`,
|
|
433
|
+
"startup_timeout_sec = 120"
|
|
434
|
+
].join("\n");
|
|
435
|
+
validateCodexToml(block, "Generated Codex Haven config");
|
|
436
|
+
const merged = `${next ? `${next}
|
|
437
|
+
|
|
416
438
|
` : ""}${block}
|
|
417
439
|
`;
|
|
418
440
|
return merged;
|
|
@@ -453,27 +475,46 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
453
475
|
}
|
|
454
476
|
async function writeCodexConfig(input) {
|
|
455
477
|
const target = codexConfigPath(input.homeDir);
|
|
478
|
+
const local = input.mode === "local";
|
|
479
|
+
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.";
|
|
456
480
|
try {
|
|
457
481
|
const existing = await readOptional(target);
|
|
458
|
-
if (
|
|
459
|
-
|
|
482
|
+
if (local) {
|
|
483
|
+
if (!input.localMcpCommand) {
|
|
484
|
+
throw new Error("local MCP wrapper command is required");
|
|
485
|
+
}
|
|
486
|
+
const merged2 = mergeCodexToml(existing ?? "", input.localMcpCommand);
|
|
487
|
+
await writeOwnerOnlyText(target, merged2);
|
|
488
|
+
return {
|
|
489
|
+
hostedConfigured: false,
|
|
490
|
+
signerConfigured: true,
|
|
491
|
+
localMcpConfigured: true,
|
|
492
|
+
runtimeMcpMode: "local_stdio",
|
|
493
|
+
target: configTargetLabel(input.runtime),
|
|
494
|
+
changed: existing !== merged2,
|
|
495
|
+
restartRequired: true,
|
|
496
|
+
messages: [
|
|
497
|
+
`Updated local Haven MCP entry in ${configTargetLabel(input.runtime)}.`,
|
|
498
|
+
// Codex Desktop loads MCP servers at app launch; Codex CLI typically
|
|
499
|
+
// picks them up in the next session. Branch the copy so desktop users
|
|
500
|
+
// get the unambiguous instruction.
|
|
501
|
+
restartMessage
|
|
502
|
+
]
|
|
503
|
+
};
|
|
460
504
|
}
|
|
461
|
-
const merged =
|
|
505
|
+
const merged = mergeCodexTomlHosted(existing ?? "", input.hostedMcpUrl, input.apiKey, input.signerPath);
|
|
462
506
|
await writeOwnerOnlyText(target, merged);
|
|
463
507
|
return {
|
|
464
|
-
hostedConfigured:
|
|
508
|
+
hostedConfigured: true,
|
|
465
509
|
signerConfigured: true,
|
|
466
|
-
localMcpConfigured:
|
|
467
|
-
runtimeMcpMode: "
|
|
510
|
+
localMcpConfigured: false,
|
|
511
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
468
512
|
target: configTargetLabel(input.runtime),
|
|
469
513
|
changed: existing !== merged,
|
|
470
514
|
restartRequired: true,
|
|
471
515
|
messages: [
|
|
472
|
-
`Updated
|
|
473
|
-
|
|
474
|
-
// picks them up in the next session. Branch the copy so desktop users
|
|
475
|
-
// get the unambiguous instruction.
|
|
476
|
-
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."
|
|
516
|
+
`Updated Haven MCP entries in ${configTargetLabel(input.runtime)}.`,
|
|
517
|
+
restartMessage
|
|
477
518
|
]
|
|
478
519
|
};
|
|
479
520
|
} catch (err) {
|
|
@@ -482,7 +523,7 @@ async function writeCodexConfig(input) {
|
|
|
482
523
|
hostedConfigured: false,
|
|
483
524
|
signerConfigured: false,
|
|
484
525
|
localMcpConfigured: false,
|
|
485
|
-
runtimeMcpMode: "local_stdio",
|
|
526
|
+
runtimeMcpMode: local ? "local_stdio" : "hosted_plus_signer",
|
|
486
527
|
target: configTargetLabel(input.runtime),
|
|
487
528
|
changed: false,
|
|
488
529
|
restartRequired: true,
|
|
@@ -862,7 +903,7 @@ async function probeLocalSignerCredential(signerPath) {
|
|
|
862
903
|
}
|
|
863
904
|
}
|
|
864
905
|
async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4) {
|
|
865
|
-
return new Promise((
|
|
906
|
+
return new Promise((resolve7) => {
|
|
866
907
|
const child = spawn(command, args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
867
908
|
let stdout = "";
|
|
868
909
|
let settled = false;
|
|
@@ -872,7 +913,7 @@ async function probeLocalMcpTools(command, args, requiredTools, timeoutMs = 1e4)
|
|
|
872
913
|
settled = true;
|
|
873
914
|
clearTimeout(timeout);
|
|
874
915
|
child.kill();
|
|
875
|
-
|
|
916
|
+
resolve7(result);
|
|
876
917
|
};
|
|
877
918
|
const timeout = setTimeout(() => finish({ status: "timeout" }), timeoutMs);
|
|
878
919
|
child.on("error", () => finish({ status: "process_error" }));
|
|
@@ -1185,7 +1226,7 @@ var execFileAsync2 = promisify(execFile);
|
|
|
1185
1226
|
async function installRuntime(input, deps = {}) {
|
|
1186
1227
|
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
1187
1228
|
const profile = runtimeProfile(runtime, deps.env);
|
|
1188
|
-
const localRuntime =
|
|
1229
|
+
const localRuntime = input.localMcp === true && supportsLocalMcp(runtime);
|
|
1189
1230
|
const consentMessages = [];
|
|
1190
1231
|
const localMcpConsent = localRuntime ? await resolveLocalMcpConsent(input, consentMessages) : void 0;
|
|
1191
1232
|
const signerConsent = localRuntime ? void 0 : await resolveSignerConsent(input, consentMessages);
|
|
@@ -1242,7 +1283,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1242
1283
|
]
|
|
1243
1284
|
};
|
|
1244
1285
|
}
|
|
1245
|
-
const configResult = runtime === "claude-code" ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await writeRuntimeConfig({
|
|
1286
|
+
const configResult = runtime === "claude-code" ? localRuntime ? await configureClaudeCode(deps, localRuntimeInstall?.command ?? "") : await configureClaudeCodeHosted(deps, input) : await writeRuntimeConfig({
|
|
1246
1287
|
runtime,
|
|
1247
1288
|
hostedMcpUrl: input.hostedMcpUrl,
|
|
1248
1289
|
apiKey: input.apiKey,
|
|
@@ -1250,7 +1291,8 @@ async function installRuntime(input, deps = {}) {
|
|
|
1250
1291
|
signerPath: input.signerPath,
|
|
1251
1292
|
credentialDirectory: input.credentialDirectory,
|
|
1252
1293
|
localMcpCommand: localRuntimeInstall?.command,
|
|
1253
|
-
homeDir: deps.homeDir
|
|
1294
|
+
homeDir: deps.homeDir,
|
|
1295
|
+
mode: localRuntime ? "local" : "hosted"
|
|
1254
1296
|
});
|
|
1255
1297
|
const localProbePromise = configResult.runtimeMcpMode === "local_stdio" && localRuntimeInstall ? runLocalMcpProbe(localRuntimeInstall, deps) : Promise.resolve(void 0);
|
|
1256
1298
|
const [hostedProbe, signerCredentialReady, localMcpProbe] = await Promise.all([
|
|
@@ -1264,6 +1306,7 @@ async function installRuntime(input, deps = {}) {
|
|
|
1264
1306
|
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
1265
1307
|
const errorCode = configResult.errorCode ?? (configResult.runtimeMcpMode === "local_stdio" ? localMcpErrorCode(signerCredentialReady, localMcpConsent, localMcpProbe?.status) : signerConsentErrorCode(signerCredentialReady, signerConsent));
|
|
1266
1308
|
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."] : [];
|
|
1309
|
+
const skillInstall = runtime === "claude-code" && !configResult.errorCode ? await installClaudeSkill(deps.homeDir) : void 0;
|
|
1267
1310
|
return {
|
|
1268
1311
|
runtime,
|
|
1269
1312
|
runtimeMcpMode: configResult.runtimeMcpMode,
|
|
@@ -1278,7 +1321,8 @@ async function installRuntime(input, deps = {}) {
|
|
|
1278
1321
|
signerAcknowledged: signerConsent?.acknowledged,
|
|
1279
1322
|
localMcpAcknowledged: localMcpConsent?.acknowledged,
|
|
1280
1323
|
activationCommand: configResult.activationCommand,
|
|
1281
|
-
|
|
1324
|
+
skillInstalled: skillInstall?.installed,
|
|
1325
|
+
messages: [...consentMessages, ...localRuntimeInstall?.messages ?? [], ...configResult.messages, ...localProbeMessages, ...skillInstall?.messages ?? []]
|
|
1282
1326
|
};
|
|
1283
1327
|
}
|
|
1284
1328
|
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
@@ -1334,9 +1378,75 @@ async function configureClaudeCode(deps, localMcpCommand) {
|
|
|
1334
1378
|
};
|
|
1335
1379
|
}
|
|
1336
1380
|
}
|
|
1381
|
+
async function configureClaudeCodeHosted(deps, input) {
|
|
1382
|
+
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
1383
|
+
const hostedJson = JSON.stringify({
|
|
1384
|
+
type: "http",
|
|
1385
|
+
url: input.hostedMcpUrl,
|
|
1386
|
+
headers: { Authorization: `Bearer ${input.apiKey}` }
|
|
1387
|
+
});
|
|
1388
|
+
const signerJson = JSON.stringify({
|
|
1389
|
+
type: "stdio",
|
|
1390
|
+
command: "npx",
|
|
1391
|
+
args: ["-y", signerPackageSpec(), "--credentials", input.signerPath],
|
|
1392
|
+
env: {}
|
|
1393
|
+
});
|
|
1394
|
+
try {
|
|
1395
|
+
await runCommand("claude", ["mcp", "remove", "haven"]).catch(() => void 0);
|
|
1396
|
+
await runCommand("claude", ["mcp", "remove", "haven-signer"]).catch(() => void 0);
|
|
1397
|
+
await runCommand("claude", ["mcp", "add-json", "haven", hostedJson, "--scope", "user"]);
|
|
1398
|
+
await runCommand("claude", ["mcp", "add-json", "haven-signer", signerJson, "--scope", "user"]);
|
|
1399
|
+
const verified = await runCommand("claude", ["mcp", "get", "haven"]).then(() => true).catch(() => false);
|
|
1400
|
+
return {
|
|
1401
|
+
hostedConfigured: true,
|
|
1402
|
+
signerConfigured: true,
|
|
1403
|
+
localMcpConfigured: false,
|
|
1404
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
1405
|
+
target: "Claude Code MCP config",
|
|
1406
|
+
changed: true,
|
|
1407
|
+
restartRequired: true,
|
|
1408
|
+
messages: [
|
|
1409
|
+
"Updated hosted Haven MCP and local signer entries with Claude Code.",
|
|
1410
|
+
...verified ? ["Verified Claude Code MCP entry."] : [],
|
|
1411
|
+
"After Haven approval, Haven tools should appear in your next Claude Code message. If they don't, restart the session to load them."
|
|
1412
|
+
]
|
|
1413
|
+
};
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
return {
|
|
1416
|
+
hostedConfigured: false,
|
|
1417
|
+
signerConfigured: false,
|
|
1418
|
+
localMcpConfigured: false,
|
|
1419
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
1420
|
+
target: "Claude Code MCP config",
|
|
1421
|
+
changed: false,
|
|
1422
|
+
restartRequired: true,
|
|
1423
|
+
messages: [
|
|
1424
|
+
`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`,
|
|
1425
|
+
"Install Claude Code or rerun the Haven setup command inside a Claude Code terminal."
|
|
1426
|
+
],
|
|
1427
|
+
errorCode: "claude_code_config_failed"
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1337
1431
|
async function defaultRunCommand(command, args) {
|
|
1338
1432
|
await execFileAsync2(command, args, { timeout: 1e4 });
|
|
1339
1433
|
}
|
|
1434
|
+
async function installClaudeSkill(homeDir) {
|
|
1435
|
+
try {
|
|
1436
|
+
const skillDir = resolve(homeDir ?? homedir(), ".claude", "skills", SKILL_FOLDER_NAME);
|
|
1437
|
+
await mkdir(skillDir, { recursive: true });
|
|
1438
|
+
await writeFile(join(skillDir, "SKILL.md"), HAVEN_SKILL_MD, "utf8");
|
|
1439
|
+
return {
|
|
1440
|
+
installed: true,
|
|
1441
|
+
messages: ["Installed the generic Haven payment skill (~/.claude/skills/haven-pay). It contains no secrets."]
|
|
1442
|
+
};
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
return {
|
|
1445
|
+
installed: false,
|
|
1446
|
+
messages: [`Could not install the Haven payment skill: ${err instanceof Error ? err.message : String(err)}. Download it from the Haven dashboard instead.`]
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1340
1450
|
function buildProbeResult(mode, hostedConfigured, hostedStatus, signerReady, localMcpReady, localMcpProbeStatus) {
|
|
1341
1451
|
if (mode === "local_stdio") {
|
|
1342
1452
|
if (localMcpReady) return "local_stdio_mcp_ready";
|
|
@@ -1390,7 +1500,7 @@ function nextAction(runtime, restartMode, errorCode) {
|
|
|
1390
1500
|
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
1391
1501
|
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
1392
1502
|
}
|
|
1393
|
-
function
|
|
1503
|
+
function supportsLocalMcp(runtime) {
|
|
1394
1504
|
return runtime === "codex-cli" || runtime === "codex-desktop" || runtime === "claude-code";
|
|
1395
1505
|
}
|
|
1396
1506
|
async function prepareRuntimeForLocalMcp(input, deps) {
|
|
@@ -1430,6 +1540,14 @@ async function runConnect(options, deps = {}) {
|
|
|
1430
1540
|
const generateKey = deps.generateKey ?? generateDelegateKey;
|
|
1431
1541
|
const generateLocalApiKey = deps.generateApiKey ?? generateAgentApiKey;
|
|
1432
1542
|
const installCapabilities = runtimeInstallCapabilities(options.runtime);
|
|
1543
|
+
if (options.localMcp) {
|
|
1544
|
+
const resolvedRuntime = normalizeRuntime(options.runtime);
|
|
1545
|
+
if (!supportsLocalMcp(resolvedRuntime)) {
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
`--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.`
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1433
1551
|
const setup = await api.resolveSetup({
|
|
1434
1552
|
setupToken: options.setupToken,
|
|
1435
1553
|
connectorVersion,
|
|
@@ -1488,7 +1606,8 @@ async function runConnect(options, deps = {}) {
|
|
|
1488
1606
|
credentialDirectory: credentialPaths.directory,
|
|
1489
1607
|
environmentLabel: options.environmentLabel ?? "Local workspace",
|
|
1490
1608
|
ackSigner: options.ackSigner,
|
|
1491
|
-
ackLocalTools: options.ackLocalTools
|
|
1609
|
+
ackLocalTools: options.ackLocalTools,
|
|
1610
|
+
localMcp: options.localMcp
|
|
1492
1611
|
});
|
|
1493
1612
|
printRuntimeInstall(runtimeInstall, log);
|
|
1494
1613
|
try {
|
|
@@ -1503,6 +1622,7 @@ async function runConnect(options, deps = {}) {
|
|
|
1503
1622
|
signerAcknowledged: runtimeInstall.signerAcknowledged,
|
|
1504
1623
|
localMcpAcknowledged: runtimeInstall.localMcpAcknowledged,
|
|
1505
1624
|
activationCommandAvailable: Boolean(runtimeInstall.activationCommand),
|
|
1625
|
+
skillInstalled: runtimeInstall.skillInstalled,
|
|
1506
1626
|
probeResult: runtimeInstall.probeResult,
|
|
1507
1627
|
restartRequired: runtimeInstall.restartRequired,
|
|
1508
1628
|
nextUserAction: runtimeInstall.nextUserAction,
|
|
@@ -1584,6 +1704,8 @@ function parseArgs(argv, env = process.env) {
|
|
|
1584
1704
|
} else if (arg === "--ack-signer") {
|
|
1585
1705
|
options.ackSigner = true;
|
|
1586
1706
|
options.ackLocalTools = true;
|
|
1707
|
+
} else if (arg === "--local" || arg === "--local-mcp") {
|
|
1708
|
+
options.localMcp = true;
|
|
1587
1709
|
} else if (arg === "--version") {
|
|
1588
1710
|
process.stdout.write(`${CONNECTOR_VERSION}
|
|
1589
1711
|
`);
|
|
@@ -1622,6 +1744,8 @@ function helpText() {
|
|
|
1622
1744
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1623
1745
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|
|
1624
1746
|
" --ack-signer Backward-compatible alias for --ack-local-tools.",
|
|
1747
|
+
" --local Advanced: install the fully-local Haven MCP (no hosted dependency).",
|
|
1748
|
+
" Only available for Claude Code and Codex. Default is hosted MCP + local signer.",
|
|
1625
1749
|
" --help Show this help.",
|
|
1626
1750
|
"",
|
|
1627
1751
|
"The connector never prints the private key and never sends it to Haven."
|