@hivefloor/cli 0.1.3 → 0.1.5

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 (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -1
  3. package/dist/{chunk-HT5KXWXM.js → chunk-3N5JYP5O.js} +7 -3
  4. package/dist/{chunk-TH4ANHSG.js → chunk-4DRHR7LR.js} +365 -129
  5. package/dist/{chunk-DQV6FAJU.js → chunk-6MDJB3BV.js} +97 -26
  6. package/dist/{chunk-NLZXB26Y.js → chunk-6XVIEX75.js} +293 -71
  7. package/dist/{chunk-KHHSRIMU.js → chunk-C627OKTW.js} +2 -2
  8. package/dist/chunk-H6NYMF2D.js +50 -0
  9. package/dist/{chunk-T7TG6WA4.js → chunk-IJRHCDIG.js} +1 -1
  10. package/dist/{chunk-3YOTXIFP.js → chunk-IWIDKNWC.js} +109 -29
  11. package/dist/{chunk-E4V47VEV.js → chunk-J36OIJBM.js} +41 -23
  12. package/dist/{chunk-O6VSA2JT.js → chunk-MZGGJ6ZS.js} +87 -13
  13. package/dist/{chunk-YRNMECUF.js → chunk-RYGL6XU4.js} +5 -3
  14. package/dist/chunk-SOXRITAW.js +139 -0
  15. package/dist/{chunk-VNCQFFQO.js → chunk-TPMFQUAW.js} +102 -1
  16. package/dist/chunk-U6RRUAC2.js +229 -0
  17. package/dist/chunk-VX7SCIF2.js +65 -0
  18. package/dist/{chunk-O2TC3WSS.js → chunk-X6B6R32A.js} +1 -1
  19. package/dist/cli-JCBZICR3.js +243 -0
  20. package/dist/connect-2HLVPEOL.js +23 -0
  21. package/dist/{disconnect-FI76IEZJ.js → disconnect-WQMDCUXT.js} +41 -27
  22. package/dist/{hook-N4ZTQEWM.js → hook-AYJNRGIY.js} +25 -9
  23. package/dist/index.js +2 -2
  24. package/dist/{link-5WSZHKBE.js → link-ORT4LID6.js} +29 -16
  25. package/dist/{machine-connect-GQJ4SB3D.js → machine-connect-N52CKQEW.js} +21 -21
  26. package/dist/{mcp-EUBTVJNX.js → mcp-2SF6Y73M.js} +27 -30
  27. package/dist/{run-BEE2ATEM.js → run-ZAZ45MKQ.js} +22 -10
  28. package/dist/{status-SMAVPW4D.js → status-GOCFNO7K.js} +25 -13
  29. package/dist/{token-AUEAWQDH.js → token-EMM4AJAU.js} +15 -6
  30. package/dist/voice-F5MIS3IZ.js +262 -0
  31. package/dist/{where-7NI7UEEF.js → where-XFXRTP7E.js} +16 -7
  32. package/package.json +2 -2
  33. package/dist/chunk-2IMJECYD.js +0 -71
  34. package/dist/chunk-DWHMBUCA.js +0 -30
  35. package/dist/chunk-NKMVO5BP.js +0 -132
  36. package/dist/chunk-PY74LT2W.js +0 -41
  37. package/dist/cli-MAHYTCYV.js +0 -103
  38. package/dist/connect-QA3QIEGL.js +0 -23
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- findBin
4
- } from "./chunk-2IMJECYD.js";
3
+ ApiError
4
+ } from "./chunk-VX7SCIF2.js";
5
5
  import {
6
6
  atomicWrite,
7
7
  backupOnce,
8
8
  dropBackupIfSame,
9
+ findBin,
9
10
  home,
10
11
  isRecord,
11
12
  manifestFile,
@@ -13,11 +14,14 @@ import {
13
14
  readText,
14
15
  removeFile,
15
16
  serializeJson
16
- } from "./chunk-VNCQFFQO.js";
17
+ } from "./chunk-TPMFQUAW.js";
17
18
  import {
18
- cliNpxCommand,
19
- runCmd
20
- } from "./chunk-TH4ANHSG.js";
19
+ runCmd,
20
+ sameOrigin
21
+ } from "./chunk-SOXRITAW.js";
22
+ import {
23
+ cliNpxCommand
24
+ } from "./chunk-4DRHR7LR.js";
21
25
 
22
26
  // src/manifest.ts
