@hivefloor/cli 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -20
- package/README.md +31 -9
- package/dist/chunk-2ERPDCOT.js +38 -0
- package/dist/chunk-4G362W6G.js +19 -0
- package/dist/chunk-5OJUJCE3.js +817 -0
- package/dist/chunk-5XXYQQG6.js +274 -0
- package/dist/chunk-AGIYVFDQ.js +10984 -0
- package/dist/{chunk-2BSA7LFD.js → chunk-BLLPDPTL.js} +7 -3
- package/dist/{chunk-R5KDI4WI.js → chunk-FPJNGKT5.js} +9 -3
- package/dist/chunk-HW3KRNCO.js +201 -0
- package/dist/{chunk-ZC2DAKB5.js → chunk-HWBUESB4.js} +1500 -316
- package/dist/{chunk-DNG5CRT3.js → chunk-K2IRTZH5.js} +8 -7
- package/dist/chunk-KQMPCV57.js +122 -0
- package/dist/{chunk-UJ4BQFWV.js → chunk-L6EFW2SJ.js} +53 -31
- package/dist/{chunk-T7TG6WA4.js → chunk-LX5A25O6.js} +4 -2
- package/dist/{chunk-W7DROKC7.js → chunk-MYJCU5JL.js} +89 -13
- package/dist/chunk-NMUH5NXN.js +52 -0
- package/dist/chunk-NRJONTFT.js +67 -0
- package/dist/chunk-QBBKGORP.js +327 -0
- package/dist/chunk-QGULSOQ2.js +142 -0
- package/dist/{chunk-PDXYOREI.js → chunk-QLMLB7RB.js} +110 -31
- package/dist/{chunk-TKMIV7BG.js → chunk-QPAFN47B.js} +3 -1
- package/dist/chunk-SBOEAOHD.js +48 -0
- package/dist/{chunk-SD65CIIY.js → chunk-TIR4CV3P.js} +117 -33
- package/dist/{chunk-QHYZBHXO.js → chunk-X2KH6AR4.js} +195 -251
- package/dist/chunk-XAXKZ572.js +106 -0
- package/dist/{chunk-VNCQFFQO.js → chunk-XPGL7VYE.js} +114 -1
- package/dist/cli-BFOJWJNG.js +279 -0
- package/dist/connect-ZJ4JXBAJ.js +27 -0
- package/dist/control-6GHTZQJV.js +25 -0
- package/dist/{disconnect-MKRHK4CX.js → disconnect-3T25NTKX.js} +55 -34
- package/dist/hook-TPSHBCWU.js +575 -0
- package/dist/index.js +9 -6
- package/dist/{link-HXBMMSG3.js → link-EAS5F5PC.js} +34 -18
- package/dist/{machine-connect-BYHZRRRB.js → machine-connect-KEI4BRWF.js} +30 -24
- package/dist/mcp-LVIYR3EN.js +1882 -0
- package/dist/orch-KVRL2YA2.js +137 -0
- package/dist/{run-336BSU72.js → run-JL2OZWK3.js} +1933 -29
- package/dist/{status-KBBSAADD.js → status-QXOOJGGN.js} +29 -14
- package/dist/token-BFWWQIDA.js +44 -0
- package/dist/voice-QXHFXRWG.js +264 -0
- package/dist/{where-KR7VWSEC.js → where-CMUORQIO.js} +20 -8
- package/package.json +6 -7
- package/dist/chunk-2IMJECYD.js +0 -71
- package/dist/chunk-7KF7IQJN.js +0 -30
- package/dist/chunk-GT3HFFT4.js +0 -132
- package/dist/chunk-JCLWCMEM.js +0 -8
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/chunk-O2TC3WSS.js +0 -303
- package/dist/chunk-PY74LT2W.js +0 -41
- package/dist/cli-P3DYE5IR.js +0 -103
- package/dist/connect-ACNM5DQG.js +0 -23
- package/dist/hook-KR2HGVAR.js +0 -301
- package/dist/mcp-N2TMD4GR.js +0 -121
- package/dist/token-JDRLEV5D.js +0 -32
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
userBinDirs
|
|
6
|
+
} from "./chunk-XAXKZ572.js";
|
|
2
7
|
import {
|
|
3
8
|
atomicWrite,
|
|
4
9
|
atomicWriteUtf16,
|
|
@@ -8,11 +13,15 @@ import {
|
|
|
8
13
|
isRecord,
|
|
9
14
|
readText,
|
|
10
15
|
removeFile
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XPGL7VYE.js";
|
|
17
|
+
import {
|
|
18
|
+
normalizeHost,
|
|
19
|
+
runCmd,
|
|
20
|
+
sameOrigin
|
|
21
|
+
} from "./chunk-QGULSOQ2.js";
|
|
12
22
|
import {
|
|
13
|
-
cliNpxCommand
|
|
14
|
-
|
|
15
|
-
} from "./chunk-ZC2DAKB5.js";
|
|
23
|
+
cliNpxCommand
|
|
24
|
+
} from "./chunk-HWBUESB4.js";
|
|
16
25
|
|
|
17
26
|
// src/service/units.ts
|
|
18
27
|
import os from "os";
|
|
@@ -23,8 +32,11 @@ var SCHTASKS_TASK = "Hivefloor Machine";
|
|
|
23
32
|
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
24
33
|
var sdEscape = (s) => s.replace(/(["\\$])/g, "\\$1").replace(/%/g, "%%");
|
|
25
34
|
var sdQuote = (s) => /^[\w@+=:,./-]+$/.test(s) ? s : `"${sdEscape(s)}"`;
|
|
35
|
+
function unitPath(plan, system, homeDir = home()) {
|
|
36
|
+
if (/[\r\n\0]/.test(homeDir)) throw new Error("refusing to write a service unit: HOME contains a control character");
|
|
37
|
+
return [path.dirname(plan.node), ...userBinDirs(homeDir), ...system].filter((d, i, a) => a.indexOf(d) === i).join(":");
|
|
38
|
+
}
|
|
26
39
|
function launchdPlist(plan) {
|
|
27
|
-
const bin = path.dirname(plan.node);
|
|
28
40
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
29
41
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
30
42
|
<plist version="1.0">
|
|
@@ -50,7 +62,7 @@ function launchdPlist(plan) {
|
|
|
50
62
|
<key>EnvironmentVariables</key>
|
|
51
63
|
<dict>
|
|
52
64
|
<key>PATH</key>
|
|
53
|
-
<string>${xml(
|
|
65
|
+
<string>${xml(unitPath(plan, ["/usr/bin", "/bin", "/usr/sbin", "/sbin"]))}</string>
|
|
54
66
|
</dict>
|
|
55
67
|
<key>StandardOutPath</key>
|
|
56
68
|
<string>/dev/null</string>
|
|
@@ -62,14 +74,14 @@ function launchdPlist(plan) {
|
|
|
62
74
|
}
|
|
63
75
|
function systemdUnit(plan) {
|
|
64
76
|
return `[Unit]
|
|
65
|
-
Description=
|
|
77
|
+
Description=HiveFloor machine service
|
|
66
78
|
After=network-online.target
|
|
67
79
|
|
|
68
80
|
[Service]
|
|
69
81
|
ExecStart=${sdQuote(plan.node)} ${sdQuote(plan.script)} service run
|
|
70
82
|
Restart=on-failure
|
|
71
83
|
RestartSec=30
|
|
72
|
-
Environment="PATH=${sdEscape(
|
|
84
|
+
Environment="PATH=${sdEscape(unitPath(plan, ["/usr/bin", "/bin"]))}"
|
|
73
85
|
|
|
74
86
|
[Install]
|
|
75
87
|
WantedBy=default.target
|
|
@@ -91,7 +103,7 @@ function windowsTaskXml(plan, user) {
|
|
|
91
103
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
92
104
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
93
105
|
<RegistrationInfo>
|
|
94
|
-
<Description>
|
|
106
|
+
<Description>HiveFloor machine service</Description>
|
|
95
107
|
</RegistrationInfo>
|
|
96
108
|
<Triggers>
|
|
97
109
|
<LogonTrigger>
|
|
@@ -215,8 +227,13 @@ async function uninstallService(svc, run, uid) {
|
|
|
215
227
|
}
|
|
216
228
|
|
|
217
229
|
// src/credentials.ts
|
|
230
|
+
import { createHash } from "crypto";
|
|
218
231
|
import fs from "fs";
|
|
219
|
-
var
|
|
232
|
+
var KEYCHAIN_SERVICE = "hivefloor";
|
|
233
|
+
var TOKEN_FP_HEX = 16;
|
|
234
|
+
function tokenFingerprint(token) {
|
|
235
|
+
return createHash("sha256").update(token, "utf8").digest("hex").slice(0, TOKEN_FP_HEX);
|
|
236
|
+
}
|
|
220
237
|
var realKeyring = async () => {
|
|
221
238
|
if (process.env.HIVEFLOOR_KEYRING === "0") return void 0;
|
|
222
239
|
try {
|
|
@@ -227,6 +244,19 @@ var realKeyring = async () => {
|
|
|
227
244
|
};
|
|
228
245
|
var loadKeyring = realKeyring;
|
|
229
246
|
var account = (host, tool) => `${host}#${tool}`;
|
|
247
|
+
function canonicalHost(host) {
|
|
248
|
+
try {
|
|
249
|
+
return normalizeHost(host);
|
|
250
|
+
} catch {
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function hostKey(creds, host) {
|
|
255
|
+
const want = canonicalHost(host);
|
|
256
|
+
if (want === void 0) return creds.hosts[host] ? host : void 0;
|
|
257
|
+
if (creds.hosts[want]) return want;
|
|
258
|
+
return Object.keys(creds.hosts).find((k) => k === want || sameOrigin(k, want));
|
|
259
|
+
}
|
|
230
260
|
function readCredentials() {
|
|
231
261
|
const text = readText(credentialsFile());
|
|
232
262
|
const empty = { version: 1, hosts: {} };
|
|
@@ -256,33 +286,53 @@ function writeCredentials(c) {
|
|
|
256
286
|
}
|
|
257
287
|
atomicWrite(file, JSON.stringify(c, null, 2) + "\n", 384);
|
|
258
288
|
}
|
|
289
|
+
var windowsAclError = () => new Error("could not restrict the HiveFloor config to your Windows user with icacls; the token was not kept");
|
|
259
290
|
async function secureWindowsCredentials(run, username) {
|
|
260
291
|
if (!username) throw new Error("could not determine the Windows user; the token was not kept");
|
|
261
292
|
const dir = prepareConfigDir();
|
|
262
293
|
const d = await run("icacls", [dir, "/inheritance:r", "/grant:r", `${username}:(OI)(CI)F`]);
|
|
263
|
-
if (d.code !== 0) throw
|
|
294
|
+
if (d.code !== 0) throw windowsAclError();
|
|
295
|
+
}
|
|
296
|
+
async function secureWindowsCredentialsFile(run, username) {
|
|
297
|
+
const f = await run("icacls", [credentialsFile(), "/inheritance:r", "/grant:r", `${username}:F`]);
|
|
298
|
+
if (f.code !== 0) throw windowsAclError();
|
|
264
299
|
}
|
|
265
300
|
async function saveToken(host, tool, token, meta = {}, opts = {}) {
|
|
266
301
|
const creds = readCredentials();
|
|
267
|
-
const
|
|
302
|
+
const key = hostKey(creds, host) ?? canonicalHost(host) ?? host;
|
|
303
|
+
const h = creds.hosts[key] ??= { tools: {} };
|
|
268
304
|
let storage = "file";
|
|
269
305
|
const kr = await loadKeyring();
|
|
270
306
|
if (kr) {
|
|
271
307
|
try {
|
|
272
|
-
const entry = new kr.Entry(
|
|
308
|
+
const entry = new kr.Entry(KEYCHAIN_SERVICE, account(key, tool));
|
|
273
309
|
entry.setPassword(token);
|
|
274
310
|
if (entry.getPassword() === token) storage = "keychain";
|
|
275
311
|
} catch {
|
|
276
312
|
storage = "file";
|
|
277
313
|
}
|
|
278
314
|
}
|
|
279
|
-
|
|
280
|
-
|
|
315
|
+
const winFile = storage === "file" && (opts.platform ?? process.platform) === "win32";
|
|
316
|
+
const run = opts.run ?? runCmd;
|
|
317
|
+
const username = opts.username ?? windowsUser(process.env);
|
|
318
|
+
if (winFile) {
|
|
319
|
+
await secureWindowsCredentials(run, username);
|
|
281
320
|
}
|
|
321
|
+
const previous = winFile ? readText(credentialsFile()) : void 0;
|
|
282
322
|
h.tools[tool] = { ...storage === "keychain" ? { keychain: true } : { token }, ...meta, saved_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
283
323
|
h.last_tool = tool;
|
|
284
|
-
|
|
324
|
+
h.token_fp = tokenFingerprint(token);
|
|
325
|
+
creds.default_host = key;
|
|
285
326
|
writeCredentials(creds);
|
|
327
|
+
if (winFile) {
|
|
328
|
+
try {
|
|
329
|
+
await secureWindowsCredentialsFile(run, username);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
if (previous === void 0) fs.rmSync(credentialsFile(), { force: true });
|
|
332
|
+
else atomicWrite(credentialsFile(), previous, 384);
|
|
333
|
+
throw e;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
286
336
|
return storage;
|
|
287
337
|
}
|
|
288
338
|
function pickTool(h, tool) {
|
|
@@ -291,8 +341,10 @@ function pickTool(h, tool) {
|
|
|
291
341
|
return Object.keys(h.tools)[0];
|
|
292
342
|
}
|
|
293
343
|
async function loadToken(host, tool) {
|
|
294
|
-
const
|
|
295
|
-
|
|
344
|
+
const creds = readCredentials();
|
|
345
|
+
const key = hostKey(creds, host);
|
|
346
|
+
const h = key ? creds.hosts[key] : void 0;
|
|
347
|
+
if (!key || !h) return void 0;
|
|
296
348
|
const t = pickTool(h, tool);
|
|
297
349
|
const c = t ? h.tools[t] : void 0;
|
|
298
350
|
if (!t || !c) return void 0;
|
|
@@ -301,7 +353,7 @@ async function loadToken(host, tool) {
|
|
|
301
353
|
const kr = await loadKeyring();
|
|
302
354
|
if (!kr) return void 0;
|
|
303
355
|
try {
|
|
304
|
-
const v = new kr.Entry(
|
|
356
|
+
const v = new kr.Entry(KEYCHAIN_SERVICE, account(key, t)).getPassword();
|
|
305
357
|
if (v) return { token: v, tool: t };
|
|
306
358
|
} catch {
|
|
307
359
|
}
|
|
@@ -312,34 +364,57 @@ function defaultHost() {
|
|
|
312
364
|
const c = readCredentials();
|
|
313
365
|
return c.default_host && c.hosts[c.default_host] ? c.default_host : Object.keys(c.hosts)[0];
|
|
314
366
|
}
|
|
367
|
+
function deletedKeychainEntry(kr, host, tool) {
|
|
368
|
+
try {
|
|
369
|
+
const e = new kr.Entry(KEYCHAIN_SERVICE, account(host, tool));
|
|
370
|
+
let dropped;
|
|
371
|
+
if (e.deletePassword) dropped = e.deletePassword();
|
|
372
|
+
else if (e.deleteCredential) dropped = e.deleteCredential();
|
|
373
|
+
else return false;
|
|
374
|
+
if (dropped !== false) return true;
|
|
375
|
+
try {
|
|
376
|
+
const still = e.getPassword();
|
|
377
|
+
return still === null || still === void 0;
|
|
378
|
+
} catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
315
385
|
async function deleteCredentials(host, tool) {
|
|
316
386
|
const creds = readCredentials();
|
|
317
387
|
const kr = await loadKeyring();
|
|
318
|
-
let
|
|
388
|
+
let removed = 0;
|
|
389
|
+
const leftover = [];
|
|
319
390
|
for (const [h, hc] of Object.entries(creds.hosts)) {
|
|
320
|
-
if (host && h !== host) continue;
|
|
391
|
+
if (host && h !== host && !sameOrigin(h, host)) continue;
|
|
392
|
+
let removedHere = 0;
|
|
321
393
|
for (const [t, c] of Object.entries(hc.tools)) {
|
|
322
394
|
if (tool && t !== tool) continue;
|
|
323
|
-
if (c.keychain && kr) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (e.deletePassword) e.deletePassword();
|
|
327
|
-
else e.deleteCredential?.();
|
|
328
|
-
} catch {
|
|
329
|
-
}
|
|
395
|
+
if (c.keychain && !(kr && deletedKeychainEntry(kr, h, t))) {
|
|
396
|
+
leftover.push(account(h, t));
|
|
397
|
+
continue;
|
|
330
398
|
}
|
|
331
399
|
delete hc.tools[t];
|
|
332
|
-
|
|
400
|
+
removed += 1;
|
|
401
|
+
removedHere += 1;
|
|
333
402
|
}
|
|
403
|
+
if (removedHere === 0) continue;
|
|
334
404
|
if (hc.last_tool && !hc.tools[hc.last_tool]) hc.last_tool = Object.keys(hc.tools)[0];
|
|
335
405
|
if (Object.keys(hc.tools).length === 0) delete creds.hosts[h];
|
|
406
|
+
else {
|
|
407
|
+
const last = hc.last_tool ? hc.tools[hc.last_tool] : void 0;
|
|
408
|
+
if (last?.token) hc.token_fp = tokenFingerprint(last.token);
|
|
409
|
+
else delete hc.token_fp;
|
|
410
|
+
}
|
|
336
411
|
}
|
|
337
412
|
if (creds.default_host && !creds.hosts[creds.default_host]) creds.default_host = Object.keys(creds.hosts)[0];
|
|
338
|
-
if (
|
|
413
|
+
if (removed > 0) {
|
|
339
414
|
if (Object.keys(creds.hosts).length === 0) fs.rmSync(credentialsFile(), { force: true });
|
|
340
415
|
else writeCredentials(creds);
|
|
341
416
|
}
|
|
342
|
-
return
|
|
417
|
+
return { removed, leftover };
|
|
343
418
|
}
|
|
344
419
|
|
|
345
420
|
export {
|
|
@@ -348,6 +423,10 @@ export {
|
|
|
348
423
|
installService,
|
|
349
424
|
windowsServiceStatus,
|
|
350
425
|
uninstallService,
|
|
426
|
+
KEYCHAIN_SERVICE,
|
|
427
|
+
TOKEN_FP_HEX,
|
|
428
|
+
tokenFingerprint,
|
|
429
|
+
hostKey,
|
|
351
430
|
readCredentials,
|
|
352
431
|
saveToken,
|
|
353
432
|
loadToken,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/args.ts
|
|
4
|
-
var BOOLEAN = /* @__PURE__ */ new Set(["no-open", "yes", "y", "reveal", "help", "h", "json", "no-cli", "agent", "no-service"]);
|
|
6
|
+
var BOOLEAN = /* @__PURE__ */ new Set(["no-open", "yes", "y", "reveal", "help", "h", "json", "no-cli", "agent", "no-service", "permission-prompt", "full", "no-full"]);
|
|
5
7
|
function parseArgs(argv) {
|
|
6
8
|
const positionals = [];
|
|
7
9
|
const flags = {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// src/service/control/private.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
var uid = () => typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
8
|
+
function ensurePrivateDir(dir) {
|
|
9
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10
|
+
if (process.platform !== "win32") fs.chmodSync(dir, 448);
|
|
11
|
+
}
|
|
12
|
+
function privateDirTrouble(dir) {
|
|
13
|
+
let st;
|
|
14
|
+
try {
|
|
15
|
+
st = fs.lstatSync(dir);
|
|
16
|
+
} catch {
|
|
17
|
+
return `${dir} does not exist`;
|
|
18
|
+
}
|
|
19
|
+
if (st.isSymbolicLink() || !st.isDirectory()) return `${dir} is not a plain folder`;
|
|
20
|
+
if (process.platform === "win32") return void 0;
|
|
21
|
+
if (uid() !== void 0 && st.uid !== uid()) return `${dir} is owned by another user`;
|
|
22
|
+
if ((st.mode & 63) !== 0) return `${dir} is open to group or others`;
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
function privateFileTrouble(file) {
|
|
26
|
+
let st;
|
|
27
|
+
try {
|
|
28
|
+
st = fs.lstatSync(file);
|
|
29
|
+
} catch {
|
|
30
|
+
return `${file} does not exist`;
|
|
31
|
+
}
|
|
32
|
+
if (st.isSymbolicLink() || !st.isFile()) return `${file} is not a regular file`;
|
|
33
|
+
if (process.platform === "win32") return void 0;
|
|
34
|
+
if (uid() !== void 0 && st.uid !== uid()) return `${file} is owned by another user`;
|
|
35
|
+
if ((st.mode & 63) !== 0) return `${file} is open to group or others`;
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
function safeName(id) {
|
|
39
|
+
const s = id.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/^\.+/, "_").slice(0, 120);
|
|
40
|
+
return s === "" ? "_" : s;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
ensurePrivateDir,
|
|
45
|
+
privateDirTrouble,
|
|
46
|
+
privateFileTrouble,
|
|
47
|
+
safeName
|
|
48
|
+
};
|
|
@@ -1,57 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __hfCreateRequire } from 'node:module';
|
|
3
|
+
const require = __hfCreateRequire(import.meta.url);
|
|
2
4
|
import {
|
|
3
5
|
deviceLogin
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FPJNGKT5.js";
|
|
5
7
|
import {
|
|
6
8
|
RC_LINE,
|
|
7
9
|
addRcLine,
|
|
8
10
|
hasRcLine,
|
|
9
|
-
installClaude,
|
|
10
|
-
installCodex,
|
|
11
11
|
installCursor,
|
|
12
12
|
installGemini,
|
|
13
13
|
installGrok,
|
|
14
|
-
rcFileFor
|
|
14
|
+
rcFileFor
|
|
15
|
+
} from "./chunk-QBBKGORP.js";
|
|
16
|
+
import {
|
|
17
|
+
emptyReport,
|
|
18
|
+
envTokenSharedWith,
|
|
19
|
+
installClaude,
|
|
20
|
+
installCodex,
|
|
21
|
+
readManifest,
|
|
22
|
+
toolReadsEnvToken,
|
|
15
23
|
updateManifest
|
|
16
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-X2KH6AR4.js";
|
|
17
25
|
import {
|
|
18
26
|
detectRepo
|
|
19
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BLLPDPTL.js";
|
|
20
28
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
isTool,
|
|
26
|
-
toolFromEnv
|
|
27
|
-
} from "./chunk-2IMJECYD.js";
|
|
29
|
+
defaultHost,
|
|
30
|
+
loadToken,
|
|
31
|
+
saveToken
|
|
32
|
+
} from "./chunk-QLMLB7RB.js";
|
|
28
33
|
import {
|
|
29
34
|
checkUnknownFlags,
|
|
30
35
|
flagStr
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import {
|
|
33
|
-
saveToken
|
|
34
|
-
} from "./chunk-PDXYOREI.js";
|
|
36
|
+
} from "./chunk-QPAFN47B.js";
|
|
35
37
|
import {
|
|
36
38
|
VERSION
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-K2IRTZH5.js";
|
|
38
40
|
import {
|
|
39
41
|
readMachine
|
|
40
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-LX5A25O6.js";
|
|
41
43
|
import {
|
|
42
44
|
ApiError
|
|
43
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-NRJONTFT.js";
|
|
46
|
+
import {
|
|
47
|
+
ConfigError,
|
|
48
|
+
REAL_TOOLS,
|
|
49
|
+
TOOL_LABEL,
|
|
50
|
+
detectTools,
|
|
51
|
+
findBin,
|
|
52
|
+
isTool,
|
|
53
|
+
toolFromEnv
|
|
54
|
+
} from "./chunk-XPGL7VYE.js";
|
|
44
55
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
hostFromEnv,
|
|
57
|
+
hostFromFlag,
|
|
58
|
+
machineName,
|
|
59
|
+
normalizeHost,
|
|
60
|
+
sameOrigin
|
|
61
|
+
} from "./chunk-QGULSOQ2.js";
|
|
47
62
|
import {
|
|
48
63
|
CLI_BIN_NAME,
|
|
49
64
|
CLI_INSTALL_COMMAND,
|
|
50
|
-
cliNpxCommand
|
|
51
|
-
|
|
52
|
-
machineName,
|
|
53
|
-
normalizeHost
|
|
54
|
-
} from "./chunk-ZC2DAKB5.js";
|
|
65
|
+
cliNpxCommand
|
|
66
|
+
} from "./chunk-HWBUESB4.js";
|
|
55
67
|
|
|
56
68
|
// src/connect.ts
|
|
57
69
|
var INSTALLERS = {
|
|
@@ -61,9 +73,59 @@ var INSTALLERS = {
|
|
|
61
73
|
gemini: installGemini,
|
|
62
74
|
grok: installGrok
|
|
63
75
|
};
|
|
76
|
+
async function freezeForeignEnvTools(ctx, host, tool, noCli) {
|
|
77
|
+
const report = emptyReport();
|
|
78
|
+
const manifest = readManifest();
|
|
79
|
+
for (const [name, entry] of Object.entries(manifest.tools)) {
|
|
80
|
+
const real = REAL_TOOLS.find((t) => t === name);
|
|
81
|
+
if (!real || real === tool || !entry || !toolReadsEnvToken(name, entry, true)) continue;
|
|
82
|
+
const owner = entry.host;
|
|
83
|
+
if (typeof owner !== "string" || owner.trim() === "") {
|
|
84
|
+
throw new ApiError(
|
|
85
|
+
0,
|
|
86
|
+
"manifest",
|
|
87
|
+
`${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.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (sameOrigin(owner, host)) continue;
|
|
91
|
+
const stored = await loadToken(owner, real);
|
|
92
|
+
if (!stored) {
|
|
93
|
+
throw new ApiError(
|
|
94
|
+
0,
|
|
95
|
+
"token",
|
|
96
|
+
`${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.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const r = await INSTALLERS[real]({
|
|
100
|
+
ctx,
|
|
101
|
+
mcpUrl: `${normalizeHost(owner)}/mcp`,
|
|
102
|
+
token: stored.token,
|
|
103
|
+
useEnvToken: false,
|
|
104
|
+
requireStoredToken: true,
|
|
105
|
+
noCli,
|
|
106
|
+
mcpOnly: true,
|
|
107
|
+
onMcpRegistered: (mcp) => {
|
|
108
|
+
updateManifest((m) => {
|
|
109
|
+
m.tools[real] = { mcp, host: owner, envToken: false };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
updateManifest((m) => {
|
|
114
|
+
m.tools[real] = { mcp: r.mcp, host: owner, envToken: false };
|
|
115
|
+
});
|
|
116
|
+
report.lines.push(...r.report.lines);
|
|
117
|
+
report.warnings.push(...r.report.warnings);
|
|
118
|
+
report.warnings.push(`${TOOL_LABEL[real]} on ${owner} now stores its own token, so HIVEFLOOR_TOKEN is not sent there.`);
|
|
119
|
+
}
|
|
120
|
+
return report;
|
|
121
|
+
}
|
|
64
122
|
function resolveHost(p, ctx) {
|
|
65
123
|
const h = flagStr(p, "host");
|
|
66
|
-
return h ?
|
|
124
|
+
return h ? hostFromFlag(ctx, h) : hostFromEnv(ctx.env);
|
|
125
|
+
}
|
|
126
|
+
function shellExportFollowsHost(env, host) {
|
|
127
|
+
if (!env.HIVEFLOOR_URL) return true;
|
|
128
|
+
return sameOrigin(env.HIVEFLOOR_URL, host);
|
|
67
129
|
}
|
|
68
130
|
async function chooseTool(ctx, p) {
|
|
69
131
|
const detected = detectTools(ctx.env).map((d) => d.tool);
|
|
@@ -95,7 +157,6 @@ async function connectAgent(ctx, p) {
|
|
|
95
157
|
};
|
|
96
158
|
const canOpen = !p.flags["no-open"] && (process.platform !== "linux" || Boolean(ctx.env.DISPLAY || ctx.env.WAYLAND_DISPLAY));
|
|
97
159
|
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
160
|
const rc = rcFileFor(ctx.env);
|
|
100
161
|
let rcInstalled = rc !== void 0 && hasRcLine(rc);
|
|
101
162
|
let rcAdded;
|
|
@@ -118,34 +179,57 @@ Add it? [y/N] `)).trim());
|
|
|
118
179
|
const lines = [];
|
|
119
180
|
const warnings = [];
|
|
120
181
|
const mcpUrl = `${host}/mcp`;
|
|
182
|
+
const exportFollowsHost = shellExportFollowsHost(ctx.env, host);
|
|
183
|
+
const previousHost = defaultHost();
|
|
184
|
+
const shared = envTokenSharedWith(host, tool, rcInstalled);
|
|
185
|
+
const allowEnv = rcInstalled && exportFollowsHost && (previousHost === void 0 || sameOrigin(previousHost, host)) && shared;
|
|
186
|
+
if (rcInstalled && !shared) {
|
|
187
|
+
try {
|
|
188
|
+
const frozen = await freezeForeignEnvTools(ctx, host, tool, Boolean(p.flags["no-cli"]));
|
|
189
|
+
lines.push(...frozen.lines);
|
|
190
|
+
warnings.push(...frozen.warnings);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
if (e instanceof ConfigError) {
|
|
193
|
+
throw new ApiError(0, "config", `${e.message} Left the other host's tool config in place and did not finish connecting ${host}.`);
|
|
194
|
+
}
|
|
195
|
+
throw e;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
121
198
|
if (tool === "other") {
|
|
122
199
|
lines.push(`MCP server: ${mcpUrl} (streamable HTTP), header "Authorization: Bearer <token>" (run: ${cliNpxCommand("token --reveal")})`);
|
|
123
200
|
lines.push("The agent shows up the first time it calls a tool.");
|
|
124
201
|
} else {
|
|
202
|
+
let mcpRegistered = false;
|
|
125
203
|
try {
|
|
126
204
|
const r = await INSTALLERS[tool]({
|
|
127
205
|
ctx,
|
|
128
206
|
mcpUrl,
|
|
129
207
|
token: result.token,
|
|
130
|
-
useEnvToken:
|
|
208
|
+
useEnvToken: allowEnv,
|
|
209
|
+
requireStoredToken: rcInstalled && !allowEnv,
|
|
131
210
|
noCli: Boolean(p.flags["no-cli"]),
|
|
132
211
|
skipHooks: ownsHooks,
|
|
133
212
|
onMcpRegistered: (mcp) => {
|
|
213
|
+
mcpRegistered = true;
|
|
134
214
|
updateManifest((m) => {
|
|
135
|
-
m.tools[tool] = { mcp, host };
|
|
215
|
+
m.tools[tool] = { mcp, host, envToken: allowEnv };
|
|
136
216
|
});
|
|
137
217
|
}
|
|
138
218
|
});
|
|
139
219
|
lines.push(...r.report.lines);
|
|
140
220
|
warnings.push(...r.report.warnings);
|
|
141
221
|
updateManifest((m) => {
|
|
142
|
-
m.tools[tool] = { mcp: r.mcp, host };
|
|
222
|
+
m.tools[tool] = { mcp: r.mcp, host, envToken: allowEnv };
|
|
143
223
|
});
|
|
144
224
|
} catch (e) {
|
|
145
225
|
if (!(e instanceof ConfigError)) throw e;
|
|
226
|
+
if (!mcpRegistered) {
|
|
227
|
+
throw new ApiError(0, "config", `${e.message} Did not finish connecting ${host}; the default host is unchanged.`);
|
|
228
|
+
}
|
|
146
229
|
warnings.push(e.message);
|
|
147
230
|
}
|
|
148
231
|
}
|
|
232
|
+
const storage = await saveToken(host, tool, result.token, { agent: result.agent, project: result.project });
|
|
149
233
|
if (rcAdded) lines.push(`Shell: added the token export to ${rcAdded}`);
|
|
150
234
|
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
235
|
ctx.out(`
|
|
@@ -172,7 +256,7 @@ async function connect(ctx, p) {
|
|
|
172
256
|
return 2;
|
|
173
257
|
}
|
|
174
258
|
if (p.flags.agent || p.flags.tool !== void 0) return connectAgent(ctx, p);
|
|
175
|
-
return (await import("./machine-connect-
|
|
259
|
+
return (await import("./machine-connect-KEI4BRWF.js")).connectMachine(ctx, p);
|
|
176
260
|
}
|
|
177
261
|
|
|
178
262
|
export {
|