@hivefloor/cli 0.1.5 → 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 (48) hide show
  1. package/LICENSE +6 -20
  2. package/README.md +29 -8
  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-U6RRUAC2.js → chunk-5XXYQQG6.js} +53 -8
  7. package/dist/chunk-AGIYVFDQ.js +10984 -0
  8. package/dist/{chunk-RYGL6XU4.js → chunk-BLLPDPTL.js} +4 -2
  9. package/dist/{chunk-3N5JYP5O.js → chunk-FPJNGKT5.js} +5 -3
  10. package/dist/chunk-HW3KRNCO.js +201 -0
  11. package/dist/{chunk-4DRHR7LR.js → chunk-HWBUESB4.js} +1158 -196
  12. package/dist/{chunk-C627OKTW.js → chunk-K2IRTZH5.js} +7 -6
  13. package/dist/chunk-KQMPCV57.js +122 -0
  14. package/dist/{chunk-J36OIJBM.js → chunk-L6EFW2SJ.js} +14 -10
  15. package/dist/{chunk-IJRHCDIG.js → chunk-LX5A25O6.js} +4 -2
  16. package/dist/{chunk-MZGGJ6ZS.js → chunk-MYJCU5JL.js} +4 -2
  17. package/dist/{chunk-H6NYMF2D.js → chunk-NMUH5NXN.js} +6 -4
  18. package/dist/{chunk-VX7SCIF2.js → chunk-NRJONTFT.js} +3 -1
  19. package/dist/chunk-QBBKGORP.js +327 -0
  20. package/dist/{chunk-SOXRITAW.js → chunk-QGULSOQ2.js} +3 -0
  21. package/dist/{chunk-6MDJB3BV.js → chunk-QLMLB7RB.js} +17 -9
  22. package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
  23. package/dist/chunk-SBOEAOHD.js +48 -0
  24. package/dist/{chunk-IWIDKNWC.js → chunk-TIR4CV3P.js} +21 -17
  25. package/dist/{chunk-6XVIEX75.js → chunk-X2KH6AR4.js} +25 -303
  26. package/dist/chunk-XAXKZ572.js +106 -0
  27. package/dist/{chunk-TPMFQUAW.js → chunk-XPGL7VYE.js} +12 -0
  28. package/dist/{cli-JCBZICR3.js → cli-BFOJWJNG.js} +56 -20
  29. package/dist/connect-ZJ4JXBAJ.js +27 -0
  30. package/dist/control-6GHTZQJV.js +25 -0
  31. package/dist/{disconnect-WQMDCUXT.js → disconnect-3T25NTKX.js} +24 -17
  32. package/dist/{hook-AYJNRGIY.js → hook-TPSHBCWU.js} +277 -17
  33. package/dist/index.js +9 -6
  34. package/dist/{link-ORT4LID6.js → link-EAS5F5PC.js} +14 -11
  35. package/dist/{machine-connect-N52CKQEW.js → machine-connect-KEI4BRWF.js} +23 -17
  36. package/dist/mcp-LVIYR3EN.js +1882 -0
  37. package/dist/orch-KVRL2YA2.js +137 -0
  38. package/dist/{run-ZAZ45MKQ.js → run-JL2OZWK3.js} +1920 -28
  39. package/dist/{status-GOCFNO7K.js → status-QXOOJGGN.js} +14 -11
  40. package/dist/{token-EMM4AJAU.js → token-BFWWQIDA.js} +10 -7
  41. package/dist/{voice-F5MIS3IZ.js → voice-QXHFXRWG.js} +5 -3
  42. package/dist/{where-XFXRTP7E.js → where-CMUORQIO.js} +11 -8
  43. package/package.json +5 -6
  44. package/dist/chunk-JCLWCMEM.js +0 -8
  45. package/dist/chunk-K3NQKI34.js +0 -10
  46. package/dist/chunk-X6B6R32A.js +0 -303
  47. package/dist/connect-2HLVPEOL.js +0 -23
  48. package/dist/mcp-2SF6Y73M.js +0 -118
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
2
4
 
3
5
  // package.json
