@hivefloor/cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ removeCliCopy,
4
+ removeMachineHooks
5
+ } from "./chunk-DIW4VIU7.js";
6
+ import {
7
+ claudeJson,
8
+ geminiSettings,
9
+ readManifest,
10
+ removeClaude,
11
+ removeCodex,
12
+ removeCursor,
13
+ removeGemini,
14
+ removeGrok,
15
+ removeRcLine,
16
+ updateManifest
17
+ } from "./chunk-LHF4KCEA.js";
18
+ import "./chunk-M3PLXINO.js";
19
+ import {
20
+ readMachine,
21
+ removeMachineFile
22
+ } from "./chunk-3IMV5EHP.js";
23
+ import {
24
+ REAL_TOOLS,
25
+ TOOL_LABEL,
26
+ isTool
27
+ } from "./chunk-2IMJECYD.js";
28
+ import {
29
+ checkUnknownFlags,
30
+ flagStr
31
+ } from "./chunk-TKMIV7BG.js";
32
+ import {
33
+ deleteCredentials,
34
+ readCredentials,
35
+ uninstallService
36
+ } from "./chunk-4A5WOW2D.js";
37
+ import {
38
+ apiRequest
39
+ } from "./chunk-PY74LT2W.js";
40
+ import {
41
+ ConfigError,
42
+ machineSpoolFile,
43
+ manifestFile,
44
+ removeFile,
45
+ serviceLogFile,
46
+ serviceStateFile
47
+ } from "./chunk-WNDUGQDX.js";
48
+ import {
49
+ normalizeHost,
50
+ runCmd
51
+ } from "./chunk-RE2EQZWL.js";
52
+ import "./chunk-K3NQKI34.js";
53
+
54
+ // src/disconnect.ts
55
+ import fs from "fs";
56
+ var DISCONNECT_FLAGS = /* @__PURE__ */ new Set(["tool", "host", "help", "h"]);
57
+ async function disconnect(ctx, p) {
58
+ const unknown = checkUnknownFlags(p, DISCONNECT_FLAGS);
59
+ if (unknown) {
60
+ ctx.err(`hivefloor: unknown flag "--${unknown}". Run "hivefloor help".
61
+ `);
62
+ return 2;
63
+ }
64
+ const toolFlag = flagStr(p, "tool");
65
+ if (toolFlag !== void 0 && (!isTool(toolFlag) || toolFlag === "other")) {
66
+ ctx.err(`hivefloor: unknown tool "${toolFlag}". Use one of: ${REAL_TOOLS.join(", ")}.
67
+ `);
68
+ return 2;
69
+ }
70
+ const hostFlag = flagStr(p, "host");
71
+ const host = hostFlag ? normalizeHost(hostFlag) : void 0;
72
+ const tools = toolFlag ? [toolFlag] : REAL_TOOLS;
73
+ const manifest = readManifest();
74
+ const lines = [];
75
+ const warnings = [];
76
+ const machine = readMachine();
77
+ if (machine && !toolFlag) {
78
+ if (!machine.revoked) {
79
+ try {
80
+ const r = await apiRequest(ctx, machine.host, "/api/v1/machine/disconnect", { method: "POST", token: machine.token, body: {}, timeoutMs: 8e3 });
81
+ if (r.status >= 200 && r.status < 300) lines.push(`Machine ${machine.machine.name} removed on ${machine.host}`);
82
+ else if (r.status !== 401) warnings.push(`the server answered ${r.status}; remove the machine on its Machines page`);
83
+ } catch {
84
+ warnings.push(`could not reach ${machine.host}; remove the machine on its Machines page`);
85
+ }
86
+ }
87
+ if (machine.installed.service) {
88
+ await uninstallService(machine.installed.service, ctx.run ?? runCmd, typeof process.getuid === "function" ? process.getuid() : void 0);
89
+ lines.push(`Service removed (${machine.installed.service.file})`);
90
+ }
91
+ lines.push(...removeMachineHooks(machine.installed));
92
+ for (const f of [machineSpoolFile(), serviceStateFile(), serviceLogFile()]) removeFile(f);
93
+ removeCliCopy();
94
+ removeMachineFile();
95
+ lines.push("Machine token and state removed");
96
+ }
97
+ const keepHooks = machine !== void 0 && !machine.revoked && toolFlag !== void 0;
98
+ for (const tool of tools) {
99
+ const entry = manifest.tools[tool];
100
+ const viaCli = entry?.mcp === "cli";
101
+ const legacyBak = tool === "claude" ? fs.existsSync(claudeJson() + ".hivefloor.bak") : tool === "gemini" ? fs.existsSync(geminiSettings() + ".hivefloor.bak") : false;
102
+ const viaFile = entry ? entry.mcp === "file" : legacyBak;
103
+ let r;
104
+ 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);
106
+ } catch (e) {
107
+ if (!(e instanceof ConfigError)) throw e;
108
+ warnings.push(e.message);
109
+ continue;
110
+ }
111
+ lines.push(...r.lines);
112
+ warnings.push(...r.warnings);
113
+ 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
+ }
115
+ const n = await deleteCredentials(host, toolFlag);
116
+ if (n > 0) lines.push(`Removed ${n} stored token${n === 1 ? "" : "s"}`);
117
+ const left = Object.keys(readCredentials().hosts).length > 0;
118
+ if (!toolFlag || !left) {
119
+ const files = readManifest().rc;
120
+ for (const f of files) if (removeRcLine(f)) lines.push(`Shell: removed the token export from ${f}`);
121
+ updateManifest((m2) => m2.rc = []);
122
+ }
123
+ updateManifest((m2) => {
124
+ for (const t of tools) delete m2.tools[t];
125
+ });
126
+ const m = readManifest();
127
+ 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 {
130
+ ctx.out("Disconnected:\n");
131
+ for (const l of lines) ctx.out(` ${l}
132
+ `);
133
+ }
134
+ for (const w of warnings) ctx.out(` note: ${w}
135
+ `);
136
+ return 0;
137
+ }
138
+ export {
139
+ disconnect
140
+ };
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HARD_EXIT_MS
4
+ } from "./chunk-JCLWCMEM.js";
5
+ import {
6
+ appendToSpool,
7
+ releaseSpool,
8
+ takeSpool
9
+ } from "./chunk-SGDM7MAT.js";
10
+ import {
11
+ readMachine,
12
+ serviceAlive
13
+ } from "./chunk-3IMV5EHP.js";
14
+ import {
15
+ detectRepo
16
+ } from "./chunk-OQ6QVFSH.js";
17
+ import {
18
+ defaultHost,
19
+ loadToken
20
+ } from "./chunk-4A5WOW2D.js";
21
+ import {
22
+ configDir,
23
+ machineSpoolFile
24
+ } from "./chunk-WNDUGQDX.js";
25
+ import {
26
+ DEFAULT_HOST,
27
+ HOOK_BATCH_MAX,
28
+ PROFILE_KEY_RE,
29
+ hostFromEnv,
30
+ redact
31
+ } from "./chunk-RE2EQZWL.js";
32
+ import "./chunk-K3NQKI34.js";
33
+
34
+ // src/hook.ts
35
+ import fs from "fs";
36
+
37
+ // src/hookmap.ts
38
+ var NAMES = {
39
+ sessionstart: "session_start",
40
+ sessionend: "session_end",
41
+ userpromptsubmit: "prompt",
42
+ beforesubmitprompt: "prompt",
43
+ stop: "stop",
44
+ afteragent: "stop",
45
+ notification: "waiting",
46
+ subagentstart: "subagent_start",
47
+ subagentstop: "subagent_stop",
48
+ pretooluse: "tool_agent_pre",
49
+ posttooluse: "tool_agent_post"
50
+ };
51
+ var WAITING_TYPES = /* @__PURE__ */ new Set(["permission_prompt", "idle_prompt"]);
52
+ var LINE_MAX = 140;
53
+ var str = (v) => typeof v === "string" && v.trim() !== "" ? v : void 0;
54
+ var rec = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
55
+ function statusLine(text) {
56
+ const t = str(text);
57
+ if (!t) return void 0;
58
+ const first = t.split(/\r?\n/).map((l) => l.trim()).find((l) => l !== "");
59
+ if (!first) return void 0;
60
+ return redact(first).text.slice(0, LINE_MAX).trim() || void 0;
61
+ }
62
+ function oneLine(text) {
63
+ const t = str(text);
64
+ return t ? redact(t.replace(/\s+/g, " ").trim()).text.slice(0, LINE_MAX) : void 0;
65
+ }
66
+ function mapHookEvent(tool, native, input) {
67
+ const event = NAMES[native.toLowerCase()];
68
+ if (!event) return null;
69
+ const session_id = str(input.conversation_id) ?? str(input.session_id);
70
+ if (!session_id) return null;
71
+ const out = { tool, event, session_id };
72
+ const model = str(input.model) ?? str(input.subagent_model);
73
+ if (model) out.model = model;
74
+ switch (event) {
75
+ case "waiting": {
76
+ if (tool !== "claude" || !WAITING_TYPES.has(String(input.notification_type))) return null;
77
+ break;
78
+ }
79
+ case "subagent_start":
80
+ case "subagent_stop": {
81
+ const id = str(input.subagent_id) ?? str(input.agent_id);
82
+ if (!id) return null;
83
+ out.subagent_id = id;
84
+ const type = str(input.subagent_type) ?? str(input.agent_type);
85
+ if (type) out.subagent_type = type;
86
+ if (event === "subagent_start") {
87
+ const d = oneLine(input.description) ?? oneLine(input.task);
88
+ if (d) out.description = d;
89
+ } else {
90
+ const s = statusLine(input.last_assistant_message) ?? statusLine(input.summary);
91
+ if (s) out.status_line = s;
92
+ }
93
+ break;
94
+ }
95
+ case "tool_agent_pre":
96
+ case "tool_agent_post": {
97
+ if (input.tool_name !== "Agent" && input.tool_name !== "Task") return null;
98
+ const ti = rec(input.tool_input);
99
+ const type = str(ti.subagent_type);
100
+ if (type) out.subagent_type = type;
101
+ const d = oneLine(ti.description);
102
+ if (d) out.description = d;
103
+ break;
104
+ }
105
+ case "stop": {
106
+ const s = statusLine(input.last_assistant_message) ?? statusLine(input.prompt_response);
107
+ if (s) out.status_line = s;
108
+ break;
109
+ }
110
+ default:
111
+ break;
112
+ }
113
+ return out;
114
+ }
115
+ function cwdOf(input) {
116
+ const roots = Array.isArray(input.workspace_roots) ? input.workspace_roots : [];
117
+ return str(input.cwd) ?? str(roots[0]);
118
+ }
119
+
120
+ // src/hook.ts
121
+ import path from "path";
122
+ var POST_TIMEOUT_MS = 2e3;
123
+ var EXIT_MARGIN_MS = 400;
124
+ var MIN_POST_MS = 250;
125
+ var TOOLS = /* @__PURE__ */ new Set(["claude", "codex", "cursor", "gemini", "grok"]);
126
+ var DEDUPE_WINDOW_MS = 2e3;
127
+ function dedupeFile() {
128
+ return path.join(configDir(), "dedupe.json");
129
+ }
130
+ function isDuplicate(key, nowMs) {
131
+ let seen = {};
132
+ try {
133
+ seen = JSON.parse(fs.readFileSync(dedupeFile(), "utf8"));
134
+ } catch {
135
+ }
136
+ const fresh = Object.fromEntries(Object.entries(seen).filter(([, t]) => nowMs - t < 1e4));
137
+ const dup = key in fresh && nowMs - fresh[key] < DEDUPE_WINDOW_MS;
138
+ if (!dup) {
139
+ fresh[key] = nowMs;
140
+ try {
141
+ fs.mkdirSync(configDir(), { recursive: true, mode: 448 });
142
+ fs.writeFileSync(dedupeFile(), JSON.stringify(fresh), { mode: 384 });
143
+ } catch {
144
+ }
145
+ }
146
+ return dup;
147
+ }
148
+ async function post(deps, host, token, events, timeoutMs, route = "/api/v1/hooks") {
149
+ try {
150
+ const res = await deps.fetch(`${host}${route}`, {
151
+ method: "POST",
152
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
153
+ body: JSON.stringify({ events }),
154
+ signal: AbortSignal.timeout(timeoutMs)
155
+ });
156
+ await res.arrayBuffer().catch(() => void 0);
157
+ if (res.status >= 200 && res.status < 300) return "ok";
158
+ if (res.status === 429 || res.status >= 500) return "retry";
159
+ return "drop";
160
+ } catch {
161
+ return "retry";
162
+ }
163
+ }
164
+ async function runHook(args, deps) {
165
+ try {
166
+ await runHookInner(args, deps, deps.now());
167
+ } catch {
168
+ }
169
+ }
170
+ function machineCoversTool(tool) {
171
+ const m = readMachine();
172
+ if (!m || m.revoked) return false;
173
+ if (tool === "claude") return m.installed.claude_dirs.length > 0;
174
+ if (tool === "codex") return m.installed.codex_dir !== void 0;
175
+ return false;
176
+ }
177
+ async function runHookInner(args, deps, startedAt) {
178
+ const [toolArg, native] = args;
179
+ if (!toolArg || !native || !TOOLS.has(toolArg)) return;
180
+ const pi = args.indexOf("--profile");
181
+ const profile = pi >= 0 ? args[pi + 1] : void 0;
182
+ const machine = profile !== void 0 ? readMachine() : void 0;
183
+ if (profile !== void 0 && (!machine || machine.revoked || !PROFILE_KEY_RE.test(profile))) return;
184
+ if (profile === void 0 && machineCoversTool(toolArg)) return;
185
+ const host = machine ? machine.host : deps.env.HIVEFLOOR_URL ? hostFromEnv(deps.env) : defaultHost() ?? DEFAULT_HOST;
186
+ const grokId = deps.env.GROK_SESSION_ID;
187
+ const tool = grokId ? "grok" : toolArg;
188
+ const envToken = deps.env.HIVEFLOOR_TOKEN;
189
+ const stored = !machine && !envToken ? await loadToken(host, tool) : void 0;
190
+ const token = machine ? machine.token : envToken || stored?.token;
191
+ if (!token) return;
192
+ let input = {};
193
+ try {
194
+ const raw = await deps.readStdin();
195
+ const parsed = raw.trim() ? JSON.parse(raw) : {};
196
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) input = parsed;
197
+ } catch {
198
+ return;
199
+ }
200
+ if (grokId && !input.session_id && !input.conversation_id) input = { ...input, session_id: grokId };
201
+ const mapped = mapHookEvent(toolArg === "grok" ? "claude" : toolArg, native, input);
202
+ if (!mapped) return;
203
+ const nowMs = deps.now();
204
+ const event = { ...mapped, tool, client_at: new Date(nowMs).toISOString() };
205
+ if (grokId && isDuplicate(`${tool}|${event.session_id}|${event.event}|${event.subagent_id ?? ""}`, nowMs)) return;
206
+ const repo = await detectRepo(cwdOf(input) ?? deps.cwd);
207
+ if (repo) event.repo = { remote: repo.remote, ...repo.branch ? { branch: repo.branch } : {}, ...repo.path ? { path: repo.path } : {} };
208
+ if (machine && profile !== void 0) {
209
+ const mevent = { ...event, profile };
210
+ if (serviceAlive(deps.now())) {
211
+ appendToSpool(mevent, machineSpoolFile());
212
+ return;
213
+ }
214
+ const claim2 = takeSpool(machineSpoolFile());
215
+ await sendBatches(deps, host, token, [...claim2?.events ?? [], mevent], startedAt, "/api/v1/machine/hooks", claim2, machineSpoolFile());
216
+ return;
217
+ }
218
+ const claim = takeSpool();
219
+ await sendBatches(deps, host, token, [...claim?.events ?? [], event], startedAt, "/api/v1/hooks", claim);
220
+ }
221
+ async function sendBatches(deps, host, token, pending, startedAt, route, claim, spool) {
222
+ const batches = [];
223
+ for (let k = 0; k < pending.length; k += HOOK_BATCH_MAX) batches.push(pending.slice(k, k + HOOK_BATCH_MAX));
224
+ const unsent = [];
225
+ let down = false;
226
+ for (const batch of batches) {
227
+ const left = HARD_EXIT_MS - EXIT_MARGIN_MS - (deps.now() - startedAt);
228
+ if (down || left < MIN_POST_MS) {
229
+ unsent.push(...batch);
230
+ down = true;
231
+ continue;
232
+ }
233
+ const r = await post(deps, host, token, batch, Math.min(POST_TIMEOUT_MS, left), route);
234
+ if (r === "retry") {
235
+ unsent.push(...batch);
236
+ down = true;
237
+ }
238
+ }
239
+ releaseSpool(claim, unsent, spool);
240
+ }
241
+ async function readStdin(timeoutMs = 1e3) {
242
+ if (process.stdin.isTTY) return "";
243
+ return await new Promise((resolve) => {
244
+ const chunks = [];
245
+ const done = () => resolve(Buffer.concat(chunks).toString("utf8"));
246
+ const timer = setTimeout(done, timeoutMs);
247
+ process.stdin.on("data", (c) => chunks.push(c));
248
+ process.stdin.on("end", () => {
249
+ clearTimeout(timer);
250
+ done();
251
+ });
252
+ process.stdin.on("error", () => {
253
+ clearTimeout(timer);
254
+ done();
255
+ });
256
+ });
257
+ }
258
+ function runHookCli(args) {
259
+ return runHook(args, { env: process.env, cwd: process.cwd(), fetch: (...a) => fetch(...a), now: () => Date.now(), readStdin });
260
+ }
261
+ export {
262
+ readStdin,
263
+ runHook,
264
+ runHookCli
265
+ };
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HARD_EXIT_MS
4
+ } from "./chunk-JCLWCMEM.js";
5
+ import "./chunk-K3NQKI34.js";
6
+
7
+ // src/index.ts
8
+ var argv = process.argv.slice(2);
9
+ if (argv[0] === "hook") {
10
+ process.on("uncaughtException", () => process.exit(0));
11
+ process.on("unhandledRejection", () => process.exit(0));
12
+ setTimeout(() => process.exit(0), HARD_EXIT_MS).unref();
13
+ try {
14
+ const { runHookCli } = await import("./hook-B2I42GE3.js");
15
+ await runHookCli(argv.slice(1));
16
+ } catch {
17
+ }
18
+ process.exit(0);
19
+ } else {
20
+ const { run } = await import("./cli-7CIYC6A3.js");
21
+ process.exitCode = await run(argv);
22
+ }
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ deviceLogin
4
+ } from "./chunk-UPRHMVYJ.js";
5
+ import {
6
+ resolveSession
7
+ } from "./chunk-BAB4DPCN.js";
8
+ import {
9
+ detectRepo
10
+ } from "./chunk-OQ6QVFSH.js";
11
+ import {
12
+ isTool
13
+ } from "./chunk-2IMJECYD.js";
14
+ import {
15
+ flagStr
16
+ } from "./chunk-TKMIV7BG.js";
17
+ import {
18
+ saveToken
19
+ } from "./chunk-4A5WOW2D.js";
20
+ import {
21
+ VERSION
22
+ } from "./chunk-IEYWMDXA.js";
23
+ import {
24
+ apiRequest,
25
+ errorOf
26
+ } from "./chunk-PY74LT2W.js";
27
+ import "./chunk-WNDUGQDX.js";
28
+ import {
29
+ cliNpxCommand,
30
+ machineName
31
+ } from "./chunk-RE2EQZWL.js";
32
+ import "./chunk-K3NQKI34.js";
33
+
34
+ // src/link.ts
35
+ async function link(ctx, p) {
36
+ const repo = await detectRepo(ctx.cwd);
37
+ if (!repo) {
38
+ ctx.err("hivefloor: this directory has no git remote to link.\n");
39
+ return 1;
40
+ }
41
+ const s = await resolveSession(ctx, p);
42
+ if (!s) {
43
+ ctx.err(`hivefloor: not connected. Run: ${cliNpxCommand("connect")}
44
+ `);
45
+ return 1;
46
+ }
47
+ const project = flagStr(p, "project");
48
+ const newName = flagStr(p, "new");
49
+ const prefix = flagStr(p, "path") ?? repo.path;
50
+ if (project || newName) {
51
+ const body = { remote: repo.remote, ...prefix ? { path_prefix: prefix } : {}, ...project ? { project_id: project } : { new_project_name: newName } };
52
+ const res = await apiRequest(ctx, s.host, "/api/v1/agent/link-repo", { token: s.token, body, headers: { "x-hf-repo": repo.remote } });
53
+ if (res.status >= 300) {
54
+ ctx.err(`hivefloor: ${errorOf(res).message}
55
+ `);
56
+ return 1;
57
+ }
58
+ const status = res.json?.status;
59
+ ctx.out(`Linked ${repo.remote}${typeof status === "string" ? ` (${status})` : ""}.
60
+ `);
61
+ return 0;
62
+ }
63
+ const toolFlag = flagStr(p, "tool");
64
+ const tool = toolFlag && isTool(toolFlag) ? toolFlag : isTool(s.tool) ? s.tool : "other";
65
+ const request = {
66
+ client: { tool, machine: machineName(), os: process.platform, cli_version: VERSION },
67
+ repo: { remote: repo.remote, ...repo.branch ? { branch: repo.branch } : {}, ...repo.path ? { path: repo.path } : {} }
68
+ };
69
+ const result = await deviceLogin(ctx, { host: s.host, request, openBrowser: !p.flags["no-open"] });
70
+ await saveToken(s.host, tool, result.token, { agent: result.agent, project: result.project });
71
+ ctx.out(`
72
+ Linked ${repo.remote} to project ${result.project.name} (${result.project.key}) for ${result.agent.name}.
73
+ `);
74
+ return 0;
75
+ }
76
+ export {
77
+ link
78
+ };
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveHost
4
+ } from "./chunk-4OFN5KHY.js";
5
+ import {
6
+ installCliCopy,
7
+ installMachineHooks
8
+ } from "./chunk-DIW4VIU7.js";
9
+ import "./chunk-LHF4KCEA.js";
10
+ import {
11
+ claudeDirsOfScan,
12
+ cliVersionOs,
13
+ scanProcesses
14
+ } from "./chunk-6UZBZ7LR.js";
15
+ import {
16
+ codexConfigDir
17
+ } from "./chunk-M3PLXINO.js";
18
+ import {
19
+ readMachine,
20
+ secureWindowsMachine,
21
+ writeMachine
22
+ } from "./chunk-3IMV5EHP.js";
23
+ import {
24
+ deviceLogin
25
+ } from "./chunk-UPRHMVYJ.js";
26
+ import "./chunk-OQ6QVFSH.js";
27
+ import {
28
+ findBin
29
+ } from "./chunk-2IMJECYD.js";
30
+ import {
31
+ flagStr
32
+ } from "./chunk-TKMIV7BG.js";
33
+ import {
34
+ installService,
35
+ windowsUser
36
+ } from "./chunk-4A5WOW2D.js";
37
+ import {
38
+ VERSION
39
+ } from "./chunk-IEYWMDXA.js";
40
+ import "./chunk-PY74LT2W.js";
41
+ import {
42
+ cliEntry
43
+ } from "./chunk-WNDUGQDX.js";
44
+ import {
45
+ cliNpxCommand,
46
+ machineName,
47
+ machineTokenSuccessSchema,
48
+ normalizeHost,
49
+ runCmd
50
+ } from "./chunk-RE2EQZWL.js";
51
+ import "./chunk-K3NQKI34.js";
52
+
53
+ // src/machine-connect.ts
54
+ import fs from "fs";
55
+ import path from "path";
56
+ import { fileURLToPath } from "url";
57
+ function bundleDir(ctx) {
58
+ const fromEnv = ctx.env.HIVEFLOOR_CLI_SRC;
59
+ if (fromEnv) return fromEnv;
60
+ const entry = process.argv[1] ? fs.realpathSync(process.argv[1]) : fileURLToPath(import.meta.url);
61
+ return path.dirname(entry);
62
+ }
63
+ async function connectMachine(ctx, p) {
64
+ const host = resolveHost(p, ctx);
65
+ const name = (flagStr(p, "name") ?? machineName()).slice(0, 100);
66
+ const platform = ctx.platform ?? process.platform;
67
+ const run = ctx.run ?? runCmd;
68
+ const existing = readMachine();
69
+ if (existing && !existing.revoked && normalizeHost(existing.host) === host && !p.flags.yes && ctx.stdoutIsTTY) {
70
+ const a = (await ctx.ask(`This computer is already connected as "${existing.machine.name}". Connect it again (the old token stops working)? [y/N] `)).trim();
71
+ if (!/^y(es)?$/i.test(a)) return 0;
72
+ }
73
+ ctx.out(`Connecting this computer (${name}) to ${host}
74
+ `);
75
+ const request = { client: { kind: "machine", tool: "other", machine: name, os: cliVersionOs(), cli_version: VERSION } };
76
+ const canOpen = !p.flags["no-open"] && (platform !== "linux" || Boolean(ctx.env.DISPLAY || ctx.env.WAYLAND_DISPLAY));
77
+ const result = await deviceLogin(ctx, { host, request, openBrowser: canOpen, parse: (j) => machineTokenSuccessSchema.parse(j) });
78
+ const lines = [];
79
+ const warnings = [];
80
+ const state = {
81
+ version: 1,
82
+ host,
83
+ machine: result.machine,
84
+ token: result.token,
85
+ connected_at: new Date(ctx.now()).toISOString(),
86
+ installed: { claude_dirs: [] }
87
+ };
88
+ writeMachine(state);
89
+ if (platform === "win32") await secureWindowsMachine(run, windowsUser(ctx.env));
90
+ const node = process.execPath;
91
+ try {
92
+ installCliCopy(bundleDir(ctx));
93
+ } catch (e) {
94
+ warnings.push(`${e.message}: hooks and the service were not installed. Run connect again from the published package.`);
95
+ writeMachine(state);
96
+ return finish(ctx, result.machine.name, result.projects.length, lines, warnings, 0, platform);
97
+ }
98
+ let processDirs = [];
99
+ try {
100
+ processDirs = claudeDirsOfScan(await scanProcesses({ platform, run, env: ctx.env }));
101
+ } catch {
102
+ }
103
+ const hooks = installMachineHooks(node, { processDirs, codex: Boolean(findBin("codex", ctx.env)) || fs.existsSync(codexConfigDir(ctx.env)), env: ctx.env });
104
+ lines.push(...hooks.lines);
105
+ warnings.push(...hooks.warnings);
106
+ state.installed.claude_dirs = hooks.claude_dirs;
107
+ if (hooks.codex_dir) {
108
+ state.installed.codex_dir = hooks.codex_dir;
109
+ warnings.push("Codex asks you to trust new hooks: run codex, open /hooks and approve the Hivefloor hooks once.");
110
+ }
111
+ writeMachine(state);
112
+ if (p.flags["no-service"]) {
113
+ lines.push(`Service: skipped (--no-service). Start it by hand with: ${cliNpxCommand("service run")}`);
114
+ } else {
115
+ const svc = await installService(platform, run, { node, script: cliEntry() }, typeof process.getuid === "function" ? process.getuid() : void 0, ctx.env);
116
+ lines.push(...svc.lines);
117
+ warnings.push(...svc.warnings);
118
+ if (svc.installed) state.installed.service = svc.installed;
119
+ writeMachine(state);
120
+ }
121
+ return finish(ctx, result.machine.name, result.projects.length, lines, warnings, 0, platform);
122
+ }
123
+ function finish(ctx, name, projects, lines, warnings, code, platform) {
124
+ ctx.out(`
125
+ Connected as ${name}. It can reach ${projects} project${projects === 1 ? "" : "s"}.
126
+ `);
127
+ ctx.out(platform === "win32" ? "Machine token stored in %APPDATA%\\hivefloor\\machine.json (user-only ACL).\n" : "Machine token stored in ~/.config/hivefloor/machine.json (mode 0600).\n");
128
+ for (const l of lines) ctx.out(` ${l}
129
+ `);
130
+ for (const w of warnings) ctx.out(` note: ${w}
131
+ `);
132
+ ctx.out("Your running agents appear on the Floor within a minute. See this machine at /machines.\n");
133
+ ctx.out(`Check it with: ${cliNpxCommand("status")} Undo everything with: ${cliNpxCommand("disconnect")}
134
+ `);
135
+ return code;
136
+ }
137
+ export {
138
+ connectMachine
139
+ };