@boxcrew/cli 0.1.16 → 0.1.17
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/dist/index.js +123 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -104,6 +104,10 @@ function registerLogoutCommand(program2) {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// src/commands/agents.ts
|
|
108
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
109
|
+
import { homedir as homedir2 } from "os";
|
|
110
|
+
|
|
107
111
|
// src/client.ts
|
|
108
112
|
function getBaseUrl() {
|
|
109
113
|
return process.env.BOXCREW_API_URL || config.get("apiUrl");
|
|
@@ -142,6 +146,64 @@ async function apiFetchJson(path, options = {}) {
|
|
|
142
146
|
return response.json();
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
// src/lib/state.ts
|
|
150
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync } from "fs";
|
|
151
|
+
import { join } from "path";
|
|
152
|
+
import { homedir } from "os";
|
|
153
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
154
|
+
var RUNTIME_NAMES = {
|
|
155
|
+
"claude-code": "Claude Code",
|
|
156
|
+
"opencode": "OpenCode",
|
|
157
|
+
"openclaw": "OpenClaw"
|
|
158
|
+
};
|
|
159
|
+
function getStateDir() {
|
|
160
|
+
const dir = join(homedir(), ".boxcrew");
|
|
161
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
162
|
+
return dir;
|
|
163
|
+
}
|
|
164
|
+
function getPidFile(key) {
|
|
165
|
+
return join(getStateDir(), `${key}.pid`);
|
|
166
|
+
}
|
|
167
|
+
function getMetaFile(key) {
|
|
168
|
+
return join(getStateDir(), `${key}.meta.json`);
|
|
169
|
+
}
|
|
170
|
+
function getLogFile(key) {
|
|
171
|
+
return join(getStateDir(), `${key}.log`);
|
|
172
|
+
}
|
|
173
|
+
function readMeta(key) {
|
|
174
|
+
const metaFile = getMetaFile(key);
|
|
175
|
+
if (!existsSync(metaFile)) return null;
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(readFileSync(metaFile, "utf-8"));
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function readPid(key) {
|
|
183
|
+
const pidFile = getPidFile(key);
|
|
184
|
+
if (!existsSync(pidFile)) return null;
|
|
185
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
186
|
+
if (isNaN(pid)) return null;
|
|
187
|
+
try {
|
|
188
|
+
process.kill(pid, 0);
|
|
189
|
+
return pid;
|
|
190
|
+
} catch {
|
|
191
|
+
unlinkSync(pidFile);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function resolveAgentKey(nameOrId) {
|
|
196
|
+
if (UUID_RE.test(nameOrId)) return nameOrId;
|
|
197
|
+
const stateDir = getStateDir();
|
|
198
|
+
const metaFiles = readdirSync(stateDir).filter((f) => f.endsWith(".meta.json"));
|
|
199
|
+
for (const file of metaFiles) {
|
|
200
|
+
const key = file.replace(".meta.json", "");
|
|
201
|
+
const meta = readMeta(key);
|
|
202
|
+
if (meta && meta.agentName === nameOrId) return key;
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
145
207
|
// src/commands/agents.ts
|
|
146
208
|
var TRANSIENT_CODES = [
|
|
147
209
|
"ETIMEDOUT",
|
|
@@ -165,10 +227,55 @@ function registerAgentsCommands(program2) {
|
|
|
165
227
|
console.log("No agents found.");
|
|
166
228
|
return;
|
|
167
229
|
}
|
|
168
|
-
|
|
230
|
+
const home = homedir2();
|
|
231
|
+
const shortenPath = (p) => p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
232
|
+
const localMeta = /* @__PURE__ */ new Map();
|
|
233
|
+
try {
|
|
234
|
+
const stateDir = getStateDir();
|
|
235
|
+
const metaFiles = readdirSync2(stateDir).filter((f) => f.endsWith(".meta.json"));
|
|
236
|
+
for (const file of metaFiles) {
|
|
237
|
+
const key = file.replace(".meta.json", "");
|
|
238
|
+
const meta = readMeta(key);
|
|
239
|
+
if (meta) {
|
|
240
|
+
localMeta.set(meta.agentId, { pid: readPid(key), cwd: meta.cwd });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
const rows = [];
|
|
169
246
|
for (const agent of data) {
|
|
170
|
-
const
|
|
171
|
-
|
|
247
|
+
const shortId = agent.id.slice(0, 8);
|
|
248
|
+
const typeDisplay = agent.agent_type === "remote" ? "BYOA" : "hosted";
|
|
249
|
+
const runtimeDisplay = RUNTIME_NAMES[agent.runtime] || agent.runtime;
|
|
250
|
+
let status;
|
|
251
|
+
let directory = "-";
|
|
252
|
+
if (agent.agent_type !== "remote") {
|
|
253
|
+
status = "-";
|
|
254
|
+
} else {
|
|
255
|
+
const local = localMeta.get(agent.id);
|
|
256
|
+
if (local?.pid) {
|
|
257
|
+
status = "online (here)";
|
|
258
|
+
directory = shortenPath(local.cwd);
|
|
259
|
+
} else if (agent.is_online) {
|
|
260
|
+
status = "online";
|
|
261
|
+
} else {
|
|
262
|
+
status = "offline";
|
|
263
|
+
if (local?.cwd) directory = shortenPath(local.cwd);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
rows.push({ name: agent.name, id: shortId, type: typeDisplay, runtime: runtimeDisplay, status, directory });
|
|
267
|
+
}
|
|
268
|
+
const headers = { name: "Name", id: "ID", type: "Type", runtime: "Runtime", status: "Status", directory: "Directory" };
|
|
269
|
+
const cols = Object.keys(headers);
|
|
270
|
+
const widths = {};
|
|
271
|
+
for (const col of cols) {
|
|
272
|
+
widths[col] = Math.max(headers[col].length, ...rows.map((r) => r[col].length));
|
|
273
|
+
}
|
|
274
|
+
const pad = (s, w) => s + " ".repeat(w - s.length);
|
|
275
|
+
console.log("");
|
|
276
|
+
console.log(` ${cols.map((c) => pad(headers[c], widths[c])).join(" ")}`);
|
|
277
|
+
for (const row of rows) {
|
|
278
|
+
console.log(` ${cols.map((c) => pad(row[c], widths[c])).join(" ")}`);
|
|
172
279
|
}
|
|
173
280
|
console.log("");
|
|
174
281
|
});
|
|
@@ -360,65 +467,12 @@ function registerApiCommand(program2) {
|
|
|
360
467
|
// src/commands/connect.ts
|
|
361
468
|
import { spawn } from "child_process";
|
|
362
469
|
import { createInterface as createInterface3 } from "readline";
|
|
363
|
-
import { openSync,
|
|
364
|
-
import { join } from "path";
|
|
365
|
-
import { homedir, platform, release, hostname } from "os";
|
|
470
|
+
import { openSync, existsSync as existsSync2, writeFileSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, renameSync } from "fs";
|
|
471
|
+
import { join as join2 } from "path";
|
|
472
|
+
import { homedir as homedir3, platform, release, hostname } from "os";
|
|
366
473
|
import WebSocket from "ws";
|
|
367
474
|
var RECONNECT_BASE_MS = 1e3;
|
|
368
475
|
var RECONNECT_MAX_MS = 3e4;
|
|
369
|
-
var RUNTIME_NAMES = {
|
|
370
|
-
"claude-code": "Claude Code",
|
|
371
|
-
"opencode": "OpenCode",
|
|
372
|
-
"openclaw": "OpenClaw"
|
|
373
|
-
};
|
|
374
|
-
function getStateDir() {
|
|
375
|
-
const dir = join(homedir(), ".boxcrew");
|
|
376
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
377
|
-
return dir;
|
|
378
|
-
}
|
|
379
|
-
function getPidFile(key) {
|
|
380
|
-
return join(getStateDir(), `${key}.pid`);
|
|
381
|
-
}
|
|
382
|
-
function getMetaFile(key) {
|
|
383
|
-
return join(getStateDir(), `${key}.meta.json`);
|
|
384
|
-
}
|
|
385
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
386
|
-
function readMeta(key) {
|
|
387
|
-
const metaFile = getMetaFile(key);
|
|
388
|
-
if (!existsSync(metaFile)) return null;
|
|
389
|
-
try {
|
|
390
|
-
return JSON.parse(readFileSync(metaFile, "utf-8"));
|
|
391
|
-
} catch {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
function getLogFile(key) {
|
|
396
|
-
return join(getStateDir(), `${key}.log`);
|
|
397
|
-
}
|
|
398
|
-
function readPid(key) {
|
|
399
|
-
const pidFile = getPidFile(key);
|
|
400
|
-
if (!existsSync(pidFile)) return null;
|
|
401
|
-
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
402
|
-
if (isNaN(pid)) return null;
|
|
403
|
-
try {
|
|
404
|
-
process.kill(pid, 0);
|
|
405
|
-
return pid;
|
|
406
|
-
} catch {
|
|
407
|
-
unlinkSync(pidFile);
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
function resolveAgentKey(nameOrId) {
|
|
412
|
-
if (UUID_RE.test(nameOrId)) return nameOrId;
|
|
413
|
-
const stateDir = getStateDir();
|
|
414
|
-
const metaFiles = readdirSync(stateDir).filter((f) => f.endsWith(".meta.json"));
|
|
415
|
-
for (const file of metaFiles) {
|
|
416
|
-
const key = file.replace(".meta.json", "");
|
|
417
|
-
const meta = readMeta(key);
|
|
418
|
-
if (meta && meta.agentName === nameOrId) return key;
|
|
419
|
-
}
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
476
|
function parseStreamJsonLine(line) {
|
|
423
477
|
let obj;
|
|
424
478
|
try {
|
|
@@ -647,7 +701,7 @@ function runDaemon(agentId) {
|
|
|
647
701
|
};
|
|
648
702
|
const cleanup = () => {
|
|
649
703
|
try {
|
|
650
|
-
|
|
704
|
+
unlinkSync2(getPidFile(agentId));
|
|
651
705
|
} catch {
|
|
652
706
|
}
|
|
653
707
|
};
|
|
@@ -747,11 +801,11 @@ function registerConnectCommand(program2) {
|
|
|
747
801
|
`/agents/${encodeURIComponent(agentName)}/connection-config`
|
|
748
802
|
);
|
|
749
803
|
const agentId = config2.agent_id;
|
|
750
|
-
if (!
|
|
804
|
+
if (!existsSync2(getMetaFile(agentId)) && existsSync2(getMetaFile(agentName))) {
|
|
751
805
|
for (const ext of [".meta.json", ".pid", ".log"]) {
|
|
752
|
-
const oldPath =
|
|
753
|
-
const newPath =
|
|
754
|
-
if (
|
|
806
|
+
const oldPath = join2(getStateDir(), `${agentName}${ext}`);
|
|
807
|
+
const newPath = join2(getStateDir(), `${agentId}${ext}`);
|
|
808
|
+
if (existsSync2(oldPath)) renameSync(oldPath, newPath);
|
|
755
809
|
}
|
|
756
810
|
}
|
|
757
811
|
const existingPid = readPid(agentId);
|
|
@@ -788,7 +842,7 @@ function registerConnectCommand(program2) {
|
|
|
788
842
|
writeFileSync(getMetaFile(agentId), JSON.stringify(meta, null, 2));
|
|
789
843
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
790
844
|
const cwd = process.cwd();
|
|
791
|
-
const home =
|
|
845
|
+
const home = homedir3();
|
|
792
846
|
const shortLog = logFile.startsWith(home) ? "~" + logFile.slice(home.length) : logFile;
|
|
793
847
|
console.log("");
|
|
794
848
|
console.log(` Agent "${config2.agent_name}" is online.`);
|
|
@@ -814,7 +868,7 @@ function registerConnectCommand(program2) {
|
|
|
814
868
|
try {
|
|
815
869
|
process.kill(pid, "SIGTERM");
|
|
816
870
|
try {
|
|
817
|
-
|
|
871
|
+
unlinkSync2(getPidFile(key));
|
|
818
872
|
} catch {
|
|
819
873
|
}
|
|
820
874
|
console.log(`Agent "${agentName}" disconnected.`);
|
|
@@ -822,50 +876,10 @@ function registerConnectCommand(program2) {
|
|
|
822
876
|
console.error(`Failed to stop process ${pid}.`);
|
|
823
877
|
}
|
|
824
878
|
});
|
|
825
|
-
program2.command("status").description("Show status of all locally connected agents.").action(() => {
|
|
826
|
-
const stateDir = getStateDir();
|
|
827
|
-
const metaFiles = readdirSync(stateDir).filter((f) => f.endsWith(".meta.json"));
|
|
828
|
-
if (metaFiles.length === 0) {
|
|
829
|
-
console.log("No agents found. Use `bx connect <agent-name>` to connect one.");
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
const home = homedir();
|
|
833
|
-
const shortenPath = (p) => p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
834
|
-
const rows = [];
|
|
835
|
-
for (const file of metaFiles) {
|
|
836
|
-
const key = file.replace(".meta.json", "");
|
|
837
|
-
const meta = readMeta(key);
|
|
838
|
-
if (!meta) continue;
|
|
839
|
-
const pid = readPid(key);
|
|
840
|
-
const isOnline = pid !== null;
|
|
841
|
-
rows.push({
|
|
842
|
-
agent: meta.agentName || key,
|
|
843
|
-
status: isOnline ? "online" : "offline",
|
|
844
|
-
runtime: RUNTIME_NAMES[meta.runtime] || meta.runtime,
|
|
845
|
-
directory: shortenPath(meta.cwd),
|
|
846
|
-
pid: isOnline ? String(pid) : "-"
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
const headers = { agent: "Agent", status: "Status", runtime: "Runtime", directory: "Directory", pid: "PID" };
|
|
850
|
-
const cols = Object.keys(headers);
|
|
851
|
-
const widths = {};
|
|
852
|
-
for (const col of cols) {
|
|
853
|
-
widths[col] = Math.max(headers[col].length, ...rows.map((r) => r[col].length));
|
|
854
|
-
}
|
|
855
|
-
const pad = (s, w) => s + " ".repeat(w - s.length);
|
|
856
|
-
const headerLine = cols.map((c) => pad(headers[c], widths[c])).join(" ");
|
|
857
|
-
console.log("");
|
|
858
|
-
console.log(` ${headerLine}`);
|
|
859
|
-
for (const row of rows) {
|
|
860
|
-
const line = cols.map((c) => pad(row[c], widths[c])).join(" ");
|
|
861
|
-
console.log(` ${line}`);
|
|
862
|
-
}
|
|
863
|
-
console.log("");
|
|
864
|
-
});
|
|
865
879
|
program2.command("reconnect [agent-name]").description("Reconnect a previously connected agent.").option("--all", "Reconnect all agents").option("--claude-path <path>", "Path to claude CLI binary").action(async (agentName, options) => {
|
|
866
880
|
if (options.all) {
|
|
867
881
|
const stateDir = getStateDir();
|
|
868
|
-
const metaFiles =
|
|
882
|
+
const metaFiles = readdirSync3(stateDir).filter((f) => f.endsWith(".meta.json"));
|
|
869
883
|
if (metaFiles.length === 0) {
|
|
870
884
|
console.log("No agents found. Use `bx connect <agent-name>` to connect one.");
|
|
871
885
|
return;
|
|
@@ -900,7 +914,7 @@ function registerConnectCommand(program2) {
|
|
|
900
914
|
} catch {
|
|
901
915
|
}
|
|
902
916
|
try {
|
|
903
|
-
|
|
917
|
+
unlinkSync2(getPidFile(key));
|
|
904
918
|
} catch {
|
|
905
919
|
}
|
|
906
920
|
}
|
|
@@ -938,7 +952,7 @@ function registerConnectCommand(program2) {
|
|
|
938
952
|
};
|
|
939
953
|
writeFileSync(getMetaFile(key), JSON.stringify(newMeta, null, 2));
|
|
940
954
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
941
|
-
const home =
|
|
955
|
+
const home = homedir3();
|
|
942
956
|
const shortCwd = meta.cwd.startsWith(home) ? "~" + meta.cwd.slice(home.length) : meta.cwd;
|
|
943
957
|
const shortLog = logFile.startsWith(home) ? "~" + logFile.slice(home.length) : logFile;
|
|
944
958
|
console.log("");
|