4
6
  var package_default = {
5
7
  name: "@hivefloor/cli",
6
- version: "0.1.5",
7
- description: "Connect your coding agents (Claude Code, Codex, Cursor, Gemini, Grok) to Hivefloor: live presence, task coordination and usage on one floor.",
8
+ version: "0.1.8",
9
+ description: "Connect your coding agents (Claude Code, Codex, Cursor, Gemini, Grok) to HiveFloor: live presence, task coordination and usage on one floor.",
8
10
  author: {
9
11
  name: "Zan",
10
12
  url: "https://github.com/Errr0rr404"
@@ -21,14 +23,13 @@ var package_default = {
21
23
  ],
22
24
  homepage: "https://hivefloor.worldofz.info",
23
25
  bugs: {
24
- url: "https://github.com/Errr0rr404/hivefloor/issues"
26
+ url: "https://github.com/Errr0rr404/hivefloor-cli/issues"
25
27
  },
26
28
  repository: {
27
29
  type: "git",
28
- url: "git+https://github.com/Errr0rr404/hivefloor.git",
29
- directory: "packages/cli"
30
+ url: "git+https://github.com/Errr0rr404/hivefloor-cli.git"
30
31
  },
31
- license: "MIT",
32
+ license: "UNLICENSED",
32
33
  type: "module",
33
34
  bin: {
34
35
  hivefloor: "dist/index.js"
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
4
+ import {
5
+ atomicWrite,
6
+ controlFile,
7
+ isRecord
8
+ } from "./chunk-XPGL7VYE.js";
9
+
10
+ // src/service/control/config.ts
11
+ import fs from "fs";
12
+ import path from "path";
13
+ var VERBS = ["message", "approve", "deny", "stop", "resume", "peek"];
14
+ var allOff = () => ({ version: 1, enabled: false, verbs: { message: false, approve: false, deny: false, stop: false, resume: false, peek: false }, from: "owner", replies: "after_message", adapters: [], orchestrator: false, orchestrator_full: false });
15
+ var MAX_CONTROL_BYTES = 1024 * 1024;
16
+ function statTrouble(file, st) {
17
+ if (!st.isFile()) return `${file} is not a regular file`;
18
+ if (process.platform === "win32") return void 0;
19
+ if (typeof process.getuid === "function" && st.uid !== process.getuid()) return `${file} is owned by another user`;
20
+ if ((st.mode & 18) !== 0) return `${file} is writable by group or others (chmod 600 it)`;
21
+ return void 0;
22
+ }
23
+ function fileTrouble(file) {
24
+ let st;
25
+ try {
26
+ st = fs.lstatSync(file);
27
+ } catch {
28
+ return void 0;
29
+ }
30
+ if (st.isSymbolicLink()) return `${file} is a symbolic link`;
31
+ return statTrouble(file, st);
32
+ }
33
+ function dirTrouble(dir) {
34
+ if (process.platform === "win32") return void 0;
35
+ let st;
36
+ try {
37
+ st = fs.statSync(dir);
38
+ } catch {
39
+ return void 0;
40
+ }
41
+ if (!st.isDirectory()) return `${dir} is not a folder`;
42
+ if (typeof process.getuid === "function" && st.uid !== process.getuid()) return `${dir} is owned by another user`;
43
+ if ((st.mode & 18) !== 0) return `${dir} is writable by group or others (chmod 700 it)`;
44
+ return void 0;
45
+ }
46
+ function sanitize(j) {
47
+ const c = allOff();
48
+ c.enabled = j.enabled === true;
49
+ const v = isRecord(j.verbs) ? j.verbs : {};
50
+ for (const k of VERBS) c.verbs[k] = v[k] === true;
51
+ c.from = j.from === "admins" ? "admins" : "owner";
52
+ c.orchestrator = j.orchestrator === true;
53
+ c.orchestrator_full = j.orchestrator_full === true;
54
+ c.replies = j.replies === "off" || j.replies === "always" ? j.replies : "after_message";
55
+ if (Array.isArray(j.adapters)) {
56
+ for (const a of j.adapters) {
57
+ if (!isRecord(a) || typeof a.name !== "string" || typeof a.exec !== "string") continue;
58
+ const verbs = Array.isArray(a.verbs) ? a.verbs.filter((x) => VERBS.includes(x)) : [];
59
+ const tools = Array.isArray(a.tools) ? a.tools.filter((x) => typeof x === "string") : [];
60
+ c.adapters.push({ name: a.name.slice(0, 60), exec: a.exec, verbs, tools });
61
+ }
62
+ }
63
+ return c;
64
+ }
65
+ function readControl(file = controlFile()) {
66
+ const bad = (reason) => ({ state: "invalid", reason, config: allOff() });
67
+ const trouble = dirTrouble(path.dirname(file)) ?? fileTrouble(file);
68
+ if (trouble) return bad(trouble);
69
+ let fd;
70
+ try {
71
+ fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
72
+ } catch (e) {
73
+ const code = e.code;
74
+ if (code === "ENOENT") return { state: "missing", config: allOff() };
75
+ if (code === "ELOOP" || code === "EMLINK") return bad(`${file} is a symbolic link`);
76
+ return bad(`${file} cannot be read`);
77
+ }
78
+ let text;
79
+ try {
80
+ const st = fs.fstatSync(fd);
81
+ const t = statTrouble(file, st);
82
+ if (t) return bad(t);
83
+ if (st.size > MAX_CONTROL_BYTES) return bad(`${file} is too large`);
84
+ text = fs.readFileSync(fd, "utf8");
85
+ } catch {
86
+ return bad(`${file} cannot be read`);
87
+ } finally {
88
+ fs.closeSync(fd);
89
+ }
90
+ try {
91
+ const j = JSON.parse(text);
92
+ if (!isRecord(j)) return { state: "invalid", reason: `${file} is not a JSON object`, config: allOff() };
93
+ return { state: "ok", config: sanitize(j) };
94
+ } catch {
95
+ return { state: "invalid", reason: `${file} is not valid JSON`, config: allOff() };
96
+ }
97
+ }
98
+ function activeVerbs(r) {
99
+ const out = allOff().verbs;
100
+ if (r.state !== "ok" || !r.config.enabled) return out;
101
+ for (const k of VERBS) out[k] = r.config.verbs[k];
102
+ return out;
103
+ }
104
+ var orchestratorOn = (r) => r.state === "ok" && r.config.enabled && r.config.orchestrator;
105
+ var orchestratorFullOn = (r) => orchestratorOn(r) && r.config.orchestrator_full === true;
106
+ var controlOn = (r) => Object.values(activeVerbs(r)).some(Boolean);
107
+ function writeControl(c, file = controlFile()) {
108
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 448 });
109
+ atomicWrite(file, `${JSON.stringify(c, null, 2)}
110
+ `, 384);
111
+ }
112
+
113
+ export {
114
+ VERBS,
115
+ allOff,
116
+ readControl,
117
+ activeVerbs,
118
+ orchestratorOn,
119
+ orchestratorFullOn,
120
+ controlOn,
121
+ writeControl
122
+ };
@@ -1,13 +1,15 @@
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
5
  home,
4
6
  isDir,
5
7
  isRecord,
6
8
  readText
7
- } from "./chunk-TPMFQUAW.js";
9
+ } from "./chunk-XPGL7VYE.js";
8
10
  import {
9
11
  PROFILE_KEY_RE
10
- } from "./chunk-4DRHR7LR.js";
12
+ } from "./chunk-HWBUESB4.js";
11
13
 
