@hivefloor/cli 0.1.4 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +6 -20
  2. package/README.md +31 -9
  3. package/dist/chunk-2ERPDCOT.js +38 -0
  4. package/dist/chunk-4G362W6G.js +19 -0
  5. package/dist/chunk-5OJUJCE3.js +817 -0
  6. package/dist/chunk-5XXYQQG6.js +274 -0
  7. package/dist/chunk-AGIYVFDQ.js +10984 -0
  8. package/dist/{chunk-2BSA7LFD.js → chunk-BLLPDPTL.js} +7 -3
  9. package/dist/{chunk-R5KDI4WI.js → chunk-FPJNGKT5.js} +9 -3
  10. package/dist/chunk-HW3KRNCO.js +201 -0
  11. package/dist/{chunk-ZC2DAKB5.js → chunk-HWBUESB4.js} +1500 -316
  12. package/dist/{chunk-DNG5CRT3.js → chunk-K2IRTZH5.js} +8 -7
  13. package/dist/chunk-KQMPCV57.js +122 -0
  14. package/dist/{chunk-UJ4BQFWV.js → chunk-L6EFW2SJ.js} +53 -31
  15. package/dist/{chunk-T7TG6WA4.js → chunk-LX5A25O6.js} +4 -2
  16. package/dist/{chunk-W7DROKC7.js → chunk-MYJCU5JL.js} +89 -13
  17. package/dist/chunk-NMUH5NXN.js +52 -0
  18. package/dist/chunk-NRJONTFT.js +67 -0
  19. package/dist/chunk-QBBKGORP.js +327 -0
  20. package/dist/chunk-QGULSOQ2.js +142 -0
  21. package/dist/{chunk-PDXYOREI.js → chunk-QLMLB7RB.js} +110 -31
  22. package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
  23. package/dist/chunk-SBOEAOHD.js +48 -0
  24. package/dist/{chunk-SD65CIIY.js → chunk-TIR4CV3P.js} +117 -33
  25. package/dist/{chunk-QHYZBHXO.js → chunk-X2KH6AR4.js} +195 -251
  26. package/dist/chunk-XAXKZ572.js +106 -0
  27. package/dist/{chunk-VNCQFFQO.js → chunk-XPGL7VYE.js} +114 -1
  28. package/dist/cli-BFOJWJNG.js +279 -0
  29. package/dist/connect-ZJ4JXBAJ.js +27 -0
  30. package/dist/control-6GHTZQJV.js +25 -0
  31. package/dist/{disconnect-MKRHK4CX.js → disconnect-3T25NTKX.js} +55 -34
  32. package/dist/hook-TPSHBCWU.js +575 -0
  33. package/dist/index.js +9 -6
  34. package/dist/{link-HXBMMSG3.js → link-EAS5F5PC.js} +34 -18
  35. package/dist/{machine-connect-BYHZRRRB.js → machine-connect-KEI4BRWF.js} +30 -24
  36. package/dist/mcp-LVIYR3EN.js +1882 -0
  37. package/dist/orch-KVRL2YA2.js +137 -0
  38. package/dist/{run-336BSU72.js → run-JL2OZWK3.js} +1933 -29
  39. package/dist/{status-KBBSAADD.js → status-QXOOJGGN.js} +29 -14
  40. package/dist/token-BFWWQIDA.js +44 -0
  41. package/dist/voice-QXHFXRWG.js +264 -0
  42. package/dist/{where-KR7VWSEC.js → where-CMUORQIO.js} +20 -8
  43. package/package.json +6 -7
  44. package/dist/chunk-2IMJECYD.js +0 -71
  45. package/dist/chunk-7KF7IQJN.js +0 -30
  46. package/dist/chunk-GT3HFFT4.js +0 -132
  47. package/dist/chunk-JCLWCMEM.js +0 -8
  48. package/dist/chunk-K3NQKI34.js +0 -10
  49. package/dist/chunk-O2TC3WSS.js +0 -303
  50. package/dist/chunk-PY74LT2W.js +0 -41
  51. package/dist/cli-P3DYE5IR.js +0 -103
  52. package/dist/connect-ACNM5DQG.js +0 -23
  53. package/dist/hook-KR2HGVAR.js +0 -301
  54. package/dist/mcp-N2TMD4GR.js +0 -121
  55. package/dist/token-JDRLEV5D.js +0 -32
@@ -1,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/fsutil.ts
4
6
  import fs from "fs";
