@boxcrew/cli 0.1.15 → 0.1.16
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 +87 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/login.ts
|
|
@@ -359,7 +360,7 @@ function registerApiCommand(program2) {
|
|
|
359
360
|
// src/commands/connect.ts
|
|
360
361
|
import { spawn } from "child_process";
|
|
361
362
|
import { createInterface as createInterface3 } from "readline";
|
|
362
|
-
import { openSync, mkdirSync, existsSync, writeFileSync, readFileSync, unlinkSync, readdirSync } from "fs";
|
|
363
|
+
import { openSync, mkdirSync, existsSync, writeFileSync, readFileSync, unlinkSync, readdirSync, renameSync } from "fs";
|
|
363
364
|
import { join } from "path";
|
|
364
365
|
import { homedir, platform, release, hostname } from "os";
|
|
365
366
|
import WebSocket from "ws";
|
|
@@ -375,14 +376,15 @@ function getStateDir() {
|
|
|
375
376
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
376
377
|
return dir;
|
|
377
378
|
}
|
|
378
|
-
function getPidFile(
|
|
379
|
-
return join(getStateDir(), `${
|
|
379
|
+
function getPidFile(key) {
|
|
380
|
+
return join(getStateDir(), `${key}.pid`);
|
|
380
381
|
}
|
|
381
|
-
function getMetaFile(
|
|
382
|
-
return join(getStateDir(), `${
|
|
382
|
+
function getMetaFile(key) {
|
|
383
|
+
return join(getStateDir(), `${key}.meta.json`);
|
|
383
384
|
}
|
|
384
|
-
|
|
385
|
-
|
|
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);
|
|
386
388
|
if (!existsSync(metaFile)) return null;
|
|
387
389
|
try {
|
|
388
390
|
return JSON.parse(readFileSync(metaFile, "utf-8"));
|
|
@@ -390,11 +392,11 @@ function readMeta(agentName) {
|
|
|
390
392
|
return null;
|
|
391
393
|
}
|
|
392
394
|
}
|
|
393
|
-
function getLogFile(
|
|
394
|
-
return join(getStateDir(), `${
|
|
395
|
+
function getLogFile(key) {
|
|
396
|
+
return join(getStateDir(), `${key}.log`);
|
|
395
397
|
}
|
|
396
|
-
function readPid(
|
|
397
|
-
const pidFile = getPidFile(
|
|
398
|
+
function readPid(key) {
|
|
399
|
+
const pidFile = getPidFile(key);
|
|
398
400
|
if (!existsSync(pidFile)) return null;
|
|
399
401
|
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
400
402
|
if (isNaN(pid)) return null;
|
|
@@ -406,6 +408,17 @@ function readPid(agentName) {
|
|
|
406
408
|
return null;
|
|
407
409
|
}
|
|
408
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
|
+
}
|
|
409
422
|
function parseStreamJsonLine(line) {
|
|
410
423
|
let obj;
|
|
411
424
|
try {
|
|
@@ -512,16 +525,16 @@ function parseOpenClawOutput(stdout) {
|
|
|
512
525
|
return [{ kind: "text", text: stdout.trim() || "No response" }];
|
|
513
526
|
}
|
|
514
527
|
}
|
|
515
|
-
function runDaemon(
|
|
528
|
+
function runDaemon(agentId) {
|
|
516
529
|
const wsUrl = process.env._BX_WS_URL;
|
|
517
530
|
const claudePath = process.env._BX_CLAUDE_PATH || "claude";
|
|
518
|
-
const agentDisplayName = process.env._BX_AGENT_NAME ||
|
|
531
|
+
const agentDisplayName = process.env._BX_AGENT_NAME || agentId;
|
|
519
532
|
const runtime = process.env._BX_RUNTIME || "claude-code";
|
|
520
533
|
if (!wsUrl) {
|
|
521
534
|
console.error("Missing _BX_WS_URL");
|
|
522
535
|
process.exit(1);
|
|
523
536
|
}
|
|
524
|
-
writeFileSync(getPidFile(
|
|
537
|
+
writeFileSync(getPidFile(agentId), String(process.pid));
|
|
525
538
|
let activeProcess = null;
|
|
526
539
|
let sendToServer = null;
|
|
527
540
|
let reconnectAttempt = 0;
|
|
@@ -634,7 +647,7 @@ function runDaemon(agentName) {
|
|
|
634
647
|
};
|
|
635
648
|
const cleanup = () => {
|
|
636
649
|
try {
|
|
637
|
-
unlinkSync(getPidFile(
|
|
650
|
+
unlinkSync(getPidFile(agentId));
|
|
638
651
|
} catch {
|
|
639
652
|
}
|
|
640
653
|
};
|
|
@@ -678,6 +691,14 @@ function runDaemon(agentName) {
|
|
|
678
691
|
if (msg.type === "chat") {
|
|
679
692
|
handleChat(msg);
|
|
680
693
|
}
|
|
694
|
+
if (msg.type === "rename") {
|
|
695
|
+
const meta = readMeta(agentId);
|
|
696
|
+
if (meta) {
|
|
697
|
+
meta.agentName = msg.newName;
|
|
698
|
+
writeFileSync(getMetaFile(agentId), JSON.stringify(meta, null, 2));
|
|
699
|
+
}
|
|
700
|
+
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Agent renamed to "${msg.newName}"`);
|
|
701
|
+
}
|
|
681
702
|
});
|
|
682
703
|
ws.on("close", (code, reason) => {
|
|
683
704
|
sendToServer = null;
|
|
@@ -718,19 +739,27 @@ function runDaemon(agentName) {
|
|
|
718
739
|
connect();
|
|
719
740
|
}
|
|
720
741
|
function registerConnectCommand(program2) {
|
|
721
|
-
program2.command("_daemon <agent-
|
|
722
|
-
runDaemon(
|
|
742
|
+
program2.command("_daemon <agent-id>", { hidden: true }).action((agentId) => {
|
|
743
|
+
runDaemon(agentId);
|
|
723
744
|
});
|
|
724
745
|
program2.command("connect <agent-name>").description("Connect a local agent to BoxCrew.").option("--claude-path <path>", "Path to claude CLI binary", "claude").action(async (agentName, options) => {
|
|
725
|
-
const
|
|
746
|
+
const config2 = await apiFetchJson(
|
|
747
|
+
`/agents/${encodeURIComponent(agentName)}/connection-config`
|
|
748
|
+
);
|
|
749
|
+
const agentId = config2.agent_id;
|
|
750
|
+
if (!existsSync(getMetaFile(agentId)) && existsSync(getMetaFile(agentName))) {
|
|
751
|
+
for (const ext of [".meta.json", ".pid", ".log"]) {
|
|
752
|
+
const oldPath = join(getStateDir(), `${agentName}${ext}`);
|
|
753
|
+
const newPath = join(getStateDir(), `${agentId}${ext}`);
|
|
754
|
+
if (existsSync(oldPath)) renameSync(oldPath, newPath);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const existingPid = readPid(agentId);
|
|
726
758
|
if (existingPid) {
|
|
727
759
|
console.log(`Agent "${agentName}" is already connected (PID ${existingPid}).`);
|
|
728
760
|
return;
|
|
729
761
|
}
|
|
730
|
-
const
|
|
731
|
-
`/agents/${encodeURIComponent(agentName)}/connection-config`
|
|
732
|
-
);
|
|
733
|
-
const logFile = getLogFile(agentName);
|
|
762
|
+
const logFile = getLogFile(agentId);
|
|
734
763
|
const logFd = openSync(logFile, "a");
|
|
735
764
|
const daemonEnv = {
|
|
736
765
|
...process.env,
|
|
@@ -741,7 +770,7 @@ function registerConnectCommand(program2) {
|
|
|
741
770
|
};
|
|
742
771
|
delete daemonEnv.CLAUDECODE;
|
|
743
772
|
delete daemonEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
744
|
-
const child = spawn(process.argv[0], [process.argv[1], "_daemon",
|
|
773
|
+
const child = spawn(process.argv[0], [process.argv[1], "_daemon", agentId], {
|
|
745
774
|
detached: true,
|
|
746
775
|
stdio: ["ignore", logFd, logFd],
|
|
747
776
|
env: daemonEnv
|
|
@@ -751,11 +780,12 @@ function registerConnectCommand(program2) {
|
|
|
751
780
|
pid: child.pid,
|
|
752
781
|
cwd: process.cwd(),
|
|
753
782
|
runtime: config2.runtime,
|
|
783
|
+
agentId,
|
|
754
784
|
agentName: config2.agent_name,
|
|
755
785
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
756
786
|
claudePath: options.claudePath
|
|
757
787
|
};
|
|
758
|
-
writeFileSync(getMetaFile(
|
|
788
|
+
writeFileSync(getMetaFile(agentId), JSON.stringify(meta, null, 2));
|
|
759
789
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
760
790
|
const cwd = process.cwd();
|
|
761
791
|
const home = homedir();
|
|
@@ -771,7 +801,12 @@ function registerConnectCommand(program2) {
|
|
|
771
801
|
console.log("");
|
|
772
802
|
});
|
|
773
803
|
program2.command("disconnect <agent-name>").description("Disconnect a local agent.").action((agentName) => {
|
|
774
|
-
const
|
|
804
|
+
const key = resolveAgentKey(agentName);
|
|
805
|
+
if (!key) {
|
|
806
|
+
console.log(`Agent "${agentName}" is not connected.`);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const pid = readPid(key);
|
|
775
810
|
if (!pid) {
|
|
776
811
|
console.log(`Agent "${agentName}" is not connected.`);
|
|
777
812
|
return;
|
|
@@ -779,7 +814,7 @@ function registerConnectCommand(program2) {
|
|
|
779
814
|
try {
|
|
780
815
|
process.kill(pid, "SIGTERM");
|
|
781
816
|
try {
|
|
782
|
-
unlinkSync(getPidFile(
|
|
817
|
+
unlinkSync(getPidFile(key));
|
|
783
818
|
} catch {
|
|
784
819
|
}
|
|
785
820
|
console.log(`Agent "${agentName}" disconnected.`);
|
|
@@ -798,13 +833,13 @@ function registerConnectCommand(program2) {
|
|
|
798
833
|
const shortenPath = (p) => p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
799
834
|
const rows = [];
|
|
800
835
|
for (const file of metaFiles) {
|
|
801
|
-
const
|
|
802
|
-
const meta = readMeta(
|
|
836
|
+
const key = file.replace(".meta.json", "");
|
|
837
|
+
const meta = readMeta(key);
|
|
803
838
|
if (!meta) continue;
|
|
804
|
-
const pid = readPid(
|
|
839
|
+
const pid = readPid(key);
|
|
805
840
|
const isOnline = pid !== null;
|
|
806
841
|
rows.push({
|
|
807
|
-
agent: meta.agentName ||
|
|
842
|
+
agent: meta.agentName || key,
|
|
808
843
|
status: isOnline ? "online" : "offline",
|
|
809
844
|
runtime: RUNTIME_NAMES[meta.runtime] || meta.runtime,
|
|
810
845
|
directory: shortenPath(meta.cwd),
|
|
@@ -836,8 +871,8 @@ function registerConnectCommand(program2) {
|
|
|
836
871
|
return;
|
|
837
872
|
}
|
|
838
873
|
for (const file of metaFiles) {
|
|
839
|
-
const
|
|
840
|
-
await reconnectAgent(
|
|
874
|
+
const key2 = file.replace(".meta.json", "");
|
|
875
|
+
await reconnectAgent(key2, options.claudePath);
|
|
841
876
|
}
|
|
842
877
|
return;
|
|
843
878
|
}
|
|
@@ -845,30 +880,36 @@ function registerConnectCommand(program2) {
|
|
|
845
880
|
console.error("Please specify an agent name or use --all.");
|
|
846
881
|
process.exit(1);
|
|
847
882
|
}
|
|
848
|
-
|
|
883
|
+
const key = resolveAgentKey(agentName);
|
|
884
|
+
if (!key) {
|
|
885
|
+
console.error(`No previous connection found for "${agentName}". Use \`bx connect ${agentName}\` instead.`);
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
await reconnectAgent(key, options.claudePath);
|
|
849
889
|
});
|
|
850
|
-
async function reconnectAgent(
|
|
851
|
-
const meta = readMeta(
|
|
890
|
+
async function reconnectAgent(key, claudePathOverride) {
|
|
891
|
+
const meta = readMeta(key);
|
|
852
892
|
if (!meta) {
|
|
853
|
-
console.error(`No previous connection found for "${
|
|
893
|
+
console.error(`No previous connection found for "${key}". Use \`bx connect <agent-name>\` instead.`);
|
|
854
894
|
return;
|
|
855
895
|
}
|
|
856
|
-
const existingPid = readPid(
|
|
896
|
+
const existingPid = readPid(key);
|
|
857
897
|
if (existingPid) {
|
|
858
898
|
try {
|
|
859
899
|
process.kill(existingPid, "SIGTERM");
|
|
860
900
|
} catch {
|
|
861
901
|
}
|
|
862
902
|
try {
|
|
863
|
-
unlinkSync(getPidFile(
|
|
903
|
+
unlinkSync(getPidFile(key));
|
|
864
904
|
} catch {
|
|
865
905
|
}
|
|
866
906
|
}
|
|
907
|
+
const agentId = meta.agentId || key;
|
|
867
908
|
const config2 = await apiFetchJson(
|
|
868
|
-
`/agents/${encodeURIComponent(
|
|
909
|
+
`/agents/${encodeURIComponent(agentId)}/connection-config`
|
|
869
910
|
);
|
|
870
911
|
const claudePath = claudePathOverride || meta.claudePath || "claude";
|
|
871
|
-
const logFile = getLogFile(
|
|
912
|
+
const logFile = getLogFile(key);
|
|
872
913
|
const logFd = openSync(logFile, "a");
|
|
873
914
|
const daemonEnv = {
|
|
874
915
|
...process.env,
|
|
@@ -879,7 +920,7 @@ function registerConnectCommand(program2) {
|
|
|
879
920
|
};
|
|
880
921
|
delete daemonEnv.CLAUDECODE;
|
|
881
922
|
delete daemonEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
882
|
-
const child = spawn(process.argv[0], [process.argv[1], "_daemon",
|
|
923
|
+
const child = spawn(process.argv[0], [process.argv[1], "_daemon", key], {
|
|
883
924
|
detached: true,
|
|
884
925
|
stdio: ["ignore", logFd, logFd],
|
|
885
926
|
cwd: meta.cwd,
|
|
@@ -890,11 +931,12 @@ function registerConnectCommand(program2) {
|
|
|
890
931
|
pid: child.pid,
|
|
891
932
|
cwd: meta.cwd,
|
|
892
933
|
runtime: config2.runtime,
|
|
934
|
+
agentId: config2.agent_id,
|
|
893
935
|
agentName: config2.agent_name,
|
|
894
936
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
895
937
|
claudePath
|
|
896
938
|
};
|
|
897
|
-
writeFileSync(getMetaFile(
|
|
939
|
+
writeFileSync(getMetaFile(key), JSON.stringify(newMeta, null, 2));
|
|
898
940
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
899
941
|
const home = homedir();
|
|
900
942
|
const shortCwd = meta.cwd.startsWith(home) ? "~" + meta.cwd.slice(home.length) : meta.cwd;
|
|
@@ -910,7 +952,9 @@ function registerConnectCommand(program2) {
|
|
|
910
952
|
}
|
|
911
953
|
|
|
912
954
|
// src/index.ts
|
|
913
|
-
var
|
|
955
|
+
var require2 = createRequire(import.meta.url);
|
|
956
|
+
var { version } = require2("../package.json");
|
|
957
|
+
var program = new Command().name("bx").description("BoxCrew CLI \u2014 manage your agents from the terminal").version(version);
|
|
914
958
|
registerLoginCommand(program);
|
|
915
959
|
registerLogoutCommand(program);
|
|
916
960
|
registerAgentsCommands(program);
|