@boxcrew/cli 0.1.14 → 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 +94 -44
- 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,9 +360,9 @@ 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
|
-
import { homedir } from "os";
|
|
365
|
+
import { homedir, platform, release, hostname } from "os";
|
|
365
366
|
import WebSocket from "ws";
|
|
366
367
|
var RECONNECT_BASE_MS = 1e3;
|
|
367
368
|
var RECONNECT_MAX_MS = 3e4;
|
|
@@ -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
|
};
|
|
@@ -649,6 +662,12 @@ function runDaemon(agentName) {
|
|
|
649
662
|
reconnectAttempt = 0;
|
|
650
663
|
sendToServer = send;
|
|
651
664
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Connected`);
|
|
665
|
+
send({
|
|
666
|
+
type: "hello",
|
|
667
|
+
os: `${platform()} ${release()}`,
|
|
668
|
+
cwd: process.cwd(),
|
|
669
|
+
hostname: hostname()
|
|
670
|
+
});
|
|
652
671
|
while (eventBuffer.length > 0) {
|
|
653
672
|
const msg = eventBuffer.shift();
|
|
654
673
|
send(msg);
|
|
@@ -672,6 +691,14 @@ function runDaemon(agentName) {
|
|
|
672
691
|
if (msg.type === "chat") {
|
|
673
692
|
handleChat(msg);
|
|
674
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
|
+
}
|
|
675
702
|
});
|
|
676
703
|
ws.on("close", (code, reason) => {
|
|
677
704
|
sendToServer = null;
|
|
@@ -712,19 +739,27 @@ function runDaemon(agentName) {
|
|
|
712
739
|
connect();
|
|
713
740
|
}
|
|
714
741
|
function registerConnectCommand(program2) {
|
|
715
|
-
program2.command("_daemon <agent-
|
|
716
|
-
runDaemon(
|
|
742
|
+
program2.command("_daemon <agent-id>", { hidden: true }).action((agentId) => {
|
|
743
|
+
runDaemon(agentId);
|
|
717
744
|
});
|
|
718
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) => {
|
|
719
|
-
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);
|
|
720
758
|
if (existingPid) {
|
|
721
759
|
console.log(`Agent "${agentName}" is already connected (PID ${existingPid}).`);
|
|
722
760
|
return;
|
|
723
761
|
}
|
|
724
|
-
const
|
|
725
|
-
`/agents/${encodeURIComponent(agentName)}/connection-config`
|
|
726
|
-
);
|
|
727
|
-
const logFile = getLogFile(agentName);
|
|
762
|
+
const logFile = getLogFile(agentId);
|
|
728
763
|
const logFd = openSync(logFile, "a");
|
|
729
764
|
const daemonEnv = {
|
|
730
765
|
...process.env,
|
|
@@ -735,7 +770,7 @@ function registerConnectCommand(program2) {
|
|
|
735
770
|
};
|
|
736
771
|
delete daemonEnv.CLAUDECODE;
|
|
737
772
|
delete daemonEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
738
|
-
const child = spawn(process.argv[0], [process.argv[1], "_daemon",
|
|
773
|
+
const child = spawn(process.argv[0], [process.argv[1], "_daemon", agentId], {
|
|
739
774
|
detached: true,
|
|
740
775
|
stdio: ["ignore", logFd, logFd],
|
|
741
776
|
env: daemonEnv
|
|
@@ -745,11 +780,12 @@ function registerConnectCommand(program2) {
|
|
|
745
780
|
pid: child.pid,
|
|
746
781
|
cwd: process.cwd(),
|
|
747
782
|
runtime: config2.runtime,
|
|
783
|
+
agentId,
|
|
748
784
|
agentName: config2.agent_name,
|
|
749
785
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
750
786
|
claudePath: options.claudePath
|
|
751
787
|
};
|
|
752
|
-
writeFileSync(getMetaFile(
|
|
788
|
+
writeFileSync(getMetaFile(agentId), JSON.stringify(meta, null, 2));
|
|
753
789
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
754
790
|
const cwd = process.cwd();
|
|
755
791
|
const home = homedir();
|
|
@@ -765,7 +801,12 @@ function registerConnectCommand(program2) {
|
|
|
765
801
|
console.log("");
|
|
766
802
|
});
|
|
767
803
|
program2.command("disconnect <agent-name>").description("Disconnect a local agent.").action((agentName) => {
|
|
768
|
-
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);
|
|
769
810
|
if (!pid) {
|
|
770
811
|
console.log(`Agent "${agentName}" is not connected.`);
|
|
771
812
|
return;
|
|
@@ -773,7 +814,7 @@ function registerConnectCommand(program2) {
|
|
|
773
814
|
try {
|
|
774
815
|
process.kill(pid, "SIGTERM");
|
|
775
816
|
try {
|
|
776
|
-
unlinkSync(getPidFile(
|
|
817
|
+
unlinkSync(getPidFile(key));
|
|
777
818
|
} catch {
|
|
778
819
|
}
|
|
779
820
|
console.log(`Agent "${agentName}" disconnected.`);
|
|
@@ -792,13 +833,13 @@ function registerConnectCommand(program2) {
|
|
|
792
833
|
const shortenPath = (p) => p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
793
834
|
const rows = [];
|
|
794
835
|
for (const file of metaFiles) {
|
|
795
|
-
const
|
|
796
|
-
const meta = readMeta(
|
|
836
|
+
const key = file.replace(".meta.json", "");
|
|
837
|
+
const meta = readMeta(key);
|
|
797
838
|
if (!meta) continue;
|
|
798
|
-
const pid = readPid(
|
|
839
|
+
const pid = readPid(key);
|
|
799
840
|
const isOnline = pid !== null;
|
|
800
841
|
rows.push({
|
|
801
|
-
agent: meta.agentName ||
|
|
842
|
+
agent: meta.agentName || key,
|
|
802
843
|
status: isOnline ? "online" : "offline",
|
|
803
844
|
runtime: RUNTIME_NAMES[meta.runtime] || meta.runtime,
|
|
804
845
|
directory: shortenPath(meta.cwd),
|
|
@@ -830,8 +871,8 @@ function registerConnectCommand(program2) {
|
|
|
830
871
|
return;
|
|
831
872
|
}
|
|
832
873
|
for (const file of metaFiles) {
|
|
833
|
-
const
|
|
834
|
-
await reconnectAgent(
|
|
874
|
+
const key2 = file.replace(".meta.json", "");
|
|
875
|
+
await reconnectAgent(key2, options.claudePath);
|
|
835
876
|
}
|
|
836
877
|
return;
|
|
837
878
|
}
|
|
@@ -839,30 +880,36 @@ function registerConnectCommand(program2) {
|
|
|
839
880
|
console.error("Please specify an agent name or use --all.");
|
|
840
881
|
process.exit(1);
|
|
841
882
|
}
|
|
842
|
-
|
|
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);
|
|
843
889
|
});
|
|
844
|
-
async function reconnectAgent(
|
|
845
|
-
const meta = readMeta(
|
|
890
|
+
async function reconnectAgent(key, claudePathOverride) {
|
|
891
|
+
const meta = readMeta(key);
|
|
846
892
|
if (!meta) {
|
|
847
|
-
console.error(`No previous connection found for "${
|
|
893
|
+
console.error(`No previous connection found for "${key}". Use \`bx connect <agent-name>\` instead.`);
|
|
848
894
|
return;
|
|
849
895
|
}
|
|
850
|
-
const existingPid = readPid(
|
|
896
|
+
const existingPid = readPid(key);
|
|
851
897
|
if (existingPid) {
|
|
852
898
|
try {
|
|
853
899
|
process.kill(existingPid, "SIGTERM");
|
|
854
900
|
} catch {
|
|
855
901
|
}
|
|
856
902
|
try {
|
|
857
|
-
unlinkSync(getPidFile(
|
|
903
|
+
unlinkSync(getPidFile(key));
|
|
858
904
|
} catch {
|
|
859
905
|
}
|
|
860
906
|
}
|
|
907
|
+
const agentId = meta.agentId || key;
|
|
861
908
|
const config2 = await apiFetchJson(
|
|
862
|
-
`/agents/${encodeURIComponent(
|
|
909
|
+
`/agents/${encodeURIComponent(agentId)}/connection-config`
|
|
863
910
|
);
|
|
864
911
|
const claudePath = claudePathOverride || meta.claudePath || "claude";
|
|
865
|
-
const logFile = getLogFile(
|
|
912
|
+
const logFile = getLogFile(key);
|
|
866
913
|
const logFd = openSync(logFile, "a");
|
|
867
914
|
const daemonEnv = {
|
|
868
915
|
...process.env,
|
|
@@ -873,7 +920,7 @@ function registerConnectCommand(program2) {
|
|
|
873
920
|
};
|
|
874
921
|
delete daemonEnv.CLAUDECODE;
|
|
875
922
|
delete daemonEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
876
|
-
const child = spawn(process.argv[0], [process.argv[1], "_daemon",
|
|
923
|
+
const child = spawn(process.argv[0], [process.argv[1], "_daemon", key], {
|
|
877
924
|
detached: true,
|
|
878
925
|
stdio: ["ignore", logFd, logFd],
|
|
879
926
|
cwd: meta.cwd,
|
|
@@ -884,11 +931,12 @@ function registerConnectCommand(program2) {
|
|
|
884
931
|
pid: child.pid,
|
|
885
932
|
cwd: meta.cwd,
|
|
886
933
|
runtime: config2.runtime,
|
|
934
|
+
agentId: config2.agent_id,
|
|
887
935
|
agentName: config2.agent_name,
|
|
888
936
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
889
937
|
claudePath
|
|
890
938
|
};
|
|
891
|
-
writeFileSync(getMetaFile(
|
|
939
|
+
writeFileSync(getMetaFile(key), JSON.stringify(newMeta, null, 2));
|
|
892
940
|
const runtimeDisplay = RUNTIME_NAMES[config2.runtime] || config2.runtime;
|
|
893
941
|
const home = homedir();
|
|
894
942
|
const shortCwd = meta.cwd.startsWith(home) ? "~" + meta.cwd.slice(home.length) : meta.cwd;
|
|
@@ -904,7 +952,9 @@ function registerConnectCommand(program2) {
|
|
|
904
952
|
}
|
|
905
953
|
|
|
906
954
|
// src/index.ts
|
|
907
|
-
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);
|
|
908
958
|
registerLoginCommand(program);
|
|
909
959
|
registerLogoutCommand(program);
|
|
910
960
|
registerAgentsCommands(program);
|