@hivefloor/cli 0.1.3 → 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.
Files changed (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -1
  3. package/dist/{chunk-HT5KXWXM.js → chunk-3N5JYP5O.js} +7 -3
  4. package/dist/{chunk-TH4ANHSG.js → chunk-4DRHR7LR.js} +365 -129
  5. package/dist/{chunk-DQV6FAJU.js → chunk-6MDJB3BV.js} +97 -26
  6. package/dist/{chunk-NLZXB26Y.js → chunk-6XVIEX75.js} +293 -71
  7. package/dist/{chunk-KHHSRIMU.js → chunk-C627OKTW.js} +2 -2
  8. package/dist/chunk-H6NYMF2D.js +50 -0
  9. package/dist/{chunk-T7TG6WA4.js → chunk-IJRHCDIG.js} +1 -1
  10. package/dist/{chunk-3YOTXIFP.js → chunk-IWIDKNWC.js} +109 -29
  11. package/dist/{chunk-E4V47VEV.js → chunk-J36OIJBM.js} +41 -23
  12. package/dist/{chunk-O6VSA2JT.js → chunk-MZGGJ6ZS.js} +87 -13
  13. package/dist/{chunk-YRNMECUF.js → chunk-RYGL6XU4.js} +5 -3
  14. package/dist/chunk-SOXRITAW.js +139 -0
  15. package/dist/{chunk-VNCQFFQO.js → chunk-TPMFQUAW.js} +102 -1
  16. package/dist/chunk-U6RRUAC2.js +229 -0
  17. package/dist/chunk-VX7SCIF2.js +65 -0
  18. package/dist/{chunk-O2TC3WSS.js → chunk-X6B6R32A.js} +1 -1
  19. package/dist/cli-JCBZICR3.js +243 -0
  20. package/dist/connect-2HLVPEOL.js +23 -0
  21. package/dist/{disconnect-FI76IEZJ.js → disconnect-WQMDCUXT.js} +41 -27
  22. package/dist/{hook-N4ZTQEWM.js → hook-AYJNRGIY.js} +25 -9
  23. package/dist/index.js +2 -2
  24. package/dist/{link-5WSZHKBE.js → link-ORT4LID6.js} +29 -16
  25. package/dist/{machine-connect-GQJ4SB3D.js → machine-connect-N52CKQEW.js} +21 -21
  26. package/dist/{mcp-EUBTVJNX.js → mcp-2SF6Y73M.js} +27 -30
  27. package/dist/{run-BEE2ATEM.js → run-ZAZ45MKQ.js} +22 -10
  28. package/dist/{status-SMAVPW4D.js → status-GOCFNO7K.js} +25 -13
  29. package/dist/{token-AUEAWQDH.js → token-EMM4AJAU.js} +15 -6
  30. package/dist/voice-F5MIS3IZ.js +262 -0
  31. package/dist/{where-7NI7UEEF.js → where-XFXRTP7E.js} +16 -7
  32. package/package.json +2 -2
  33. package/dist/chunk-2IMJECYD.js +0 -71
  34. package/dist/chunk-DWHMBUCA.js +0 -30
  35. package/dist/chunk-NKMVO5BP.js +0 -132
  36. package/dist/chunk-PY74LT2W.js +0 -41
  37. package/dist/cli-MAHYTCYV.js +0 -103
  38. package/dist/connect-QA3QIEGL.js +0 -23
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  deviceLogin
4
- } from "./chunk-HT5KXWXM.js";
4
+ } from "./chunk-3N5JYP5O.js";
5
5
  import {
6
6
  RC_LINE,
7
7
  addRcLine,
8
+ emptyReport,
9
+ envTokenSharedWith,
8
10
  hasRcLine,
9
11
  installClaude,
10
12
  installCodex,
@@ -12,46 +14,52 @@ import {
12
14
  installGemini,
13
15
  installGrok,
14
16
  rcFileFor,
17
+ readManifest,
18
+ toolReadsEnvToken,
15
19
  updateManifest
16
- } from "./chunk-NLZXB26Y.js";
20
+ } from "./chunk-6XVIEX75.js";
17
21
  import {
18
22
  detectRepo
19
- } from "./chunk-YRNMECUF.js";
23
+ } from "./chunk-RYGL6XU4.js";
20
24
  import {
21
- REAL_TOOLS,
22
- TOOL_LABEL,
23
- detectTools,
24
- findBin,
25
- isTool,
26
- toolFromEnv
27
- } from "./chunk-2IMJECYD.js";
25
+ defaultHost,
26
+ loadToken,
27
+ saveToken
28
+ } from "./chunk-6MDJB3BV.js";
28
29
  import {
29
30
  checkUnknownFlags,
30
31
  flagStr
31
32
  } from "./chunk-TKMIV7BG.js";