12
14
  // src/profiles.ts
13
15
  import fs from "fs";
@@ -121,14 +123,16 @@ function codexConfigDir(env = process.env) {
121
123
  return env.CODEX_HOME ? path.resolve(env.CODEX_HOME) : path.join(home(), ".codex");
122
124
  }
123
125
  var PLAN = { max: "Max", pro: "Pro", team: "Team", enterprise: "Enterprise", free: "Free" };
124
- function claudeLabel(dir, key) {
125
- let plan;
126
- try {
127
- const j = JSON.parse(fs.readFileSync(path.join(dir, ".credentials.json"), "utf8"));
128
- const oauth = isRecord(j) ? j.claudeAiOauth : void 0;
129
- const t = isRecord(oauth) ? oauth.subscriptionType : void 0;
130
- if (typeof t === "string") plan = PLAN[t.toLowerCase()];
131
- } catch {
126
+ function claudeLabel(dir, key, tokenPlan) {
127
+ let plan = tokenPlan;
128
+ if (!plan) {
129
+ try {
130
+ const j = JSON.parse(fs.readFileSync(path.join(dir, ".credentials.json"), "utf8"));
131
+ const oauth = isRecord(j) ? j.claudeAiOauth : void 0;
132
+ const t = isRecord(oauth) ? oauth.subscriptionType : void 0;
133
+ if (typeof t === "string") plan = PLAN[t.toLowerCase()];
134
+ } catch {
135
+ }
132
136
  }
133
137
  return `Claude${plan ? ` ${plan}` : ""} (${key})`;
134
138
  }
@@ -1,4 +1,6 @@
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
5
  atomicWrite,
4
6
  configDir,
@@ -6,7 +8,7 @@ import {
6
8
  machineFile,
7
9
  readText,
8
10
  serviceStateFile
9
- } from "./chunk-TPMFQUAW.js";
11
+ } from "./chunk-XPGL7VYE.js";
10
12
 
11
13
  // src/machine.ts
12
14
  import fs from "fs";
@@ -61,7 +63,7 @@ async function secureWindowsMachine(run, username = os.userInfo().username) {
61
63
  if (f.code === 0) return;
62
64
  }
63
65
  removeMachineFile();
64
- throw new Error("could not restrict the Hivefloor config to your Windows user with icacls; the machine token was not kept");
66
+ throw new Error("could not restrict the HiveFloor config to your Windows user with icacls; the machine token was not kept");
65
67
  }
