@hivefloor/cli 0.0.0-stage → 0.1.1

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,139 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveHost
4
+ } from "./chunk-76HDATOB.js";
5
+ import {
6
+ installCliCopy,
7
+ installMachineHooks
8
+ } from "./chunk-WHHV2NZA.js";
9
+ import "./chunk-YCZ7XJJF.js";
10
+ import {
11
+ claudeDirsOfScan,
12
+ cliVersionOs,
13
+ scanProcesses
14
+ } from "./chunk-U7PMOZGI.js";
15
+ import {
16
+ codexConfigDir
17
+ } from "./chunk-NCRQGIJY.js";
18
+ import {
19
+ readMachine,
20
+ secureWindowsMachine,
21
+ writeMachine
22
+ } from "./chunk-3IMV5EHP.js";
23
+ import {
24
+ deviceLogin
25
+ } from "./chunk-WO7NALQO.js";
26
+ import "./chunk-CWEEIRW2.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-Q2TS3GT3.js";
37
+ import {
38
+ VERSION
39
+ } from "./chunk-WLHRUZTR.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-ZYRPQPGL.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
+ };
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveSession
4
+ } from "./chunk-MMWX62ZU.js";
5
+ import {
6
+ detectRepo
7
+ } from "./chunk-CWEEIRW2.js";
8
+ import {
9
+ toolFromEnv
10
+ } from "./chunk-2IMJECYD.js";
11
+ import {
12
+ flagStr
13
+ } from "./chunk-TKMIV7BG.js";
14
+ import {
15
+ loadToken
16
+ } from "./chunk-Q2TS3GT3.js";
17
+ import "./chunk-WNDUGQDX.js";
18
+ import {
19
+ cliNpxCommand,
20
+ isSecureUrl,
21
+ normalizeHost
22
+ } from "./chunk-ZYRPQPGL.js";
23
+ import "./chunk-K3NQKI34.js";
24
+
25
+ // src/mcp.ts
26
+ import os from "os";
27
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
28
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
29
+ async function contextHeaders(ctx, token, toolName) {
30
+ const h = { Authorization: `Bearer ${token}`, "X-HF-Tool": ctx.env.HIVEFLOOR_TOOL || toolFromEnv(ctx.env) || toolName, "X-HF-Machine": os.hostname().split(".")[0] ?? "machine" };
31
+ const session = ctx.env.HIVEFLOOR_SESSION || ctx.env.CLAUDE_SESSION_ID || ctx.env.GROK_SESSION_ID || ctx.env.CODEX_SESSION_ID;
32
+ if (session) h["X-HF-Session"] = session;
33
+ const model = ctx.env.HIVEFLOOR_MODEL || ctx.env.ANTHROPIC_MODEL;
34
+ if (model) h["X-HF-Model"] = model;
35
+ const repo = await detectRepo(ctx.cwd);
36
+ if (repo) {
37
+ h["X-HF-Repo"] = repo.remote;
38
+ if (repo.branch) h["X-HF-Branch"] = repo.branch;
39
+ if (repo.path) h["X-HF-Path"] = repo.path;
40
+ }
41
+ return h;
42
+ }
43
+ var isRequest = (m) => "method" in m && "id" in m;
44
+ function mcpTargetUrl(flagUrl, host) {
45
+ if (!flagUrl) return new URL(`${host}/mcp`);
46
+ let u;
47
+ try {
48
+ u = new URL(flagUrl);
49
+ } catch {
50
+ throw new Error("--url is not a URL");
51
+ }
52
+ if (!isSecureUrl(u)) throw new Error(`--url must be https (http is only allowed on localhost): ${u.origin}`);
53
+ return u;
54
+ }
55
+ async function runMcpProxy(ctx, p, io = { stdin: process.stdin, stdout: process.stdout }) {
56
+ const s = await resolveSession(ctx, p);
57
+ if (!s) {
58
+ ctx.err(`hivefloor: not connected. Run: ${cliNpxCommand("connect")}
59
+ `);
60
+ return 1;
61
+ }
62
+ let url;
63
+ try {
64
+ url = mcpTargetUrl(flagStr(p, "url"), s.host);
65
+ } catch (e) {
66
+ ctx.err(`hivefloor mcp: ${e.message}
67
+ `);
68
+ return 1;
69
+ }
70
+ let token = s.token;
71
+ let tool = s.tool;
72
+ if (url.origin !== new URL(s.host).origin) {
73
+ const other = await loadToken(normalizeHost(url.origin));
74
+ if (!other) {
75
+ ctx.err(`hivefloor mcp: refusing to send the token of ${s.host} to ${url.origin}; connect to that host first
76
+ `);
77
+ return 1;
78
+ }
79
+ token = other.token;
80
+ tool = other.tool;
81
+ }
82
+ const headers = await contextHeaders(ctx, token, tool);
83
+ const upstream = new StreamableHTTPClientTransport(url, { requestInit: { headers } });
84
+ const downstream = new StdioServerTransport(io.stdin, io.stdout);
85
+ return await new Promise((resolve) => {
86
+ let closing = false;
87
+ const finish = (code) => {
88
+ if (closing) return;
89
+ closing = true;
90
+ void Promise.allSettled([upstream.close(), downstream.close()]).then(() => resolve(code));
91
+ };
92
+ downstream.onmessage = (msg) => {
93
+ upstream.send(msg).catch((e) => {
94
+ if (isRequest(msg)) void downstream.send({ jsonrpc: "2.0", id: msg.id, error: { code: -32e3, message: `Hivefloor is not reachable: ${e.message}` } });
95
+ });
96
+ };
97
+ upstream.onmessage = (msg) => {
98
+ const result = msg.result;
99
+ if (result && typeof result.protocolVersion === "string") upstream.setProtocolVersion?.(result.protocolVersion);
100
+ void downstream.send(msg);
101
+ };
102
+ upstream.onerror = (e) => ctx.err(`hivefloor mcp: ${e.message}
103
+ `);
104
+ downstream.onerror = (e) => ctx.err(`hivefloor mcp: ${e.message}
105
+ `);
106
+ downstream.onclose = () => finish(0);
107
+ io.stdin.once("end", () => finish(0));
108
+ io.stdin.once("close", () => finish(0));
109
+ upstream.onclose = () => finish(0);
110
+ upstream.start().then(() => downstream.start()).catch((e) => {
111
+ ctx.err(`hivefloor mcp: ${e.message}
112
+ `);
113
+ finish(1);
114
+ });
115
+ });
116
+ }
117
+ export {
118
+ contextHeaders,
119
+ mcpTargetUrl,
120
+ runMcpProxy
121
+ };