32
- import {
33
- saveToken
34
- } from "./chunk-DQV6FAJU.js";
35
33
  import {
36
34
  VERSION
37
- } from "./chunk-KHHSRIMU.js";
35
+ } from "./chunk-C627OKTW.js";
38
36
  import {
39
37
  readMachine
40
- } from "./chunk-T7TG6WA4.js";
38
+ } from "./chunk-IJRHCDIG.js";
41
39
  import {
42
40
  ApiError
43
- } from "./chunk-PY74LT2W.js";
41
+ } from "./chunk-VX7SCIF2.js";
44
42
  import {
45
- ConfigError
46
- } from "./chunk-VNCQFFQO.js";
43
+ ConfigError,
44
+ REAL_TOOLS,
45
+ TOOL_LABEL,
46
+ detectTools,
47
+ findBin,
48
+ isTool,
49
+ toolFromEnv
50
+ } from "./chunk-TPMFQUAW.js";
47
51
  import {
48
- CLI_BIN_NAME,
49
- CLI_INSTALL_COMMAND,
50
- cliNpxCommand,
51
52
  hostFromEnv,
53
+ hostFromFlag,
52
54
  machineName,
53
- normalizeHost
54
- } from "./chunk-TH4ANHSG.js";
55
+ normalizeHost,
56
+ sameOrigin
57
+ } from "./chunk-SOXRITAW.js";
58
+ import {
59
+ CLI_BIN_NAME,
60
+ CLI_INSTALL_COMMAND,
61
+ cliNpxCommand
62
+ } from "./chunk-4DRHR7LR.js";
55
63
 
56
64
  // src/connect.ts