@@ -17,6 +19,11 @@ var machineFile = () => path.join(configDir(), "machine.json");
17
19
  var machineSpoolFile = () => path.join(configDir(), "machine-spool.jsonl");
18
20
  var serviceStateFile = () => path.join(configDir(), "service.json");
19
21
  var serviceLogFile = () => path.join(configDir(), "service.log");
22
+ var controlFile = () => path.join(configDir(), "control.json");
23
+ var sessionsFile = () => path.join(configDir(), "sessions.json");
24
+ var controlLogFile = () => path.join(configDir(), "control.log");
25
+ var controlInboxDir = () => path.join(configDir(), "control", "inbox");
26
+ var controlDecisionsDir = () => path.join(configDir(), "control", "decisions");
20
27
  var cliDir = () => path.join(configDir(), "cli");
21
28
  var cliEntry = () => path.join(cliDir(), "index.js");
22
29
  function readText(file) {
@@ -117,6 +124,100 @@ function serializeJson(data, style) {
117
124
  }
118
125
  var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
119
126
 
127
+ // src/tools.ts
128
+ import fs2 from "fs";
129
+ import path2 from "path";
130
+ var REAL_TOOLS = ["claude", "codex", "cursor", "gemini", "grok"];
131
+ var TOOL_LABEL = {
132
+ claude: "Claude Code",
133
+ codex: "Codex",
134
+ cursor: "Cursor",
135
+ gemini: "Gemini CLI",
136
+ grok: "Grok CLI",
137
+ other: "Other"
138
+ };
139
+ var BINARIES = {
140
+ claude: ["claude"],
141
+ codex: ["codex"],
142
+ gemini: ["gemini"],
143
+ grok: ["grok"]
144
+ };
145
+ function isTool(s) {
146
+ return s === "other" || REAL_TOOLS.includes(s);
147
+ }
148
+ function findBin(name, env) {
149
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
150
+ for (const dir of (env.PATH ?? "").split(path2.delimiter)) {
151
+ if (!dir) continue;
152
+ for (const ext of exts) {
153
+ const p = path2.join(dir, name + ext);
154
+ try {
155
+ if (fs2.statSync(p).isFile()) {
156
+ fs2.accessSync(p, fs2.constants.X_OK);
157
+ return p;
158
+ }
159
+ } catch {
160
+ }
161
+ }
162
+ }
163
+ return void 0;
164
+ }
165
+ function firstBin(names, env) {
166
+ for (const name of names) {
167
+ const bin = findBin(name, env);
168
+ if (bin) return bin;
169
+ }
170
+ return void 0;
171
+ }
172
+ function sameFile(a, b) {
173
+ try {
174
+ return fs2.realpathSync(a) === fs2.realpathSync(b);
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+ function agentIsGrok(agentPath, env) {
180
+ const grok = findBin("grok", env);
181
+ if (grok && sameFile(agentPath, grok)) return true;
182
+ return /(?:^|\/)\.grok(?:\/|$)/.test(agentPath.replace(/\\/g, "/").toLowerCase());
183
+ }
184
+ function hasCursorSignal(agentPath) {
185
+ let resolved = agentPath;
186
+ try {
187
+ resolved = fs2.realpathSync(agentPath);
188
+ } catch {
189
+ }
190
+ return resolved.replace(/\\/g, "/").toLowerCase().includes("cursor");
191
+ }
192
+ function agentIsCursor(agentPath, env) {
193
+ if (agentIsGrok(agentPath, env)) return false;
194
+ const cursor = findBin("cursor-agent", env);
195
+ if (cursor && sameFile(agentPath, cursor)) return true;
196
+ return hasCursorSignal(agentPath);
197
+ }
198
+ function cursorBin(env) {
199
+ const linked = findBin("cursor-agent", env);
200
+ if (linked) return linked;
201
+ const agent = findBin("agent", env);
202
+ return agent && agentIsCursor(agent, env) ? agent : void 0;
203
+ }
204
+ function detectTools(env) {
205
+ const found = [];
206
+ for (const tool of REAL_TOOLS) {
207
+ const bin = tool === "cursor" ? cursorBin(env) : firstBin(BINARIES[tool], env);
208
+ if (bin) found.push({ tool, bin });
209
+ }
210
+ return found;
211
+ }
212
+ function toolFromEnv(env) {
213
+ if (env.GROK_SESSION_ID) return "grok";
214
+ if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT) return "claude";
215
+ if (env.CODEX_SANDBOX || env.CODEX_SESSION_ID || env.CODEX_THREAD_ID || env.CODEX_CI) return "codex";
216
+ if (env.CURSOR_AGENT || env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID) return "cursor";
217
+ if (env.GEMINI_CLI || env.GEMINI_SESSION_ID) return "gemini";
218
+ return void 0;
219
+ }
220
+
120
221
  export {
121
222
  home,
122
223
  configDir,
@@ -127,6 +228,11 @@ export {
127
228
  machineSpoolFile,
128
229
  serviceStateFile,
129
230
  serviceLogFile,
231
+ controlFile,
232
+ sessionsFile,
233
+ controlLogFile,
234
+ controlInboxDir,
235
+ controlDecisionsDir,
130
236
  cliDir,
131
237
  cliEntry,
132
238
  readText,
@@ -139,5 +245,12 @@ export {
139
245
  ConfigError,
140
246
  readJsonFile,
141
247
  serializeJson,
142
- isRecord
248
+ isRecord,
249
+ REAL_TOOLS,
250
+ TOOL_LABEL,
251
+ isTool,
252
+ findBin,
253
+ agentIsCursor,
254
+ detectTools,
255
+ toolFromEnv
143
256
  };
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
4
+ import {
5
+ parseArgs
6
+ } from "./chunk-QPAFN47B.js";
7
+ import {
8
+ VERSION
9
+ } from "./chunk-K2IRTZH5.js";
10
+ import {
11
+ ApiError
12
+ } from "./chunk-NRJONTFT.js";
13
+ import {
14
+ defaultCtx
15
+ } from "./chunk-QGULSOQ2.js";
16
+ import {
17
+ CLI_BIN_NAME,
18
+ CLI_INSTALL_COMMAND,
19
+ cliNpxCommand
20
+ } from "./chunk-HWBUESB4.js";
21
+ import "./chunk-2ERPDCOT.js";
22
+
23
+ // src/help.ts
24
+ var HOST_NOTE = `Host: --host URL, else HIVEFLOOR_URL, else the hosted instance. Self-hosting: pass --host or set HIVEFLOOR_URL.`;
25
+ var COMMAND_HELP = {
26
+ connect: `Usage: ${CLI_BIN_NAME} connect [options]
27
+
28
+ Connect this computer once: every agent on it (Claude profiles, Codex, ...) shows up.
29
+
30
+ Options:
31
+ --host URL HiveFloor server (default: HIVEFLOOR_URL, else the hosted instance)
32
+ --name NAME Display name for this machine (default: the computer's hostname)
33
+ --no-open Print the sign-in link instead of opening a browser
34
+ --no-service Do not install the background service
35
+ --yes, -y Skip confirmation prompts and accept defaults
36
+ --agent Connect one agent for the current git repo (MCP and tasks) instead of the machine
37
+ --tool NAME Like --agent for one tool: claude, codex, cursor, gemini, grok or other
38
+ --no-cli With --agent or --tool: write the tool's config files directly instead of running its own CLI (claude mcp add, ...)
39
+
40
+ ${HOST_NOTE}
41
+ `,
42
+ disconnect: `Usage: ${CLI_BIN_NAME} disconnect [options]
43
+
44
+ Remove everything connect added: hooks, MCP entries, service, stored tokens, and the machine on the server.
45
+
46
+ Options:
47
+ --host URL Only remove credentials for this server (a different server leaves this machine in place)
48
+ --tool NAME Remove one agent tool only: claude, codex, cursor, gemini or grok
49
+ `,
50
+ status: `Usage: ${CLI_BIN_NAME} status [options]
51
+
52
+ This machine and its service; the per-repo agent, its projects and live sessions.
53
+
54
+ Options:
55
+ --host URL Server to query (default: HIVEFLOOR_URL, else the last connected host)
56
+ --tool NAME Look at one agent tool's token
57
+
58
+ ${HOST_NOTE}
59
+ `,
60
+ where: `Usage: ${CLI_BIN_NAME} where [tool] [options]
61
+
62
+ Which account to run next on. Read only: nothing is switched.
63
+
64
+ Arguments:
65
+ tool claude, codex, ... (a tool or provider name)
66
+
67
+ Options:
68
+ --json Print the raw answer
69
+ --host URL Server to query (default: HIVEFLOOR_URL, else the last connected host)
70
+ --tool NAME Look at one agent tool's token
71
+
72
+ ${HOST_NOTE}
73
+ `,
74
+ service: `Usage: ${CLI_BIN_NAME} service run
75
+
76
+ Run the background service in the foreground. Your OS starts it for you after connect; you rarely run it by hand.
77
+ `,
78
+ control: `Usage: ${CLI_BIN_NAME} control on [--verbs message,approve,deny,stop|none] [--from owner|admins] [--replies off|after_message|always] [--orchestrator on|off]
79
+ ${CLI_BIN_NAME} control off
80
+ ${CLI_BIN_NAME} control status
81
+
82
+ Session control: let people you allow send a message to, approve or deny a permission prompt of, or stop a live Claude Code session on this computer.
83
+ Off until you turn it on here. Nothing opens a port: the service asks the server for commands. status says what a compromised server could do with the verbs that are on.
84
+
85
+ Options:
86
+ --verbs LIST Which verbs to turn on (default: message,stop). approve and deny add one synchronous PermissionRequest hook to Claude Code's settings.json
87
+ --from WHO owner (default): only the machine's owner; admins: also the project's owners and admins
88
+ --replies MODE Send the agent's last message to the server: off, after_message (default) or always
89
+ --orchestrator on|off Let the server start one Claude Code session in its own folder and type to it (docs/ORCHESTRATOR.md).
90
+ Its tool calls outside reading that folder are asked on the orchestrator page. --verbs none --orchestrator on turns on only this.
91
+ `,
92
+ orch: `Usage: ${CLI_BIN_NAME} orch enable [--full | --no-full]
93
+ ${CLI_BIN_NAME} orch disable
94
+ ${CLI_BIN_NAME} orch status
95
+
96
+ The orchestrator: let your HiveFloor account start one Claude Code session on this computer and talk to it (docs/ORCHESTRATOR.md).
97
+
98
+ enable Turn the orchestrator on. By default the session is sandboxed: its own folder, none of your settings, every tool call asked on the orchestrator page.
99
+ --full Also allow full power: a start from HiveFloor may run the session as a normal Claude Code session of yours (your settings, MCP servers,
100
+ skills and environment, any folder, resume any of your sessions by id, permission prompts bypassed when the start asks for it).
101
+ That is remote code as you: a session with prompts bypassed asks nothing, so text from fleet reports, scheduled check-ins and
102
+ tool output can make it run commands as you. Only this command can turn it on; the server cannot. It asks you to type FULL,
103
+ needs a terminal (no pipe or script), and is refused inside a Claude Code session.
104
+ --no-full Turn full power off again (a running full-power session is stopped).
105
+ disable Turn the orchestrator off (full power too). Session control verbs stay as they are.
106
+ `,
107
+ link: `Usage: ${CLI_BIN_NAME} link [options]
108
+
109
+ Link this repo to another project. With neither --project nor --new, your browser opens to pick one.
110
+
111
+ Options:
112
+ --project ID Link to an existing project
113
+ --new NAME Create a project and link to it (not together with --project)
114
+ --path PREFIX Repo path inside the project (monorepo subfolder)
115
+ --host URL Server (default: HIVEFLOOR_URL, else the stored host)
116
+ --tool NAME Which agent tool's token to use
117
+ --no-open Print the sign-in link instead of opening a browser
118
+
119
+ ${HOST_NOTE}
120
+ `,
121
+ token: `Usage: ${CLI_BIN_NAME} token [options]
122
+
123
+ Print the stored token for shell use, for example: export HIVEFLOOR_TOKEN=$(${CLI_BIN_NAME} token)
124
+ It refuses to print to a terminal unless you pass --reveal.
125
+
126
+ Options:
127
+ --reveal Print to a terminal anyway
128
+ --host URL Host to look up the token for (default: HIVEFLOOR_URL, else the last connected host)
129
+ --tool NAME Which agent tool's token to print
130
+ `,
131
+ mcp: `Usage: ${CLI_BIN_NAME} mcp [options]
132
+
133
+ Stdio MCP server that proxies to HiveFloor, for tools that cannot send HTTP headers. Needs a stored token (run ${cliNpxCommand("connect")} first).
134
+
135
+ Options:
136
+ --url URL MCP endpoint (default: <host>/mcp)
137
+ --host URL Host that resolves the stored token
138
+ --tool NAME Which agent tool's token to use
139
+
140
+ ${HOST_NOTE}
141
+ `,
142
+ voice: `Usage: ${CLI_BIN_NAME} voice status
143
+ ${CLI_BIN_NAME} voice list [--agent-id ID]
144
+ ${CLI_BIN_NAME} voice say "<text>" --out FILE.wav [--voice ID | --agent-id AGENT_ID] [--speed N]
145
+
146
+ Speech from TalkBack's Kokoro voice, which runs on this computer (https://github.com/Errr0rr404/talkback).
147
+
148
+ status Is TalkBack's voice running? (reads ~/.talkback/voiced.json and pings it)
149
+ list Print the Kokoro voice ids. Does not contact TalkBack.
150
+ say Synthesize the text to a 24 kHz mono WAV file. Nothing is played.
151
+
152
+ Options for list:
153
+ --agent-id ID Print the voice and speed hivefloor gives that agent id
154
+
155
+ Options for say:
156
+ --out FILE Where to write the WAV (required)
157
+ --voice ID A Kokoro voice id, for example af_heart or bf_emma
158
+ --agent-id ID Use the stable voice and speed hivefloor gives that agent id
159
+ --speed N 0.5 to 2 (default: TalkBack's setting)
160
+
161
+ If TalkBack is not installed, status and say print the install line and exit 1. list does not contact TalkBack. Text for say is limited to 2000 characters.
162
+ `,
163
+ hook: `Usage: ${CLI_BIN_NAME} hook <tool> <event> [--profile KEY]
164
+
165
+ The hook runner that Claude Code, Codex and other tools call. It reads the event JSON on stdin, reports it to HiveFloor, and always exits 0 so a hook never fails the agent. You do not run it by hand; ${cliNpxCommand("connect")} installs the hook entries.
166
+
167
+ Arguments:
168
+ tool claude, codex, ...
169
+ event the tool's native event name, for example PreToolUse
170
+ --profile KEY Use this machine profile's token and host (set by machine connect)
171
+ `
172
+ };
173
+ function commandHelp(cmd) {
174
+ return Object.hasOwn(COMMAND_HELP, cmd) ? COMMAND_HELP[cmd] : void 0;
175
+ }
176
+
177
+ // src/cli.ts
178
+ var HELP = `hivefloor ${VERSION}
179
+
180
+ Usage: ${CLI_BIN_NAME} <command> [options]
181
+ ${cliNpxCommand("<command> [options]")} (no install)
182
+
183
+ Commands:
184
+ connect Connect this computer once: every agent on it shows up (Claude profiles, Codex, ...)
185
+ --host URL --name NAME --no-open --no-service --yes
186
+ --agent | --tool claude|codex|cursor|gemini|grok|other connect one agent for one repo (MCP and tasks) instead
187
+ disconnect Remove everything connect added, the machine on the server included (--tool <name> for one agent tool)
188
+ status This machine and its service; the per-repo agent, its projects and live sessions
189
+ where [tool] Which account to run next on (read only; --json for the raw answer)
190
+ control on [--verbs ...] [--from owner|admins] | off | status: let people message, approve or stop live sessions
191
+ service run: the background service (started by your OS; you rarely run it by hand)
192
+ link Link this repo to another project (--project ID | --new NAME [--path PREFIX])
193
+ token Print the stored token for shell use (--reveal to print to a terminal)
194
+ voice status | list [--agent-id ID] | say "<text>" --out file.wav: TalkBack's spoken voice on this computer
195
+ mcp Stdio MCP server that proxies to HiveFloor (for tools without HTTP header support)
196
+ hook <tool> <event>: the hook runner the tools call (reads JSON on stdin)
197
+ help Show this help (hivefloor <command> --help, or help <command>, for one command)
198
+ --version Print the version
199
+
200
+ Install once: ${CLI_INSTALL_COMMAND}
201
+ Host: https://hivefloor.worldofz.info. Self-hosting: pass --host URL or set HIVEFLOOR_URL.
202
+ `;
203
+ async function run(argv, ctx = defaultCtx()) {
204
+ const [cmd, ...rest] = argv;
205
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
206
+ ctx.out(`${VERSION}
207
+ `);
208
+ return 0;
209
+ }
210
+ if (cmd === "help" && rest[0] !== void 0 && !rest[0].startsWith("-")) {
211
+ const h = rest[0] === "help" || rest[0] === "version" ? HELP : commandHelp(rest[0]);
212
+ if (h === void 0) {
213
+ ctx.err(`hivefloor: unknown command "${rest[0]}". Run "hivefloor help".
214
+ `);
215
+ return 2;
216
+ }
217
+ ctx.out(h);
218
+ return 0;
219
+ }
220
+ if (cmd === void 0 || cmd === "help" || cmd === "--help" || cmd === "-h") {
221
+ ctx.out(HELP);
222
+ return 0;
223
+ }
224
+ const p = parseArgs(rest);
225
+ if (p.flags.help) {
226
+ ctx.out(commandHelp(cmd) ?? HELP);
227
+ return 0;
228
+ }
229
+ try {
230
+ switch (cmd) {
231
+ case "connect":
232
+ return await (await import("./connect-ZJ4JXBAJ.js")).connect(ctx, p);
233
+ case "disconnect":
234
+ return await (await import("./disconnect-3T25NTKX.js")).disconnect(ctx, p);
235
+ case "status":
236
+ return await (await import("./status-QXOOJGGN.js")).status(ctx, p);
237
+ case "where":
238
+ return await (await import("./where-CMUORQIO.js")).where(ctx, p);
239
+ case "service":
240
+ if (p.positionals[0] !== "run") {
241
+ ctx.err("hivefloor: usage: hivefloor service run\n");
242
+ return 2;
243
+ }
244
+ return await (await import("./run-JL2OZWK3.js")).runService({ ctx });
245
+ case "control":
246
+ return await (await import("./control-6GHTZQJV.js")).control(ctx, p);
247
+ case "orch":
248
+ return await (await import("./orch-KVRL2YA2.js")).orch(ctx, p);
249
+ case "link":
250
+ return await (await import("./link-EAS5F5PC.js")).link(ctx, p);
251
+ case "token":
252
+ return await (await import("./token-BFWWQIDA.js")).token(ctx, p);
253
+ case "voice":
254
+ return await (await import("./voice-QXHFXRWG.js")).voice(ctx, p);
255
+ case "mcp":
256
+ return await (await import("./mcp-LVIYR3EN.js")).runMcpProxy(ctx, p);
257
+ case "hook": {
258
+ const { runHook, readStdin } = await import("./hook-TPSHBCWU.js");
259
+ await runHook(rest, { env: ctx.env, cwd: ctx.cwd, fetch: ctx.fetch, now: ctx.now, readStdin });
260
+ return 0;
261
+ }
262
+ default:
263
+ ctx.err(`hivefloor: unknown command "${cmd}". Run "hivefloor help".
264
+ `);
265
+ return 2;
266
+ }
267
+ } catch (e) {
268
+ if (e instanceof ApiError) ctx.err(`hivefloor: ${e.message}
269
+ `);
270
+ else ctx.err(`hivefloor: ${e.message}
271
+ `);
272
+ return 1;
273
+ }
274
+ }
275
+ export {
276
+ HELP,
277
+ VERSION,
278
+ run
279
+ };
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
4
+ import {
5
+ connect,
6
+ connectAgent,
7
+ resolveHost
8
+ } from "./chunk-TIR4CV3P.js";
9
+ import "./chunk-FPJNGKT5.js";
10
+ import "./chunk-QBBKGORP.js";
11
+ import "./chunk-X2KH6AR4.js";
12
+ import "./chunk-BLLPDPTL.js";
13
+ import "./chunk-QLMLB7RB.js";
14
+ import "./chunk-QPAFN47B.js";
15
+ import "./chunk-K2IRTZH5.js";
16
+ import "./chunk-LX5A25O6.js";
17
+ import "./chunk-NRJONTFT.js";
18
+ import "./chunk-XAXKZ572.js";
19
+ import "./chunk-XPGL7VYE.js";
20
+ import "./chunk-QGULSOQ2.js";
21
+ import "./chunk-HWBUESB4.js";
22
+ import "./chunk-2ERPDCOT.js";
23
+ export {
24
+ connect,
25
+ connectAgent,
26
+ resolveHost
27
+ };
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __hfCreateRequire } from 'node:module';
3
+ const require = __hfCreateRequire(import.meta.url);
4
+ import {
5
+ FULL_POWER_MEANS,
6
+ control,
7
+ describeFull
8
+ } from "./chunk-HW3KRNCO.js";
9
+ import "./chunk-5XXYQQG6.js";
10
+ import "./chunk-4G362W6G.js";
11
+ import "./chunk-X2KH6AR4.js";
12
+ import "./chunk-QPAFN47B.js";
13
+ import "./chunk-KQMPCV57.js";
14
+ import "./chunk-L6EFW2SJ.js";
15
+ import "./chunk-LX5A25O6.js";
16
+ import "./chunk-NRJONTFT.js";
17
+ import "./chunk-XPGL7VYE.js";
18
+ import "./chunk-QGULSOQ2.js";
19
+ import "./chunk-HWBUESB4.js";
20
+ import "./chunk-2ERPDCOT.js";
21
+ export {
22
+ FULL_POWER_MEANS,
23
+ control,
24
+ describeFull
25
+ };
@@ -1,55 +1,63 @@
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
  removeCliCopy,
4
6
  removeMachineHooks
5
- } from "./chunk-GT3HFFT4.js";
7
+ } from "./chunk-5XXYQQG6.js";
8
+ import "./chunk-4G362W6G.js";
6
9
  import {
7
- claudeJson,
8
10
  geminiSettings,
9
- readManifest,
10
- removeClaude,
11
- removeCodex,
12
11
  removeCursor,
13
12
  removeGemini,
14
13
  removeGrok,
15
- removeRcLine,
16
- updateManifest
17
- } from "./chunk-QHYZBHXO.js";
14
+ removeRcLine
15
+ } from "./chunk-QBBKGORP.js";
18
16
  import {
19
- REAL_TOOLS,
20
- TOOL_LABEL,
21
- isTool
22
- } from "./chunk-2IMJECYD.js";
23
- import {
24
- checkUnknownFlags,
25
- flagStr
26
- } from "./chunk-TKMIV7BG.js";
17
+ claudeJson,
18
+ readManifest,
19
+ removeClaude,
20
+ removeCodex,
21
+ updateManifest
22
+ } from "./chunk-X2KH6AR4.js";
27
23
  import {
24
+ KEYCHAIN_SERVICE,
28
25
  deleteCredentials,
29
26
  readCredentials,
30
27
  uninstallService
31
- } from "./chunk-PDXYOREI.js";
32
- import "./chunk-UJ4BQFWV.js";
28
+ } from "./chunk-QLMLB7RB.js";
29
+ import {
30
+ checkUnknownFlags,
31
+ flagStr
32
+ } from "./chunk-QPAFN47B.js";
33
+ import "./chunk-KQMPCV57.js";
34
+ import "./chunk-L6EFW2SJ.js";
33
35
  import {
34
36
  readMachine,
35
37
  removeMachineFile
36
- } from "./chunk-T7TG6WA4.js";
38
+ } from "./chunk-LX5A25O6.js";
37
39
  import {
38
40
  apiRequest
39
- } from "./chunk-PY74LT2W.js";
41
+ } from "./chunk-NRJONTFT.js";
42
+ import "./chunk-XAXKZ572.js";
40
43
  import {
41
44
  ConfigError,
45
+ REAL_TOOLS,
46
+ TOOL_LABEL,
47
+ isTool,
42
48
  machineSpoolFile,
43
49
  manifestFile,
44
50
  removeFile,
45
51
  serviceLogFile,
46
52
  serviceStateFile
47
- } from "./chunk-VNCQFFQO.js";
53
+ } from "./chunk-XPGL7VYE.js";
48
54
  import {
49
- normalizeHost,
50
- runCmd
51
- } from "./chunk-ZC2DAKB5.js";
52
- import "./chunk-K3NQKI34.js";
55
+ hostFromFlag,
56
+ runCmd,
57
+ sameOrigin
58
+ } from "./chunk-QGULSOQ2.js";
59
+ import "./chunk-HWBUESB4.js";
60
+ import "./chunk-2ERPDCOT.js";
53
61
 
