@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.
Files changed (55) hide show
  1. package/LICENSE +6 -20
  2. package/README.md +31 -9
  3. package/dist/chunk-2ERPDCOT.js +38 -0
  4. package/dist/chunk-4G362W6G.js +19 -0
  5. package/dist/chunk-5OJUJCE3.js +817 -0
  6. package/dist/chunk-5XXYQQG6.js +274 -0
  7. package/dist/chunk-AGIYVFDQ.js +10984 -0
  8. package/dist/{chunk-2BSA7LFD.js → chunk-BLLPDPTL.js} +7 -3
  9. package/dist/{chunk-R5KDI4WI.js → chunk-FPJNGKT5.js} +9 -3
  10. package/dist/chunk-HW3KRNCO.js +201 -0
  11. package/dist/{chunk-ZC2DAKB5.js → chunk-HWBUESB4.js} +1500 -316
  12. package/dist/{chunk-DNG5CRT3.js → chunk-K2IRTZH5.js} +8 -7
  13. package/dist/chunk-KQMPCV57.js +122 -0
  14. package/dist/{chunk-UJ4BQFWV.js → chunk-L6EFW2SJ.js} +53 -31
  15. package/dist/{chunk-T7TG6WA4.js → chunk-LX5A25O6.js} +4 -2
  16. package/dist/{chunk-W7DROKC7.js → chunk-MYJCU5JL.js} +89 -13
  17. package/dist/chunk-NMUH5NXN.js +52 -0
  18. package/dist/chunk-NRJONTFT.js +67 -0
  19. package/dist/chunk-QBBKGORP.js +327 -0
  20. package/dist/chunk-QGULSOQ2.js +142 -0
  21. package/dist/{chunk-PDXYOREI.js → chunk-QLMLB7RB.js} +110 -31
  22. package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
  23. package/dist/chunk-SBOEAOHD.js +48 -0
  24. package/dist/{chunk-SD65CIIY.js → chunk-TIR4CV3P.js} +117 -33
  25. package/dist/{chunk-QHYZBHXO.js → chunk-X2KH6AR4.js} +195 -251
  26. package/dist/chunk-XAXKZ572.js +106 -0
  27. package/dist/{chunk-VNCQFFQO.js → chunk-XPGL7VYE.js} +114 -1
  28. package/dist/cli-BFOJWJNG.js +279 -0
  29. package/dist/connect-ZJ4JXBAJ.js +27 -0
  30. package/dist/control-6GHTZQJV.js +25 -0
  31. package/dist/{disconnect-MKRHK4CX.js → disconnect-3T25NTKX.js} +55 -34
  32. package/dist/hook-TPSHBCWU.js +575 -0
  33. package/dist/index.js +9 -6
  34. package/dist/{link-HXBMMSG3.js → link-EAS5F5PC.js} +34 -18
  35. package/dist/{machine-connect-BYHZRRRB.js → machine-connect-KEI4BRWF.js} +30 -24
  36. package/dist/mcp-LVIYR3EN.js +1882 -0
  37. package/dist/orch-KVRL2YA2.js +137 -0
  38. package/dist/{run-336BSU72.js → run-JL2OZWK3.js} +1933 -29
  39. package/dist/{status-KBBSAADD.js → status-QXOOJGGN.js} +29 -14
  40. package/dist/token-BFWWQIDA.js +44 -0
  41. package/dist/voice-QXHFXRWG.js +264 -0
  42. package/dist/{where-KR7VWSEC.js → where-CMUORQIO.js} +20 -8
  43. package/package.json +6 -7
  44. package/dist/chunk-2IMJECYD.js +0 -71
  45. package/dist/chunk-7KF7IQJN.js +0 -30
  46. package/dist/chunk-GT3HFFT4.js +0 -132
  47. package/dist/chunk-JCLWCMEM.js +0 -8
  48. package/dist/chunk-K3NQKI34.js +0 -10
  49. package/dist/chunk-O2TC3WSS.js +0 -303
  50. package/dist/chunk-PY74LT2W.js +0 -41
  51. package/dist/cli-P3DYE5IR.js +0 -103
  52. package/dist/connect-ACNM5DQG.js +0 -23
  53. package/dist/hook-KR2HGVAR.js +0 -301
  54. package/dist/mcp-N2TMD4GR.js +0 -121
  55. 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