57
65
  var INSTALLERS = {
@@ -61,9 +69,59 @@ var INSTALLERS = {
61
69
  gemini: installGemini,
62
70
  grok: installGrok
63
71
  };
72
+ async function freezeForeignEnvTools(ctx, host, tool, noCli) {
73
+ const report = emptyReport();
74
+ const manifest = readManifest();
75
+ for (const [name, entry] of Object.entries(manifest.tools)) {
76
+ const real = REAL_TOOLS.find((t) => t === name);
77
+ if (!real || real === tool || !entry || !toolReadsEnvToken(name, entry, true)) continue;
78
+ const owner = entry.host;
79
+ if (typeof owner !== "string" || owner.trim() === "") {
80
+ throw new ApiError(
81
+ 0,
82
+ "manifest",
83
+ `${TOOL_LABEL[real]} reads HIVEFLOOR_TOKEN, but its manifest entry has no host. Reconnect it with ${cliNpxCommand(`connect --tool ${real}`)} so the shell token is not sent to the wrong server.`
84
+ );
85
+ }
86
+ if (sameOrigin(owner, host)) continue;
87
+ const stored = await loadToken(owner, real);
88
+ if (!stored) {
89
+ throw new ApiError(
90
+ 0,
91
+ "token",
92
+ `${TOOL_LABEL[real]} on ${owner} reads HIVEFLOOR_TOKEN, which would now be the token for ${host}, and no stored token is left for it. Run ${cliNpxCommand(`connect --host ${owner} --tool ${real}`)} again.`
93
+ );
94
+ }
95
+ const r = await INSTALLERS[real]({
96
+ ctx,
97
+ mcpUrl: `${normalizeHost(owner)}/mcp`,
98
+ token: stored.token,
99
+ useEnvToken: false,
100
+ requireStoredToken: true,
101
+ noCli,
102
+ mcpOnly: true,
103
+ onMcpRegistered: (mcp) => {
104
+ updateManifest((m) => {
105
+ m.tools[real] = { mcp, host: owner, envToken: false };
106
+ });
107
+ }
108
+ });
109
+ updateManifest((m) => {
110
+ m.tools[real] = { mcp: r.mcp, host: owner, envToken: false };
111
+ });
112
+ report.lines.push(...r.report.lines);
113
+ report.warnings.push(...r.report.warnings);
114
+ report.warnings.push(`${TOOL_LABEL[real]} on ${owner} now stores its own token, so HIVEFLOOR_TOKEN is not sent there.`);
115
+ }
116
+ return report;
117
+ }
64
118
  function resolveHost(p, ctx) {
65
119
  const h = flagStr(p, "host");
66
- return h ? normalizeHost(h) : hostFromEnv(ctx.env);
120
+ return h ? hostFromFlag(ctx, h) : hostFromEnv(ctx.env);
121
+ }
122
+ function shellExportFollowsHost(env, host) {
123
+ if (!env.HIVEFLOOR_URL) return true;
124
+ return sameOrigin(env.HIVEFLOOR_URL, host);
67
125
  }
68
126
  async function chooseTool(ctx, p) {
69
127
  const detected = detectTools(ctx.env).map((d) => d.tool);
@@ -95,7 +153,6 @@ async function connectAgent(ctx, p) {
95
153
  };
96
154
  const canOpen = !p.flags["no-open"] && (process.platform !== "linux" || Boolean(ctx.env.DISPLAY || ctx.env.WAYLAND_DISPLAY));
97
155
  const result = await deviceLogin(ctx, { host, request, openBrowser: canOpen });
98
- const storage = await saveToken(host, tool, result.token, { agent: result.agent, project: result.project });
99
156
  const rc = rcFileFor(ctx.env);
100
157
  let rcInstalled = rc !== void 0 && hasRcLine(rc);
101
158
  let rcAdded;
@@ -118,34 +175,57 @@ Add it? [y/N] `)).trim());
118
175
  const lines = [];
119
176
  const warnings = [];
120
177
  const mcpUrl = `${host}/mcp`;
178
+ const exportFollowsHost = shellExportFollowsHost(ctx.env, host);
179
+ const previousHost = defaultHost();
180
+ const shared = envTokenSharedWith(host, tool, rcInstalled);
181
+ const allowEnv = rcInstalled && exportFollowsHost && (previousHost === void 0 || sameOrigin(previousHost, host)) && shared;
182
+ if (rcInstalled && !shared) {
183
+ try {
184
+ const frozen = await freezeForeignEnvTools(ctx, host, tool, Boolean(p.flags["no-cli"]));
185
+ lines.push(...frozen.lines);
186
+ warnings.push(...frozen.warnings);
187
+ } catch (e) {
188
+ if (e instanceof ConfigError) {
189
+ throw new ApiError(0, "config", `${e.message} Left the other host's tool config in place and did not finish connecting ${host}.`);
190
+ }
191
+ throw e;
192
+ }
193
+ }
121
194
  if (tool === "other") {
122
195
  lines.push(`MCP server: ${mcpUrl} (streamable HTTP), header "Authorization: Bearer <token>" (run: ${cliNpxCommand("token --reveal")})`);
123
196
  lines.push("The agent shows up the first time it calls a tool.");
124
197
  } else {
198
+ let mcpRegistered = false;
125
199
  try {
126
200
  const r = await INSTALLERS[tool]({
127
201
  ctx,
128
202
  mcpUrl,
129
203
  token: result.token,
130
- useEnvToken: rcInstalled,
204
+ useEnvToken: allowEnv,
205
+ requireStoredToken: rcInstalled && !allowEnv,
131
206
  noCli: Boolean(p.flags["no-cli"]),
132
207
  skipHooks: ownsHooks,
133
208
  onMcpRegistered: (mcp) => {
209
+ mcpRegistered = true;
134
210
  updateManifest((m) => {
135
- m.tools[tool] = { mcp, host };
211
+ m.tools[tool] = { mcp, host, envToken: allowEnv };
136
212
  });
137
213
  }
138
214
  });
139
215
  lines.push(...r.report.lines);
140
216
  warnings.push(...r.report.warnings);
141
217
  updateManifest((m) => {
142
- m.tools[tool] = { mcp: r.mcp, host };
218
+ m.tools[tool] = { mcp: r.mcp, host, envToken: allowEnv };
143
219
  });
144
220
  } catch (e) {
145
221
  if (!(e instanceof ConfigError)) throw e;
222
+ if (!mcpRegistered) {
223
+ throw new ApiError(0, "config", `${e.message} Did not finish connecting ${host}; the default host is unchanged.`);
224
+ }
146
225
  warnings.push(e.message);
147
226
  }
148
227
  }
228
+ const storage = await saveToken(host, tool, result.token, { agent: result.agent, project: result.project });
149
229
  if (rcAdded) lines.push(`Shell: added the token export to ${rcAdded}`);
150
230
  if (!findBin(CLI_BIN_NAME, ctx.env) && tool !== "other") warnings.push(`"${CLI_BIN_NAME}" is not on your PATH, so the hooks cannot run it. Install it once: ${CLI_INSTALL_COMMAND}`);
