@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.
Files changed (2) hide show
  1. package/dist/index.js +94 -44
  2. 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(agentName) {
379
- return join(getStateDir(), `${agentName}.pid`);
379
+ function getPidFile(key) {
380
+ return join(getStateDir(), `${key}.pid`);
380
381
  }
381
- function getMetaFile(agentName) {
382
- return join(getStateDir(), `${agentName}.meta.json`);
382
+ function getMetaFile(key) {
383
+ return join(getStateDir(), `${key}.meta.json`);
383
384
  }
384
- function readMeta(agentName) {
385
- const metaFile = getMetaFile(agentName);
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(agentName) {
394
- return join(getStateDir(), `${agentName}.log`);
395
+ function getLogFile(key) {
396
+ return join(getStateDir(), `${key}.log`);
395
397
  }
396
- function readPid(agentName) {
397
- const pidFile = getPidFile(agentName);
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(agentName) {
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 || agentName;
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(agentName), String(process.pid));
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(agentName));
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-name>", { hidden: true }).action((agentName) => {
716
- runDaemon(agentName);
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 existingPid = readPid(agentName);
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 config2 = await apiFetchJson(
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", agentName], {
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(agentName), JSON.stringify(meta, null, 2));
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 pid = readPid(agentName);
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(agentName));
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 agentName = file.replace(".meta.json", "");
796
- const meta = readMeta(agentName);
836
+ const key = file.replace(".meta.json", "");
837
+ const meta = readMeta(key);
797
838
  if (!meta) continue;
798
- const pid = readPid(agentName);
839
+ const pid = readPid(key);
799
840
  const isOnline = pid !== null;
800
841
  rows.push({
801
- agent: meta.agentName || 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 name = file.replace(".meta.json", "");
834
- await reconnectAgent(name, options.claudePath);
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
- await reconnectAgent(agentName, options.claudePath);
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(agentName, claudePathOverride) {
845
- const meta = readMeta(agentName);
890
+ async function reconnectAgent(key, claudePathOverride) {
891
+ const meta = readMeta(key);
846
892
  if (!meta) {
847
- console.error(`No previous connection found for "${agentName}". Use \`bx connect ${agentName}\` instead.`);
893
+ console.error(`No previous connection found for "${key}". Use \`bx connect <agent-name>\` instead.`);
848
894
  return;
849
895
  }
850
- const existingPid = readPid(agentName);
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(agentName));
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(agentName)}/connection-config`
909
+ `/agents/${encodeURIComponent(agentId)}/connection-config`
863
910
  );
864
911
  const claudePath = claudePathOverride || meta.claudePath || "claude";
865
- const logFile = getLogFile(agentName);
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", agentName], {
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(agentName), JSON.stringify(newMeta, null, 2));
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 program = new Command().name("bx").description("BoxCrew CLI \u2014 manage your agents from the terminal").version("0.1.0");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcrew/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "BoxCrew CLI — manage your agents from the terminal",
5
5
  "type": "module",
6
6
  "bin": {