66
68
  var SERVICE_ALIVE_MS = 9e4;
67
69
  function readServiceState() {
@@ -1,15 +1,17 @@
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
5
  claudeConfigDirs,
4
6
  claudeLabel,
5
7
  codexConfigDir,
6
8
  codexLabel,
7
9
  profileKeys
8
- } from "./chunk-J36OIJBM.js";
10
+ } from "./chunk-L6EFW2SJ.js";
9
11
  import {
10
12
  agentIsCursor,
11
13
  findBin
12
- } from "./chunk-TPMFQUAW.js";
14
+ } from "./chunk-XPGL7VYE.js";
13
15
 
14
16
  // src/osinfo.ts
15
17
  import os from "os";
@@ -1,4 +1,6 @@
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
5
  TOKEN_FP_HEX,
4
6
  defaultHost,
@@ -6,18 +8,18 @@ import {
6
8
  loadToken,
7
9
  readCredentials,
8
10
  tokenFingerprint
9
- } from "./chunk-6MDJB3BV.js";
11
+ } from "./chunk-QLMLB7RB.js";
10
12
  import {
11
13
  flagStr
12
- } from "./chunk-TKMIV7BG.js";
14
+ } from "./chunk-QPAFN47B.js";
13
15
  import {
14
16
  isTool
15
- } from "./chunk-TPMFQUAW.js";
17
+ } from "./chunk-XPGL7VYE.js";
16
18
  import {
17
19
  hostFromEnv,
18
20
  hostFromFlag,
19
21
  sameOrigin
20
- } from "./chunk-SOXRITAW.js";
22
+ } from "./chunk-QGULSOQ2.js";
21
23
 
22
24
  // src/session.ts
23
25
  function envTokenForHost(env, host) {
@@ -1,7 +1,9 @@
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
5
  assertTokenTransport
4
- } from "./chunk-SOXRITAW.js";
6
+ } from "./chunk-QGULSOQ2.js";
5
7
 
6
8
  // src/api.ts
