@haven_ai/connect 0.1.20-alpha.0 → 0.1.22-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 +22 -0
- package/dist/cli.cjs +220 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +221 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +220 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +221 -6
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -25,3 +25,25 @@ Do not point it at a project repository, shared folder, or cloud-synced folder.
|
|
|
25
25
|
Use `--ack-local-tools` with Haven-generated setup prompts. It prepares the
|
|
26
26
|
local Haven tools acknowledgement during setup so Codex and Claude Code can load
|
|
27
27
|
Haven after a normal restart.
|
|
28
|
+
|
|
29
|
+
## Supported runtimes
|
|
30
|
+
|
|
31
|
+
The default setup writes the hosted Haven MCP (using the agent API key for
|
|
32
|
+
identity) plus a separate local signer. The API key identifies the agent; the
|
|
33
|
+
locally held signer key and the user's approved Haven wallet rules remain the
|
|
34
|
+
spending authority.
|
|
35
|
+
|
|
36
|
+
| Runtime | Configuration written by setup | Reload behaviour |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Claude Code | User MCP registry | Start a new session |
|
|
39
|
+
| Codex CLI / Codex Desktop | `~/.codex/config.toml` | Start a new session / restart the app |
|
|
40
|
+
| Cursor | Cursor MCP configuration | Reloads automatically |
|
|
41
|
+
| VS Code / VS Code Insiders | VS Code MCP configuration | Reloads automatically |
|
|
42
|
+
| Claude Desktop | Claude Desktop MCP configuration | Restart the app |
|
|
43
|
+
| Hermes Agent | `$HERMES_HOME/config.yaml` + `.env`, or `~/.hermes/config.yaml` + `.env` | Start a new session; gateway users run `/restart` |
|
|
44
|
+
|
|
45
|
+
For Hermes, Connect stores the hosted-MCP API key in the matching owner-only
|
|
46
|
+
`.env` file and keeps only `Bearer ${MCP_HAVEN_API_KEY}` in `config.yaml`.
|
|
47
|
+
Hermes requires its Python MCP SDK support to be installed. If Haven tools do
|
|
48
|
+
not appear after restart, run `pip install mcp` in the Hermes environment, then
|
|
49
|
+
restart Hermes and check `hermes mcp list`.
|
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 yaml = require('yaml');
|
|
11
12
|
var mcp = require('@haven_ai/mcp');
|
|
12
13
|
var sdk = require('@haven_ai/sdk');
|
|
13
14
|
var signer = require('@haven_ai/signer');
|
|
@@ -236,9 +237,9 @@ var MCP_RUNTIME_MANIFEST = {
|
|
|
236
237
|
mcpPackage: "@haven_ai/mcp",
|
|
237
238
|
mcpVersion: mcp.MCP_VERSION,
|
|
238
239
|
sdkPackage: "@haven_ai/sdk",
|
|
239
|
-
sdkVersion: "0.1.
|
|
240
|
+
sdkVersion: "0.1.22-alpha.0",
|
|
240
241
|
signerPackage: "@haven_ai/signer",
|
|
241
|
-
signerVersion: "0.1.
|
|
242
|
+
signerVersion: "0.1.22-alpha.0",
|
|
242
243
|
// Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
|
|
243
244
|
// while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
|
|
244
245
|
// so the guard that was supposed to enforce the floor waved Node v23 through
|
|
@@ -260,13 +261,20 @@ function signerPackageSpec() {
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
// src/config-writers.ts
|
|
264
|
+
var HERMES_API_KEY_ENV = "MCP_HAVEN_API_KEY";
|
|
265
|
+
var HermesConfigRecoveryError = class extends Error {
|
|
266
|
+
constructor() {
|
|
267
|
+
super("Hermes configuration recovery did not complete");
|
|
268
|
+
this.name = "HermesConfigRecoveryError";
|
|
269
|
+
}
|
|
270
|
+
};
|
|
263
271
|
var InvalidCodexTomlError = class extends Error {
|
|
264
272
|
constructor(message) {
|
|
265
273
|
super(message);
|
|
266
274
|
this.name = "InvalidCodexTomlError";
|
|
267
275
|
}
|
|
268
276
|
};
|
|
269
|
-
async function writeRuntimeConfig(input) {
|
|
277
|
+
async function writeRuntimeConfig(input, deps = {}) {
|
|
270
278
|
switch (input.runtime) {
|
|
271
279
|
case "codex-cli":
|
|
272
280
|
case "codex-desktop":
|
|
@@ -279,6 +287,8 @@ async function writeRuntimeConfig(input) {
|
|
|
279
287
|
return writeJsonRuntimeConfig(input, vscodeInsidersConfigPath(input.homeDir), "servers");
|
|
280
288
|
case "claude-desktop":
|
|
281
289
|
return writeJsonRuntimeConfig(input, claudeDesktopConfigPath(input.homeDir), "mcpServers");
|
|
290
|
+
case "hermes":
|
|
291
|
+
return writeHermesConfig(input, deps);
|
|
282
292
|
default:
|
|
283
293
|
return {
|
|
284
294
|
hostedConfigured: false,
|
|
@@ -332,6 +342,115 @@ function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer
|
|
|
332
342
|
return `${JSON.stringify(config, null, 2)}
|
|
333
343
|
`;
|
|
334
344
|
}
|
|
345
|
+
function mergeHermesYaml(existingYaml, hostedServer, signerServer) {
|
|
346
|
+
if (!existingYaml?.trim()) return renderHermesYaml({ haven: hostedServer, "haven-signer": signerServer });
|
|
347
|
+
const doc = yaml.parseDocument(existingYaml, { keepSourceTokens: true });
|
|
348
|
+
if (doc.errors.length > 0 || !yaml.isMap(doc.contents)) {
|
|
349
|
+
throw new Error("Hermes config must be a YAML object");
|
|
350
|
+
}
|
|
351
|
+
const mcpPair = doc.contents.items.find((item) => item.key?.toString() === "mcp_servers");
|
|
352
|
+
const existingServers = mcpPair && yaml.isMap(mcpPair.value) ? mcpPair.value.toJSON() : {};
|
|
353
|
+
const servers = isRecord(existingServers) ? existingServers : {};
|
|
354
|
+
const mergedServers = {
|
|
355
|
+
...servers,
|
|
356
|
+
haven: hostedServer,
|
|
357
|
+
"haven-signer": signerServer
|
|
358
|
+
};
|
|
359
|
+
if (!mcpPair) {
|
|
360
|
+
return appendHermesMcpServers(existingYaml, mergedServers);
|
|
361
|
+
}
|
|
362
|
+
if (!mcpPair.value?.range) return replaceEmptyHermesMcpServers(existingYaml, mcpPair.key?.range, mergedServers);
|
|
363
|
+
return replaceHermesMcpServers(existingYaml, mcpPair.key?.range, mcpPair.value.range, mergedServers);
|
|
364
|
+
}
|
|
365
|
+
function mergeHermesEnv(existingEnv, apiKey) {
|
|
366
|
+
if (/[\r\n]/.test(apiKey)) throw new Error("Hermes API key must be a single line");
|
|
367
|
+
const assignment = `${HERMES_API_KEY_ENV}=${apiKey}`;
|
|
368
|
+
if (!existingEnv) return `${assignment}
|
|
369
|
+
`;
|
|
370
|
+
const lineEnding = existingEnv.includes("\r\n") ? "\r\n" : "\n";
|
|
371
|
+
const hasTrailingNewline = /\r?\n$/.test(existingEnv);
|
|
372
|
+
const lines = existingEnv.split(/\r?\n/);
|
|
373
|
+
if (hasTrailingNewline) lines.pop();
|
|
374
|
+
let found = false;
|
|
375
|
+
const merged = lines.flatMap((line) => {
|
|
376
|
+
if (isHermesEnvAssignment(line)) {
|
|
377
|
+
if (found) return [];
|
|
378
|
+
found = true;
|
|
379
|
+
return [assignment];
|
|
380
|
+
}
|
|
381
|
+
if (isAmbiguousHermesEnvLine(line)) {
|
|
382
|
+
throw new Error("Hermes environment contains an ambiguous managed key");
|
|
383
|
+
}
|
|
384
|
+
return [line];
|
|
385
|
+
});
|
|
386
|
+
if (!found) {
|
|
387
|
+
return `${existingEnv}${hasTrailingNewline ? "" : lineEnding}${assignment}${lineEnding}`;
|
|
388
|
+
}
|
|
389
|
+
return `${merged.join(lineEnding)}${hasTrailingNewline ? lineEnding : ""}`;
|
|
390
|
+
}
|
|
391
|
+
function isHermesEnvAssignment(line) {
|
|
392
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY[ \t]*=/.test(line);
|
|
393
|
+
}
|
|
394
|
+
function isAmbiguousHermesEnvLine(line) {
|
|
395
|
+
return /^\s*(?:export[ \t]+)?MCP_HAVEN_API_KEY\b/.test(line);
|
|
396
|
+
}
|
|
397
|
+
function appendHermesMcpServers(source, servers) {
|
|
398
|
+
const documentEnd = /(?:^|\n)[ \t]*\.\.\.[ \t]*(?:#[^\n]*)?\r?\n?$/.exec(source);
|
|
399
|
+
if (documentEnd) {
|
|
400
|
+
const markerStart = documentEnd.index + (documentEnd[0].startsWith("\n") ? 1 : 0);
|
|
401
|
+
const beforeMarker = source.slice(0, markerStart);
|
|
402
|
+
return `${beforeMarker}${beforeMarker.endsWith("\n") ? "" : "\n"}${renderHermesMcpServers(servers, "")}
|
|
403
|
+
${source.slice(markerStart)}`;
|
|
404
|
+
}
|
|
405
|
+
const separator = source.endsWith("\n") ? "" : "\n";
|
|
406
|
+
return `${source}${separator}${renderHermesMcpServers(servers, "")}
|
|
407
|
+
`;
|
|
408
|
+
}
|
|
409
|
+
function replaceHermesMcpServers(source, keyRange, valueRange, servers) {
|
|
410
|
+
const valueStart = valueRange[0];
|
|
411
|
+
const valueEnd = valueRange[1];
|
|
412
|
+
const valueLineStart = source.lastIndexOf("\n", valueStart - 1) + 1;
|
|
413
|
+
const keyStartsOnValueLine = keyRange?.[0] !== void 0 && keyRange[0] >= valueLineStart;
|
|
414
|
+
const nestedIndent = `${source.slice(valueLineStart, keyRange?.[0] ?? valueLineStart)} `;
|
|
415
|
+
if (keyStartsOnValueLine) {
|
|
416
|
+
const keyStart = keyRange[0];
|
|
417
|
+
const keyEnd = keyRange[1];
|
|
418
|
+
const replacement2 = `${source.slice(keyStart, keyEnd)}:
|
|
419
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
420
|
+
const needsTrailingNewline2 = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
421
|
+
return `${source.slice(0, keyStart)}${replacement2}${needsTrailingNewline2 ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
422
|
+
}
|
|
423
|
+
const indent = source.slice(valueLineStart, valueStart);
|
|
424
|
+
const rendered = yaml.stringify(servers, { lineWidth: 120 }).trimEnd();
|
|
425
|
+
const replacement = rendered.split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`).join("\n");
|
|
426
|
+
const needsTrailingNewline = valueEnd < source.length && !source.slice(valueEnd).startsWith("\n");
|
|
427
|
+
return `${source.slice(0, valueStart)}${replacement}${needsTrailingNewline ? "\n" : ""}${source.slice(valueEnd)}`;
|
|
428
|
+
}
|
|
429
|
+
function replaceEmptyHermesMcpServers(source, keyRange, servers) {
|
|
430
|
+
if (!keyRange) throw new Error("Hermes config mcp_servers key is invalid");
|
|
431
|
+
const keyStart = keyRange[0];
|
|
432
|
+
const keyEnd = keyRange[1];
|
|
433
|
+
const lineStart = source.lastIndexOf("\n", keyStart - 1) + 1;
|
|
434
|
+
const lineEnd = source.indexOf("\n", keyEnd);
|
|
435
|
+
const nestedIndent = `${source.slice(lineStart, keyStart)} `;
|
|
436
|
+
const replacement = `${source.slice(keyStart, keyEnd)}:
|
|
437
|
+
${renderHermesMcpServerEntries(servers, nestedIndent)}`;
|
|
438
|
+
return `${source.slice(0, keyStart)}${replacement}${lineEnd === -1 ? "\n" : source.slice(lineEnd)}`;
|
|
439
|
+
}
|
|
440
|
+
function renderHermesYaml(servers) {
|
|
441
|
+
return `${renderHermesMcpServers(servers, "")}
|
|
442
|
+
`;
|
|
443
|
+
}
|
|
444
|
+
function renderHermesMcpServers(servers, indent) {
|
|
445
|
+
return `mcp_servers:
|
|
446
|
+
${renderHermesMcpServerEntries(servers, `${indent} `)}`;
|
|
447
|
+
}
|
|
448
|
+
function renderHermesMcpServerEntries(servers, indent) {
|
|
449
|
+
return yaml.stringify(servers, { lineWidth: 120 }).trimEnd().split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
450
|
+
}
|
|
451
|
+
function isRecord(value) {
|
|
452
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
453
|
+
}
|
|
335
454
|
function mergeCodexToml(existingToml, localMcpCommand) {
|
|
336
455
|
let next = removeTomlTableTree(removeTomlTableTree(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
337
456
|
next = next.trimEnd();
|
|
@@ -402,6 +521,82 @@ async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
|
402
521
|
};
|
|
403
522
|
}
|
|
404
523
|
}
|
|
524
|
+
async function writeHermesConfig(input, deps) {
|
|
525
|
+
const target = hermesConfigPath(input.homeDir);
|
|
526
|
+
const envTarget = hermesEnvPath(input.homeDir);
|
|
527
|
+
try {
|
|
528
|
+
const [existing, existingEnv] = await Promise.all([readOptional(target), readOptional(envTarget)]);
|
|
529
|
+
const hostedServer = {
|
|
530
|
+
...buildHostedServer(input.hostedMcpUrl, `\${${HERMES_API_KEY_ENV}}`, input.runtime),
|
|
531
|
+
enabled: true
|
|
532
|
+
};
|
|
533
|
+
const signerServer = {
|
|
534
|
+
...buildSignerServer(resolveSignerLaunchSpec(input), input.runtime),
|
|
535
|
+
enabled: true
|
|
536
|
+
};
|
|
537
|
+
const merged = mergeHermesYaml(
|
|
538
|
+
existing,
|
|
539
|
+
hostedServer,
|
|
540
|
+
signerServer
|
|
541
|
+
);
|
|
542
|
+
const mergedEnv = mergeHermesEnv(existingEnv, input.apiKey);
|
|
543
|
+
const writeText = deps.writeOwnerOnlyText ?? writeOwnerOnlyText;
|
|
544
|
+
await writeText(envTarget, mergedEnv);
|
|
545
|
+
try {
|
|
546
|
+
await writeText(target, merged);
|
|
547
|
+
} catch (err) {
|
|
548
|
+
const recovered = await restoreHermesFiles(target, existing, envTarget, existingEnv, writeText);
|
|
549
|
+
if (!recovered) throw new HermesConfigRecoveryError();
|
|
550
|
+
throw err;
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
hostedConfigured: true,
|
|
554
|
+
signerConfigured: true,
|
|
555
|
+
localMcpConfigured: false,
|
|
556
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
557
|
+
target: "Hermes Agent config",
|
|
558
|
+
changed: existing !== merged || existingEnv !== mergedEnv,
|
|
559
|
+
restartRequired: true,
|
|
560
|
+
messages: [
|
|
561
|
+
`Updated Haven MCP entries in ${target}; stored the hosted MCP identity in ${envTarget}.`,
|
|
562
|
+
"Restart Hermes (start a new session; gateway users: /restart), then verify with `hermes mcp list`, `hermes mcp test haven`, and `hermes mcp test haven-signer`.",
|
|
563
|
+
"If no mcp_* tools appear after restart, ensure the MCP SDK is installed in Hermes: pip install mcp"
|
|
564
|
+
]
|
|
565
|
+
};
|
|
566
|
+
} catch (err) {
|
|
567
|
+
const recoveryIncomplete = err instanceof HermesConfigRecoveryError;
|
|
568
|
+
return {
|
|
569
|
+
hostedConfigured: false,
|
|
570
|
+
signerConfigured: false,
|
|
571
|
+
localMcpConfigured: false,
|
|
572
|
+
runtimeMcpMode: "hosted_plus_signer",
|
|
573
|
+
target: "Hermes Agent config",
|
|
574
|
+
changed: false,
|
|
575
|
+
restartRequired: true,
|
|
576
|
+
messages: [recoveryIncomplete ? "Could not update Hermes Agent config. Recovery did not complete; inspect the Hermes configuration before retrying." : "Could not update Hermes Agent config. Existing configuration was left unchanged."],
|
|
577
|
+
errorCode: "runtime_config_write_failed"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function restoreHermesFiles(configPath, existingConfig, envPath, existingEnv, writeText) {
|
|
582
|
+
const [envResult, configResult] = await Promise.allSettled([
|
|
583
|
+
restoreOptionalText(envPath, existingEnv, writeText),
|
|
584
|
+
restoreOptionalText(configPath, existingConfig, writeText)
|
|
585
|
+
]);
|
|
586
|
+
return envResult.status === "fulfilled" && configResult.status === "fulfilled";
|
|
587
|
+
}
|
|
588
|
+
async function restoreOptionalText(path, existing, writeText) {
|
|
589
|
+
if (existing !== null) {
|
|
590
|
+
await writeText(path, existing);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
await promises.unlink(path);
|
|
595
|
+
} catch (err) {
|
|
596
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
405
600
|
async function writeCodexConfig(input) {
|
|
406
601
|
const target = codexConfigPath(input.homeDir);
|
|
407
602
|
const local = input.mode === "local";
|
|
@@ -692,6 +887,15 @@ function claudeDesktopConfigPath(homeDir = os.homedir()) {
|
|
|
692
887
|
}
|
|
693
888
|
return path.resolve(homeDir, ".config", "Claude", "claude_desktop_config.json");
|
|
694
889
|
}
|
|
890
|
+
function hermesConfigPath(homeDir) {
|
|
891
|
+
return path.join(hermesHomePath(homeDir), "config.yaml");
|
|
892
|
+
}
|
|
893
|
+
function hermesEnvPath(homeDir) {
|
|
894
|
+
return path.join(hermesHomePath(homeDir), ".env");
|
|
895
|
+
}
|
|
896
|
+
function hermesHomePath(homeDir) {
|
|
897
|
+
return process.env.HERMES_HOME ?? path.join(homeDir ?? os.homedir(), ".hermes");
|
|
898
|
+
}
|
|
695
899
|
function configTargetLabel(runtime) {
|
|
696
900
|
switch (runtime) {
|
|
697
901
|
case "codex-cli":
|
|
@@ -1235,6 +1439,12 @@ var RUNTIME_PROFILES = {
|
|
|
1235
1439
|
restartMode: "restart-app",
|
|
1236
1440
|
canWriteRuntimeConfig: true
|
|
1237
1441
|
},
|
|
1442
|
+
hermes: {
|
|
1443
|
+
id: "hermes",
|
|
1444
|
+
label: "Hermes Agent",
|
|
1445
|
+
restartMode: "restart-session",
|
|
1446
|
+
canWriteRuntimeConfig: true
|
|
1447
|
+
},
|
|
1238
1448
|
other: {
|
|
1239
1449
|
id: "other",
|
|
1240
1450
|
label: "Other agent runtime",
|
|
@@ -1272,6 +1482,10 @@ var RUNTIME_ALIASES = {
|
|
|
1272
1482
|
"claude_desktop": "claude-desktop",
|
|
1273
1483
|
claudesktop: "claude-desktop",
|
|
1274
1484
|
desktop: "claude-desktop",
|
|
1485
|
+
hermes: "hermes",
|
|
1486
|
+
"hermes-agent": "hermes",
|
|
1487
|
+
hermes_agent: "hermes",
|
|
1488
|
+
hermesagent: "hermes",
|
|
1275
1489
|
other: "other",
|
|
1276
1490
|
manual: "other"
|
|
1277
1491
|
};
|
|
@@ -1299,6 +1513,7 @@ function detectRuntime(env) {
|
|
|
1299
1513
|
if (env.CLAUDECODE || env.CLAUDE_CODE || env.CLAUDECODE_CWD) return "claude-code";
|
|
1300
1514
|
if (env.CODEX_SANDBOX || env.CODEX_HOME || env.CODEX_CWD) return "codex-cli";
|
|
1301
1515
|
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
1516
|
+
if (env.HERMES_HOME || env.HERMES_AGENT) return "hermes";
|
|
1302
1517
|
return null;
|
|
1303
1518
|
}
|
|
1304
1519
|
async function acknowledgeLocalSignerConsent(signerPath, log) {
|
|
@@ -1708,7 +1923,7 @@ function localRuntimePrepareErrorCode(err) {
|
|
|
1708
1923
|
}
|
|
1709
1924
|
|
|
1710
1925
|
// src/runtime.ts
|
|
1711
|
-
var CONNECTOR_VERSION = "0.1.
|
|
1926
|
+
var CONNECTOR_VERSION = "0.1.22-alpha.0";
|
|
1712
1927
|
async function runConnect(options, deps = {}) {
|
|
1713
1928
|
assertSupportedNodeVersion(deps.nodeVersion, MCP_RUNTIME_MANIFEST.minimumNodeVersion);
|
|
1714
1929
|
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
@@ -1941,7 +2156,7 @@ function helpText() {
|
|
|
1941
2156
|
"Options:",
|
|
1942
2157
|
" --setup <token> Short-lived setup token from Haven.",
|
|
1943
2158
|
" --api <url> Haven backend API URL. Defaults to HAVEN_API_URL or http://localhost:3001.",
|
|
1944
|
-
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode,
|
|
2159
|
+
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, codex-desktop, cursor, vscode, claude-desktop, or hermes.",
|
|
1945
2160
|
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
1946
2161
|
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
1947
2162
|
" --ack-local-tools Write the one-time local Haven tools acknowledgement during setup.",
|