- findBin
4
- } from "./chunk-2IMJECYD.js";
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-VNCQFFQO.js";
19
+ } from "./chunk-XPGL7VYE.js";
17
20
  import {
18
- cliNpxCommand,
19
- runCmd
20
- } from "./chunk-ZC2DAKB5.js";
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
- // src/shellrc.ts
58
- import fs from "fs";
59
- import path from "path";
60
- var RC_MARK = "# hivefloor";
61
- var RC_LINE = `export HIVEFLOOR_TOKEN="$(hivefloor token)" ${RC_MARK}`;
62
- function rcFileFor(env) {
63
- const shell = path.basename(env.SHELL ?? "");
64
- if (shell === "zsh") return path.join(home(), ".zshrc");
65
- if (shell === "bash") {
66
- const profile = path.join(home(), ".bash_profile");
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 fs2 from "fs";
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
- return /hivefloor[/\\]cli[/\\]index\.js"?\s+hook\s/.test(cmd);
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
- fs2.chmodSync(f, 384);
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 = () => path2.join(home(), ".claude.json");
202
- var claudeSettings = () => path2.join(home(), ".claude", "settings.json");
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 bin = i.noCli || !i.useEnvToken ? void 0 : findBin("claude", i.ctx.env);
218
- if (bin) {
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
- const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "--header", `Authorization: ${auth}`, "-s", "user"], { env: i.ctx.env });
221
- if (r.code === 0) {
222
- mcp = "cli";
223
- report.lines.push(`Claude Code MCP: claude mcp add (user scope) -> ${i.mcpUrl}`);
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.skipHooks) report.lines.push("Claude Code hooks: the machine connection already forwards them");
241
- else {
242
- editJson(claudeSettings(), (d) => (addHooks(d, "nested", claudeHookSpecs()), true));
243
- report.lines.push(`Claude Code hooks: ${claudeSettings()} (${CLAUDE_EVENTS.join(", ")})`);
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 path3 from "path";
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 = () => path3.join(process.env.CODEX_HOME || path3.join(home(), ".codex"));
350
- var codexConfig = () => path3.join(codexDir(), "config.toml");
351
- var codexHooks = () => path3.join(codexDir(), "hooks.json");
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
- const bin = i.noCli ? void 0 : findBin("codex", i.ctx.env);
375
- if (bin) {
376
- await runCmd(bin, ["mcp", "remove", MCP_NAME], { env: i.ctx.env });
377
- const r = await runCmd(bin, ["mcp", "add", MCP_NAME, "--url", i.mcpUrl, "--bearer-token-env-var", "HIVEFLOOR_TOKEN"], { env: i.ctx.env });
378
- if (r.code === 0) {
379
- mcp = "cli";
380
- report.lines.push(`Codex MCP: codex mcp add -> ${i.mcpUrl}`);
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
- if (mcp === "file") {
384
- editToml((c) => {
385
- if (hasTable(c, `mcp_servers.${MCP_NAME}`)) {
386
- report.warnings.push(`${codexConfig()} already defines [mcp_servers.${MCP_NAME}]; left as it is`);
387
- return c;
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
- return setBlock(c, "mcp", `[mcp_servers.${MCP_NAME}]
390
- url = ${tomlString(i.mcpUrl)}
391
- bearer_token_env_var = "HIVEFLOOR_TOKEN"`);
392
- });
393
- report.lines.push(`Codex MCP: ${codexConfig()} ([mcp_servers.${MCP_NAME}])`);
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.skipHooks) report.lines.push("Codex hooks: the machine connection already forwards them");
397
- else {
398
- editJson(codexHooks(), (d) => (addHooks(d, "nested", codexHookSpecs()), true));
399
- report.lines.push(`Codex hooks: ${codexHooks()} (${CODEX_EVENTS.join(", ")})`);
400
- editToml(enableFeatureHooks);
401
- report.lines.push(`Codex: features.hooks = true in ${codexConfig()}`);
402
- report.warnings.push("Codex asks you to trust new hooks: run codex, open /hooks and approve the Hivefloor hooks once.");
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
- RC_LINE,
571
- rcFileFor,
572
- hasRcLine,
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
+ };