@hivefloor/cli 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -20
- package/README.md +31 -9
- package/dist/chunk-2ERPDCOT.js +38 -0
- package/dist/chunk-4G362W6G.js +19 -0
- package/dist/chunk-5OJUJCE3.js +817 -0
- package/dist/chunk-5XXYQQG6.js +274 -0
- package/dist/chunk-AGIYVFDQ.js +10984 -0
- package/dist/{chunk-2BSA7LFD.js → chunk-BLLPDPTL.js} +7 -3
- package/dist/{chunk-R5KDI4WI.js → chunk-FPJNGKT5.js} +9 -3
- package/dist/chunk-HW3KRNCO.js +201 -0
- package/dist/{chunk-ZC2DAKB5.js → chunk-HWBUESB4.js} +1500 -316
- package/dist/{chunk-DNG5CRT3.js → chunk-K2IRTZH5.js} +8 -7
- package/dist/chunk-KQMPCV57.js +122 -0
- package/dist/{chunk-UJ4BQFWV.js → chunk-L6EFW2SJ.js} +53 -31
- package/dist/{chunk-T7TG6WA4.js → chunk-LX5A25O6.js} +4 -2
- package/dist/{chunk-W7DROKC7.js → chunk-MYJCU5JL.js} +89 -13
- package/dist/chunk-NMUH5NXN.js +52 -0
- package/dist/chunk-NRJONTFT.js +67 -0
- package/dist/chunk-QBBKGORP.js +327 -0
- package/dist/chunk-QGULSOQ2.js +142 -0
- package/dist/{chunk-PDXYOREI.js → chunk-QLMLB7RB.js} +110 -31
- package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
- package/dist/chunk-SBOEAOHD.js +48 -0
- package/dist/{chunk-SD65CIIY.js → chunk-TIR4CV3P.js} +117 -33
- package/dist/{chunk-QHYZBHXO.js → chunk-X2KH6AR4.js} +195 -251
- package/dist/chunk-XAXKZ572.js +106 -0
- package/dist/{chunk-VNCQFFQO.js → chunk-XPGL7VYE.js} +114 -1
- package/dist/cli-BFOJWJNG.js +279 -0
- package/dist/connect-ZJ4JXBAJ.js +27 -0
- package/dist/control-6GHTZQJV.js +25 -0
- package/dist/{disconnect-MKRHK4CX.js → disconnect-3T25NTKX.js} +55 -34
- package/dist/hook-TPSHBCWU.js +575 -0
- package/dist/index.js +9 -6
- package/dist/{link-HXBMMSG3.js → link-EAS5F5PC.js} +34 -18
- package/dist/{machine-connect-BYHZRRRB.js → machine-connect-KEI4BRWF.js} +30 -24
- package/dist/mcp-LVIYR3EN.js +1882 -0
- package/dist/orch-KVRL2YA2.js +137 -0
- package/dist/{run-336BSU72.js → run-JL2OZWK3.js} +1933 -29
- package/dist/{status-KBBSAADD.js → status-QXOOJGGN.js} +29 -14
- package/dist/token-BFWWQIDA.js +44 -0
- package/dist/voice-QXHFXRWG.js +264 -0
- package/dist/{where-KR7VWSEC.js → where-CMUORQIO.js} +20 -8
- package/package.json +6 -7
- package/dist/chunk-2IMJECYD.js +0 -71
- package/dist/chunk-7KF7IQJN.js +0 -30
- package/dist/chunk-GT3HFFT4.js +0 -132
- package/dist/chunk-JCLWCMEM.js +0 -8
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/chunk-O2TC3WSS.js +0 -303
- package/dist/chunk-PY74LT2W.js +0 -41
- package/dist/cli-P3DYE5IR.js +0 -103
- package/dist/connect-ACNM5DQG.js +0 -23
- package/dist/hook-KR2HGVAR.js +0 -301
- package/dist/mcp-N2TMD4GR.js +0 -121
- package/dist/token-JDRLEV5D.js +0 -32
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// src/ctx.ts
|
|
6
|
+
import { execFile } from "child_process";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import readline from "readline";
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
var DEFAULT_HOST = "https://hivefloor.worldofz.info";
|
|
11
|
+
function defaultCtx() {
|
|
12
|
+
const ctx = {
|
|
13
|
+
out: (s) => void process.stdout.write(s),
|
|
14
|
+
err: (s) => void process.stderr.write(s),
|
|
15
|
+
env: process.env,
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
fetch: (...args) => fetch(...args),
|
|
18
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
19
|
+
now: () => Date.now(),
|
|
20
|
+
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
21
|
+
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
22
|
+
ask: askOnTerminal,
|
|
23
|
+
open: async (url) => openBrowser(url, ctx.platform ?? process.platform)
|
|
24
|
+
};
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
27
|
+
async function askOnTerminal(question) {
|
|
28
|
+
if (!process.stdin.isTTY) return "";
|
|
29
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
30
|
+
try {
|
|
31
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
32
|
+
} finally {
|
|
33
|
+
rl.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function webUrl(url) {
|
|
37
|
+
let u;
|
|
38
|
+
try {
|
|
39
|
+
u = new URL(url);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error("not a URL");
|
|
42
|
+
}
|
|
43
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") throw new Error(`refusing to open a ${u.protocol} URL`);
|
|
44
|
+
return u;
|
|
45
|
+
}
|
|
46
|
+
function browserCommand(url, platform = process.platform) {
|
|
47
|
+
const u = webUrl(url);
|
|
48
|
+
if (platform === "win32") return { cmd: "rundll32.exe", args: ["url.dll,FileProtocolHandler", u.toString()] };
|
|
49
|
+
return { cmd: platform === "darwin" ? "open" : "xdg-open", args: [u.toString()] };
|
|
50
|
+
}
|
|
51
|
+
async function openBrowser(url, platform = process.platform) {
|
|
52
|
+
let target;
|
|
53
|
+
try {
|
|
54
|
+
target = browserCommand(url, platform);
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await new Promise((resolve) => {
|
|
59
|
+
try {
|
|
60
|
+
const child = spawn(target.cmd, target.args, { stdio: "ignore", detached: true });
|
|
61
|
+
child.on("error", () => resolve());
|
|
62
|
+
child.unref();
|
|
63
|
+
setTimeout(resolve, 200);
|
|
64
|
+
} catch {
|
|
65
|
+
resolve();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function hostFromEnv(env) {
|
|
70
|
+
return normalizeHost(env.HIVEFLOOR_URL || DEFAULT_HOST);
|
|
71
|
+
}
|
|
72
|
+
function normalizeHost(input) {
|
|
73
|
+
const shown = redactUserinfo(input);
|
|
74
|
+
let h = input.trim();
|
|
75
|
+
if (!/^https?:\/\//i.test(h)) h = `https://${h}`;
|
|
76
|
+
let u;
|
|
77
|
+
try {
|
|
78
|
+
u = new URL(h);
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error(`"${shown}" is not a valid host. Use a URL such as https://hivefloor.example.com or http://localhost:3000.`);
|
|
81
|
+
}
|
|
82
|
+
if (u.hostname === "") throw new Error(`"${shown}" is not a valid host. Use a URL such as https://hivefloor.example.com or http://localhost:3000.`);
|
|
83
|
+
const bare = u.hostname.replace(/\.+$/, "");
|
|
84
|
+
if (bare !== "" && bare !== u.hostname) u.hostname = bare;
|
|
85
|
+
return u.origin;
|
|
86
|
+
}
|
|
87
|
+
function redactUserinfo(raw) {
|
|
88
|
+
return raw.trim().replace(/^([a-z][a-z0-9+.-]*:\/\/)?[^/?#]*@/i, "$1");
|
|
89
|
+
}
|
|
90
|
+
function hostFromFlag(ctx, raw) {
|
|
91
|
+
const host = normalizeHost(raw);
|
|
92
|
+
if (host !== raw.trim()) ctx.err(`hivefloor: using host ${host} (from --host ${redactUserinfo(raw)})
|
|
93
|
+
`);
|
|
94
|
+
return host;
|
|
95
|
+
}
|
|
96
|
+
function isLoopbackHost(hostname) {
|
|
97
|
+
const h = hostname.toLowerCase();
|
|
98
|
+
return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
|
|
99
|
+
}
|
|
100
|
+
function isSecureUrl(u) {
|
|
101
|
+
if (u.protocol === "https:") return true;
|
|
102
|
+
return u.protocol === "http:" && isLoopbackHost(u.hostname);
|
|
103
|
+
}
|
|
104
|
+
function sameOrigin(a, b) {
|
|
105
|
+
try {
|
|
106
|
+
return normalizeHost(a) === normalizeHost(b);
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function assertTokenTransport(target) {
|
|
112
|
+
let u;
|
|
113
|
+
try {
|
|
114
|
+
u = new URL(target);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error("not a URL");
|
|
117
|
+
}
|
|
118
|
+
if (!isSecureUrl(u)) throw new Error(`https is required (http is only allowed on localhost): ${u.origin}`);
|
|
119
|
+
return u;
|
|
120
|
+
}
|
|
121
|
+
var machineName = () => os.hostname().split(".")[0] || "machine";
|
|
122
|
+
function runCmd(bin, args, opts = {}) {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
execFile(bin, args, { cwd: opts.cwd, env: opts.env, timeout: opts.timeoutMs ?? 2e4, maxBuffer: 1 << 20 }, (error, stdout, stderr) => {
|
|
125
|
+
if (!error) return resolve({ code: 0, stdout: String(stdout), stderr: String(stderr) });
|
|
126
|
+
const code = typeof error.code === "number" ? error.code : 127;
|
|
127
|
+
resolve({ code, stdout: String(stdout), stderr: String(stderr) });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
DEFAULT_HOST,
|
|
134
|
+
defaultCtx,
|
|
135
|
+
hostFromEnv,
|
|
136
|
+
normalizeHost,
|
|
137
|
+
hostFromFlag,
|
|
138
|
+
sameOrigin,
|
|
139
|
+
assertTokenTransport,
|
|
140
|
+
machineName,
|
|
141
|
+
runCmd
|
|
142
|
+
};
|