23
27
  function readManifest() {
@@ -53,6 +57,19 @@ var wasCreated = (file) => readManifest().created.includes(file);
53
57
  var forgetCreated = (file) => updateManifest((m) => {
54
58
  m.created = m.created.filter((f) => f !== file);
55
59
  });
60
+ function toolReadsEnvToken(name, entry, shellExportsToken) {
61
+ if (!entry) return false;
62
+ if (typeof entry.envToken === "boolean") return entry.envToken;
63
+ return name === "codex" || shellExportsToken;
64
+ }
65
+ function envTokenSharedWith(host, tool, shellExportsToken) {
66
+ const manifest = readManifest();
67
+ return Object.entries(manifest.tools).every(([name, entry]) => {
68
+ if (name === tool) return true;
69
+ if (!toolReadsEnvToken(name, entry, shellExportsToken)) return true;
70
+ return typeof entry?.host === "string" && entry.host.length > 0 && sameOrigin(entry.host, host);
71
+ });
72
+ }
56
73
 
57
74
  // src/shellrc.ts
58
75
  import fs from "fs";
@@ -68,11 +85,12 @@ function rcFileFor(env) {
68
85
  }
69
86
  return void 0;
70
87
  }
88
+ function isRcLine(line) {
89
+ const t = line.trim();
90
+ return !t.startsWith("#") && t.includes("HIVEFLOOR_TOKEN") && t.includes("hivefloor token") && t.endsWith(RC_MARK);
91
+ }
71
92
  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);
75
- });
93
+ return (readText(file) ?? "").split("\n").some(isRcLine);
76
94
  }