7
9
  var ApiError = class extends Error {
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
4
+ import {
5
+ MCP_NAME,
6
+ addCreated,
7
+ addHooks,
8
+ dropBackupWithEntry,
9
+ editJson,
10
+ emptyReport,
11
+ forgetCreated,
12
+ hasBlock,
13
+ hasOutsideTable,
14
+ readManifest,
15
+ removeBlock,
16
+ removeHooks,
17
+ removeOutsideTable,
18
+ secureFile,
19
+ setBlock,
20
+ tomlString,
21
+ wasCreated
22
+ } from "./chunk-X2KH6AR4.js";
23
+ import {
24
+ atomicWrite,
25
+ backupOnce,
26
+ dropBackupIfSame,
27
+ findBin,
28
+ home,
29
+ isRecord,
30
+ readText,
31
+ removeFile
32
+ } from "./chunk-XPGL7VYE.js";
33
+ import {
34
+ runCmd
35
+ } from "./chunk-QGULSOQ2.js";
36
+ import {
37
+ cliNpxCommand
38
+ } from "./chunk-HWBUESB4.js";
39
+
40
+ // src/shellrc.ts
41
+ import fs from "fs";
42
+ import path from "path";
43
+ var RC_MARK = "# hivefloor";
44
+ var RC_LINE = `export HIVEFLOOR_TOKEN="$(hivefloor token)" ${RC_MARK}`;
45
+ function rcFileFor(env) {
46
+ const shell = path.basename(env.SHELL ?? "");
47
+ if (shell === "zsh") return path.join(home(), ".zshrc");
48
+ if (shell === "bash") {
49
+ const profile = path.join(home(), ".bash_profile");
50
+ return process.platform === "darwin" && fs.existsSync(profile) ? profile : path.join(home(), ".bashrc");
51
+ }
52
+ return void 0;
53
+ }
54
+ function isRcLine(line) {
55
+ const t = line.trim();
56
+ return !t.startsWith("#") && t.includes("HIVEFLOOR_TOKEN") && t.includes("hivefloor token") && t.endsWith(RC_MARK);
57
+ }
58
+ function hasRcLine(file) {
59
+ return (readText(file) ?? "").split("\n").some(isRcLine);
60
+ }
61
+ function addRcLine(file) {
62
+ if (hasRcLine(file)) return false;
63
+ const cur = readText(file);
64
+ if (cur === void 0) addCreated(file);
65
+ const sep = cur && !cur.endsWith("\n") ? "\n" : "";
66
+ atomicWrite(file, `${cur ?? ""}${sep}${RC_LINE}
67
+ `);
68
+ return true;
69
+ }
70
+ function removeRcLine(file) {
71
+ const cur = readText(file);
72
+ if (cur === void 0) return false;
73
+ const lines = cur.split("\n");
74
+ const kept = lines.filter((l) => !isRcLine(l));
75
+ if (kept.length === lines.length) return false;
76
+ const next = kept.join("\n");
77
+ if (next.trim() === "" && wasCreated(file)) {
78
+ fs.rmSync(file, { force: true });
79
+ forgetCreated(file);
80
+ } else {
81
+ atomicWrite(file, next);
82
+ }
83
+ return true;
84
+ }
85
+
86
+ // src/writers/cursor.ts
87
+ import path2 from "path";
88
+ var CURSOR_EVENTS = ["sessionStart", "sessionEnd", "beforeSubmitPrompt", "stop", "subagentStart", "subagentStop"];
89
+ var cursorMcp = () => path2.join(home(), ".cursor", "mcp.json");
90
+ var cursorHooks = () => path2.join(home(), ".cursor", "hooks.json");
91
+ function cursorHookSpecs() {
92
+ return CURSOR_EVENTS.map((event) => ({ event, entry: { command: `hivefloor hook cursor ${event}` } }));
93
+ }
94
+ async function installCursor(i) {
95
+ const report = emptyReport();
96
+ const auth = i.useEnvToken ? "Bearer ${env:HIVEFLOOR_TOKEN}" : `Bearer ${i.token}`;
97
+ editJson(
98
+ cursorMcp(),
99
+ (d) => {
100
+ const servers = isRecord(d.mcpServers) ? d.mcpServers : d.mcpServers = {};
101
+ servers[MCP_NAME] = { url: i.mcpUrl, headers: { Authorization: auth } };
102
+ return true;
103
+ },
104
+ i.useEnvToken ? {} : { mode: 384 }
105
+ );
106
+ report.lines.push(`Cursor MCP: ${cursorMcp()} (mcpServers.${MCP_NAME})`);
107
+ i.onMcpRegistered?.("file");
108
+ if (!i.mcpOnly) {
109
+ editJson(cursorHooks(), (d) => {
110
+ if (d.version === void 0) d.version = 1;
111
+ addHooks(d, "flat", cursorHookSpecs());
112
+ return true;
113
+ });
114
+ report.lines.push(`Cursor hooks: ${cursorHooks()} (${CURSOR_EVENTS.join(", ")})`);
115
+ }
116
+ return { report, mcp: "file" };
117
+ }
118
+ async function removeCursor() {
119
+ const report = emptyReport();
120
+ const changed = editJson(
121
+ cursorMcp(),
122
+ (d) => {
123
+ if (!isRecord(d.mcpServers) || !(MCP_NAME in d.mcpServers)) return false;
124
+ delete d.mcpServers[MCP_NAME];
125
+ if (Object.keys(d.mcpServers).length === 0) delete d.mcpServers;
126
+ return true;
127
+ },
128
+ { drop: true }
129
+ );
130
+ if (changed) report.lines.push(`Cursor MCP: removed from ${cursorMcp()}`);
131
+ if (editJson(cursorHooks(), (d) => removeHooks(d, "flat") > 0, { drop: true })) report.lines.push(`Cursor hooks: removed from ${cursorHooks()}`);
132
+ return report;
133
+ }
134
+
135
+ // src/writers/gemini.ts
136
+ import path3 from "path";
137
+ var GEMINI_EVENTS = ["SessionStart", "SessionEnd", "AfterAgent"];
138
+ var geminiSettings = () => path3.join(home(), ".gemini", "settings.json");
139
+ function geminiHookSpecs() {
140
+ return GEMINI_EVENTS.map((event) => ({
141
+ event,
142
+ // Gemini hook entries carry a name: ours is "hivefloor", which is also how disconnect finds them.
143
+ entry: { name: "hivefloor", type: "command", command: `hivefloor hook gemini ${event}` }
144
+ }));
145
+ }
146
+ async function installGemini(i) {
147
+ const report = emptyReport();
148
+ const auth = i.useEnvToken ? "Bearer $HIVEFLOOR_TOKEN" : `Bearer ${i.token}`;
149
+ let mcp = "file";
150
+ const priorCli = !i.useEnvToken && readManifest().tools.gemini?.mcp === "cli";
151
+ const bin = i.noCli ? void 0 : findBin("gemini", i.ctx.env);
152
+ if (bin && (i.useEnvToken || priorCli)) {
153
+ await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
154
+ if (i.useEnvToken) {
155
+ const r = await runCmd(bin, ["mcp", "add", MCP_NAME, i.mcpUrl, "-t", "http", "-s", "user", "-H", `Authorization: ${auth}`], { env: i.ctx.env });
156
+ if (r.code === 0) {
157
+ mcp = "cli";
158
+ report.lines.push(`Gemini MCP: gemini mcp add (user scope) -> ${i.mcpUrl}`);
159
+ }
160
+ }
161
+ }
162
+ if (mcp === "file") {
163
+ editJson(
164
+ geminiSettings(),
165
+ (d) => {
166
+ const servers = isRecord(d.mcpServers) ? d.mcpServers : d.mcpServers = {};
167
+ servers[MCP_NAME] = { httpUrl: i.mcpUrl, headers: { Authorization: auth } };
168
+ return true;
169
+ },
170
+ i.useEnvToken ? {} : { mode: 384 }
171
+ );
172
+ if (!i.useEnvToken) secureFile(geminiSettings());
173
+ report.lines.push(`Gemini MCP: ${geminiSettings()} (mcpServers.${MCP_NAME})`);
174
+ }
175
+ i.onMcpRegistered?.(mcp);
176
+ if (!i.mcpOnly) {
177
+ editJson(geminiSettings(), (d) => (addHooks(d, "nested", geminiHookSpecs()), true), i.useEnvToken ? {} : { mode: 384 });
178
+ report.lines.push(`Gemini hooks: ${geminiSettings()} (${GEMINI_EVENTS.join(", ")})`);
179
+ }
180
+ return { report, mcp };
181
+ }
182
+ async function removeGemini(ctx, viaCli, viaFile = false) {
183
+ const report = emptyReport();
184
+ if (viaCli) {
185
+ const bin = findBin("gemini", ctx.env);
186
+ if (bin && (await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: ctx.env })).code === 0) report.lines.push("Gemini MCP: gemini mcp remove");
187
+ }
188
+ const changed = editJson(
189
+ geminiSettings(),
190
+ (d) => {
191
+ let did = removeHooks(d, "nested") > 0;
192
+ if (viaFile && isRecord(d.mcpServers) && MCP_NAME in d.mcpServers) {
193
+ delete d.mcpServers[MCP_NAME];
194
+ if (Object.keys(d.mcpServers).length === 0) delete d.mcpServers;
195
+ did = true;
196
+ }
197
+ return did;
198
+ },
199
+ { drop: true }
200
+ );
201
+ if (changed) report.lines.push(`Gemini: removed our entries from ${geminiSettings()}`);
202
+ dropBackupWithEntry(geminiSettings(), MCP_NAME);
203
+ return report;
204
+ }
205
+
206
+ // src/writers/grok.ts
207
+ import path4 from "path";
208
+ var grokConfig = () => path4.join(home(), ".grok", "config.toml");
209
+ var HOOKS_LINE = "Grok hooks: none needed (Grok reads the Claude Code hooks in ~/.claude/settings.json; connect Claude Code too for live sessions)";
210
+ function editGrokToml(fn, opts = {}) {
211
+ const file = grokConfig();
212
+ const before = readText(file);
213
+ const next = fn(before ?? "");
214
+ if (next === (before ?? "")) return false;
215
+ if (opts.drop && wasCreated(file) && next.trim() === "") {
216
+ removeFile(file);
217
+ forgetCreated(file);
218
+ return true;
219
+ }
220
+ if (before === void 0) addCreated(file);
221
+ else if (!wasCreated(file)) backupOnce(file);
222
+ atomicWrite(file, next, opts.mode);
223
+ if (opts.drop) dropBackupIfSame(file);
224
+ return true;
225
+ }
226
+ function grokMcpBlock(mcpUrl, auth) {
227
+ return `[mcp_servers.${MCP_NAME}]
228
+ url = ${tomlString(mcpUrl)}
229
+ enabled = true
230
+
231
+ [mcp_servers.${MCP_NAME}.headers]
232
+ Authorization = ${tomlString(auth)}`;
233
+ }
234
+ var GROK_TABLE = `mcp_servers.${MCP_NAME}`;
235
+ var ENV_AUTH = "Bearer ${HIVEFLOOR_TOKEN}";
236
+ function outsideHasRawAuthorization(content) {
237
+ const stripped = content.replace(/# hivefloor:begin[\s\S]*?# hivefloor:end [^\n]*(?:\n|$)/g, "");
238
+ let inTable = false;
239
+ for (const line of stripped.split("\n")) {
240
+ const bare = line.replace(/\r$/, "");
241
+ const header = /^\s*\[\s*([^\]]+?)\s*\]/.exec(bare);
242
+ if (header) {
243
+ const name = header[1].replace(/\s+/g, "");
244
+ inTable = name === GROK_TABLE || name.startsWith(`${GROK_TABLE}.`);
245
+ continue;
246
+ }
247
+ if (!inTable) continue;
248
+ const auth = /Authorization\s*=\s*["']([^"']*)["']/.exec(bare);
249
+ if (auth && auth[1] !== ENV_AUTH) return true;
250
+ }
251
+ return false;
252
+ }
253
+ async function dropOutsideServer(env, bin) {
254
+ const file = grokConfig();
255
+ const bak = `${file}.hivefloor.bak`;
256
+ const before = readText(file);
257
+ const beforeBak = readText(bak);
258
+ const staleLive = before !== void 0 && hasOutsideTable(before, GROK_TABLE);
259
+ const staleBak = beforeBak !== void 0 && hasOutsideTable(beforeBak, GROK_TABLE);
260
+ if (!staleLive && !staleBak) return false;
261
+ if (bin && staleLive) await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env });
262
+ for (const f of [file, bak]) {
263
+ const cur = readText(f);
264
+ if (cur === void 0 || !hasOutsideTable(cur, GROK_TABLE)) continue;
265
+ const next = removeOutsideTable(cur, GROK_TABLE);
266
+ if (next.trim() === "") removeFile(f);
267
+ else atomicWrite(f, next);
268
+ }
269
+ const left = readText(file);
270
+ if (left !== void 0 && left.trim() === "") removeFile(file);
271
+ return true;
272
+ }
273
+ async function installGrok(i) {
274
+ const report = emptyReport();
275
+ const auth = i.useEnvToken ? ENV_AUTH : `Bearer ${i.token}`;
276
+ const bin = i.noCli ? void 0 : findBin("grok", i.ctx.env);
277
+ const liveBefore = readText(grokConfig());
278
+ const bakBefore = readText(`${grokConfig()}.hivefloor.bak`);
279
+ const hadRaw = [liveBefore, bakBefore].some((t) => t !== void 0 && outsideHasRawAuthorization(t));
280
+ const removed = await dropOutsideServer(i.ctx.env, bin);
281
+ if (removed && hadRaw) report.warnings.push(`${grokConfig()} had [${GROK_TABLE}] outside our block; removed it so an older token does not stay on disk`);
282
+ if (bin && i.useEnvToken) {
283
+ await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: i.ctx.env });
284
+ const r = await runCmd(bin, ["mcp", "add", "--transport", "http", MCP_NAME, i.mcpUrl, "-s", "user", "--header", `Authorization: ${auth}`], { env: i.ctx.env });
285
+ if (r.code === 0) {
286
+ report.lines.push(`Grok MCP: grok mcp add (user scope) -> ${i.mcpUrl}`);
287
+ if (!i.mcpOnly) report.lines.push(HOOKS_LINE);
288
+ i.onMcpRegistered?.("cli");
289
+ return { report, mcp: "cli" };
290
+ }
291
+ }
292
+ const file = grokConfig();
293
+ const changed = editGrokToml((c) => setBlock(c, "mcp", grokMcpBlock(i.mcpUrl, auth)), i.useEnvToken ? {} : { mode: 384 });
294
+ if (!i.useEnvToken && (changed || (readText(file) ?? "").includes(i.token))) secureFile(file);
295
+ if (changed || hasBlock(readText(file) ?? "", "mcp")) {
296
+ report.lines.push(`Grok MCP: ${file} ([${GROK_TABLE}])`);
297
+ if (!i.mcpOnly) report.lines.push(HOOKS_LINE);
298
+ i.onMcpRegistered?.("file");
299
+ return { report, mcp: "file" };
300
+ }
301
+ 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")}>"`);
302
+ return { report, mcp: "manual" };
303
+ }
304
+ async function removeGrok(ctx, viaCli, viaFile = false) {
305
+ const report = emptyReport();
306
+ if (viaCli) {
307
+ const bin = findBin("grok", ctx.env);
308
+ if (bin && (await runCmd(bin, ["mcp", "remove", MCP_NAME, "-s", "user"], { env: ctx.env })).code === 0) report.lines.push("Grok MCP: grok mcp remove");
309
+ }
310
+ if (viaFile && editGrokToml((c) => removeBlock(c, "mcp"), { drop: true })) report.lines.push(`Grok MCP: removed from ${grokConfig()}`);
311
+ return report;
312
+ }
313
+
314
+ export {
315
+ RC_LINE,
316
+ rcFileFor,
317
+ hasRcLine,
318
+ addRcLine,
319
+ removeRcLine,
320
+ installCursor,
321
+ removeCursor,
322
+ geminiSettings,
323
+ installGemini,
324
+ removeGemini,
325
+ installGrok,
326
+ removeGrok
327
+ };
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
2
4
 
3
5
  // src/ctx.ts
4
6
  import { execFile } from "child_process";
@@ -16,6 +18,7 @@ function defaultCtx() {
16
18
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
17
19
  now: () => Date.now(),
18
20
  stdoutIsTTY: Boolean(process.stdout.isTTY),
21
+ stdinIsTTY: Boolean(process.stdin.isTTY),
19
22
  ask: askOnTerminal,
20
23
  open: async (url) => openBrowser(url, ctx.platform ?? process.platform)
21
24
  };