@hivefloor/cli 0.1.4 → 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.
- package/LICENSE +1 -1
- package/README.md +2 -1
- package/dist/{chunk-R5KDI4WI.js → chunk-3N5JYP5O.js} +7 -3
- package/dist/{chunk-ZC2DAKB5.js → chunk-4DRHR7LR.js} +349 -127
- package/dist/{chunk-PDXYOREI.js → chunk-6MDJB3BV.js} +97 -26
- package/dist/{chunk-QHYZBHXO.js → chunk-6XVIEX75.js} +293 -71
- package/dist/{chunk-DNG5CRT3.js → chunk-C627OKTW.js} +2 -2
- package/dist/chunk-H6NYMF2D.js +50 -0
- package/dist/{chunk-T7TG6WA4.js → chunk-IJRHCDIG.js} +1 -1
- package/dist/{chunk-SD65CIIY.js → chunk-IWIDKNWC.js} +109 -29
- package/dist/{chunk-UJ4BQFWV.js → chunk-J36OIJBM.js} +41 -23
- package/dist/{chunk-W7DROKC7.js → chunk-MZGGJ6ZS.js} +87 -13
- package/dist/{chunk-2BSA7LFD.js → chunk-RYGL6XU4.js} +5 -3
- package/dist/chunk-SOXRITAW.js +139 -0
- package/dist/{chunk-VNCQFFQO.js → chunk-TPMFQUAW.js} +102 -1
- package/dist/chunk-U6RRUAC2.js +229 -0
- package/dist/chunk-VX7SCIF2.js +65 -0
- package/dist/{chunk-O2TC3WSS.js → chunk-X6B6R32A.js} +1 -1
- package/dist/cli-JCBZICR3.js +243 -0
- package/dist/connect-2HLVPEOL.js +23 -0
- package/dist/{disconnect-MKRHK4CX.js → disconnect-WQMDCUXT.js} +41 -27
- package/dist/{hook-KR2HGVAR.js → hook-AYJNRGIY.js} +22 -8
- package/dist/index.js +2 -2
- package/dist/{link-HXBMMSG3.js → link-ORT4LID6.js} +29 -16
- package/dist/{machine-connect-BYHZRRRB.js → machine-connect-N52CKQEW.js} +21 -21
- package/dist/{mcp-N2TMD4GR.js → mcp-2SF6Y73M.js} +27 -30
- package/dist/{run-336BSU72.js → run-ZAZ45MKQ.js} +22 -10
- package/dist/{status-KBBSAADD.js → status-GOCFNO7K.js} +25 -13
- package/dist/{token-JDRLEV5D.js → token-EMM4AJAU.js} +15 -6
- package/dist/voice-F5MIS3IZ.js +262 -0
- package/dist/{where-KR7VWSEC.js → where-XFXRTP7E.js} +16 -7
- package/package.json +2 -2
- package/dist/chunk-2IMJECYD.js +0 -71
- package/dist/chunk-7KF7IQJN.js +0 -30
- package/dist/chunk-GT3HFFT4.js +0 -132
- package/dist/chunk-PY74LT2W.js +0 -41
- package/dist/cli-P3DYE5IR.js +0 -103
- package/dist/connect-ACNM5DQG.js +0 -23
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
17
|
+
} from "./chunk-TPMFQUAW.js";
|
|
17
18
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "./chunk-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
218
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
487
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
510
|
-
|
|
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 ?
|
|
744
|
+
const auth = i.useEnvToken ? ENV_AUTH : `Bearer ${i.token}`;
|
|
541
745
|
const bin = i.noCli ? void 0 : findBin("grok", i.ctx.env);
|
|
542
|
-
|
|
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(
|
|
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.
|
|
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: "
|
|
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
|
+
};
|