54
62
  // src/disconnect.ts
55
63
  import fs from "fs";
@@ -68,13 +76,13 @@ async function disconnect(ctx, p) {
68
76
  return 2;
69
77
  }
70
78
  const hostFlag = flagStr(p, "host");
71
- const host = hostFlag ? normalizeHost(hostFlag) : void 0;
79
+ const host = hostFlag ? hostFromFlag(ctx, hostFlag) : void 0;
72
80
  const tools = toolFlag ? [toolFlag] : REAL_TOOLS;
73
81
  const manifest = readManifest();
74
82
  const lines = [];
75
83
  const warnings = [];
76
84
  const machine = readMachine();
77
- if (machine && !toolFlag) {
85
+ if (machine && !toolFlag && (host === void 0 || sameOrigin(machine.host, host))) {
78
86
  if (!machine.revoked) {
79
87
  try {
80
88
  const r = await apiRequest(ctx, machine.host, "/api/v1/machine/disconnect", { method: "POST", token: machine.token, body: {}, timeoutMs: 8e3 });
@@ -95,14 +103,26 @@ async function disconnect(ctx, p) {
95
103
  lines.push("Machine token and state removed");
96
104
  }
97
105
  const keepHooks = machine !== void 0 && !machine.revoked && toolFlag !== void 0;
98
- for (const tool of tools) {
106
+ const credHosts = Object.keys(readCredentials().hosts);
107
+ const otherHosts = host === void 0 ? [] : credHosts.filter((h) => h !== host && !sameOrigin(h, host));
108
+ const disconnectsStoredHost = host !== void 0 && credHosts.some((h) => h === host || sameOrigin(h, host));
109
+ const lastHost = disconnectsStoredHost && otherHosts.length === 0;
110
+ const ownedHere = (owner) => host === void 0 || lastHost || typeof owner === "string" && owner.length > 0 && sameOrigin(owner, host);
111
+ const dropping = tools.filter((tool) => toolFlag !== void 0 || ownedHere(manifest.tools[tool]?.host));
112
+ const owners = Object.values(manifest.tools).map((e) => e?.host).filter((h) => typeof h === "string" && h.length > 0);
113
+ const rcOwnedHere = host === void 0 || lastHost || owners.length > 0 && owners.every((o) => sameOrigin(o, host));
114
+ const mixedOwners = owners.length > 1 && owners.some((o) => !sameOrigin(o, owners[0]));
115
+ if (host !== void 0 && !rcOwnedHere && mixedOwners && manifest.rc.length > 0) {
116
+ warnings.push(`shell export kept while tools point at more than one host; "hivefloor token" follows the last host, so it can still be a token for ${host} after this disconnect`);
117
+ }
118
+ for (const tool of dropping) {
99
119
  const entry = manifest.tools[tool];
100
120
  const viaCli = entry?.mcp === "cli";
101
121
  const legacyBak = tool === "claude" ? fs.existsSync(claudeJson() + ".hivefloor.bak") : tool === "gemini" ? fs.existsSync(geminiSettings() + ".hivefloor.bak") : false;
102
122
  const viaFile = entry ? entry.mcp === "file" : legacyBak;
103
123
  let r;
104
124
  try {
105
- r = tool === "claude" ? await removeClaude(ctx, viaCli, keepHooks, viaFile) : tool === "codex" ? await removeCodex(ctx, viaCli, keepHooks) : tool === "cursor" ? await removeCursor() : tool === "gemini" ? await removeGemini(ctx, viaCli, viaFile) : await removeGrok(ctx, viaCli);
125
+ r = tool === "claude" ? await removeClaude(ctx, viaCli, keepHooks, viaFile) : tool === "codex" ? await removeCodex(ctx, viaCli, keepHooks) : tool === "cursor" ? await removeCursor() : tool === "gemini" ? await removeGemini(ctx, viaCli, viaFile) : await removeGrok(ctx, viaCli, viaFile);
106
126
  } catch (e) {
107
127
  if (!(e instanceof ConfigError)) throw e;
108
128
  warnings.push(e.message);
@@ -112,21 +132,22 @@ async function disconnect(ctx, p) {
112
132
  warnings.push(...r.warnings);
113
133
  if (viaCli && r.lines.length === 0) warnings.push(`${TOOL_LABEL[tool]}: could not run its CLI to remove the "hivefloor" MCP server; remove it by hand`);
114
134
  }
115
- const n = await deleteCredentials(host, toolFlag);
135
+ const { removed: n, leftover } = await deleteCredentials(host, toolFlag);
116
136
  if (n > 0) lines.push(`Removed ${n} stored token${n === 1 ? "" : "s"}`);
137
+ for (const a of leftover) warnings.push(`keychain entry ${a} (service ${KEYCHAIN_SERVICE}) was not removed; delete it in your OS keychain app`);
117
138
  const left = Object.keys(readCredentials().hosts).length > 0;
118
- if (!toolFlag || !left) {
139
+ if ((!toolFlag || !left) && rcOwnedHere) {
119
140
  const files = readManifest().rc;
120
141
  for (const f of files) if (removeRcLine(f)) lines.push(`Shell: removed the token export from ${f}`);
121
142
  updateManifest((m2) => m2.rc = []);
122
143
  }
123
144
  updateManifest((m2) => {
124
- for (const t of tools) delete m2.tools[t];
145
+ for (const t of dropping) delete m2.tools[t];
125
146
  });
126
147
  const m = readManifest();
127
148
  if (Object.keys(m.tools).length === 0 && m.rc.length === 0) removeFile(manifestFile());
128
- if (lines.length === 0) ctx.out("Nothing to remove: no Hivefloor entries found.\n");
129
- else {
149
+ if (lines.length === 0 && warnings.length === 0) ctx.out("Nothing to remove: no HiveFloor entries found.\n");
150
+ else if (lines.length > 0) {
130
151
  ctx.out("Disconnected:\n");
131
152
  for (const l of lines) ctx.out(` ${l}
132
153
  `);