@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.
- package/LICENSE +1 -1
- package/README.md +2 -1
- package/dist/{chunk-HT5KXWXM.js → chunk-3N5JYP5O.js} +7 -3
- package/dist/{chunk-TH4ANHSG.js → chunk-4DRHR7LR.js} +365 -129
- package/dist/{chunk-DQV6FAJU.js → chunk-6MDJB3BV.js} +97 -26
- package/dist/{chunk-NLZXB26Y.js → chunk-6XVIEX75.js} +293 -71
- package/dist/{chunk-KHHSRIMU.js → chunk-C627OKTW.js} +2 -2
- package/dist/chunk-H6NYMF2D.js +50 -0
- package/dist/{chunk-T7TG6WA4.js → chunk-IJRHCDIG.js} +1 -1
- package/dist/{chunk-3YOTXIFP.js → chunk-IWIDKNWC.js} +109 -29
- package/dist/{chunk-E4V47VEV.js → chunk-J36OIJBM.js} +41 -23
- package/dist/{chunk-O6VSA2JT.js → chunk-MZGGJ6ZS.js} +87 -13
- package/dist/{chunk-YRNMECUF.js → chunk-RYGL6XU4.js} +5 -3
- package/dist/chunk-SOXRITAW.js +139 -0
- package/dist/{chunk-VNCQFFQO.js → chunk-TPMFQUAW.js} +102 -1
- package/dist/chunk-U6RRUAC2.js +229 -0
- package/dist/chunk-VX7SCIF2.js +65 -0
- package/dist/{chunk-O2TC3WSS.js → chunk-X6B6R32A.js} +1 -1
- package/dist/cli-JCBZICR3.js +243 -0
- package/dist/connect-2HLVPEOL.js +23 -0
- package/dist/{disconnect-FI76IEZJ.js → disconnect-WQMDCUXT.js} +41 -27
- package/dist/{hook-N4ZTQEWM.js → hook-AYJNRGIY.js} +25 -9
- package/dist/index.js +2 -2
- package/dist/{link-5WSZHKBE.js → link-ORT4LID6.js} +29 -16
- package/dist/{machine-connect-GQJ4SB3D.js → machine-connect-N52CKQEW.js} +21 -21
- package/dist/{mcp-EUBTVJNX.js → mcp-2SF6Y73M.js} +27 -30
- package/dist/{run-BEE2ATEM.js → run-ZAZ45MKQ.js} +22 -10
- package/dist/{status-SMAVPW4D.js → status-GOCFNO7K.js} +25 -13
- package/dist/{token-AUEAWQDH.js → token-EMM4AJAU.js} +15 -6
- package/dist/voice-F5MIS3IZ.js +262 -0
- package/dist/{where-7NI7UEEF.js → where-XFXRTP7E.js} +16 -7
- package/package.json +2 -2
- package/dist/chunk-2IMJECYD.js +0 -71
- package/dist/chunk-DWHMBUCA.js +0 -30
- package/dist/chunk-NKMVO5BP.js +0 -132
- package/dist/chunk-PY74LT2W.js +0 -41
- package/dist/cli-MAHYTCYV.js +0 -103
- 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-
|
|
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-
|
|
20
|
+
} from "./chunk-6XVIEX75.js";
|
|
17
21
|
import {
|
|
18
22
|
detectRepo
|
|
19
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-RYGL6XU4.js";
|
|
20
24
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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-
|
|
35
|
+
} from "./chunk-C627OKTW.js";
|
|
38
36
|
import {
|
|
39
37
|
readMachine
|
|
40
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-IJRHCDIG.js";
|
|
41
39
|
import {
|
|
42
40
|
ApiError
|
|
43
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-VX7SCIF2.js";
|
|
44
42
|
import {
|
|
45
|
-
ConfigError
|
|
46
|
-
|
|
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
|
-
|
|
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 ?
|
|
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:
|
|
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-
|
|
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-
|
|
7
|
+
} from "./chunk-TPMFQUAW.js";
|
|
8
8
|
import {
|
|
9
9
|
PROFILE_KEY_RE
|
|
10
|
-
} from "./chunk-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 = [
|
|
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(
|
|
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 =
|
|
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
|
|
66
|
-
|
|
67
|
-
if (/[$`]/.test(s) || !
|
|
68
|
-
|
|
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
|
|
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
|
|
96
|
-
if (seen.has(
|
|
97
|
-
seen.add(
|
|
98
|
-
out.push(
|
|
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-
|
|
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
|
-
|
|
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 =
|
|
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()
|
|
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
|
-
|
|
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-
|
|
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
|
+
};
|