@haven_ai/connect 0.1.0-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 +23 -0
- package/dist/cli.cjs +884 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +878 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +883 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +193 -0
- package/dist/index.d.ts +193 -0
- package/dist/index.js +863 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { Wallet } from 'ethers';
|
|
3
|
+
import { mkdir, rm, writeFile, access, chmod, readFile } from 'fs/promises';
|
|
4
|
+
import { homedir, platform } from 'os';
|
|
5
|
+
import { join, resolve, dirname } from 'path';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
|
|
9
|
+
// src/api.ts
|
|
10
|
+
function createConnectApiClient(baseUrl, fetchImpl = fetch) {
|
|
11
|
+
const root = baseUrl.replace(/\/+$/, "");
|
|
12
|
+
return {
|
|
13
|
+
resolveSetup: (input) => request(fetchImpl, `${root}/agent-connection-setups/resolve`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
setup_token: input.setupToken,
|
|
17
|
+
connector_version: input.connectorVersion,
|
|
18
|
+
runtime: input.runtime
|
|
19
|
+
})
|
|
20
|
+
}),
|
|
21
|
+
registerSetup: (input) => request(fetchImpl, `${root}/agent-connection-setups/register`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
setup_token: input.setupToken,
|
|
25
|
+
challenge_id: input.challengeId,
|
|
26
|
+
delegate_address: input.delegateAddress,
|
|
27
|
+
proof_signature: input.proofSignature,
|
|
28
|
+
api_key_hash: input.apiKeyHash,
|
|
29
|
+
api_key_prefix: input.apiKeyPrefix,
|
|
30
|
+
runtime: input.runtime,
|
|
31
|
+
connector_version: input.connectorVersion,
|
|
32
|
+
connector_context: input.connectorContext,
|
|
33
|
+
install_capabilities: input.installCapabilities && {
|
|
34
|
+
can_write_runtime_config: input.installCapabilities.canWriteRuntimeConfig,
|
|
35
|
+
restart_required: input.installCapabilities.restartRequired
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
updateInstallStatus: async (setupId, apiKey, input) => {
|
|
40
|
+
await request(fetchImpl, `${root}/agent-connection-setups/${encodeURIComponent(setupId)}/install-status`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
runtime: input.runtime,
|
|
45
|
+
connector_version: input.connectorVersion,
|
|
46
|
+
hosted_mcp_configured: input.hostedMcpConfigured,
|
|
47
|
+
local_signer_configured: input.localSignerConfigured,
|
|
48
|
+
credential_files_written: input.credentialFilesWritten,
|
|
49
|
+
probe_result: input.probeResult,
|
|
50
|
+
restart_required: input.restartRequired,
|
|
51
|
+
next_user_action: input.nextUserAction,
|
|
52
|
+
error_code: input.errorCode ?? null,
|
|
53
|
+
environment_label: input.environmentLabel
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function request(fetchImpl, url, init) {
|
|
60
|
+
const response = await fetchImpl(url, {
|
|
61
|
+
...init,
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
...init.headers ?? {}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
const body = text ? JSON.parse(text) : null;
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const message = body?.error ?? body?.message ?? `${response.status} ${response.statusText}`;
|
|
71
|
+
throw new Error(`Haven setup request failed: ${message}`);
|
|
72
|
+
}
|
|
73
|
+
return body;
|
|
74
|
+
}
|
|
75
|
+
function generateDelegateKey() {
|
|
76
|
+
return delegateKeyFromPrivateKey(Wallet.createRandom().privateKey);
|
|
77
|
+
}
|
|
78
|
+
function delegateKeyFromPrivateKey(privateKey) {
|
|
79
|
+
const wallet = new Wallet(privateKey);
|
|
80
|
+
return {
|
|
81
|
+
privateKey: wallet.privateKey,
|
|
82
|
+
address: wallet.address,
|
|
83
|
+
signChallenge: (message) => wallet.signMessage(message)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function generateAgentApiKey() {
|
|
87
|
+
return `sk_agent_${crypto.randomBytes(24).toString("hex")}`;
|
|
88
|
+
}
|
|
89
|
+
function hashAgentApiKey(apiKey) {
|
|
90
|
+
return crypto.createHash("sha256").update(apiKey).digest("hex");
|
|
91
|
+
}
|
|
92
|
+
function agentApiKeyPrefix(apiKey) {
|
|
93
|
+
return apiKey.slice(0, 12);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/redact.ts
|
|
97
|
+
var API_KEY_RE = /sk_agent_[A-Za-z0-9]+/g;
|
|
98
|
+
var PRIVATE_KEY_RE = /0x[0-9a-fA-F]{64}/g;
|
|
99
|
+
function redactSecrets(value) {
|
|
100
|
+
return value.replace(API_KEY_RE, "sk_agent_[redacted]").replace(PRIVATE_KEY_RE, "0x[redacted-private-key]");
|
|
101
|
+
}
|
|
102
|
+
function shortAddress(address) {
|
|
103
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) return address;
|
|
104
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
105
|
+
}
|
|
106
|
+
async function preflightCredentialStorage(input = {}) {
|
|
107
|
+
const directory = defaultCredentialRoot(input.baseDir);
|
|
108
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
109
|
+
await restrictPermissions(directory, 448, input.warn);
|
|
110
|
+
const probePath = join(directory, `.haven-connect-preflight-${crypto.randomBytes(8).toString("hex")}`);
|
|
111
|
+
try {
|
|
112
|
+
await writeOwnerOnlyJson(probePath, { ok: true }, input.warn);
|
|
113
|
+
} finally {
|
|
114
|
+
await rm(probePath, { force: true }).catch(() => void 0);
|
|
115
|
+
}
|
|
116
|
+
return directory;
|
|
117
|
+
}
|
|
118
|
+
async function writeCredentialFiles(input) {
|
|
119
|
+
const directory = defaultAgentDirectory(input.agentId, input.baseDir);
|
|
120
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
121
|
+
await restrictPermissions(directory, 448, input.warn);
|
|
122
|
+
const identityPath = join(directory, "identity.json");
|
|
123
|
+
const signerPath = join(directory, "signer.json");
|
|
124
|
+
await assertDoesNotExist(identityPath);
|
|
125
|
+
await assertDoesNotExist(signerPath);
|
|
126
|
+
await writeOwnerOnlyJson(
|
|
127
|
+
signerPath,
|
|
128
|
+
{
|
|
129
|
+
delegate_key: input.delegateKey,
|
|
130
|
+
agent_id: input.agentId,
|
|
131
|
+
safe_address: input.safeAddress,
|
|
132
|
+
chain_id: input.chainId,
|
|
133
|
+
network: input.network,
|
|
134
|
+
note: "Local signer credential. Haven backend never receives this private key."
|
|
135
|
+
},
|
|
136
|
+
input.warn
|
|
137
|
+
);
|
|
138
|
+
try {
|
|
139
|
+
await writeOwnerOnlyJson(
|
|
140
|
+
identityPath,
|
|
141
|
+
{
|
|
142
|
+
api_key: input.apiKey,
|
|
143
|
+
agent_id: input.agentId,
|
|
144
|
+
safe_address: input.safeAddress,
|
|
145
|
+
chain_id: input.chainId,
|
|
146
|
+
network: input.network,
|
|
147
|
+
api_url: input.apiUrl,
|
|
148
|
+
hosted_mcp_url: input.hostedMcpUrl,
|
|
149
|
+
note: "Haven API key identifies the agent only. It cannot spend without the local signer key and on-chain Haven wallet rules."
|
|
150
|
+
},
|
|
151
|
+
input.warn
|
|
152
|
+
);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
await rm(signerPath, { force: true }).catch(() => void 0);
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
return { directory, identityPath, signerPath };
|
|
158
|
+
}
|
|
159
|
+
function defaultAgentDirectory(agentId, baseDir = join(homedir(), ".haven", "agents")) {
|
|
160
|
+
return resolve(defaultCredentialRoot(baseDir), safePathPart(agentId));
|
|
161
|
+
}
|
|
162
|
+
function defaultCredentialRoot(baseDir = join(homedir(), ".haven", "agents")) {
|
|
163
|
+
return resolve(baseDir);
|
|
164
|
+
}
|
|
165
|
+
async function writeOwnerOnlyJson(path, value, warn) {
|
|
166
|
+
const json = JSON.stringify(dropUndefined(value), null, 2);
|
|
167
|
+
await writeFile(path, `${json}
|
|
168
|
+
`, { mode: 384, flag: "wx" });
|
|
169
|
+
await restrictPermissions(path, 384, warn);
|
|
170
|
+
}
|
|
171
|
+
function safePathPart(value) {
|
|
172
|
+
return value.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
173
|
+
}
|
|
174
|
+
function dropUndefined(value) {
|
|
175
|
+
return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0));
|
|
176
|
+
}
|
|
177
|
+
async function assertDoesNotExist(path) {
|
|
178
|
+
try {
|
|
179
|
+
await access(path);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Refusing to overwrite existing Haven credential file: ${path}`);
|
|
185
|
+
}
|
|
186
|
+
async function restrictPermissions(path, mode, warn) {
|
|
187
|
+
try {
|
|
188
|
+
await chmod(path, mode);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
warn?.(
|
|
191
|
+
`Warning: could not restrict permissions on ${path} to ${mode.toString(8)}. Move this credential to a private location or run chmod ${mode.toString(8)} ${path}. ${err instanceof Error ? err.message : String(err)}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function writeRuntimeConfig(input) {
|
|
196
|
+
switch (input.runtime) {
|
|
197
|
+
case "codex-cli":
|
|
198
|
+
return writeCodexConfig(input);
|
|
199
|
+
case "cursor":
|
|
200
|
+
return writeJsonRuntimeConfig(input, cursorConfigPath(input.homeDir), "mcpServers");
|
|
201
|
+
case "vscode":
|
|
202
|
+
return writeJsonRuntimeConfig(input, vscodeConfigPath(input.homeDir), "servers");
|
|
203
|
+
case "claude-desktop":
|
|
204
|
+
return writeJsonRuntimeConfig(input, claudeDesktopConfigPath(input.homeDir), "mcpServers");
|
|
205
|
+
default:
|
|
206
|
+
return {
|
|
207
|
+
hostedConfigured: false,
|
|
208
|
+
signerConfigured: false,
|
|
209
|
+
target: "manual runtime setup",
|
|
210
|
+
changed: false,
|
|
211
|
+
restartRequired: true,
|
|
212
|
+
messages: ["Runtime config needs to be added manually for this agent environment."],
|
|
213
|
+
errorCode: "manual_runtime_setup_required"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function buildHostedServer(hostedMcpUrl, apiKey, runtime) {
|
|
218
|
+
if (runtime === "vscode") {
|
|
219
|
+
return {
|
|
220
|
+
type: "http",
|
|
221
|
+
url: hostedMcpUrl,
|
|
222
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
url: hostedMcpUrl,
|
|
227
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function buildSignerServer(signerPath, runtime) {
|
|
231
|
+
const server = {
|
|
232
|
+
command: "npx",
|
|
233
|
+
args: ["-y", "@haven_ai/signer", "--credentials", signerPath]
|
|
234
|
+
};
|
|
235
|
+
if (runtime === "vscode") return { type: "stdio", ...server };
|
|
236
|
+
return server;
|
|
237
|
+
}
|
|
238
|
+
function mergeJsonMcpConfig(existingJson, serverRoot, hostedServer, signerServer) {
|
|
239
|
+
const config = existingJson?.trim() ? parseJsonObject(existingJson) : {};
|
|
240
|
+
const existingRoot = config[serverRoot];
|
|
241
|
+
const servers = existingRoot && typeof existingRoot === "object" && !Array.isArray(existingRoot) ? existingRoot : {};
|
|
242
|
+
config[serverRoot] = {
|
|
243
|
+
...servers,
|
|
244
|
+
haven: hostedServer,
|
|
245
|
+
"haven-signer": signerServer
|
|
246
|
+
};
|
|
247
|
+
return `${JSON.stringify(config, null, 2)}
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
function mergeCodexToml(existingToml, hostedMcpUrl, signerPath) {
|
|
251
|
+
let next = removeTomlTable(removeTomlTable(existingToml, "mcp_servers.haven"), "mcp_servers.haven_signer");
|
|
252
|
+
next = next.trimEnd();
|
|
253
|
+
const block = [
|
|
254
|
+
"[mcp_servers.haven]",
|
|
255
|
+
`url = ${tomlString(hostedMcpUrl)}`,
|
|
256
|
+
'bearer_token_env_var = "HAVEN_TOKEN"',
|
|
257
|
+
"",
|
|
258
|
+
"[mcp_servers.haven_signer]",
|
|
259
|
+
'command = "npx"',
|
|
260
|
+
`args = ["-y", "@haven_ai/signer", "--credentials", ${tomlString(signerPath)}]`
|
|
261
|
+
].join("\n");
|
|
262
|
+
return `${next ? `${next}
|
|
263
|
+
|
|
264
|
+
` : ""}${block}
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
async function writeJsonRuntimeConfig(input, target, serverRoot) {
|
|
268
|
+
try {
|
|
269
|
+
const existing = await readOptional(target);
|
|
270
|
+
const merged = mergeJsonMcpConfig(
|
|
271
|
+
existing,
|
|
272
|
+
serverRoot,
|
|
273
|
+
buildHostedServer(input.hostedMcpUrl, input.apiKey, input.runtime),
|
|
274
|
+
buildSignerServer(input.signerPath, input.runtime)
|
|
275
|
+
);
|
|
276
|
+
await writeOwnerOnlyText(target, merged);
|
|
277
|
+
return {
|
|
278
|
+
hostedConfigured: true,
|
|
279
|
+
signerConfigured: true,
|
|
280
|
+
target: configTargetLabel(input.runtime),
|
|
281
|
+
changed: existing !== merged,
|
|
282
|
+
restartRequired: input.runtime === "claude-desktop",
|
|
283
|
+
messages: [`Updated Haven MCP entries in ${configTargetLabel(input.runtime)}.`]
|
|
284
|
+
};
|
|
285
|
+
} catch (err) {
|
|
286
|
+
return {
|
|
287
|
+
hostedConfigured: false,
|
|
288
|
+
signerConfigured: false,
|
|
289
|
+
target: configTargetLabel(input.runtime),
|
|
290
|
+
changed: false,
|
|
291
|
+
restartRequired: true,
|
|
292
|
+
messages: [`Could not update ${configTargetLabel(input.runtime)}: ${err instanceof Error ? err.message : String(err)}`],
|
|
293
|
+
errorCode: "runtime_config_write_failed"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function writeCodexConfig(input) {
|
|
298
|
+
const target = codexConfigPath(input.homeDir);
|
|
299
|
+
const envTarget = join(input.credentialDirectory, "identity.env");
|
|
300
|
+
try {
|
|
301
|
+
const existing = await readOptional(target);
|
|
302
|
+
const merged = mergeCodexToml(existing ?? "", input.hostedMcpUrl, input.signerPath);
|
|
303
|
+
await writeOwnerOnlyText(target, merged);
|
|
304
|
+
await writeOwnerOnlyText(envTarget, `HAVEN_TOKEN=${shellToken(input.apiKey)}
|
|
305
|
+
`);
|
|
306
|
+
return {
|
|
307
|
+
hostedConfigured: false,
|
|
308
|
+
signerConfigured: true,
|
|
309
|
+
target: "Codex CLI config",
|
|
310
|
+
changed: existing !== merged,
|
|
311
|
+
restartRequired: true,
|
|
312
|
+
messages: [
|
|
313
|
+
"Updated Haven MCP entries in Codex CLI config.",
|
|
314
|
+
"Wrote the hosted MCP token to a private env file. Launch Codex with that env file before using Haven tools."
|
|
315
|
+
],
|
|
316
|
+
errorCode: "codex_env_activation_required"
|
|
317
|
+
};
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return {
|
|
320
|
+
hostedConfigured: false,
|
|
321
|
+
signerConfigured: false,
|
|
322
|
+
target: "Codex CLI config",
|
|
323
|
+
changed: false,
|
|
324
|
+
restartRequired: true,
|
|
325
|
+
messages: [`Could not update Codex CLI config: ${err instanceof Error ? err.message : String(err)}`],
|
|
326
|
+
errorCode: "runtime_config_write_failed"
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function readOptional(path) {
|
|
331
|
+
try {
|
|
332
|
+
return await readFile(path, "utf8");
|
|
333
|
+
} catch (err) {
|
|
334
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function writeOwnerOnlyText(path, value) {
|
|
339
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
340
|
+
await writeFile(path, value, { mode: 384 });
|
|
341
|
+
await chmod(path, 384).catch(() => void 0);
|
|
342
|
+
}
|
|
343
|
+
function parseJsonObject(value) {
|
|
344
|
+
const parsed = JSON.parse(value);
|
|
345
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
346
|
+
throw new Error("runtime config must be a JSON object");
|
|
347
|
+
}
|
|
348
|
+
return parsed;
|
|
349
|
+
}
|
|
350
|
+
function removeTomlTable(toml, table) {
|
|
351
|
+
const lines = toml.split(/\r?\n/);
|
|
352
|
+
const start = `[${table}]`;
|
|
353
|
+
const kept = [];
|
|
354
|
+
let skipping = false;
|
|
355
|
+
for (const line of lines) {
|
|
356
|
+
const trimmed = line.trim();
|
|
357
|
+
if (trimmed === start) {
|
|
358
|
+
skipping = true;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (skipping && trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
362
|
+
skipping = false;
|
|
363
|
+
}
|
|
364
|
+
if (!skipping) kept.push(line);
|
|
365
|
+
}
|
|
366
|
+
return kept.join("\n");
|
|
367
|
+
}
|
|
368
|
+
function tomlString(value) {
|
|
369
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
370
|
+
}
|
|
371
|
+
function shellToken(value) {
|
|
372
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
373
|
+
}
|
|
374
|
+
function cursorConfigPath(homeDir = homedir()) {
|
|
375
|
+
return resolve(homeDir, ".cursor", "mcp.json");
|
|
376
|
+
}
|
|
377
|
+
function codexConfigPath(homeDir = homedir()) {
|
|
378
|
+
return resolve(homeDir, ".codex", "config.toml");
|
|
379
|
+
}
|
|
380
|
+
function vscodeConfigPath(homeDir = homedir()) {
|
|
381
|
+
if (platform() === "darwin") return resolve(homeDir, "Library", "Application Support", "Code", "User", "mcp.json");
|
|
382
|
+
if (platform() === "win32") {
|
|
383
|
+
return resolve(process.env.APPDATA ?? join(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
|
|
384
|
+
}
|
|
385
|
+
return resolve(homeDir, ".config", "Code", "User", "mcp.json");
|
|
386
|
+
}
|
|
387
|
+
function claudeDesktopConfigPath(homeDir = homedir()) {
|
|
388
|
+
if (platform() === "darwin") {
|
|
389
|
+
return resolve(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
390
|
+
}
|
|
391
|
+
if (platform() === "win32") {
|
|
392
|
+
return resolve(process.env.APPDATA ?? join(homeDir, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
393
|
+
}
|
|
394
|
+
return resolve(homeDir, ".config", "Claude", "claude_desktop_config.json");
|
|
395
|
+
}
|
|
396
|
+
function configTargetLabel(runtime) {
|
|
397
|
+
switch (runtime) {
|
|
398
|
+
case "cursor":
|
|
399
|
+
return "Cursor MCP config";
|
|
400
|
+
case "vscode":
|
|
401
|
+
return "VS Code MCP config";
|
|
402
|
+
case "claude-desktop":
|
|
403
|
+
return "Claude Desktop config";
|
|
404
|
+
default:
|
|
405
|
+
return "runtime MCP config";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function probeHostedMcpTools(apiKey, hostedMcpUrl, fetchImpl = fetch) {
|
|
409
|
+
let response;
|
|
410
|
+
try {
|
|
411
|
+
response = await fetchWithTimeout(fetchImpl, hostedMcpUrl, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers: {
|
|
414
|
+
Authorization: `Bearer ${apiKey}`,
|
|
415
|
+
"Content-Type": "application/json",
|
|
416
|
+
Accept: "application/json, text/event-stream"
|
|
417
|
+
},
|
|
418
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" })
|
|
419
|
+
});
|
|
420
|
+
} catch {
|
|
421
|
+
return { status: "network_error" };
|
|
422
|
+
}
|
|
423
|
+
if (response.status === 401 || response.status === 403) return { status: "unauthorized" };
|
|
424
|
+
if (!response.ok) return { status: "bad_response" };
|
|
425
|
+
try {
|
|
426
|
+
const payload = parseJsonRpcPayload(await response.text());
|
|
427
|
+
if (!payload || payload.error) return { status: "bad_response" };
|
|
428
|
+
const tools = payload.result?.tools;
|
|
429
|
+
return { status: "ok", toolCount: Array.isArray(tools) ? tools.length : void 0 };
|
|
430
|
+
} catch {
|
|
431
|
+
return { status: "bad_response" };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function probeLocalSignerCredential(signerPath) {
|
|
435
|
+
try {
|
|
436
|
+
const parsed = JSON.parse(await readFile(signerPath, "utf8"));
|
|
437
|
+
return Boolean(
|
|
438
|
+
parsed && typeof parsed === "object" && "delegate_key" in parsed && typeof parsed.delegate_key === "string"
|
|
439
|
+
);
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function parseJsonRpcPayload(raw) {
|
|
445
|
+
const trimmed = raw.trim();
|
|
446
|
+
if (!trimmed) return null;
|
|
447
|
+
if (trimmed.startsWith("{")) {
|
|
448
|
+
try {
|
|
449
|
+
return JSON.parse(trimmed);
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const dataLines = trimmed.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
|
|
455
|
+
for (let i = dataLines.length - 1; i >= 0; i -= 1) {
|
|
456
|
+
try {
|
|
457
|
+
return JSON.parse(dataLines[i]);
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
async function fetchWithTimeout(fetchImpl, url, init) {
|
|
464
|
+
const controller = new AbortController();
|
|
465
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
466
|
+
try {
|
|
467
|
+
return await fetchImpl(url, { ...init, signal: controller.signal });
|
|
468
|
+
} finally {
|
|
469
|
+
clearTimeout(timeout);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/runtime-registry.ts
|
|
474
|
+
var RUNTIME_PROFILES = {
|
|
475
|
+
"claude-code": {
|
|
476
|
+
id: "claude-code",
|
|
477
|
+
label: "Claude Code",
|
|
478
|
+
restartMode: "restart-session",
|
|
479
|
+
canWriteRuntimeConfig: true
|
|
480
|
+
},
|
|
481
|
+
"codex-cli": {
|
|
482
|
+
id: "codex-cli",
|
|
483
|
+
label: "Codex CLI",
|
|
484
|
+
restartMode: "restart-session",
|
|
485
|
+
canWriteRuntimeConfig: true
|
|
486
|
+
},
|
|
487
|
+
cursor: {
|
|
488
|
+
id: "cursor",
|
|
489
|
+
label: "Cursor",
|
|
490
|
+
restartMode: "hot-reload",
|
|
491
|
+
canWriteRuntimeConfig: true
|
|
492
|
+
},
|
|
493
|
+
vscode: {
|
|
494
|
+
id: "vscode",
|
|
495
|
+
label: "VS Code",
|
|
496
|
+
restartMode: "hot-reload",
|
|
497
|
+
canWriteRuntimeConfig: true
|
|
498
|
+
},
|
|
499
|
+
"claude-desktop": {
|
|
500
|
+
id: "claude-desktop",
|
|
501
|
+
label: "Claude Desktop",
|
|
502
|
+
restartMode: "restart-app",
|
|
503
|
+
canWriteRuntimeConfig: true
|
|
504
|
+
},
|
|
505
|
+
other: {
|
|
506
|
+
id: "other",
|
|
507
|
+
label: "Other agent runtime",
|
|
508
|
+
restartMode: "manual",
|
|
509
|
+
canWriteRuntimeConfig: false
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
var RUNTIME_ALIASES = {
|
|
513
|
+
claude: "claude-code",
|
|
514
|
+
"claude-code": "claude-code",
|
|
515
|
+
claudecode: "claude-code",
|
|
516
|
+
"claude_code": "claude-code",
|
|
517
|
+
codex: "codex-cli",
|
|
518
|
+
"codex-cli": "codex-cli",
|
|
519
|
+
codexcli: "codex-cli",
|
|
520
|
+
"codex_cli": "codex-cli",
|
|
521
|
+
cursor: "cursor",
|
|
522
|
+
vscode: "vscode",
|
|
523
|
+
"vs-code": "vscode",
|
|
524
|
+
"vs_code": "vscode",
|
|
525
|
+
code: "vscode",
|
|
526
|
+
"claude-desktop": "claude-desktop",
|
|
527
|
+
"claude_desktop": "claude-desktop",
|
|
528
|
+
claudesktop: "claude-desktop",
|
|
529
|
+
desktop: "claude-desktop",
|
|
530
|
+
other: "other",
|
|
531
|
+
manual: "other"
|
|
532
|
+
};
|
|
533
|
+
function runtimeProfile(runtime, env = process.env) {
|
|
534
|
+
return RUNTIME_PROFILES[normalizeRuntime(runtime, env)];
|
|
535
|
+
}
|
|
536
|
+
function normalizeRuntime(runtime, env = process.env) {
|
|
537
|
+
const explicit = normalizeRuntimeName(runtime);
|
|
538
|
+
if (explicit) return explicit;
|
|
539
|
+
return detectRuntime(env) ?? "other";
|
|
540
|
+
}
|
|
541
|
+
function restartRequiredForRuntime(runtime, env = process.env) {
|
|
542
|
+
const mode = runtimeProfile(runtime, env).restartMode;
|
|
543
|
+
return mode === "restart-session" || mode === "restart-app";
|
|
544
|
+
}
|
|
545
|
+
function normalizeRuntimeName(runtime) {
|
|
546
|
+
const key = runtime?.trim().toLowerCase();
|
|
547
|
+
if (!key) return null;
|
|
548
|
+
return RUNTIME_ALIASES[key.replace(/\s+/g, "-")] ?? null;
|
|
549
|
+
}
|
|
550
|
+
function detectRuntime(env) {
|
|
551
|
+
if (env.CLAUDECODE || env.CLAUDE_CODE || env.CLAUDECODE_CWD) return "claude-code";
|
|
552
|
+
if (env.CODEX_SANDBOX || env.CODEX_HOME || env.CODEX_CWD) return "codex-cli";
|
|
553
|
+
if (env.VSCODE_CWD || env.VSCODE_IPC_HOOK_CLI || env.TERM_PROGRAM === "vscode") return "vscode";
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/runtime-install.ts
|
|
558
|
+
var execFileAsync = promisify(execFile);
|
|
559
|
+
async function installRuntime(input, deps = {}) {
|
|
560
|
+
const runtime = normalizeRuntime(input.runtime, deps.env);
|
|
561
|
+
const profile = runtimeProfile(runtime, deps.env);
|
|
562
|
+
if (runtime === "other") {
|
|
563
|
+
const signerReady2 = await probeLocalSignerCredential(input.signerPath);
|
|
564
|
+
return {
|
|
565
|
+
runtime,
|
|
566
|
+
hostedMcpConfigured: false,
|
|
567
|
+
localSignerConfigured: false,
|
|
568
|
+
probeResult: signerReady2 ? "manual_runtime_setup_required_local_signer_ready" : "manual_runtime_setup_required_local_signer_unavailable",
|
|
569
|
+
restartRequired: true,
|
|
570
|
+
nextUserAction: "return_to_haven_for_wallet_approval_then_configure_runtime",
|
|
571
|
+
errorCode: "manual_runtime_setup_required",
|
|
572
|
+
configTarget: "manual runtime setup",
|
|
573
|
+
messages: ["Runtime was not recognized. Keep the local credentials and add Haven MCP entries manually after wallet approval."]
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const configResult = runtime === "claude-code" ? await configureClaudeCode(input, deps) : await writeRuntimeConfig({
|
|
577
|
+
runtime,
|
|
578
|
+
hostedMcpUrl: input.hostedMcpUrl,
|
|
579
|
+
apiKey: input.apiKey,
|
|
580
|
+
signerPath: input.signerPath,
|
|
581
|
+
credentialDirectory: input.credentialDirectory,
|
|
582
|
+
homeDir: deps.homeDir
|
|
583
|
+
});
|
|
584
|
+
const [hostedProbe, signerReady] = await Promise.all([
|
|
585
|
+
configResult.hostedConfigured ? probeHostedMcpTools(input.apiKey, input.hostedMcpUrl, deps.fetch) : Promise.resolve({ status: "bad_response" }),
|
|
586
|
+
probeLocalSignerCredential(input.signerPath)
|
|
587
|
+
]);
|
|
588
|
+
const hostedOk = configResult.hostedConfigured && hostedProbe.status !== "unauthorized";
|
|
589
|
+
const signerOk = configResult.signerConfigured && signerReady;
|
|
590
|
+
const restartRequired = configResult.restartRequired || restartRequiredForRuntime(runtime, deps.env);
|
|
591
|
+
return {
|
|
592
|
+
runtime,
|
|
593
|
+
hostedMcpConfigured: hostedOk,
|
|
594
|
+
localSignerConfigured: signerOk,
|
|
595
|
+
probeResult: buildProbeResult(configResult.hostedConfigured, hostedProbe.status, signerOk),
|
|
596
|
+
restartRequired,
|
|
597
|
+
nextUserAction: nextAction(profile.restartMode, configResult.errorCode),
|
|
598
|
+
errorCode: configResult.errorCode,
|
|
599
|
+
configTarget: configResult.target,
|
|
600
|
+
messages: configResult.messages
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function runtimeInstallCapabilities(runtime, env = process.env) {
|
|
604
|
+
const profile = runtimeProfile(runtime, env);
|
|
605
|
+
return {
|
|
606
|
+
canWriteRuntimeConfig: profile.canWriteRuntimeConfig,
|
|
607
|
+
restartRequired: restartRequiredForRuntime(runtime, env)
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async function configureClaudeCode(input, deps) {
|
|
611
|
+
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
612
|
+
try {
|
|
613
|
+
await runCommand("claude", [
|
|
614
|
+
"mcp",
|
|
615
|
+
"add",
|
|
616
|
+
"--transport",
|
|
617
|
+
"http",
|
|
618
|
+
"haven",
|
|
619
|
+
input.hostedMcpUrl,
|
|
620
|
+
"--header",
|
|
621
|
+
`Authorization: Bearer ${input.apiKey}`
|
|
622
|
+
]);
|
|
623
|
+
await runCommand("claude", [
|
|
624
|
+
"mcp",
|
|
625
|
+
"add",
|
|
626
|
+
"haven-signer",
|
|
627
|
+
"npx",
|
|
628
|
+
"-y",
|
|
629
|
+
"@haven_ai/signer",
|
|
630
|
+
"--credentials",
|
|
631
|
+
input.signerPath
|
|
632
|
+
]);
|
|
633
|
+
return {
|
|
634
|
+
hostedConfigured: true,
|
|
635
|
+
signerConfigured: true,
|
|
636
|
+
target: "Claude Code MCP config",
|
|
637
|
+
changed: true,
|
|
638
|
+
restartRequired: true,
|
|
639
|
+
messages: ["Updated Haven MCP entries with Claude Code."]
|
|
640
|
+
};
|
|
641
|
+
} catch (err) {
|
|
642
|
+
return {
|
|
643
|
+
hostedConfigured: false,
|
|
644
|
+
signerConfigured: false,
|
|
645
|
+
target: "Claude Code MCP config",
|
|
646
|
+
changed: false,
|
|
647
|
+
restartRequired: true,
|
|
648
|
+
messages: [`Could not update Claude Code MCP config: ${err instanceof Error ? err.message : String(err)}`],
|
|
649
|
+
errorCode: "claude_code_config_failed"
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async function defaultRunCommand(command, args) {
|
|
654
|
+
await execFileAsync(command, args, { timeout: 1e4 });
|
|
655
|
+
}
|
|
656
|
+
function buildProbeResult(hostedConfigured, hostedStatus, signerReady) {
|
|
657
|
+
const hostedPart = hostedConfigured ? `hosted_${hostedStatus}` : "hosted_not_configured";
|
|
658
|
+
const signerPart = signerReady ? "local_signer_ready" : "local_signer_unavailable";
|
|
659
|
+
return `${hostedPart}_${signerPart}`.slice(0, 120);
|
|
660
|
+
}
|
|
661
|
+
function nextAction(restartMode, errorCode) {
|
|
662
|
+
if (errorCode) return "return_to_haven_for_wallet_approval_then_finish_runtime_setup";
|
|
663
|
+
if (restartMode === "hot-reload") return "return_to_haven_for_wallet_approval";
|
|
664
|
+
if (restartMode === "restart-app") return "return_to_haven_for_wallet_approval_then_restart_app";
|
|
665
|
+
if (restartMode === "restart-session") return "return_to_haven_for_wallet_approval_then_restart_agent_session";
|
|
666
|
+
return "return_to_haven_for_wallet_approval_then_configure_runtime";
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/runtime.ts
|
|
670
|
+
var CONNECTOR_VERSION = "0.1.0";
|
|
671
|
+
async function runConnect(options, deps = {}) {
|
|
672
|
+
const connectorVersion = options.connectorVersion ?? CONNECTOR_VERSION;
|
|
673
|
+
const api = deps.api ?? createConnectApiClient(options.apiBaseUrl);
|
|
674
|
+
const log = secureLogger(deps.log ?? ((message) => process.stdout.write(`${message}
|
|
675
|
+
`)));
|
|
676
|
+
const writeCredentials = deps.writeCredentials ?? writeCredentialFiles;
|
|
677
|
+
const preflightStorage = deps.preflightStorage ?? preflightCredentialStorage;
|
|
678
|
+
const runRuntimeInstall = deps.installRuntime ?? installRuntime;
|
|
679
|
+
const generateKey = deps.generateKey ?? generateDelegateKey;
|
|
680
|
+
const generateLocalApiKey = deps.generateApiKey ?? generateAgentApiKey;
|
|
681
|
+
const installCapabilities = runtimeInstallCapabilities(options.runtime);
|
|
682
|
+
const setup = await api.resolveSetup({
|
|
683
|
+
setupToken: options.setupToken,
|
|
684
|
+
connectorVersion,
|
|
685
|
+
runtime: options.runtime
|
|
686
|
+
});
|
|
687
|
+
printSetupSummary(setup, log);
|
|
688
|
+
await preflightStorage({ baseDir: options.credentialsDir, warn: log });
|
|
689
|
+
log("Checked local credential storage.");
|
|
690
|
+
const localKey = generateKey();
|
|
691
|
+
const localApiKey = generateLocalApiKey();
|
|
692
|
+
log("Generated local signing key.");
|
|
693
|
+
log("Generated local Haven API key.");
|
|
694
|
+
const proofSignature = await localKey.signChallenge(setup.challenge.message);
|
|
695
|
+
const registration = await api.registerSetup({
|
|
696
|
+
setupToken: options.setupToken,
|
|
697
|
+
connectorVersion,
|
|
698
|
+
runtime: options.runtime,
|
|
699
|
+
challengeId: setup.challenge.id,
|
|
700
|
+
delegateAddress: localKey.address,
|
|
701
|
+
proofSignature,
|
|
702
|
+
apiKeyHash: hashAgentApiKey(localApiKey),
|
|
703
|
+
apiKeyPrefix: agentApiKeyPrefix(localApiKey),
|
|
704
|
+
connectorContext: {
|
|
705
|
+
environment_label: options.environmentLabel ?? "Local workspace",
|
|
706
|
+
config_target: installCapabilities.canWriteRuntimeConfig ? "agent runtime MCP config" : "local credential files"
|
|
707
|
+
},
|
|
708
|
+
installCapabilities
|
|
709
|
+
});
|
|
710
|
+
log(`Registered signing address with Haven: ${shortAddress(registration.delegate_address)}.`);
|
|
711
|
+
const credentialPaths = await writeCredentials({
|
|
712
|
+
baseDir: options.credentialsDir,
|
|
713
|
+
agentId: registration.agent_id,
|
|
714
|
+
apiKey: localApiKey,
|
|
715
|
+
delegateKey: localKey.privateKey,
|
|
716
|
+
safeAddress: setup.haven_wallet.address,
|
|
717
|
+
chainId: setup.haven_wallet.chain_id,
|
|
718
|
+
network: setup.haven_wallet.network,
|
|
719
|
+
apiUrl: options.apiBaseUrl,
|
|
720
|
+
hostedMcpUrl: registration.hosted_mcp_url,
|
|
721
|
+
warn: log
|
|
722
|
+
});
|
|
723
|
+
log(`Stored Haven identity credential locally: ${credentialPaths.identityPath}`);
|
|
724
|
+
log(`Stored local signer credential locally: ${credentialPaths.signerPath}`);
|
|
725
|
+
const runtimeInstall = await runRuntimeInstall({
|
|
726
|
+
runtime: options.runtime,
|
|
727
|
+
hostedMcpUrl: registration.hosted_mcp_url,
|
|
728
|
+
apiKey: localApiKey,
|
|
729
|
+
signerPath: credentialPaths.signerPath,
|
|
730
|
+
identityPath: credentialPaths.identityPath,
|
|
731
|
+
credentialDirectory: credentialPaths.directory,
|
|
732
|
+
environmentLabel: options.environmentLabel ?? "Local workspace"
|
|
733
|
+
});
|
|
734
|
+
printRuntimeInstall(runtimeInstall, log);
|
|
735
|
+
try {
|
|
736
|
+
await api.updateInstallStatus(registration.setup_id, localApiKey, {
|
|
737
|
+
runtime: runtimeInstall.runtime,
|
|
738
|
+
connectorVersion,
|
|
739
|
+
hostedMcpConfigured: runtimeInstall.hostedMcpConfigured,
|
|
740
|
+
localSignerConfigured: runtimeInstall.localSignerConfigured,
|
|
741
|
+
credentialFilesWritten: true,
|
|
742
|
+
probeResult: runtimeInstall.probeResult,
|
|
743
|
+
restartRequired: runtimeInstall.restartRequired,
|
|
744
|
+
nextUserAction: runtimeInstall.nextUserAction,
|
|
745
|
+
errorCode: runtimeInstall.errorCode,
|
|
746
|
+
environmentLabel: options.environmentLabel ?? "Local workspace"
|
|
747
|
+
});
|
|
748
|
+
} catch (err) {
|
|
749
|
+
log(`Could not report install status to Haven: ${err instanceof Error ? err.message : String(err)}`);
|
|
750
|
+
}
|
|
751
|
+
log("Return to Haven to approve the agent rules.");
|
|
752
|
+
if (runtimeInstall.restartRequired) {
|
|
753
|
+
log("Restart this agent session after approval if your runtime loads MCP tools at startup.");
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
setupId: registration.setup_id,
|
|
757
|
+
agentId: registration.agent_id,
|
|
758
|
+
delegateAddress: registration.delegate_address,
|
|
759
|
+
credentialPaths
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function printSetupSummary(setup, log) {
|
|
763
|
+
log(`Fetched Haven setup for ${setup.agent.name}.`);
|
|
764
|
+
log(`Haven wallet: ${setup.haven_wallet.name} on ${setup.haven_wallet.network}.`);
|
|
765
|
+
if (setup.agent_budget.length > 0) {
|
|
766
|
+
for (const budget of setup.agent_budget) {
|
|
767
|
+
log(
|
|
768
|
+
`Agent budget: ${budget.allowance_amount} atomic ${budget.token_symbol} / ${budget.reset_period_min} minute reset.`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
log(`Setup challenge expires at ${setup.challenge.expires_at}.`);
|
|
773
|
+
}
|
|
774
|
+
function secureLogger(log) {
|
|
775
|
+
return (message) => log(redactSecrets(message));
|
|
776
|
+
}
|
|
777
|
+
function printRuntimeInstall(result, log) {
|
|
778
|
+
for (const message of result.messages) log(message);
|
|
779
|
+
if (result.hostedMcpConfigured) {
|
|
780
|
+
log("Configured hosted Haven MCP identity.");
|
|
781
|
+
} else {
|
|
782
|
+
log("Hosted Haven MCP identity still needs runtime setup.");
|
|
783
|
+
}
|
|
784
|
+
if (result.localSignerConfigured) {
|
|
785
|
+
log("Configured local Haven signer.");
|
|
786
|
+
} else {
|
|
787
|
+
log("Local Haven signer still needs runtime setup.");
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/args.ts
|
|
792
|
+
function parseArgs(argv, env = process.env) {
|
|
793
|
+
const options = {
|
|
794
|
+
apiBaseUrl: env.HAVEN_API_URL ?? "http://localhost:3001",
|
|
795
|
+
connectorVersion: CONNECTOR_VERSION
|
|
796
|
+
};
|
|
797
|
+
let help = false;
|
|
798
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
799
|
+
const arg = argv[i];
|
|
800
|
+
if (arg === "--help" || arg === "-h") {
|
|
801
|
+
help = true;
|
|
802
|
+
} else if (arg === "--setup" || arg === "--setup-token") {
|
|
803
|
+
options.setupToken = requireValue(argv, ++i, arg);
|
|
804
|
+
} else if (arg === "--api" || arg === "--api-url") {
|
|
805
|
+
options.apiBaseUrl = requireValue(argv, ++i, arg);
|
|
806
|
+
} else if (arg === "--runtime") {
|
|
807
|
+
options.runtime = requireValue(argv, ++i, arg);
|
|
808
|
+
} else if (arg === "--credentials-dir") {
|
|
809
|
+
options.credentialsDir = requireValue(argv, ++i, arg);
|
|
810
|
+
} else if (arg === "--environment-label") {
|
|
811
|
+
options.environmentLabel = requireValue(argv, ++i, arg);
|
|
812
|
+
} else if (arg === "--version") {
|
|
813
|
+
process.stdout.write(`${CONNECTOR_VERSION}
|
|
814
|
+
`);
|
|
815
|
+
process.exit(0);
|
|
816
|
+
} else {
|
|
817
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (help) {
|
|
821
|
+
return { options, help };
|
|
822
|
+
}
|
|
823
|
+
if (!options.setupToken) {
|
|
824
|
+
throw new Error("Missing --setup <hv_setup_...> setup token.");
|
|
825
|
+
}
|
|
826
|
+
if (!options.apiBaseUrl) {
|
|
827
|
+
throw new Error("Missing --api <Haven API URL>.");
|
|
828
|
+
}
|
|
829
|
+
options.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
|
|
830
|
+
return { options, help };
|
|
831
|
+
}
|
|
832
|
+
function helpText() {
|
|
833
|
+
return [
|
|
834
|
+
"Haven Connect Agent 2 local connector",
|
|
835
|
+
"",
|
|
836
|
+
"Generates the agent signing key locally, stores it on this machine, and",
|
|
837
|
+
"sends Haven only the public signing address plus a proof signature.",
|
|
838
|
+
"",
|
|
839
|
+
"Usage:",
|
|
840
|
+
" npx -y @haven_ai/connect --setup hv_setup_... --api https://api.haven.example --runtime claude-code",
|
|
841
|
+
"",
|
|
842
|
+
"Options:",
|
|
843
|
+
" --setup <token> Short-lived setup token from Haven.",
|
|
844
|
+
" --api <url> Haven backend API URL. Defaults to HAVEN_API_URL or http://localhost:3001.",
|
|
845
|
+
" --runtime <name> Agent runtime hint, such as claude-code, codex-cli, cursor, vscode, or claude-desktop.",
|
|
846
|
+
" --credentials-dir <path> Credential directory fallback. Defaults to ~/.haven/agents.",
|
|
847
|
+
" --environment-label <text> Non-sensitive label shown in Haven setup review.",
|
|
848
|
+
" --help Show this help.",
|
|
849
|
+
"",
|
|
850
|
+
"The connector never prints the private key and never sends it to Haven."
|
|
851
|
+
].join("\n");
|
|
852
|
+
}
|
|
853
|
+
function requireValue(argv, index, option) {
|
|
854
|
+
const value = argv[index];
|
|
855
|
+
if (!value || value.startsWith("--")) {
|
|
856
|
+
throw new Error(`Missing value for ${option}.`);
|
|
857
|
+
}
|
|
858
|
+
return value;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
export { CONNECTOR_VERSION, createConnectApiClient, defaultAgentDirectory, delegateKeyFromPrivateKey, generateDelegateKey, helpText, installRuntime, normalizeRuntime, parseArgs, redactSecrets, runConnect, runtimeInstallCapabilities, runtimeProfile, shortAddress, writeCredentialFiles };
|
|
862
|
+
//# sourceMappingURL=index.js.map
|
|
863
|
+
//# sourceMappingURL=index.js.map
|