151
231
  ctx.out(`
@@ -172,7 +252,7 @@ async function connect(ctx, p) {
172
252
  return 2;
173
253
  }
174
254
  if (p.flags.agent || p.flags.tool !== void 0) return connectAgent(ctx, p);
175
- return (await import("./machine-connect-GQJ4SB3D.js")).connectMachine(ctx, p);
255
+ return (await import("./machine-connect-N52CKQEW.js")).connectMachine(ctx, p);
176
256
  }
177
257
 
178
258
  export {
@@ -4,27 +4,35 @@ import {
4
4
  isDir,
5
5
  isRecord,
6
6
  readText
7
- } from "./chunk-VNCQFFQO.js";
7
+ } from "./chunk-TPMFQUAW.js";
8
8
  import {
9
9
  PROFILE_KEY_RE
10
- } from "./chunk-TH4ANHSG.js";
10
+ } from "./chunk-4DRHR7LR.js";
11
11
 
12
12
  // src/profiles.ts
13
13
  import fs from "fs";
14
14
  import path from "path";
15
- function profileKeyOf(tool, dir, homeDir = home()) {
16
- const std = tool === "claude" ? path.join(homeDir, ".claude") : path.join(homeDir, ".codex");
17
- if (path.resolve(dir) === path.resolve(std)) return "default";
18
- let base = path.basename(path.resolve(dir)).toLowerCase().replace(/^\.+/, "");
15
+ function pathFor(platform) {
16
+ return platform === "win32" ? path.win32 : path;
17
+ }
18
+ function resolvedKey(dir, platform) {
19
+ const r = pathFor(platform).resolve(dir);
20
+ return platform === "win32" ? r.toLowerCase() : r;
21
+ }
22
+ function profileKeyOf(tool, dir, homeDir = home(), platform = process.platform) {
23
+ const p = pathFor(platform);
24
+ const std = tool === "claude" ? p.join(homeDir, ".claude") : p.join(homeDir, ".codex");
25
+ if (resolvedKey(dir, platform) === resolvedKey(std, platform)) return "default";
26
+ let base = p.basename(p.resolve(dir)).toLowerCase().replace(/^\.+/, "");
19
27
  base = base.replace(new RegExp(`^${tool}[-_.]?`), "").replace(new RegExp(`[-_.]?${tool}$`), "");
20
28
  base = base.replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").slice(0, 40);
21
29
  return PROFILE_KEY_RE.test(base) ? base : "profile";
22
30
  }
23
- function profileKeys(tool, dirs, homeDir = home()) {
31
+ function profileKeys(tool, dirs, homeDir = home(), platform = process.platform) {
24
32
  const out = /* @__PURE__ */ new Map();
25
33
  const used = /* @__PURE__ */ new Set();
26
34
  for (const d of dirs) {
27
- out.set(d, uniqueProfileKey(profileKeyOf(tool, d, homeDir), used));
35
+ out.set(d, uniqueProfileKey(profileKeyOf(tool, d, homeDir, platform), used));
28
36
  }
29
37
  return out;
30
38
  }
@@ -38,17 +46,27 @@ function uniqueProfileKey(base, used) {
38
46
  used.add(k);
39
47
  return k;
40
48
  }
41
- var RC_FILES = [".zshrc", ".zprofile", ".zshenv", ".bashrc", ".bash_profile", ".profile", ".config/fish/config.fish"];
49
+ var RC_FILES = [
50
+ ".zshrc",
51
+ ".zprofile",
52
+ ".zshenv",
53
+ ".bashrc",
54
+ ".bash_profile",
55
+ ".profile",
56
+ ".config/fish/config.fish",
57
+ ["Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1"],
58
+ ["Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1"]
59
+ ];
42
60
  function claudeDirsFromRc(homeDir = home()) {
43
61
  const found = [];
44
62
  for (const rel of RC_FILES) {
45
- const text = readText0(path.join(homeDir, rel));
63
+ const text = readText0(Array.isArray(rel) ? path.join(homeDir, ...rel) : path.join(homeDir, rel));
46
64
  if (!text) continue;
47
65
  for (const line of text.split("\n")) {
48
66
  if (/^\s*#/.test(line)) continue;
49
- for (const m of line.matchAll(/CLAUDE_CONFIG_DIR(?:=|\s+)(?:"([^"\n]+)"|'([^'\n]+)'|([^\s"'`;&|)]+))/g)) {
67
+ for (const m of line.matchAll(/CLAUDE_CONFIG_DIR\s*(?:=\s*|\s+)(?:"([^"\n]+)"|'([^'\n]+)'|([^\s"'`;&|)]+))/g)) {
50
68
  const raw = (m[1] ?? m[2] ?? m[3] ?? "").trim();
51
- const dir = expand(raw, homeDir);
69
+ const dir = expandHome(raw, homeDir);
52
70
  if (dir && isDir(dir)) found.push(dir);
53
71
  }
54
72
  }
@@ -62,11 +80,10 @@ function readText0(file) {
62
80
  return void 0;
63
81
  }
64
82
  }