77
95
  function addRcLine(file) {
78
96
  if (hasRcLine(file)) return false;
@@ -87,7 +105,7 @@ function removeRcLine(file) {
87
105
  const cur = readText(file);
88
106
  if (cur === void 0) return false;
89
107
  const lines = cur.split("\n");
90
- const kept = lines.filter((l) => l.trim() !== RC_LINE);
108
+ const kept = lines.filter((l) => !isRcLine(l));
91
109
  if (kept.length === lines.length) return false;
92
110
  const next = kept.join("\n");
93
111
  if (next.trim() === "" && wasCreated(file)) {
@@ -99,19 +117,18 @@ function removeRcLine(file) {
99
117
  return true;
100
118
  }
101
119
 
102
- // src/writers/claude.ts
103
- import path2 from "path";
104
-
105
120
  // src/writers/common.ts
106
121
  import fs2 from "fs";
107
122
  var emptyReport = () => ({ lines: [], warnings: [] });
108
123
  var CLI_COPY_RE = /hivefloor[/\\]cli[/\\]index\.js$/;
124
+ var MACHINE_CMD_RE = /hivefloor[/\\]+cli[/\\]+index\.js"?\s+hook\s/;
109
125
  function isMachineHook(h) {
110
126
  if (!isRecord(h)) return false;
111
127
  const cmd = h.command;
112
128
  if (typeof cmd !== "string") return false;
113
129
  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);
130
+ if (MACHINE_CMD_RE.test(cmd)) return true;
131
+ return typeof h.commandWindows === "string" && MACHINE_CMD_RE.test(h.commandWindows);
115
132
  }
116
133
  function isAgentHook(h) {
117
134
  if (!isRecord(h) || isMachineHook(h)) return false;
@@ -197,6 +214,7 @@ function dropBackupWithEntry(file, name) {
197
214
  }
198
215
 
199
216
  // src/writers/claude.ts
217
+ import path2 from "path";
200
218
  var CLAUDE_EVENTS = ["SessionStart", "SessionEnd", "UserPromptSubmit", "Stop", "Notification", "SubagentStart", "SubagentStop"];
201
219
  var claudeJson = () => path2.join(home(), ".claude.json");
202
220
  var claudeSettings = () => path2.join(home(), ".claude", "settings.json");
@@ -214,13 +232,16 @@ async function installClaude(i) {
214
232
  const report = emptyReport();
215
233
  const auth = i.useEnvToken ? "Bearer ${HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
216
234
  let mcp = "file";
217
- const bin = i.noCli || !i.useEnvToken ? void 0 : findBin("claude", i.ctx.env);
218
- if (bin) {
235
+ const priorCli = !i.useEnvToken && readManifest().tools.claude?.mcp === "cli";
236
+ const bin = i.noCli ? void 0 : findBin("claude", i.ctx.env);
237
+ if (bin && (i.useEnvToken || priorCli)) {
219
238
  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}`);
239
+ if (i.useEnvToken) {
240
+ const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "--header", `Authorization: ${auth}`, "-s", "user"], { env: i.ctx.env });
241
+ if (r.code === 0) {
242
+ mcp = "cli";
243
+ report.lines.push(`Claude Code MCP: claude mcp add (user scope) -> ${i.mcpUrl}`);
244
+ }
224
245
  }
225
246
  }
226
247
  if (mcp === "file") {
@@ -237,10 +258,12 @@ async function installClaude(i) {
237
258
  report.lines.push(`Claude Code MCP: ${claudeJson()} (mcpServers.${MCP_NAME})`);
238
259
  }
239
260
  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(", ")})`);
261
+ if (!i.mcpOnly) {
262
+ if (i.skipHooks) report.lines.push("Claude Code hooks: the machine connection already forwards them");
263
+ else {
264
+ editJson(claudeSettings(), (d) => (addHooks(d, "nested", claudeHookSpecs()), true));
265
+ report.lines.push(`Claude Code hooks: ${claudeSettings()} (${CLAUDE_EVENTS.join(", ")})`);
266
+ }
244
267
  }
245
268
  return { report, mcp };
246
269
  }
@@ -312,6 +335,60 @@ function hasTable(content, table) {
312
335
  const re = new RegExp(`^\\s*\\[\\s*${table.replace(/\./g, "\\s*\\.\\s*")}\\s*\\]`, "m");
313
336
  return re.test(stripped);
314
337
  }
338
+ var tableName = (header) => /^\s*\[\s*([^\]]+?)\s*\]/.exec(header)?.[1]?.replace(/\s+/g, "");
339
+ function isNamedTable(name, table) {
340
+ const want = table.replace(/\s+/g, "");
341
+ return name === want || name.startsWith(`${want}.`);
342
+ }
343
+ function removeOutsideTable(content, table) {
344
+ const lines = content.split("\n");
345
+ const out = [];
346
+ const pending = [];
347
+ let inFence = false;
348
+ let skipping = false;
349
+ const dropPending = () => {
350
+ pending.length = 0;
351
+ };
352
+ const flushPending = () => {
353
+ out.push(...pending);
354
+ dropPending();
355
+ };
356
+ for (const line of lines) {
357
+ const bare = line.replace(/\r$/, "");
358
+ if (!inFence && /^# hivefloor:begin /.test(bare)) {
359
+ if (skipping) dropPending();
360
+ inFence = true;
361
+ }
362
+ if (inFence) {
363
+ out.push(line);
364
+ if (/^# hivefloor:end /.test(bare)) inFence = false;
365
+ continue;
366
+ }
367
+ const name = tableName(bare);
368
+ if (name !== void 0) {
369
+ if (isNamedTable(name, table)) {
370
+ skipping = true;
371
+ dropPending();
372
+ continue;
373
+ }
374
+ if (skipping) flushPending();
375
+ skipping = false;
376
+ out.push(line);
377
+ continue;
378
+ }
379
+ if (skipping) {
380
+ if (bare.trim() === "" || /^\s*#/.test(bare)) pending.push(line);
381
+ else dropPending();
382
+ continue;
383
+ }
384
+ out.push(line);
385
+ }
386
+ if (skipping && pending.some((l) => /^\s*#/.test(l.replace(/\r$/, "")))) flushPending();
387
+ return out.join("\n");
388
+ }
389
+ function hasOutsideTable(content, table) {
390
+ return removeOutsideTable(content, table) !== content;
391
+ }
315
392
  var FEATURE_TAG = "# hivefloor";
316
393
  function enableFeatureHooks(content) {
317
394
  const lines = content.split("\n");
@@ -364,44 +441,98 @@ function editToml(fn, opts = {}) {
364
441
  }
365
442
  if (before === void 0) addCreated(file);
366
443
  else if (!wasCreated(file)) backupOnce(file);
367
- atomicWrite(file, next);
444
+ atomicWrite(file, next, opts.mode);
368
445
  if (opts.drop) dropBackupIfSame(file);
369
446
  return true;
370
447
  }
448
+ var CODEX_TABLE = `mcp_servers.${MCP_NAME}`;
449
+ function codexEnvBlock(mcpUrl) {
450
+ return `[mcp_servers.${MCP_NAME}]
451
+ url = ${tomlString(mcpUrl)}
452
+ bearer_token_env_var = "HIVEFLOOR_TOKEN"`;
453
+ }
454
+ function codexLiteralBlock(mcpUrl, token) {
455
+ return `[mcp_servers.${MCP_NAME}]
456
+ url = ${tomlString(mcpUrl)}
457
+ http_headers = { Authorization = ${tomlString(`Bearer ${token}`)} }`;
458
+ }
459
+ async function writeCodexLiteral(i) {
460
+ const prior = readManifest().tools.codex;
461
+ const ownedCli = prior?.mcp === "cli";
462
+ if (ownedCli && !i.noCli) {
463
+ const bin = findBin("codex", i.ctx.env);
464
+ if (bin) await runCmd(bin, ["mcp", "remove", MCP_NAME], { env: i.ctx.env });
465
+ }
466
+ const body = codexLiteralBlock(i.mcpUrl, i.token);
467
+ let blocked = false;
468
+ const wrote = editToml(
469
+ (c) => {
470
+ if (hasTable(c, CODEX_TABLE) && !ownedCli) {
471
+ blocked = true;
472
+ return c;
473
+ }
474
+ return setBlock(ownedCli ? removeOutsideTable(c, CODEX_TABLE) : c, "mcp", body);
475
+ },
476
+ { mode: 384 }
477
+ );
478
+ const file = codexConfig();
479
+ if (blocked || !wrote && !(readText(file) ?? "").includes(body)) return false;
480
+ secureFile(file);
481
+ return true;
482
+ }
371
483
  async function installCodex(i) {
372
484
  const report = emptyReport();
373
485
  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}`);
486
+ if (i.useEnvToken) {
487
+ const bin = i.noCli ? void 0 : findBin("codex", i.ctx.env);
488
+ if (bin) {
489
+ await runCmd(bin, ["mcp", "remove", MCP_NAME], { env: i.ctx.env });
490
+ const r = await runCmd(bin, ["mcp", "add", MCP_NAME, "--url", i.mcpUrl, "--bearer-token-env-var", "HIVEFLOOR_TOKEN"], { env: i.ctx.env });
491
+ if (r.code === 0) {
492
+ mcp = "cli";
493
+ report.lines.push(`Codex MCP: codex mcp add -> ${i.mcpUrl}`);
494
+ }
381
495
  }
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;
496
+ if (mcp === "file") {
497
+ let wrote = false;
498
+ editToml((c) => {
499
+ if (hasTable(c, CODEX_TABLE)) {
500
+ report.warnings.push(`${codexConfig()} already defines [mcp_servers.${MCP_NAME}]; left as it is`);
501
+ return c;
502
+ }
503
+ wrote = true;
504
+ return setBlock(c, "mcp", codexEnvBlock(i.mcpUrl));
505
+ });
506
+ if (wrote) report.lines.push(`Codex MCP: ${codexConfig()} ([mcp_servers.${MCP_NAME}])`);
507
+ }
508
+ } else {
509
+ const wrote = await writeCodexLiteral(i);
510
+ if (!wrote) {
511
+ const file = codexConfig();
512
+ if (i.requireStoredToken) {
513
+ throw new ApiError(
514
+ 0,
515
+ "codex_config",
516
+ `${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.`
517
+ );
388
518
  }
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}])`);
519
+ report.warnings.push(`${file} already defines [mcp_servers.${MCP_NAME}]; left as it is`);
520
+ report.warnings.push('Codex reads the token from HIVEFLOOR_TOKEN: add export HIVEFLOOR_TOKEN="$(hivefloor token)" to your shell rc.');
521
+ } else {
522
+ report.lines.push(`Codex MCP: ${codexConfig()} ([mcp_servers.${MCP_NAME}], token stored in the file)`);
523
+ }
394
524
  }
395
525
  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.");
403
- }
404
- if (!i.useEnvToken) report.warnings.push('Codex reads the token from HIVEFLOOR_TOKEN: add export HIVEFLOOR_TOKEN="$(hivefloor token)" to your shell rc.');
526
+ if (!i.mcpOnly) {
527
+ if (i.skipHooks) report.lines.push("Codex hooks: the machine connection already forwards them");
528
+ else {
529
+ editJson(codexHooks(), (d) => (addHooks(d, "nested", codexHookSpecs()), true));
530
+ report.lines.push(`Codex hooks: ${codexHooks()} (${CODEX_EVENTS.join(", ")})`);
531
+ editToml(enableFeatureHooks);
532
+ report.lines.push(`Codex: features.hooks = true in ${codexConfig()}`);
533
+ report.warnings.push("Codex asks you to trust new hooks: run codex, open /hooks and approve the Hivefloor hooks once.");
534
+ }
535
+ }
405
536
  return { report, mcp };
406
537
  }
407
538
  async function removeCodex(ctx, viaCli, keepHooks = false) {
@@ -443,12 +574,14 @@ async function installCursor(i) {
443
574
  );
444
575
  report.lines.push(`Cursor MCP: ${cursorMcp()} (mcpServers.${MCP_NAME})`);
445
576
  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(", ")})`);
577
+ if (!i.mcpOnly) {
578
+ editJson(cursorHooks(), (d) => {
579
+ if (d.version === void 0) d.version = 1;
580
+ addHooks(d, "flat", cursorHookSpecs());
581
+ return true;
582
+ });
583
+ report.lines.push(`Cursor hooks: ${cursorHooks()} (${CURSOR_EVENTS.join(", ")})`);
584
+ }
452
585
  return { report, mcp: "file" };
453
586
  }
454
587
  async function removeCursor() {
@@ -483,13 +616,16 @@ async function installGemini(i) {
483
616
  const report = emptyReport();
484
617
  const auth = i.useEnvToken ? "Bearer $HIVEFLOOR_TOKEN" : `Bearer ${i.token}`;
485
618
  let mcp = "file";
486
- const bin = i.noCli || !i.useEnvToken ? void 0 : findBin("gemini", i.ctx.env);
487
- if (bin) {
619
+ const priorCli = !i.useEnvToken && readManifest().tools.gemini?.mcp === "cli";
620
+ const bin = i.noCli ? void 0 : findBin("gemini", i.ctx.env);
621
+ if (bin && (i.useEnvToken || priorCli)) {
488
622
  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}`);
