@hivefloor/cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -2
- package/dist/chunk-2IMJECYD.js +71 -0
- package/dist/chunk-3IMV5EHP.js +94 -0
- package/dist/chunk-4A5WOW2D.js +356 -0
- package/dist/chunk-4OFN5KHY.js +182 -0
- package/dist/chunk-6UZBZ7LR.js +182 -0
- package/dist/chunk-BAB4DPCN.js +30 -0
- package/dist/chunk-DIW4VIU7.js +138 -0
- package/dist/chunk-IEYWMDXA.js +67 -0
- package/dist/chunk-JCLWCMEM.js +8 -0
- package/dist/chunk-K3NQKI34.js +10 -0
- package/dist/chunk-LHF4KCEA.js +594 -0
- package/dist/chunk-M3PLXINO.js +143 -0
- package/dist/chunk-OQ6QVFSH.js +38 -0
- package/dist/chunk-PY74LT2W.js +41 -0
- package/dist/chunk-RE2EQZWL.js +21226 -0
- package/dist/chunk-SGDM7MAT.js +237 -0
- package/dist/chunk-TKMIV7BG.js +46 -0
- package/dist/chunk-UPRHMVYJ.js +74 -0
- package/dist/chunk-WNDUGQDX.js +135 -0
- package/dist/cli-7CIYC6A3.js +100 -0
- package/dist/connect-R3PMIY7G.js +23 -0
- package/dist/disconnect-NRYU6W5P.js +140 -0
- package/dist/hook-B2I42GE3.js +265 -0
- package/dist/index.js +22 -0
- package/dist/link-EPWADGYT.js +78 -0
- package/dist/machine-connect-RWNZ77NO.js +139 -0
- package/dist/mcp-QX6VMTOD.js +121 -0
- package/dist/run-64ZYCJU2.js +629 -0
- package/dist/status-HADESN2E.js +113 -0
- package/dist/token-SM5MDJKY.js +32 -0
- package/package.json +42 -4
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configDir,
|
|
4
|
+
readText,
|
|
5
|
+
removeFile,
|
|
6
|
+
serviceLogFile,
|
|
7
|
+
spoolFile
|
|
8
|
+
} from "./chunk-WNDUGQDX.js";
|
|
9
|
+
|
|
10
|
+
// src/spool.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
var SPOOL_MAX_BYTES = 1024 * 1024;
|
|
14
|
+
var LOCK_STALE_MS = 1e4;
|
|
15
|
+
var CLAIM_STALE_MS = 6e4;
|
|
16
|
+
var LOCK_WAIT_MS = 250;
|
|
17
|
+
var CAP_SLACK = 64 * 1024;
|
|
18
|
+
var counter = 0;
|
|
19
|
+
var uniq = () => `${Date.now()}-${process.pid}-${counter++}`;
|
|
20
|
+
function parseSpool(text) {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const line of text.split("\n")) {
|
|
23
|
+
if (!line.trim()) continue;
|
|
24
|
+
try {
|
|
25
|
+
out.push(JSON.parse(line));
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function capped(events, maxBytes = SPOOL_MAX_BYTES) {
|
|
32
|
+
const lines = events.map((e) => JSON.stringify(e));
|
|
33
|
+
let total = lines.reduce((n, l) => n + Buffer.byteLength(l) + 1, 0);
|
|
34
|
+
let start = 0;
|
|
35
|
+
while (total > maxBytes && start < lines.length) {
|
|
36
|
+
total -= Buffer.byteLength(lines[start]) + 1;
|
|
37
|
+
start += 1;
|
|
38
|
+
}
|
|
39
|
+
return { events: events.slice(start), dropped: start };
|
|
40
|
+
}
|
|
41
|
+
function droppedEvents(file = spoolFile()) {
|
|
42
|
+
try {
|
|
43
|
+
return Number(fs.readFileSync(`${file}.dropped`, "utf8").trim()) || 0;
|
|
44
|
+
} catch {
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function noteDropped(spool, dropped) {
|
|
49
|
+
if (dropped <= 0) return;
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(`${spool}.dropped`, String(droppedEvents(spool) + dropped), { mode: 384 });
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
fs.appendFileSync(serviceLogFile(), `${(/* @__PURE__ */ new Date()).toISOString()} spool full: dropped ${dropped} event(s), the cap is ${SPOOL_MAX_BYTES} bytes
|
|
56
|
+
`, { mode: 384 });
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function pause(ms) {
|
|
61
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
62
|
+
}
|
|
63
|
+
var lockPath = (file) => `${file}.lock`;
|
|
64
|
+
function acquireLock(file) {
|
|
65
|
+
const lp = lockPath(file);
|
|
66
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
67
|
+
for (; ; ) {
|
|
68
|
+
try {
|
|
69
|
+
fs.closeSync(fs.openSync(lp, "wx", 384));
|
|
70
|
+
return true;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e.code !== "EEXIST") return false;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (Date.now() - fs.statSync(lp).mtimeMs > LOCK_STALE_MS) {
|
|
76
|
+
fs.rmSync(lp, { force: true });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (Date.now() >= deadline) return false;
|
|
83
|
+
pause(5);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function releaseLock(file) {
|
|
87
|
+
removeFile(lockPath(file));
|
|
88
|
+
}
|
|
89
|
+
var claimPath = (file) => `${file}.claim-${uniq()}`;
|
|
90
|
+
var pendingPath = (file) => `${file}.pending-${uniq()}`;
|
|
91
|
+
function sideFiles(dir, file, kind) {
|
|
92
|
+
const prefix = `${path.basename(file)}.${kind}-`;
|
|
93
|
+
let names;
|
|
94
|
+
try {
|
|
95
|
+
names = fs.readdirSync(dir);
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return names.filter((n) => n.startsWith(prefix)).sort().map((n) => path.join(dir, n));
|
|
100
|
+
}
|
|
101
|
+
function owner(file) {
|
|
102
|
+
const m = /\.(?:claim|pending)-(\d+)-(\d+)-\d+$/.exec(path.basename(file));
|
|
103
|
+
if (!m) return void 0;
|
|
104
|
+
return { ts: Number(m[1]), pid: Number(m[2]) };
|
|
105
|
+
}
|
|
106
|
+
function alive(pid) {
|
|
107
|
+
if (pid === process.pid) return true;
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
return true;
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return e.code === "EPERM";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function abandoned(file) {
|
|
116
|
+
const o = owner(file);
|
|
117
|
+
if (!o) return false;
|
|
118
|
+
if (!alive(o.pid)) return true;
|
|
119
|
+
return Date.now() - o.ts > CLAIM_STALE_MS;
|
|
120
|
+
}
|
|
121
|
+
function writeEvents(file, events) {
|
|
122
|
+
const tmp = `${file}.tmp-${process.pid}-${counter++}`;
|
|
123
|
+
try {
|
|
124
|
+
fs.writeFileSync(tmp, events.map((e) => JSON.stringify(e)).join("\n") + (events.length ? "\n" : ""), { mode: 384 });
|
|
125
|
+
fs.renameSync(tmp, file);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
removeFile(tmp);
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function writeSpool(file, events, maxBytes = SPOOL_MAX_BYTES) {
|
|
132
|
+
const { events: kept, dropped } = capped(events, maxBytes);
|
|
133
|
+
if (dropped > 0) noteDropped(file, dropped);
|
|
134
|
+
writeEvents(file, kept);
|
|
135
|
+
}
|
|
136
|
+
function recover(file) {
|
|
137
|
+
const dir = path.dirname(file);
|
|
138
|
+
const stale = [...sideFiles(dir, file, "claim").filter(abandoned), ...sideFiles(dir, file, "pending")].sort();
|
|
139
|
+
if (stale.length === 0) return;
|
|
140
|
+
const events = [];
|
|
141
|
+
for (const f of stale) events.push(...parseSpool(readText(f) ?? ""));
|
|
142
|
+
if (events.length > 0) {
|
|
143
|
+
const existing = parseSpool(readText(file) ?? "");
|
|
144
|
+
writeSpool(file, [...events, ...existing]);
|
|
145
|
+
}
|
|
146
|
+
for (const f of stale) removeFile(f);
|
|
147
|
+
}
|
|
148
|
+
function takeSpool(file = spoolFile()) {
|
|
149
|
+
fs.mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
150
|
+
if (!acquireLock(file)) return void 0;
|
|
151
|
+
try {
|
|
152
|
+
recover(file);
|
|
153
|
+
const claim = claimPath(file);
|
|
154
|
+
try {
|
|
155
|
+
fs.renameSync(file, claim);
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
let events;
|
|
160
|
+
try {
|
|
161
|
+
events = parseSpool(fs.readFileSync(claim, "utf8"));
|
|
162
|
+
} catch {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
if (events.length === 0) {
|
|
166
|
+
removeFile(claim);
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
return { spool: file, file: claim, events };
|
|
170
|
+
} finally {
|
|
171
|
+
releaseLock(file);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function releaseSpool(claim, keep, spool = spoolFile()) {
|
|
175
|
+
if (keep.length === 0) {
|
|
176
|
+
if (claim) removeFile(claim.file);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const target = claim?.spool ?? spool;
|
|
180
|
+
try {
|
|
181
|
+
returnToSpool(keep, target);
|
|
182
|
+
} catch {
|
|
183
|
+
if (claim) {
|
|
184
|
+
try {
|
|
185
|
+
writeEvents(claim.file, keep);
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
writeEvents(pendingPath(target), keep);
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (claim) removeFile(claim.file);
|
|
197
|
+
}
|
|
198
|
+
function returnToSpool(events, file = spoolFile()) {
|
|
199
|
+
if (events.length === 0) return;
|
|
200
|
+
fs.mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
201
|
+
if (!acquireLock(file)) {
|
|
202
|
+
writeEvents(pendingPath(file), events);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const existing = parseSpool(readText(file) ?? "");
|
|
207
|
+
writeSpool(file, [...events, ...existing]);
|
|
208
|
+
} finally {
|
|
209
|
+
releaseLock(file);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function appendToSpool(event, file = spoolFile()) {
|
|
213
|
+
fs.mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
214
|
+
const line = JSON.stringify(event) + "\n";
|
|
215
|
+
if (!acquireLock(file)) {
|
|
216
|
+
fs.appendFileSync(file, line, { mode: 384 });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
fs.appendFileSync(file, line, { mode: 384 });
|
|
221
|
+
let size = 0;
|
|
222
|
+
try {
|
|
223
|
+
size = fs.statSync(file).size;
|
|
224
|
+
} catch {
|
|
225
|
+
size = 0;
|
|
226
|
+
}
|
|
227
|
+
if (size > SPOOL_MAX_BYTES) writeSpool(file, parseSpool(readText(file) ?? ""), SPOOL_MAX_BYTES - CAP_SLACK);
|
|
228
|
+
} finally {
|
|
229
|
+
releaseLock(file);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
takeSpool,
|
|
235
|
+
releaseSpool,
|
|
236
|
+
appendToSpool
|
|
237
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/args.ts
|
|
4
|
+
var BOOLEAN = /* @__PURE__ */ new Set(["no-open", "yes", "y", "reveal", "help", "h", "json", "no-cli", "agent", "no-service"]);
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const positionals = [];
|
|
7
|
+
const flags = {};
|
|
8
|
+
for (let k = 0; k < argv.length; k++) {
|
|
9
|
+
const a = argv[k];
|
|
10
|
+
if (a === "--") {
|
|
11
|
+
positionals.push(...argv.slice(k + 1));
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
if (a.startsWith("--") && a.length > 2) {
|
|
15
|
+
const eq = a.indexOf("=");
|
|
16
|
+
if (eq > 0) {
|
|
17
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
18
|
+
} else {
|
|
19
|
+
const name = a.slice(2);
|
|
20
|
+
const next = argv[k + 1];
|
|
21
|
+
if (BOOLEAN.has(name) || next === void 0 || next.startsWith("--") || next === "-y" || next === "-h") {
|
|
22
|
+
flags[name] = true;
|
|
23
|
+
} else {
|
|
24
|
+
flags[name] = next;
|
|
25
|
+
k += 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} else if (a === "-y") flags.yes = true;
|
|
29
|
+
else if (a === "-h") flags.help = true;
|
|
30
|
+
else positionals.push(a);
|
|
31
|
+
}
|
|
32
|
+
return { positionals, flags };
|
|
33
|
+
}
|
|
34
|
+
var flagStr = (p, name) => typeof p.flags[name] === "string" ? p.flags[name] : void 0;
|
|
35
|
+
function checkUnknownFlags(p, allowed) {
|
|
36
|
+
for (const f of Object.keys(p.flags)) {
|
|
37
|
+
if (!allowed.has(f)) return f;
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
parseArgs,
|
|
44
|
+
flagStr,
|
|
45
|
+
checkUnknownFlags
|
|
46
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ApiError,
|
|
4
|
+
apiRequest,
|
|
5
|
+
errorOf
|
|
6
|
+
} from "./chunk-PY74LT2W.js";
|
|
7
|
+
import {
|
|
8
|
+
deviceCodeResponseSchema,
|
|
9
|
+
deviceTokenErrorSchema,
|
|
10
|
+
deviceTokenSuccessSchema
|
|
11
|
+
} from "./chunk-RE2EQZWL.js";
|
|
12
|
+
|
|
13
|
+
// src/device.ts
|
|
14
|
+
var printable = (s) => [...s].filter((c) => c.charCodeAt(0) >= 32 && c.charCodeAt(0) !== 127).join("");
|
|
15
|
+
async function deviceLogin(ctx, opts) {
|
|
16
|
+
const started = await apiRequest(ctx, opts.host, "/api/v1/device/code", { method: "POST", body: opts.request });
|
|
17
|
+
if (started.status !== 200) throw errorOf(started);
|
|
18
|
+
const code = deviceCodeResponseSchema.parse(started.json);
|
|
19
|
+
const userCode = printable(code.user_code);
|
|
20
|
+
const url = printable(code.verification_uri_complete);
|
|
21
|
+
ctx.out(`
|
|
22
|
+
Your code is ${userCode}
|
|
23
|
+
Confirm it at ${url}
|
|
24
|
+
`);
|
|
25
|
+
if (opts.openBrowser) {
|
|
26
|
+
try {
|
|
27
|
+
await ctx.open(url);
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
ctx.out("Waiting for you to confirm in the browser...\n");
|
|
32
|
+
const deadline = ctx.now() + code.expires_in * 1e3;
|
|
33
|
+
let interval = code.interval;
|
|
34
|
+
let failures = 0;
|
|
35
|
+
for (; ; ) {
|
|
36
|
+
await ctx.sleep(interval * 1e3);
|
|
37
|
+
if (ctx.now() >= deadline) throw new ApiError(400, "expired_token", "The code expired before it was confirmed. Run the command again.");
|
|
38
|
+
let res;
|
|
39
|
+
try {
|
|
40
|
+
res = await apiRequest(ctx, opts.host, "/api/v1/device/token", { method: "POST", body: { device_code: code.device_code } });
|
|
41
|
+
} catch (e) {
|
|
42
|
+
failures += 1;
|
|
43
|
+
if (failures >= 5) throw new ApiError(0, "network", `Could not reach ${opts.host}: ${e.message}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (res.status === 200) return opts.parse ? opts.parse(res.json) : deviceTokenSuccessSchema.parse(res.json);
|
|
47
|
+
const err = deviceTokenErrorSchema.safeParse(res.json);
|
|
48
|
+
if (res.status === 429) {
|
|
49
|
+
interval += 5;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!err.success) {
|
|
53
|
+
failures += 1;
|
|
54
|
+
if (failures >= 5 || res.status >= 400 && res.status < 500) throw errorOf(res);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
failures = 0;
|
|
58
|
+
switch (err.data.error) {
|
|
59
|
+
case "authorization_pending":
|
|
60
|
+
break;
|
|
61
|
+
case "slow_down":
|
|
62
|
+
interval += 5;
|
|
63
|
+
break;
|
|
64
|
+
case "access_denied":
|
|
65
|
+
throw new ApiError(400, "access_denied", "The request was denied in the browser.");
|
|
66
|
+
case "expired_token":
|
|
67
|
+
throw new ApiError(400, "expired_token", "The code expired before it was confirmed. Run the command again.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
deviceLogin
|
|
74
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/fsutil.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
var home = () => os.homedir();
|
|
8
|
+
function configDirFor(platform, env, homeDir) {
|
|
9
|
+
if (platform === "win32") return path.win32.join(env.APPDATA || path.win32.join(homeDir, "AppData", "Roaming"), "hivefloor");
|
|
10
|
+
return path.join(homeDir, ".config", "hivefloor");
|
|
11
|
+
}
|
|
12
|
+
var configDir = () => configDirFor(process.platform, process.env, home());
|
|
13
|
+
var credentialsFile = () => path.join(configDir(), "credentials.json");
|
|
14
|
+
var spoolFile = () => path.join(configDir(), "spool.jsonl");
|
|
15
|
+
var manifestFile = () => path.join(configDir(), "manifest.json");
|
|
16
|
+
var machineFile = () => path.join(configDir(), "machine.json");
|
|
17
|
+
var machineSpoolFile = () => path.join(configDir(), "machine-spool.jsonl");
|
|
18
|
+
var serviceStateFile = () => path.join(configDir(), "service.json");
|
|
19
|
+
var serviceLogFile = () => path.join(configDir(), "service.log");
|
|
20
|
+
var cliDir = () => path.join(configDir(), "cli");
|
|
21
|
+
var cliEntry = () => path.join(cliDir(), "index.js");
|
|
22
|
+
function readText(file) {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(file, "utf8");
|
|
25
|
+
} catch (e) {
|
|
26
|
+
if (e.code === "ENOENT") return void 0;
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function resolveLink(file) {
|
|
31
|
+
try {
|
|
32
|
+
return fs.realpathSync(file);
|
|
33
|
+
} catch {
|
|
34
|
+
try {
|
|
35
|
+
return path.resolve(path.dirname(file), fs.readlinkSync(file));
|
|
36
|
+
} catch {
|
|
37
|
+
return file;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function atomicWrite(link, content, mode) {
|
|
42
|
+
atomicWriteBytes(link, Buffer.from(content, "utf8"), mode);
|
|
43
|
+
}
|
|
44
|
+
function atomicWriteBytes(link, content, mode) {
|
|
45
|
+
const file = resolveLink(link);
|
|
46
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
47
|
+
let m = mode;
|
|
48
|
+
if (m === void 0) {
|
|
49
|
+
try {
|
|
50
|
+
m = fs.statSync(file).mode & 511;
|
|
51
|
+
} catch {
|
|
52
|
+
m = 420;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
56
|
+
try {
|
|
57
|
+
fs.writeFileSync(tmp, content, { mode: m });
|
|
58
|
+
fs.chmodSync(tmp, m);
|
|
59
|
+
fs.renameSync(tmp, file);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(tmp);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function atomicWriteUtf16(link, content, mode) {
|
|
69
|
+
atomicWriteBytes(link, Buffer.concat([Buffer.from([255, 254]), Buffer.from(content, "utf16le")]), mode);
|
|
70
|
+
}
|
|
71
|
+
function backupOnce(file) {
|
|
72
|
+
const bak = `${file}.hivefloor.bak`;
|
|
73
|
+
if (fs.existsSync(file) && !fs.existsSync(bak)) fs.copyFileSync(file, bak);
|
|
74
|
+
}
|
|
75
|
+
function dropBackupIfSame(file) {
|
|
76
|
+
const bak = `${file}.hivefloor.bak`;
|
|
77
|
+
const a = readText(file);
|
|
78
|
+
const b = readText(bak);
|
|
79
|
+
if (b !== void 0 && a === b) fs.unlinkSync(bak);
|
|
80
|
+
}
|
|
81
|
+
function removeFile(file) {
|
|
82
|
+
try {
|
|
83
|
+
fs.unlinkSync(file);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
var ConfigError = class extends Error {
|
|
88
|
+
};
|
|
89
|
+
function readJsonFile(file) {
|
|
90
|
+
const text = readText(file);
|
|
91
|
+
if (text === void 0 || text.trim() === "") {
|
|
92
|
+
return { data: {}, style: { indent: 2, eol: "\n", finalNewline: true }, existed: text !== void 0 };
|
|
93
|
+
}
|
|
94
|
+
let data;
|
|
95
|
+
try {
|
|
96
|
+
data = JSON.parse(text);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new ConfigError(`${file} is not plain JSON (comments or a syntax error), so it was left untouched`);
|
|
99
|
+
}
|
|
100
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) throw new ConfigError(`${file} is not a JSON object`);
|
|
101
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
102
|
+
const m = /\n([ \t]+)\S/.exec(text);
|
|
103
|
+
const indent = m ? m[1].startsWith(" ") ? " " : m[1].length : text.trim().includes("\n") ? 2 : 0;
|
|
104
|
+
return { data, style: { indent, eol, finalNewline: text.endsWith("\n") }, existed: true };
|
|
105
|
+
}
|
|
106
|
+
function serializeJson(data, style) {
|
|
107
|
+
let s = JSON.stringify(data, null, style.indent);
|
|
108
|
+
if (style.eol === "\r\n") s = s.replace(/\n/g, "\r\n");
|
|
109
|
+
return style.finalNewline ? s + style.eol : s;
|
|
110
|
+
}
|
|
111
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
home,
|
|
115
|
+
configDir,
|
|
116
|
+
credentialsFile,
|
|
117
|
+
spoolFile,
|
|
118
|
+
manifestFile,
|
|
119
|
+
machineFile,
|
|
120
|
+
machineSpoolFile,
|
|
121
|
+
serviceStateFile,
|
|
122
|
+
serviceLogFile,
|
|
123
|
+
cliDir,
|
|
124
|
+
cliEntry,
|
|
125
|
+
readText,
|
|
126
|
+
atomicWrite,
|
|
127
|
+
atomicWriteUtf16,
|
|
128
|
+
backupOnce,
|
|
129
|
+
dropBackupIfSame,
|
|
130
|
+
removeFile,
|
|
131
|
+
ConfigError,
|
|
132
|
+
readJsonFile,
|
|
133
|
+
serializeJson,
|
|
134
|
+
isRecord
|
|
135
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
parseArgs
|
|
4
|
+
} from "./chunk-TKMIV7BG.js";
|
|
5
|
+
import {
|
|
6
|
+
VERSION
|
|
7
|
+
} from "./chunk-IEYWMDXA.js";
|
|
8
|
+
import {
|
|
9
|
+
ApiError
|
|
10
|
+
} from "./chunk-PY74LT2W.js";
|
|
11
|
+
import {
|
|
12
|
+
CLI_BIN_NAME,
|
|
13
|
+
CLI_INSTALL_COMMAND,
|
|
14
|
+
cliNpxCommand,
|
|
15
|
+
defaultCtx
|
|
16
|
+
} from "./chunk-RE2EQZWL.js";
|
|
17
|
+
import "./chunk-K3NQKI34.js";
|
|
18
|
+
|
|
19
|
+
// src/cli.ts
|
|
20
|
+
var HELP = `hivefloor ${VERSION}
|
|
21
|
+
|
|
22
|
+
Usage: ${CLI_BIN_NAME} <command> [options]
|
|
23
|
+
${cliNpxCommand("<command> [options]")} (no install)
|
|
24
|
+
|
|
25
|
+
Commands:
|
|
26
|
+
connect Connect this computer once: every agent on it shows up (Claude profiles, Codex, ...)
|
|
27
|
+
--host URL --name NAME --no-open --no-service --yes
|
|
28
|
+
--agent | --tool claude|codex|cursor|gemini|grok|other connect one agent for one repo (MCP and tasks) instead
|
|
29
|
+
disconnect Remove everything connect added, the machine on the server included (--tool <name> for one agent tool)
|
|
30
|
+
status This machine and its service; the per-repo agent, its projects and live sessions
|
|
31
|
+
service run: the background service (started by your OS; you rarely run it by hand)
|
|
32
|
+
link Link this repo to another project (--project ID | --new NAME [--path PREFIX])
|
|
33
|
+
token Print the stored token for shell use (--reveal to print to a terminal)
|
|
34
|
+
mcp Stdio MCP server that proxies to Hivefloor (for tools without HTTP header support)
|
|
35
|
+
hook <tool> <event>: the hook runner the tools call (reads JSON on stdin)
|
|
36
|
+
help Show this help
|
|
37
|
+
--version Print the version
|
|
38
|
+
|
|
39
|
+
Install once: ${CLI_INSTALL_COMMAND}
|
|
40
|
+
Host: https://hivefloor.worldofz.info, or set HIVEFLOOR_URL.
|
|
41
|
+
`;
|
|
42
|
+
async function run(argv, ctx = defaultCtx()) {
|
|
43
|
+
const [cmd, ...rest] = argv;
|
|
44
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
45
|
+
ctx.out(`${VERSION}
|
|
46
|
+
`);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
if (cmd === void 0 || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
50
|
+
ctx.out(HELP);
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const p = parseArgs(rest);
|
|
54
|
+
if (p.flags.help) {
|
|
55
|
+
ctx.out(HELP);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
switch (cmd) {
|
|
60
|
+
case "connect":
|
|
61
|
+
return await (await import("./connect-R3PMIY7G.js")).connect(ctx, p);
|
|
62
|
+
case "disconnect":
|
|
63
|
+
return await (await import("./disconnect-NRYU6W5P.js")).disconnect(ctx, p);
|
|
64
|
+
case "status":
|
|
65
|
+
return await (await import("./status-HADESN2E.js")).status(ctx, p);
|
|
66
|
+
case "service":
|
|
67
|
+
if (p.positionals[0] !== "run") {
|
|
68
|
+
ctx.err("hivefloor: usage: hivefloor service run\n");
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
return await (await import("./run-64ZYCJU2.js")).runService({ ctx });
|
|
72
|
+
case "link":
|
|
73
|
+
return await (await import("./link-EPWADGYT.js")).link(ctx, p);
|
|
74
|
+
case "token":
|
|
75
|
+
return await (await import("./token-SM5MDJKY.js")).token(ctx, p);
|
|
76
|
+
case "mcp":
|
|
77
|
+
return await (await import("./mcp-QX6VMTOD.js")).runMcpProxy(ctx, p);
|
|
78
|
+
case "hook": {
|
|
79
|
+
const { runHook, readStdin } = await import("./hook-B2I42GE3.js");
|
|
80
|
+
await runHook(rest, { env: ctx.env, cwd: ctx.cwd, fetch: ctx.fetch, now: ctx.now, readStdin });
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
default:
|
|
84
|
+
ctx.err(`hivefloor: unknown command "${cmd}". Run "hivefloor help".
|
|
85
|
+
`);
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e instanceof ApiError) ctx.err(`hivefloor: ${e.message}
|
|
90
|
+
`);
|
|
91
|
+
else ctx.err(`hivefloor: ${e.message}
|
|
92
|
+
`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
HELP,
|
|
98
|
+
VERSION,
|
|
99
|
+
run
|
|
100
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
connect,
|
|
4
|
+
connectAgent,
|
|
5
|
+
resolveHost
|
|
6
|
+
} from "./chunk-4OFN5KHY.js";
|
|
7
|
+
import "./chunk-LHF4KCEA.js";
|
|
8
|
+
import "./chunk-3IMV5EHP.js";
|
|
9
|
+
import "./chunk-UPRHMVYJ.js";
|
|
10
|
+
import "./chunk-OQ6QVFSH.js";
|
|
11
|
+
import "./chunk-2IMJECYD.js";
|
|
12
|
+
import "./chunk-TKMIV7BG.js";
|
|
13
|
+
import "./chunk-4A5WOW2D.js";
|
|
14
|
+
import "./chunk-IEYWMDXA.js";
|
|
15
|
+
import "./chunk-PY74LT2W.js";
|
|
16
|
+
import "./chunk-WNDUGQDX.js";
|
|
17
|
+
import "./chunk-RE2EQZWL.js";
|
|
18
|
+
import "./chunk-K3NQKI34.js";
|
|
19
|
+
export {
|
|
20
|
+
connect,
|
|
21
|
+
connectAgent,
|
|
22
|
+
resolveHost
|
|
23
|
+
};
|