65
- function expand(raw, homeDir) {
66
- let s = raw.replace(/^~(?=\/|$)/, homeDir).replace(/\$\{?HOME\}?/g, homeDir);
67
- if (/[$`]/.test(s) || !path.isAbsolute(s)) return void 0;
68
- s = path.resolve(s);
69
- return s;
83
+ function expandHome(raw, homeDir, p = path) {
84
+ const s = raw.replace(/^~(?=[/\\]|$)/, () => homeDir).replace(/\$\{?HOME\}?/g, () => homeDir);
85
+ if (/[$`]/.test(s) || !p.isAbsolute(s)) return void 0;
86
+ return p.resolve(s);
70
87
  }
71
88
  function claudeSiblingDirs(homeDir = home()) {
72
89
  const out = [];
@@ -87,15 +104,16 @@ function claudeSiblingDirs(homeDir = home()) {
87
104
  }
88
105
  return out;
89
106
  }
90
- function claudeConfigDirs(fromProcesses, homeDir = home()) {
91
- const all = [path.join(homeDir, ".claude"), ...fromProcesses, ...claudeDirsFromRc(homeDir), ...claudeSiblingDirs(homeDir)];
107
+ function claudeConfigDirs(fromProcesses, homeDir = home(), platform = process.platform) {
108
+ const p = pathFor(platform);
109
+ const all = [p.join(homeDir, ".claude"), ...fromProcesses, ...claudeDirsFromRc(homeDir), ...claudeSiblingDirs(homeDir)];
92
110
  const seen = /* @__PURE__ */ new Set();
93
111
  const out = [];
94
112
  for (const d of all) {
95
- const r = path.resolve(d);
96
- if (seen.has(r)) continue;
97
- seen.add(r);
98
- out.push(r);
113
+ const key = resolvedKey(d, platform);
114
+ if (seen.has(key)) continue;
115
+ seen.add(key);
116
+ out.push(p.resolve(d));
99
117
  }
100
118
  return out;
101
119
  }
@@ -5,7 +5,11 @@ import {
5
5
  codexConfigDir,
6
6
  codexLabel,
7
7
  profileKeys
8
- } from "./chunk-E4V47VEV.js";
8
+ } from "./chunk-J36OIJBM.js";
9
+ import {
10
+ agentIsCursor,
11
+ findBin
12
+ } from "./chunk-TPMFQUAW.js";
9
13
 
10
14
  // src/osinfo.ts
11
15
  import os from "os";
@@ -47,7 +51,78 @@ function splitCommandLine(line) {
47
51
  if (started) toks.push(cur);
48
52
  return toks;
49
53
  }