623
+ if (i.useEnvToken) {
624
+ const r = await runCmd(bin, ["mcp", "add", MCP_NAME, i.mcpUrl, "-t", "http", "-s", "user", "-H", `Authorization: ${auth}`], { env: i.ctx.env });
625
+ if (r.code === 0) {
626
+ mcp = "cli";
627
+ report.lines.push(`Gemini MCP: gemini mcp add (user scope) -> ${i.mcpUrl}`);
628
+ }
493
629
  }
494
630
  }
495
631
  if (mcp === "file") {
@@ -506,8 +642,10 @@ async function installGemini(i) {
506
642
  report.lines.push(`Gemini MCP: ${geminiSettings()} (mcpServers.${MCP_NAME})`);
507
643
  }
508
644
  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(", ")})`);
645
+ if (!i.mcpOnly) {
646
+ editJson(geminiSettings(), (d) => (addHooks(d, "nested", geminiHookSpecs()), true), i.useEnvToken ? {} : { mode: 384 });
647
+ report.lines.push(`Gemini hooks: ${geminiSettings()} (${GEMINI_EVENTS.join(", ")})`);
648
+ }
511
649
  return { report, mcp };
512
650
  }
513
651
  async function removeGemini(ctx, viaCli, viaFile = false) {
@@ -535,29 +673,110 @@ async function removeGemini(ctx, viaCli, viaFile = false) {
535
673
  }
536
674
 
537
675
  // src/writers/grok.ts
676
+ import path6 from "path";
677
+ var grokConfig = () => path6.join(home(), ".grok", "config.toml");
678
+ var HOOKS_LINE = "Grok hooks: none needed (Grok reads the Claude Code hooks in ~/.claude/settings.json; connect Claude Code too for live sessions)";
679
+ function editGrokToml(fn, opts = {}) {
680
+ const file = grokConfig();
681
+ const before = readText(file);
682
+ const next = fn(before ?? "");
683
+ if (next === (before ?? "")) return false;
684
+ if (opts.drop && wasCreated(file) && next.trim() === "") {
685
+ removeFile(file);
686
+ forgetCreated(file);
687
+ return true;
688
+ }
689
+ if (before === void 0) addCreated(file);
690
+ else if (!wasCreated(file)) backupOnce(file);
691
+ atomicWrite(file, next, opts.mode);
692
+ if (opts.drop) dropBackupIfSame(file);
693
+ return true;
694
+ }
695
+ function grokMcpBlock(mcpUrl, auth) {
696
+ return `[mcp_servers.${MCP_NAME}]
697
+ url = ${tomlString(mcpUrl)}
698
+ enabled = true
699
+
700
+ [mcp_servers.${MCP_NAME}.headers]
701
+ Authorization = ${tomlString(auth)}`;
702
+ }
703
+ var GROK_TABLE = `mcp_servers.${MCP_NAME}`;
704
+ var ENV_AUTH = "Bearer ${HIVEFLOOR_TOKEN}";
705
+ function outsideHasRawAuthorization(content) {
706
+ const stripped = content.replace(/# hivefloor:begin[\s\S]*?# hivefloor:end [^\n]*(?:\n|$)/g, "");
707
+ let inTable = false;
708
+ for (const line of stripped.split("\n")) {
709
+ const bare = line.replace(/\r$/, "");
710
+ const header = /^\s*\[\s*([^\]]+?)\s*\]/.exec(bare);
711
+ if (header) {
712
+ const name = header[1].replace(/\s+/g, "");
713
+ inTable = name === GROK_TABLE || name.startsWith(`${GROK_TABLE}.`);
714
+ continue;
715
+ }
716
+ if (!inTable) continue;
717
+ const auth = /Authorization\s*=\s*["']([^"']*)["']/.exec(bare);
718
+ if (auth && auth[1] !== ENV_AUTH) return true;
719
+ }
720
+ return false;
721
+ }
722
+ async function dropOutsideServer(env, bin) {
723
+ const file = grokConfig();
724
+ const bak = `${file}.hivefloor.bak`;
725
+ const before = readText(file);
726
+ const beforeBak = readText(bak);
727
+ const staleLive = before !== void 0 && hasOutsideTable(before, GROK_TABLE);
728
+ const staleBak = beforeBak !== void 0 && hasOutsideTable(beforeBak, GROK_TABLE);
729
+ if (!staleLive && !staleBak) return false;
730
+ if (bin && staleLive) await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env });
731
+ for (const f of [file, bak]) {
732
+ const cur = readText(f);
733
+ if (cur === void 0 || !hasOutsideTable(cur, GROK_TABLE)) continue;
734
+ const next = removeOutsideTable(cur, GROK_TABLE);
735
+ if (next.trim() === "") removeFile(f);
736
+ else atomicWrite(f, next);
737
+ }
738
+ const left = readText(file);
739
+ if (left !== void 0 && left.trim() === "") removeFile(file);
740
+ return true;
741
+ }
538
742
  async function installGrok(i) {
539
743
  const report = emptyReport();
540
- const auth = i.useEnvToken ? "Bearer ${HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
744
+ const auth = i.useEnvToken ? ENV_AUTH : `Bearer ${i.token}`;
541
745
  const bin = i.noCli ? void 0 : findBin("grok", i.ctx.env);
542
- if (bin) {
746
+ const liveBefore = readText(grokConfig());
747
+ const bakBefore = readText(`${grokConfig()}.hivefloor.bak`);
748
+ const hadRaw = [liveBefore, bakBefore].some((t) => t !== void 0 && outsideHasRawAuthorization(t));
749
+ const removed = await dropOutsideServer(i.ctx.env, bin);
750
+ if (removed && hadRaw) report.warnings.push(`${grokConfig()} had [${GROK_TABLE}] outside our block; removed it so an older token does not stay on disk`);
751
+ if (bin && i.useEnvToken) {
543
752
  await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
544
753
  const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "-s", "user", "--header", `Authorization: ${auth}`], { env: i.ctx.env });
545
754
  if (r.code === 0) {
546
755
  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)");
756
+ if (!i.mcpOnly) report.lines.push(HOOKS_LINE);
548
757
  i.onMcpRegistered?.("cli");
549
758
  return { report, mcp: "cli" };
550
759
  }
551
760
  }
761
+ const file = grokConfig();
762
+ const changed = editGrokToml((c) => setBlock(c, "mcp", grokMcpBlock(i.mcpUrl, auth)), i.useEnvToken ? {} : { mode: 384 });
763
+ if (!i.useEnvToken && (changed || (readText(file) ?? "").includes(i.token))) secureFile(file);
764
+ if (changed || hasBlock(readText(file) ?? "", "mcp")) {
765
+ report.lines.push(`Grok MCP: ${file} ([${GROK_TABLE}])`);
766
+ if (!i.mcpOnly) report.lines.push(HOOKS_LINE);
767
+ i.onMcpRegistered?.("file");
768
+ return { report, mcp: "file" };
769
+ }
552
770
  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
771
  return { report, mcp: "manual" };
554
772
  }
555
- async function removeGrok(ctx, viaCli) {
773
+ async function removeGrok(ctx, viaCli, viaFile = false) {
556
774
  const report = emptyReport();
557
775
  if (viaCli) {
558
776
  const bin = findBin("grok", ctx.env);
559
777
  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
778
  }
779
+ if (viaFile && editGrokToml((c) => removeBlock(c, "mcp"), { drop: true })) report.lines.push(`Grok MCP: removed from ${grokConfig()}`);
561
780
  return report;
562
781
  }
563
782
 
@@ -567,11 +786,14 @@ export {
567
786
  addCreated,
568
787
  wasCreated,
569
788
  forgetCreated,
789
+ toolReadsEnvToken,
790
+ envTokenSharedWith,
570
791
  RC_LINE,
571
792
  rcFileFor,
572
793
  hasRcLine,
573
794
  addRcLine,
574
795
  removeRcLine,
796
+ emptyReport,
575
797
  addHooks,
576
798
  removeHooks,
577
799
  editJson,
@@ -3,10 +3,10 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@hivefloor/cli",
6
- version: "0.1.3",
6
+ version: "0.1.5",
7
7
  description: "Connect your coding agents (Claude Code, Codex, Cursor, Gemini, Grok) to Hivefloor: live presence, task coordination and usage on one floor.",
8
8
  author: {
9
- name: "Errr0rr404",
9
+ name: "Zan",
10
10
  url: "https://github.com/Errr0rr404"
11
11
  },
12
12
  keywords: [
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ TOKEN_FP_HEX,
4
+ defaultHost,
5
+ hostKey,
6
+ loadToken,
7
+ readCredentials,
8
+ tokenFingerprint
9
+ } from "./chunk-6MDJB3BV.js";
10
+ import {
11
+ flagStr
12
+ } from "./chunk-TKMIV7BG.js";
13
+ import {
14
+ isTool
15
+ } from "./chunk-TPMFQUAW.js";
16
+ import {
17
+ hostFromEnv,
18
+ hostFromFlag,
19
+ sameOrigin
20
+ } from "./chunk-SOXRITAW.js";
21
+
22
+ // src/session.ts
23
+ function envTokenForHost(env, host) {
24
+ const token = env.HIVEFLOOR_TOKEN;
25
+ if (!token) return void 0;
26
+ const creds = readCredentials();
27
+ const key = hostKey(creds, host);
28
+ if (!key) {
29
+ if (Object.keys(creds.hosts).length > 0) return void 0;
30
+ return sameOrigin(hostFromEnv(env), host) ? token : void 0;
31
+ }
32
+ const fp = creds.hosts[key]?.token_fp;
33
+ if (typeof fp !== "string" || fp.length !== TOKEN_FP_HEX || !/^[0-9a-f]+$/.test(fp)) return void 0;
34
+ return tokenFingerprint(token) === fp ? token : void 0;
35
+ }
36
+ async function resolveSession(ctx, p) {
37
+ const flagHost = flagStr(p, "host");
38
+ const host = flagHost ? hostFromFlag(ctx, flagHost) : ctx.env.HIVEFLOOR_URL ? hostFromEnv(ctx.env) : defaultHost() ?? hostFromEnv(ctx.env);
39
+ const toolFlag = flagStr(p, "tool");
40
+ const t = await loadToken(host, toolFlag && isTool(toolFlag) ? toolFlag : void 0);
41
+ if (t) return { host, token: t.token, tool: t.tool };
42
+ const envToken = envTokenForHost(ctx.env, host);
43
+ if (envToken) return { host, token: envToken, tool: toolFlag ?? "other" };
44
+ return void 0;
45
+ }
46
+
47
+ export {
48
+ envTokenForHost,
49
+ resolveSession
50
+ };
@@ -6,7 +6,7 @@ import {
6
6
  machineFile,
7
7
  readText,
8
8
  serviceStateFile
9
- } from "./chunk-VNCQFFQO.js";
9
+ } from "./chunk-TPMFQUAW.js";
10
10
 
11
11
  // src/machine.ts
12
12
  import fs from "fs";