@hivefloor/cli 0.1.4 → 0.1.8
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/LICENSE +6 -20
- package/README.md +31 -9
- package/dist/chunk-2ERPDCOT.js +38 -0
- package/dist/chunk-4G362W6G.js +19 -0
- package/dist/chunk-5OJUJCE3.js +817 -0
- package/dist/chunk-5XXYQQG6.js +274 -0
- package/dist/chunk-AGIYVFDQ.js +10984 -0
- package/dist/{chunk-2BSA7LFD.js → chunk-BLLPDPTL.js} +7 -3
- package/dist/{chunk-R5KDI4WI.js → chunk-FPJNGKT5.js} +9 -3
- package/dist/chunk-HW3KRNCO.js +201 -0
- package/dist/{chunk-ZC2DAKB5.js → chunk-HWBUESB4.js} +1500 -316
- package/dist/{chunk-DNG5CRT3.js → chunk-K2IRTZH5.js} +8 -7
- package/dist/chunk-KQMPCV57.js +122 -0
- package/dist/{chunk-UJ4BQFWV.js → chunk-L6EFW2SJ.js} +53 -31
- package/dist/{chunk-T7TG6WA4.js → chunk-LX5A25O6.js} +4 -2
- package/dist/{chunk-W7DROKC7.js → chunk-MYJCU5JL.js} +89 -13
- package/dist/chunk-NMUH5NXN.js +52 -0
- package/dist/chunk-NRJONTFT.js +67 -0
- package/dist/chunk-QBBKGORP.js +327 -0
- package/dist/chunk-QGULSOQ2.js +142 -0
- package/dist/{chunk-PDXYOREI.js → chunk-QLMLB7RB.js} +110 -31
- package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
- package/dist/chunk-SBOEAOHD.js +48 -0
- package/dist/{chunk-SD65CIIY.js → chunk-TIR4CV3P.js} +117 -33
- package/dist/{chunk-QHYZBHXO.js → chunk-X2KH6AR4.js} +195 -251
- package/dist/chunk-XAXKZ572.js +106 -0
- package/dist/{chunk-VNCQFFQO.js → chunk-XPGL7VYE.js} +114 -1
- package/dist/cli-BFOJWJNG.js +279 -0
- package/dist/connect-ZJ4JXBAJ.js +27 -0
- package/dist/control-6GHTZQJV.js +25 -0
- package/dist/{disconnect-MKRHK4CX.js → disconnect-3T25NTKX.js} +55 -34
- package/dist/hook-TPSHBCWU.js +575 -0
- package/dist/index.js +9 -6
- package/dist/{link-HXBMMSG3.js → link-EAS5F5PC.js} +34 -18
- package/dist/{machine-connect-BYHZRRRB.js → machine-connect-KEI4BRWF.js} +30 -24
- package/dist/mcp-LVIYR3EN.js +1882 -0
- package/dist/orch-KVRL2YA2.js +137 -0
- package/dist/{run-336BSU72.js → run-JL2OZWK3.js} +1933 -29
- package/dist/{status-KBBSAADD.js → status-QXOOJGGN.js} +29 -14
- package/dist/token-BFWWQIDA.js +44 -0
- package/dist/voice-QXHFXRWG.js +264 -0
- package/dist/{where-KR7VWSEC.js → where-CMUORQIO.js} +20 -8
- package/package.json +6 -7
- package/dist/chunk-2IMJECYD.js +0 -71
- package/dist/chunk-7KF7IQJN.js +0 -30
- package/dist/chunk-GT3HFFT4.js +0 -132
- package/dist/chunk-JCLWCMEM.js +0 -8
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/chunk-O2TC3WSS.js +0 -303
- package/dist/chunk-PY74LT2W.js +0 -41
- package/dist/cli-P3DYE5IR.js +0 -103
- package/dist/connect-ACNM5DQG.js +0 -23
- package/dist/hook-KR2HGVAR.js +0 -301
- package/dist/mcp-N2TMD4GR.js +0 -121
- package/dist/token-JDRLEV5D.js +0 -32
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
2
4
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
ApiError
|
|
6
|
+
} from "./chunk-NRJONTFT.js";
|
|
5
7
|
import {
|
|
6
8
|
atomicWrite,
|
|
7
9
|
backupOnce,
|
|
8
10
|
dropBackupIfSame,
|
|
11
|
+
findBin,
|
|
9
12
|
home,
|
|
10
13
|
isRecord,
|
|
11
14
|
manifestFile,
|
|
@@ -13,11 +16,11 @@ import {
|
|
|
13
16
|
readText,
|
|
14
17
|
removeFile,
|
|
15
18
|
serializeJson
|
|
16
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-XPGL7VYE.js";
|
|
17
20
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
runCmd,
|
|
22
|
+
sameOrigin
|
|
23
|
+
} from "./chunk-QGULSOQ2.js";
|
|
21
24
|
|
|
22
25
|
// src/manifest.ts
|
|
23
26
|
function readManifest() {
|
|
@@ -53,65 +56,32 @@ var wasCreated = (file) => readManifest().created.includes(file);
|
|
|
53
56
|
var forgetCreated = (file) => updateManifest((m) => {
|
|
54
57
|
m.created = m.created.filter((f) => f !== file);
|
|
55
58
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return process.platform === "darwin" && fs.existsSync(profile) ? profile : path.join(home(), ".bashrc");
|
|
68
|
-
}
|
|
69
|
-
return void 0;
|
|
70
|
-
}
|
|
71
|
-
function hasRcLine(file) {
|
|
72
|
-
return (readText(file) ?? "").split("\n").some((l) => {
|
|
73
|
-
const t = l.trim();
|
|
74
|
-
return !t.startsWith("#") && t.includes("HIVEFLOOR_TOKEN") && t.includes("hivefloor token") && t.endsWith(RC_MARK);
|
|
59
|
+
function toolReadsEnvToken(name, entry, shellExportsToken) {
|
|
60
|
+
if (!entry) return false;
|
|
61
|
+
if (typeof entry.envToken === "boolean") return entry.envToken;
|
|
62
|
+
return name === "codex" || shellExportsToken;
|
|
63
|
+
}
|
|
64
|
+
function envTokenSharedWith(host, tool, shellExportsToken) {
|
|
65
|
+
const manifest = readManifest();
|
|
66
|
+
return Object.entries(manifest.tools).every(([name, entry]) => {
|
|
67
|
+
if (name === tool) return true;
|
|
68
|
+
if (!toolReadsEnvToken(name, entry, shellExportsToken)) return true;
|
|
69
|
+
return typeof entry?.host === "string" && entry.host.length > 0 && sameOrigin(entry.host, host);
|
|
75
70
|
});
|
|
76
71
|
}
|
|
77
|
-
function addRcLine(file) {
|
|
78
|
-
if (hasRcLine(file)) return false;
|
|
79
|
-
const cur = readText(file);
|
|
80
|
-
if (cur === void 0) addCreated(file);
|
|
81
|
-
const sep = cur && !cur.endsWith("\n") ? "\n" : "";
|
|
82
|
-
atomicWrite(file, `${cur ?? ""}${sep}${RC_LINE}
|
|
83
|
-
`);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
function removeRcLine(file) {
|
|
87
|
-
const cur = readText(file);
|
|
88
|
-
if (cur === void 0) return false;
|
|
89
|
-
const lines = cur.split("\n");
|
|
90
|
-
const kept = lines.filter((l) => l.trim() !== RC_LINE);
|
|
91
|
-
if (kept.length === lines.length) return false;
|
|
92
|
-
const next = kept.join("\n");
|
|
93
|
-
if (next.trim() === "" && wasCreated(file)) {
|
|
94
|
-
fs.rmSync(file, { force: true });
|
|
95
|
-
forgetCreated(file);
|
|
96
|
-
} else {
|
|
97
|
-
atomicWrite(file, next);
|
|
98
|
-
}
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// src/writers/claude.ts
|
|
103
|
-
import path2 from "path";
|
|
104
72
|
|
|
105
73
|
// src/writers/common.ts
|
|
106
|
-
import
|
|
74
|
+
import fs from "fs";
|
|
107
75
|
var emptyReport = () => ({ lines: [], warnings: [] });
|
|
108
76
|
var CLI_COPY_RE = /hivefloor[/\\]cli[/\\]index\.js$/;
|
|
77
|
+
var MACHINE_CMD_RE = /hivefloor[/\\]+cli[/\\]+index\.js"?\s+hook\s/;
|
|
109
78
|
function isMachineHook(h) {
|
|
110
79
|
if (!isRecord(h)) return false;
|
|
111
80
|
const cmd = h.command;
|
|
112
81
|
if (typeof cmd !== "string") return false;
|
|
113
82
|
if (Array.isArray(h.args) && h.args[1] === "hook" && typeof h.args[0] === "string" && CLI_COPY_RE.test(h.args[0])) return true;
|
|
114
|
-
|
|
83
|
+
if (MACHINE_CMD_RE.test(cmd)) return true;
|
|
84
|
+
return typeof h.commandWindows === "string" && MACHINE_CMD_RE.test(h.commandWindows);
|
|
115
85
|
}
|
|
116
86
|
function isAgentHook(h) {
|
|
117
87
|
if (!isRecord(h) || isMachineHook(h)) return false;
|
|
@@ -180,7 +150,7 @@ function editJson(file, mutate, opts = {}) {
|
|
|
180
150
|
function secureFile(file) {
|
|
181
151
|
for (const f of [file, `${file}.hivefloor.bak`]) {
|
|
182
152
|
try {
|
|
183
|
-
|
|
153
|
+
fs.chmodSync(f, 384);
|
|
184
154
|
} catch {
|
|
185
155
|
}
|
|
186
156
|
}
|
|
@@ -197,9 +167,10 @@ function dropBackupWithEntry(file, name) {
|
|
|
197
167
|
}
|
|
198
168
|
|
|
199
169
|
// src/writers/claude.ts
|
|
170
|
+
import path from "path";
|
|
200
171
|
var CLAUDE_EVENTS = ["SessionStart", "SessionEnd", "UserPromptSubmit", "Stop", "Notification", "SubagentStart", "SubagentStop"];
|
|
201
|
-
var claudeJson = () =>
|
|
202
|
-
var claudeSettings = () =>
|
|
172
|
+
var claudeJson = () => path.join(home(), ".claude.json");
|
|
173
|
+
var claudeSettings = () => path.join(home(), ".claude", "settings.json");
|
|
203
174
|
var MCP_NAME = "hivefloor";
|
|
204
175
|
function claudeHookSpecs() {
|
|
205
176
|
return CLAUDE_EVENTS.map((event) => ({
|
|
@@ -214,13 +185,16 @@ async function installClaude(i) {
|
|
|
214
185
|
const report = emptyReport();
|
|
215
186
|
const auth = i.useEnvToken ? "Bearer ${HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
|
|
216
187
|
let mcp = "file";
|
|
217
|
-
const
|
|
218
|
-
|
|
188
|
+
const priorCli = !i.useEnvToken && readManifest().tools.claude?.mcp === "cli";
|
|
189
|
+
const bin = i.noCli ? void 0 : findBin("claude", i.ctx.env);
|
|
190
|
+
if (bin && (i.useEnvToken || priorCli)) {
|
|
219
191
|
await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
192
|
+
if (i.useEnvToken) {
|
|
193
|
+
const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "--header", `Authorization: ${auth}`, "-s", "user"], { env: i.ctx.env });
|
|
194
|
+
if (r.code === 0) {
|
|
195
|
+
mcp = "cli";
|
|
196
|
+
report.lines.push(`Claude Code MCP: claude mcp add (user scope) -> ${i.mcpUrl}`);
|
|
197
|
+
}
|
|
224
198
|
}
|
|
225
199
|
}
|
|
226
200
|
if (mcp === "file") {
|
|
@@ -237,10 +211,12 @@ async function installClaude(i) {
|
|
|
237
211
|
report.lines.push(`Claude Code MCP: ${claudeJson()} (mcpServers.${MCP_NAME})`);
|
|
238
212
|
}
|
|
239
213
|
i.onMcpRegistered?.(mcp);
|
|
240
|
-
if (i.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
214
|
+
if (!i.mcpOnly) {
|
|
215
|
+
if (i.skipHooks) report.lines.push("Claude Code hooks: the machine connection already forwards them");
|
|
216
|
+
else {
|
|
217
|
+
editJson(claudeSettings(), (d) => (addHooks(d, "nested", claudeHookSpecs()), true));
|
|
218
|
+
report.lines.push(`Claude Code hooks: ${claudeSettings()} (${CLAUDE_EVENTS.join(", ")})`);
|
|
219
|
+
}
|
|
244
220
|
}
|
|
245
221
|
return { report, mcp };
|
|
246
222
|
}
|
|
@@ -272,7 +248,7 @@ async function removeClaude(ctx, viaCli, keepHooks = false, viaFile = false) {
|
|
|
272
248
|
}
|
|
273
249
|
|
|
274
250
|
// src/writers/codex.ts
|
|
275
|
-
import
|
|
251
|
+
import path2 from "path";
|
|
276
252
|
|
|
277
253
|
// src/writers/toml.ts
|
|
278
254
|
var BEGIN = (name, sep) => `# hivefloor:begin ${name} sep=${sep}`;
|
|
@@ -312,6 +288,60 @@ function hasTable(content, table) {
|
|
|
312
288
|
const re = new RegExp(`^\\s*\\[\\s*${table.replace(/\./g, "\\s*\\.\\s*")}\\s*\\]`, "m");
|
|
313
289
|
return re.test(stripped);
|
|
314
290
|
}
|
|
291
|
+
var tableName = (header) => /^\s*\[\s*([^\]]+?)\s*\]/.exec(header)?.[1]?.replace(/\s+/g, "");
|
|
292
|
+
function isNamedTable(name, table) {
|
|
293
|
+
const want = table.replace(/\s+/g, "");
|
|
294
|
+
return name === want || name.startsWith(`${want}.`);
|
|
295
|
+
}
|
|
296
|
+
function removeOutsideTable(content, table) {
|
|
297
|
+
const lines = content.split("\n");
|
|
298
|
+
const out = [];
|
|
299
|
+
const pending = [];
|
|
300
|
+
let inFence = false;
|
|
301
|
+
let skipping = false;
|
|
302
|
+
const dropPending = () => {
|
|
303
|
+
pending.length = 0;
|
|
304
|
+
};
|
|
305
|
+
const flushPending = () => {
|
|
306
|
+
out.push(...pending);
|
|
307
|
+
dropPending();
|
|
308
|
+
};
|
|
309
|
+
for (const line of lines) {
|
|
310
|
+
const bare = line.replace(/\r$/, "");
|
|
311
|
+
if (!inFence && /^# hivefloor:begin /.test(bare)) {
|
|
312
|
+
if (skipping) dropPending();
|
|
313
|
+
inFence = true;
|
|
314
|
+
}
|
|
315
|
+
if (inFence) {
|
|
316
|
+
out.push(line);
|
|
317
|
+
if (/^# hivefloor:end /.test(bare)) inFence = false;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const name = tableName(bare);
|
|
321
|
+
if (name !== void 0) {
|
|
322
|
+
if (isNamedTable(name, table)) {
|
|
323
|
+
skipping = true;
|
|
324
|
+
dropPending();
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (skipping) flushPending();
|
|
328
|
+
skipping = false;
|
|
329
|
+
out.push(line);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (skipping) {
|
|
333
|
+
if (bare.trim() === "" || /^\s*#/.test(bare)) pending.push(line);
|
|
334
|
+
else dropPending();
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
out.push(line);
|
|
338
|
+
}
|
|
339
|
+
if (skipping && pending.some((l) => /^\s*#/.test(l.replace(/\r$/, "")))) flushPending();
|
|
340
|
+
return out.join("\n");
|
|
341
|
+
}
|
|
342
|
+
function hasOutsideTable(content, table) {
|
|
343
|
+
return removeOutsideTable(content, table) !== content;
|
|
344
|
+
}
|
|
315
345
|
var FEATURE_TAG = "# hivefloor";
|
|
316
346
|
function enableFeatureHooks(content) {
|
|
317
347
|
const lines = content.split("\n");
|
|
@@ -346,9 +376,9 @@ function restoreFeatureHooks(content) {
|
|
|
346
376
|
|
|
347
377
|
// src/writers/codex.ts
|
|
348
378
|
var CODEX_EVENTS = ["SessionStart", "SessionEnd", "UserPromptSubmit", "Stop", "SubagentStart", "SubagentStop"];
|
|
349
|
-
var codexDir = () =>
|
|
350
|
-
var codexConfig = () =>
|
|
351
|
-
var codexHooks = () =>
|
|
379
|
+
var codexDir = () => path2.join(process.env.CODEX_HOME || path2.join(home(), ".codex"));
|
|
380
|
+
var codexConfig = () => path2.join(codexDir(), "config.toml");
|
|
381
|
+
var codexHooks = () => path2.join(codexDir(), "hooks.json");
|
|
352
382
|
function codexHookSpecs() {
|
|
353
383
|
return CODEX_EVENTS.map((event) => ({ event, entry: { type: "command", command: `hivefloor hook codex ${event}` } }));
|
|
354
384
|
}
|
|
@@ -364,44 +394,98 @@ function editToml(fn, opts = {}) {
|
|
|
364
394
|
}
|
|
365
395
|
if (before === void 0) addCreated(file);
|
|
366
396
|
else if (!wasCreated(file)) backupOnce(file);
|
|
367
|
-
atomicWrite(file, next);
|
|
397
|
+
atomicWrite(file, next, opts.mode);
|
|
368
398
|
if (opts.drop) dropBackupIfSame(file);
|
|
369
399
|
return true;
|
|
370
400
|
}
|
|
401
|
+
var CODEX_TABLE = `mcp_servers.${MCP_NAME}`;
|
|
402
|
+
function codexEnvBlock(mcpUrl) {
|
|
403
|
+
return `[mcp_servers.${MCP_NAME}]
|
|
404
|
+
url = ${tomlString(mcpUrl)}
|
|
405
|
+
bearer_token_env_var = "HIVEFLOOR_TOKEN"`;
|
|
406
|
+
}
|
|
407
|
+
function codexLiteralBlock(mcpUrl, token) {
|
|
408
|
+
return `[mcp_servers.${MCP_NAME}]
|
|
409
|
+
url = ${tomlString(mcpUrl)}
|
|
410
|
+
http_headers = { Authorization = ${tomlString(`Bearer ${token}`)} }`;
|
|
411
|
+
}
|
|
412
|
+
async function writeCodexLiteral(i) {
|
|
413
|
+
const prior = readManifest().tools.codex;
|
|
414
|
+
const ownedCli = prior?.mcp === "cli";
|
|
415
|
+
if (ownedCli && !i.noCli) {
|
|
416
|
+
const bin = findBin("codex", i.ctx.env);
|
|
417
|
+
if (bin) await runCmd(bin, ["mcp", "remove", MCP_NAME], { env: i.ctx.env });
|
|
418
|
+
}
|
|
419
|
+
const body = codexLiteralBlock(i.mcpUrl, i.token);
|
|
420
|
+
let blocked = false;
|
|
421
|
+
const wrote = editToml(
|
|
422
|
+
(c) => {
|
|
423
|
+
if (hasTable(c, CODEX_TABLE) && !ownedCli) {
|
|
424
|
+
blocked = true;
|
|
425
|
+
return c;
|
|
426
|
+
}
|
|
427
|
+
return setBlock(ownedCli ? removeOutsideTable(c, CODEX_TABLE) : c, "mcp", body);
|
|
428
|
+
},
|
|
429
|
+
{ mode: 384 }
|
|
430
|
+
);
|
|
431
|
+
const file = codexConfig();
|
|
432
|
+
if (blocked || !wrote && !(readText(file) ?? "").includes(body)) return false;
|
|
433
|
+
secureFile(file);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
371
436
|
async function installCodex(i) {
|
|
372
437
|
const report = emptyReport();
|
|
373
438
|
let mcp = "file";
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
439
|
+
if (i.useEnvToken) {
|
|
440
|
+
const bin = i.noCli ? void 0 : findBin("codex", i.ctx.env);
|
|
441
|
+
if (bin) {
|
|
442
|
+
await runCmd(bin, ["mcp", "remove", MCP_NAME], { env: i.ctx.env });
|
|
443
|
+
const r = await runCmd(bin, ["mcp", "add", MCP_NAME, "--url", i.mcpUrl, "--bearer-token-env-var", "HIVEFLOOR_TOKEN"], { env: i.ctx.env });
|
|
444
|
+
if (r.code === 0) {
|
|
445
|
+
mcp = "cli";
|
|
446
|
+
report.lines.push(`Codex MCP: codex mcp add -> ${i.mcpUrl}`);
|
|
447
|
+
}
|
|
381
448
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
449
|
+
if (mcp === "file") {
|
|
450
|
+
let wrote = false;
|
|
451
|
+
editToml((c) => {
|
|
452
|
+
if (hasTable(c, CODEX_TABLE)) {
|
|
453
|
+
report.warnings.push(`${codexConfig()} already defines [mcp_servers.${MCP_NAME}]; left as it is`);
|
|
454
|
+
return c;
|
|
455
|
+
}
|
|
456
|
+
wrote = true;
|
|
457
|
+
return setBlock(c, "mcp", codexEnvBlock(i.mcpUrl));
|
|
458
|
+
});
|
|
459
|
+
if (wrote) report.lines.push(`Codex MCP: ${codexConfig()} ([mcp_servers.${MCP_NAME}])`);
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
const wrote = await writeCodexLiteral(i);
|
|
463
|
+
if (!wrote) {
|
|
464
|
+
const file = codexConfig();
|
|
465
|
+
if (i.requireStoredToken) {
|
|
466
|
+
throw new ApiError(
|
|
467
|
+
0,
|
|
468
|
+
"codex_config",
|
|
469
|
+
`${file} already defines [mcp_servers.hivefloor], and Codex cannot store this host's token apart from HIVEFLOOR_TOKEN. Remove that table and run connect again.`
|
|
470
|
+
);
|
|
388
471
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
472
|
+
report.warnings.push(`${file} already defines [mcp_servers.${MCP_NAME}]; left as it is`);
|
|
473
|
+
report.warnings.push('Codex reads the token from HIVEFLOOR_TOKEN: add export HIVEFLOOR_TOKEN="$(hivefloor token)" to your shell rc.');
|
|
474
|
+
} else {
|
|
475
|
+
report.lines.push(`Codex MCP: ${codexConfig()} ([mcp_servers.${MCP_NAME}], token stored in the file)`);
|
|
476
|
+
}
|
|
394
477
|
}
|
|
395
478
|
i.onMcpRegistered?.(mcp);
|
|
396
|
-
if (i.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
479
|
+
if (!i.mcpOnly) {
|
|
480
|
+
if (i.skipHooks) report.lines.push("Codex hooks: the machine connection already forwards them");
|
|
481
|
+
else {
|
|
482
|
+
editJson(codexHooks(), (d) => (addHooks(d, "nested", codexHookSpecs()), true));
|
|
483
|
+
report.lines.push(`Codex hooks: ${codexHooks()} (${CODEX_EVENTS.join(", ")})`);
|
|
484
|
+
editToml(enableFeatureHooks);
|
|
485
|
+
report.lines.push(`Codex: features.hooks = true in ${codexConfig()}`);
|
|
486
|
+
report.warnings.push("Codex asks you to trust new hooks: run codex, open /hooks and approve the HiveFloor hooks once.");
|
|
487
|
+
}
|
|
403
488
|
}
|
|
404
|
-
if (!i.useEnvToken) report.warnings.push('Codex reads the token from HIVEFLOOR_TOKEN: add export HIVEFLOOR_TOKEN="$(hivefloor token)" to your shell rc.');
|
|
405
489
|
return { report, mcp };
|
|
406
490
|
}
|
|
407
491
|
async function removeCodex(ctx, viaCli, keepHooks = false) {
|
|
@@ -421,174 +505,34 @@ async function removeCodex(ctx, viaCli, keepHooks = false) {
|
|
|
421
505
|
return report;
|
|
422
506
|
}
|
|
423
507
|
|
|
424
|
-
// src/writers/cursor.ts
|
|
425
|
-
import path4 from "path";
|
|
426
|
-
var CURSOR_EVENTS = ["sessionStart", "sessionEnd", "beforeSubmitPrompt", "stop", "subagentStart", "subagentStop"];
|
|
427
|
-
var cursorMcp = () => path4.join(home(), ".cursor", "mcp.json");
|
|
428
|
-
var cursorHooks = () => path4.join(home(), ".cursor", "hooks.json");
|
|
429
|
-
function cursorHookSpecs() {
|
|
430
|
-
return CURSOR_EVENTS.map((event) => ({ event, entry: { command: `hivefloor hook cursor ${event}` } }));
|
|
431
|
-
}
|
|
432
|
-
async function installCursor(i) {
|
|
433
|
-
const report = emptyReport();
|
|
434
|
-
const auth = i.useEnvToken ? "Bearer ${env:HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
|
|
435
|
-
editJson(
|
|
436
|
-
cursorMcp(),
|
|
437
|
-
(d) => {
|
|
438
|
-
const servers = isRecord(d.mcpServers) ? d.mcpServers : d.mcpServers = {};
|
|
439
|
-
servers[MCP_NAME] = { url: i.mcpUrl, headers: { Authorization: auth } };
|
|
440
|
-
return true;
|
|
441
|
-
},
|
|
442
|
-
i.useEnvToken ? {} : { mode: 384 }
|
|
443
|
-
);
|
|
444
|
-
report.lines.push(`Cursor MCP: ${cursorMcp()} (mcpServers.${MCP_NAME})`);
|
|
445
|
-
i.onMcpRegistered?.("file");
|
|
446
|
-
editJson(cursorHooks(), (d) => {
|
|
447
|
-
if (d.version === void 0) d.version = 1;
|
|
448
|
-
addHooks(d, "flat", cursorHookSpecs());
|
|
449
|
-
return true;
|
|
450
|
-
});
|
|
451
|
-
report.lines.push(`Cursor hooks: ${cursorHooks()} (${CURSOR_EVENTS.join(", ")})`);
|
|
452
|
-
return { report, mcp: "file" };
|
|
453
|
-
}
|
|
454
|
-
async function removeCursor() {
|
|
455
|
-
const report = emptyReport();
|
|
456
|
-
const changed = editJson(
|
|
457
|
-
cursorMcp(),
|
|
458
|
-
(d) => {
|
|
459
|
-
if (!isRecord(d.mcpServers) || !(MCP_NAME in d.mcpServers)) return false;
|
|
460
|
-
delete d.mcpServers[MCP_NAME];
|
|
461
|
-
if (Object.keys(d.mcpServers).length === 0) delete d.mcpServers;
|
|
462
|
-
return true;
|
|
463
|
-
},
|
|
464
|
-
{ drop: true }
|
|
465
|
-
);
|
|
466
|
-
if (changed) report.lines.push(`Cursor MCP: removed from ${cursorMcp()}`);
|
|
467
|
-
if (editJson(cursorHooks(), (d) => removeHooks(d, "flat") > 0, { drop: true })) report.lines.push(`Cursor hooks: removed from ${cursorHooks()}`);
|
|
468
|
-
return report;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// src/writers/gemini.ts
|
|
472
|
-
import path5 from "path";
|
|
473
|
-
var GEMINI_EVENTS = ["SessionStart", "SessionEnd", "AfterAgent"];
|
|
474
|
-
var geminiSettings = () => path5.join(home(), ".gemini", "settings.json");
|
|
475
|
-
function geminiHookSpecs() {
|
|
476
|
-
return GEMINI_EVENTS.map((event) => ({
|
|
477
|
-
event,
|
|
478
|
-
// Gemini hook entries carry a name: ours is "hivefloor", which is also how disconnect finds them.
|
|
479
|
-
entry: { name: "hivefloor", type: "command", command: `hivefloor hook gemini ${event}` }
|
|
480
|
-
}));
|
|
481
|
-
}
|
|
482
|
-
async function installGemini(i) {
|
|
483
|
-
const report = emptyReport();
|
|
484
|
-
const auth = i.useEnvToken ? "Bearer $HIVEFLOOR_TOKEN" : `Bearer ${i.token}`;
|
|
485
|
-
let mcp = "file";
|
|
486
|
-
const bin = i.noCli || !i.useEnvToken ? void 0 : findBin("gemini", i.ctx.env);
|
|
487
|
-
if (bin) {
|
|
488
|
-
await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
|
|
489
|
-
const r = await runCmd(bin, ["mcp", "add", MCP_NAME, i.mcpUrl, "-t", "http", "-s", "user", "-H", `Authorization: ${auth}`], { env: i.ctx.env });
|
|
490
|
-
if (r.code === 0) {
|
|
491
|
-
mcp = "cli";
|
|
492
|
-
report.lines.push(`Gemini MCP: gemini mcp add (user scope) -> ${i.mcpUrl}`);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (mcp === "file") {
|
|
496
|
-
editJson(
|
|
497
|
-
geminiSettings(),
|
|
498
|
-
(d) => {
|
|
499
|
-
const servers = isRecord(d.mcpServers) ? d.mcpServers : d.mcpServers = {};
|
|
500
|
-
servers[MCP_NAME] = { httpUrl: i.mcpUrl, headers: { Authorization: auth } };
|
|
501
|
-
return true;
|
|
502
|
-
},
|
|
503
|
-
i.useEnvToken ? {} : { mode: 384 }
|
|
504
|
-
);
|
|
505
|
-
if (!i.useEnvToken) secureFile(geminiSettings());
|
|
506
|
-
report.lines.push(`Gemini MCP: ${geminiSettings()} (mcpServers.${MCP_NAME})`);
|
|
507
|
-
}
|
|
508
|
-
i.onMcpRegistered?.(mcp);
|
|
509
|
-
editJson(geminiSettings(), (d) => (addHooks(d, "nested", geminiHookSpecs()), true), i.useEnvToken ? {} : { mode: 384 });
|
|
510
|
-
report.lines.push(`Gemini hooks: ${geminiSettings()} (${GEMINI_EVENTS.join(", ")})`);
|
|
511
|
-
return { report, mcp };
|
|
512
|
-
}
|
|
513
|
-
async function removeGemini(ctx, viaCli, viaFile = false) {
|
|
514
|
-
const report = emptyReport();
|
|
515
|
-
if (viaCli) {
|
|
516
|
-
const bin = findBin("gemini", ctx.env);
|
|
517
|
-
if (bin && (await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: ctx.env })).code === 0) report.lines.push("Gemini MCP: gemini mcp remove");
|
|
518
|
-
}
|
|
519
|
-
const changed = editJson(
|
|
520
|
-
geminiSettings(),
|
|
521
|
-
(d) => {
|
|
522
|
-
let did = removeHooks(d, "nested") > 0;
|
|
523
|
-
if (viaFile && isRecord(d.mcpServers) && MCP_NAME in d.mcpServers) {
|
|
524
|
-
delete d.mcpServers[MCP_NAME];
|
|
525
|
-
if (Object.keys(d.mcpServers).length === 0) delete d.mcpServers;
|
|
526
|
-
did = true;
|
|
527
|
-
}
|
|
528
|
-
return did;
|
|
529
|
-
},
|
|
530
|
-
{ drop: true }
|
|
531
|
-
);
|
|
532
|
-
if (changed) report.lines.push(`Gemini: removed our entries from ${geminiSettings()}`);
|
|
533
|
-
dropBackupWithEntry(geminiSettings(), MCP_NAME);
|
|
534
|
-
return report;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// src/writers/grok.ts
|
|
538
|
-
async function installGrok(i) {
|
|
539
|
-
const report = emptyReport();
|
|
540
|
-
const auth = i.useEnvToken ? "Bearer ${HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
|
|
541
|
-
const bin = i.noCli ? void 0 : findBin("grok", i.ctx.env);
|
|
542
|
-
if (bin) {
|
|
543
|
-
await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
|
|
544
|
-
const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "-s", "user", "--header", `Authorization: ${auth}`], { env: i.ctx.env });
|
|
545
|
-
if (r.code === 0) {
|
|
546
|
-
report.lines.push(`Grok MCP: grok mcp add (user scope) -> ${i.mcpUrl}`);
|
|
547
|
-
report.lines.push("Grok hooks: none needed (Grok reads the Claude Code hooks in ~/.claude/settings.json; connect Claude Code too for live sessions)");
|
|
548
|
-
i.onMcpRegistered?.("cli");
|
|
549
|
-
return { report, mcp: "cli" };
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
report.warnings.push(`Could not run the grok CLI. Add the server by hand: grok mcp add --transport http ${MCP_NAME} ${i.mcpUrl} -s user --header "Authorization: Bearer <token from ${cliNpxCommand("token --reveal")}>"`);
|
|
553
|
-
return { report, mcp: "manual" };
|
|
554
|
-
}
|
|
555
|
-
async function removeGrok(ctx, viaCli) {
|
|
556
|
-
const report = emptyReport();
|
|
557
|
-
if (viaCli) {
|
|
558
|
-
const bin = findBin("grok", ctx.env);
|
|
559
|
-
if (bin && (await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: ctx.env })).code === 0) report.lines.push("Grok MCP: grok mcp remove");
|
|
560
|
-
}
|
|
561
|
-
return report;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
508
|
export {
|
|
565
509
|
readManifest,
|
|
566
510
|
updateManifest,
|
|
567
511
|
addCreated,
|
|
568
512
|
wasCreated,
|
|
569
513
|
forgetCreated,
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
addRcLine,
|
|
574
|
-
removeRcLine,
|
|
514
|
+
toolReadsEnvToken,
|
|
515
|
+
envTokenSharedWith,
|
|
516
|
+
emptyReport,
|
|
575
517
|
addHooks,
|
|
576
518
|
removeHooks,
|
|
577
519
|
editJson,
|
|
520
|
+
secureFile,
|
|
521
|
+
dropBackupWithEntry,
|
|
578
522
|
CLAUDE_EVENTS,
|
|
579
523
|
claudeJson,
|
|
524
|
+
MCP_NAME,
|
|
580
525
|
installClaude,
|
|
581
526
|
removeClaude,
|
|
527
|
+
tomlString,
|
|
528
|
+
setBlock,
|
|
529
|
+
removeBlock,
|
|
530
|
+
hasBlock,
|
|
531
|
+
removeOutsideTable,
|
|
532
|
+
hasOutsideTable,
|
|
582
533
|
enableFeatureHooks,
|
|
583
534
|
restoreFeatureHooks,
|
|
584
535
|
CODEX_EVENTS,
|
|
585
536
|
installCodex,
|
|
586
|
-
removeCodex
|
|
587
|
-
installCursor,
|
|
588
|
-
removeCursor,
|
|
589
|
-
geminiSettings,
|
|
590
|
-
installGemini,
|
|
591
|
-
removeGemini,
|
|
592
|
-
installGrok,
|
|
593
|
-
removeGrok
|
|
537
|
+
removeCodex
|
|
594
538
|
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
findBin,
|
|
6
|
+
home
|
|
7
|
+
} from "./chunk-XPGL7VYE.js";
|
|
8
|
+
|
|
9
|
+
// src/service/control/proc.ts
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
function runFile(file, args, opts = {}) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
try {
|
|
14
|
+
const child = execFile(file, args, { timeout: opts.timeoutMs ?? 5e3, maxBuffer: 1024 * 1024, windowsHide: true, ...opts.env ? { env: opts.env } : {} }, (err, stdout, stderr) => {
|
|
15
|
+
if (!err) return resolve({ code: 0, stdout: String(stdout), stderr: String(stderr) });
|
|
16
|
+
const e = err;
|
|
17
|
+
const spawnError = typeof e.code === "string";
|
|
18
|
+
resolve({ code: typeof e.code === "number" ? e.code : null, stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), ...spawnError ? { spawnError: true } : {} });
|
|
19
|
+
});
|
|
20
|
+
if (opts.input !== void 0) {
|
|
21
|
+
child.stdin?.on("error", () => void 0);
|
|
22
|
+
child.stdin?.end(opts.input);
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
resolve({ code: null, stdout: "", stderr: "", spawnError: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/service/claude-bin.ts
|
|
31
|
+
import fs from "fs";
|
|
32
|
+
import path from "path";
|
|
33
|
+
var CLAUDE_BIN_ENV = "HIVEFLOOR_CLAUDE_BIN";
|
|
34
|
+
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "fish", "dash", "ksh"]);
|
|
35
|
+
var NOT_FOUND_TTL_MS = 6e4;
|
|
36
|
+
var LOGIN_SHELL_TIMEOUT_MS = 3e3;
|
|
37
|
+
function userBinDirs(homeDir = home()) {
|
|
38
|
+
return [path.join(homeDir, ".local", "bin"), path.join(homeDir, ".claude", "local"), "/opt/homebrew/bin", "/usr/local/bin"];
|
|
39
|
+
}
|
|
40
|
+
function claudeFallbackPaths(homeDir = home()) {
|
|
41
|
+
return userBinDirs(homeDir).map((d) => path.join(d, "claude"));
|
|
42
|
+
}
|
|
43
|
+
var isExecutableFile = (p) => {
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.statSync(p).isFile()) return false;
|
|
46
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var lastAbsLine = (out) => {
|
|
53
|
+
const lines = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
54
|
+
for (let i = lines.length - 1; i >= 0; i--) if (path.isAbsolute(lines[i])) return lines[i];
|
|
55
|
+
return void 0;
|
|
56
|
+
};
|
|
57
|
+
function createClaudeResolver(deps = {}) {
|
|
58
|
+
const isExec = deps.isExecutable ?? isExecutableFile;
|
|
59
|
+
const now = deps.now ?? Date.now;
|
|
60
|
+
let found;
|
|
61
|
+
let missAt = -Infinity;
|
|
62
|
+
let warnedBadEnv = false;
|
|
63
|
+
const search = async () => {
|
|
64
|
+
const env = deps.env ?? process.env;
|
|
65
|
+
const homeDir = deps.home ?? home();
|
|
66
|
+
if ((deps.platform ?? process.platform) === "win32") {
|
|
67
|
+
return findBin("claude", env);
|
|
68
|
+
}
|
|
69
|
+
const explicit = env[CLAUDE_BIN_ENV];
|
|
70
|
+
if (explicit) {
|
|
71
|
+
if (path.isAbsolute(explicit) && isExec(explicit)) return explicit;
|
|
72
|
+
if (!warnedBadEnv) {
|
|
73
|
+
warnedBadEnv = true;
|
|
74
|
+
(deps.log ?? ((m) => console.error(m)))(`hivefloor: ${CLAUDE_BIN_ENV} is set but is not an absolute path to an executable file; ignoring it and searching for claude`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const dir of (env.PATH ?? "").split(path.delimiter)) {
|
|
78
|
+
if (!dir || !path.isAbsolute(dir)) continue;
|
|
79
|
+
const p2 = path.join(dir, "claude");
|
|
80
|
+
if (isExec(p2)) return p2;
|
|
81
|
+
}
|
|
82
|
+
for (const p2 of claudeFallbackPaths(homeDir)) if (isExec(p2)) return p2;
|
|
83
|
+
const shell = env.SHELL;
|
|
84
|
+
if (!shell || !path.isAbsolute(shell) || !SHELLS.has(path.basename(shell)) || !isExec(shell)) return void 0;
|
|
85
|
+
const r = await (deps.run ?? runFile)(shell, ["-lc", "command -v claude"], { timeoutMs: LOGIN_SHELL_TIMEOUT_MS });
|
|
86
|
+
if (r.code !== 0) return void 0;
|
|
87
|
+
const p = lastAbsLine(r.stdout);
|
|
88
|
+
return p && isExec(p) ? p : void 0;
|
|
89
|
+
};
|
|
90
|
+
return async () => {
|
|
91
|
+
if (found && isExec(found)) return found;
|
|
92
|
+
found = void 0;
|
|
93
|
+
if (now() - missAt < NOT_FOUND_TTL_MS) return void 0;
|
|
94
|
+
found = await search();
|
|
95
|
+
if (!found) missAt = now();
|
|
96
|
+
return found;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
var shared;
|
|
100
|
+
var resolveClaudeBin = () => (shared ??= createClaudeResolver())();
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
runFile,
|
|
104
|
+
userBinDirs,
|
|
105
|
+
resolveClaudeBin
|
|
106
|
+
};
|