50
- function fromTokens(pid, toks) {
54
+ var KNOWN_EXE = /* @__PURE__ */ new Set([...Object.keys(BY_EXE), ...RUNNERS, "agent"]);
55
+ function exeTool(tok, pid, env) {
56
+ const name = exeName(tok);
57
+ if (name !== "agent") return BY_EXE[name];
58
+ const file = agentFile(tok, pid, env);
59
+ return file && agentIsCursor(file, env) ? "cursor" : void 0;
60
+ }
61
+ function agentFile(tok, pid, env) {
62
+ const bare = !tok.includes("/") && !tok.includes("\\");
63
+ if (bare && process.platform === "linux" && pid > 0) {
64
+ try {
65
+ const link = fs.readlinkSync(`/proc/${pid}/exe`).replace(/ \(deleted\)$/, "");
66
+ if (link) return link;
67
+ } catch {
68
+ }
69
+ }
70
+ if (!bare) return tok.replace(/^"|"$/g, "");
71
+ return findBin("agent", env);
72
+ }
73
+ function psCommandTokens(command) {
74
+ const trimmed = command.trim();
75
+ let quotedExe;
76
+ let body = trimmed;
77
+ if (trimmed.startsWith('"')) {
78
+ const end = trimmed.indexOf('"', 1);
79
+ if (end > 1) {
80
+ quotedExe = trimmed.slice(1, end);
81
+ body = trimmed.slice(end + 1).trim();
82
+ }
83
+ }
84
+ const parts = body === "" ? [] : body.split(/\s+/);
85
+ const exe = quotedExe !== void 0 ? { token: quotedExe, next: 0 } : takeKnown(parts, 0);
86
+ if (!exe) return quotedExe !== void 0 ? [quotedExe, ...parts] : parts;
87
+ let rest = parts.slice(exe.next);
88
+ if (RUNNERS.has(exeName(exe.token)) && rest[0] && !rest[0].startsWith("-")) {
89
+ const script = takeScript(rest);
90
+ rest = [script.token, ...rest.slice(script.next)];
91
+ }
92
+ return [exe.token, ...rest];
93
+ }
94
+ var unwrap = (token) => token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
95
+ function isFile(file) {
96
+ try {
97
+ return fs.statSync(file).isFile();
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ function takeKnown(parts, start) {
103
+ let token = "";
104
+ let best;
105
+ for (let i = start; i < parts.length; i++) {
106
+ const piece = parts[i];
107
+ if (i > start && (piece.startsWith("/") || piece.startsWith("\\") || piece.startsWith("-"))) break;
108
+ token = token ? `${token} ${piece}` : piece;
109
+ const bare = !token.includes("/") && !token.includes("\\");
110
+ const cleaned = unwrap(token);
111
+ if (KNOWN_EXE.has(exeName(cleaned)) || !bare && isFile(cleaned)) best = { token: cleaned, next: i + 1 };
112
+ if (bare) break;
113
+ }
114
+ return best;
115
+ }
116
+ function takeScript(parts) {
117
+ const first = parts[0];
118
+ if (/\.(?:js|mjs|cjs|exe|cmd)$/i.test(first.split(/[\\/]/).pop() ?? "")) return { token: first, next: 1 };
119
+ return takeKnown(parts, 0) ?? { token: first, next: 1 };
120
+ }
121
+ function psEnvValue(text, name) {
122
+ const m = new RegExp(`(?:^|\\s)${name}=(.*?)(?=\\s+[A-Za-z_][A-Za-z0-9_]*=|\\s*$)`).exec(text);
123
+ return m?.[1] || void 0;
124
+ }
125
+ function fromTokens(pid, toks, env) {
51
126
  const first = exeName(toks[0] ?? "");
52
127
  let rest = toks.slice(1);
53
128
  let tool;
@@ -55,7 +130,7 @@ function fromTokens(pid, toks) {
55
130
  tool = BY_EXE[exeName(toks[1])] ?? dirTool(toks[1]);
56
131
  rest = toks.slice(2);
57
132
  } else {
58
- tool = BY_EXE[first];
133
+ tool = exeTool(toks[0] ?? "", pid, env);
59
134
  }
60
135
  if (!tool || pid === process.pid) return void 0;
61
136
  if (HELPER_ARGS.has(rest[0] ?? "")) return void 0;
@@ -64,19 +139,19 @@ function fromTokens(pid, toks) {
64
139
  const model = eq ? eq.slice(8) : mi >= 0 ? rest[mi + 1] : void 0;
65
140
  return { pid, tool, ...model && /^[\w.:\-/[\]]{1,100}$/.test(model) ? { model } : {} };
66
141
  }
67
- function parsePs(text, uid) {
142
+ function parsePs(text, uid, env = process.env) {
68
143
  const out = [];
69
144
  for (const line of text.split("\n")) {
70
145
  const m = /^\s*(\d+)\s+(\d+)\s+\S+\s+(.+)$/.exec(line);
71
146
  if (!m) continue;
72
147
  const pid = Number(m[1]);
73
148
  if (uid !== void 0 && Number(m[2]) !== uid) continue;
74
- const p = fromTokens(pid, m[3].trim().split(/\s+/));
149
+ const p = fromTokens(pid, psCommandTokens(m[3].trim()), env);
75
150
  if (p) out.push(p);
76
151
  }
77
152
  return out;
78
153
  }
79
- function parseWindowsProcesses(text) {
154
+ function parseWindowsProcesses(text, env = process.env) {
80
155
  const raw = text.replace(/^\uFEFF/, "").trim();
81
156
  if (raw === "" || raw === "null") return [];
82
157
  let data;
@@ -91,7 +166,7 @@ function parseWindowsProcesses(text) {
91
166
  const p = item;
92
167
  const pid = typeof p.ProcessId === "number" ? p.ProcessId : typeof p.ProcessId === "string" ? Number(p.ProcessId) : NaN;
93
168
  if (!Number.isInteger(pid) || pid <= 0 || typeof p.CommandLine !== "string" || p.CommandLine.trim() === "") continue;
94
- const found = fromTokens(pid, splitCommandLine(p.CommandLine));
169
+ const found = fromTokens(pid, splitCommandLine(p.CommandLine), env);
95
170
  if (found) out.push(found);
96
171
  }
97
172
  return out;
@@ -107,8 +182,7 @@ function makeEnvReader(platform, run) {
107
182
  }
108
183
  if (platform === "darwin") {
109
184
  const r = await run("ps", ["-E", "-ww", "-p", String(pid), "-o", "command="], { timeoutMs: 5e3 });
110
- const m = new RegExp(`(?:^|\\s)${name}=(\\S+)`).exec(r.stdout);
111
- return m?.[1];
185
+ return psEnvValue(r.stdout, name);
112
186
  }
113
187
  } catch {
114
188
  }
@@ -118,7 +192,7 @@ function makeEnvReader(platform, run) {
118
192
  var WINDOWS_PROCESS_QUERY = [
119
193
  "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
120
194
  "$me = [Security.Principal.WindowsIdentity]::GetCurrent().Name",
121
- "$names = @('claude.exe', 'claude', 'codex.exe', 'codex', 'cursor-agent.exe', 'cursor-agent', 'gemini.exe', 'gemini', 'grok.exe', 'grok', 'node.exe', 'node', 'bun.exe', 'bun', 'deno.exe', 'deno', 'npx.cmd', 'npx')",
195
+ "$names = @('claude.exe', 'claude', 'codex.exe', 'codex', 'cursor-agent.exe', 'cursor-agent', 'agent.exe', 'agent', 'gemini.exe', 'gemini', 'grok.exe', 'grok', 'node.exe', 'node', 'bun.exe', 'bun', 'deno.exe', 'deno', 'npx.cmd', 'npx')",
122
196
  '$rows = @(Get-CimInstance Win32_Process | Where-Object { $names -contains $_.Name.ToLowerInvariant() } | ForEach-Object { $p = $_; $o = Invoke-CimMethod -InputObject $p -MethodName GetOwner; if ($o.ReturnValue -eq 0) { $owner = if ($o.Domain) { "$($o.Domain)\\$($o.User)" } else { $o.User }; if ($owner -ieq $me) { [pscustomobject]@{ ProcessId = $p.ProcessId; CommandLine = $p.CommandLine } } } })',
123
197
  "ConvertTo-Json -InputObject $rows -Compress"
124
198
  ].join("; ");
@@ -126,14 +200,14 @@ async function scanProcesses(d) {
126
200
  if (d.platform === "win32") {
127
201
  const r2 = await d.run("powershell", ["-NoProfile", "-NonInteractive", "-Command", WINDOWS_PROCESS_QUERY], { timeoutMs: 1e4 });
128
202
  if (r2.code !== 0) return { procs: [] };
129
- return { procs: parseWindowsProcesses(r2.stdout) };
203
+ return { procs: parseWindowsProcesses(r2.stdout, d.env) };
130
204
  }
131
- const r = await d.run("ps", ["-axo", "pid=,uid=,etime=,command="], { timeoutMs: 1e4 });
205
+ const r = await d.run("ps", ["-ww", "-axo", "pid=,uid=,etime=,command="], { timeoutMs: 1e4 });
132
206
  if (r.code !== 0) return { procs: [] };
133
207
  const readEnv = d.readEnv ?? makeEnvReader(d.platform, d.run);
134
208
  const procs = [];
135
209
  const uid = d.uid ?? (typeof process.getuid === "function" ? process.getuid() : void 0);
136
- for (const p of parsePs(r.stdout, uid)) {
210
+ for (const p of parsePs(r.stdout, uid, d.env)) {
137
211
  const dir = p.tool === "claude" ? await readEnv(p.pid, "CLAUDE_CONFIG_DIR") : p.tool === "codex" ? await readEnv(p.pid, "CODEX_HOME") : void 0;
138
212
  procs.push({ ...p, ...dir ? { dir } : {} });
139
213
  }
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- formatRemote,
4
- normalizeRemote,
5
3
  runCmd
6
- } from "./chunk-TH4ANHSG.js";
4
+ } from "./chunk-SOXRITAW.js";
5
+ import {
6
+ formatRemote,
7
+ normalizeRemote
8
+ } from "./chunk-4DRHR7LR.js";
7
9
 
8
10
  // src/git.ts
9
11
  function remoteOf(url) {
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/ctx.ts
4
+ import { execFile } from "child_process";
5
+ import os from "os";
6
+ import readline from "readline";
7
+ import { spawn } from "child_process";
8
+ var DEFAULT_HOST = "https://hivefloor.worldofz.info";
9
+ function defaultCtx() {
10
+ const ctx = {
11
+ out: (s) => void process.stdout.write(s),
12
+ err: (s) => void process.stderr.write(s),
13
+ env: process.env,
14
+ cwd: process.cwd(),
15
+ fetch: (...args) => fetch(...args),
16
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
17
+ now: () => Date.now(),
18
+ stdoutIsTTY: Boolean(process.stdout.isTTY),
19
+ ask: askOnTerminal,
20
+ open: async (url) => openBrowser(url, ctx.platform ?? process.platform)
21
+ };
22
+ return ctx;
23
+ }
24
+ async function askOnTerminal(question) {
25
+ if (!process.stdin.isTTY) return "";
26
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
27
+ try {
28
+ return await new Promise((resolve) => rl.question(question, resolve));
29
+ } finally {
30
+ rl.close();
31
+ }
32
+ }
33
+ function webUrl(url) {
34
+ let u;
35
+ try {
36
+ u = new URL(url);
37
+ } catch {
38
+ throw new Error("not a URL");
39
+ }
40
+ if (u.protocol !== "https:" && u.protocol !== "http:") throw new Error(`refusing to open a ${u.protocol} URL`);
41
+ return u;
42
+ }
43
+ function browserCommand(url, platform = process.platform) {
44
+ const u = webUrl(url);
45
+ if (platform === "win32") return { cmd: "rundll32.exe", args: ["url.dll,FileProtocolHandler", u.toString()] };
46
+ return { cmd: platform === "darwin" ? "open" : "xdg-open", args: [u.toString()] };
47
+ }
48
+ async function openBrowser(url, platform = process.platform) {
49
+ let target;
50
+ try {
51
+ target = browserCommand(url, platform);
52
+ } catch {
53
+ return;
54
+ }
55
+ await new Promise((resolve) => {
56
+ try {
57
+ const child = spawn(target.cmd, target.args, { stdio: "ignore", detached: true });
58
+ child.on("error", () => resolve());
59
+ child.unref();
60
+ setTimeout(resolve, 200);
61
+ } catch {
62
+ resolve();
63
+ }
64
+ });
65
+ }
66
+ function hostFromEnv(env) {
67
+ return normalizeHost(env.HIVEFLOOR_URL || DEFAULT_HOST);
68
+ }
69
+ function normalizeHost(input) {
70
+ const shown = redactUserinfo(input);
71
+ let h = input.trim();
72
+ if (!/^https?:\/\//i.test(h)) h = `https://${h}`;
73
+ let u;
74
+ try {
75
+ u = new URL(h);
76
+ } catch {
77
+ throw new Error(`"${shown}" is not a valid host. Use a URL such as https://hivefloor.example.com or http://localhost:3000.`);
78
+ }
79
+ 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.`);
80
+ const bare = u.hostname.replace(/\.+$/, "");
81
+ if (bare !== "" && bare !== u.hostname) u.hostname = bare;
82
+ return u.origin;
83
+ }
84
+ function redactUserinfo(raw) {
85
+ return raw.trim().replace(/^([a-z][a-z0-9+.-]*:\/\/)?[^/?#]*@/i, "$1");
86
+ }
87
+ function hostFromFlag(ctx, raw) {
88
+ const host = normalizeHost(raw);
89
+ if (host !== raw.trim()) ctx.err(`hivefloor: using host ${host} (from --host ${redactUserinfo(raw)})
90
+ `);
91
+ return host;
92
+ }
93
+ function isLoopbackHost(hostname) {
94
+ const h = hostname.toLowerCase();
95
+ return h === "localhost" || h === "127.0.0.1" || h === "[::1]" || h === "::1";
96
+ }
97
+ function isSecureUrl(u) {
98
+ if (u.protocol === "https:") return true;
99
+ return u.protocol === "http:" && isLoopbackHost(u.hostname);
100
+ }
101
+ function sameOrigin(a, b) {
102
+ try {
103
+ return normalizeHost(a) === normalizeHost(b);
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+ function assertTokenTransport(target) {
109
+ let u;
110
+ try {
111
+ u = new URL(target);
112
+ } catch {
113
+ throw new Error("not a URL");
114
+ }
115
+ if (!isSecureUrl(u)) throw new Error(`https is required (http is only allowed on localhost): ${u.origin}`);
116
+ return u;
117
+ }
118
+ var machineName = () => os.hostname().split(".")[0] || "machine";
119
+ function runCmd(bin, args, opts = {}) {
120
+ return new Promise((resolve) => {
121
+ execFile(bin, args, { cwd: opts.cwd, env: opts.env, timeout: opts.timeoutMs ?? 2e4, maxBuffer: 1 << 20 }, (error, stdout, stderr) => {
122
+ if (!error) return resolve({ code: 0, stdout: String(stdout), stderr: String(stderr) });
123
+ const code = typeof error.code === "number" ? error.code : 127;
124
+ resolve({ code, stdout: String(stdout), stderr: String(stderr) });
125
+ });
126
+ });
127
+ }
128
+
129
+ export {
130
+ DEFAULT_HOST,
131
+ defaultCtx,
132
+ hostFromEnv,
133
+ normalizeHost,
134
+ hostFromFlag,
135
+ sameOrigin,
136
+ assertTokenTransport,
137
+ machineName,
138
+ runCmd
139
+ };