@boxcrew/cli 0.1.6 → 0.1.8

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 +132 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -451,10 +451,55 @@ function parseStreamJsonLine(line) {
451
451
  }
452
452
  return null;
453
453
  }
454
+ function parseOpenCodeLine(line) {
455
+ let event;
456
+ try {
457
+ event = JSON.parse(line);
458
+ } catch {
459
+ return null;
460
+ }
461
+ if (event.type === "text" && event.part?.type === "text" && event.part.text) {
462
+ return { kind: "text", text: event.part.text };
463
+ }
464
+ if (event.type) {
465
+ return { kind: "raw", raw: event, rawType: event.type };
466
+ }
467
+ return null;
468
+ }
469
+ function parseOpenClawOutput(stdout) {
470
+ try {
471
+ const firstBrace = stdout.indexOf("{");
472
+ if (firstBrace === -1) {
473
+ return [{ kind: "text", text: stdout.trim() || "No response" }];
474
+ }
475
+ const response = JSON.parse(stdout.slice(firstBrace));
476
+ const events = [];
477
+ events.push({ kind: "raw", raw: response, rawType: "openclaw_response" });
478
+ const sessionId = response.meta?.agentMeta?.sessionId;
479
+ if (sessionId) {
480
+ events.push({ kind: "session_id", sessionId });
481
+ }
482
+ const payloads = response.payloads;
483
+ if (Array.isArray(payloads)) {
484
+ const texts = payloads.map((p) => p.text).filter((t) => typeof t === "string" && t.length > 0);
485
+ if (texts.length > 0) {
486
+ events.push({ kind: "text", text: texts.join("\n\n") });
487
+ } else {
488
+ events.push({ kind: "text", text: "No response" });
489
+ }
490
+ } else {
491
+ events.push({ kind: "text", text: "No response" });
492
+ }
493
+ return events;
494
+ } catch {
495
+ return [{ kind: "text", text: stdout.trim() || "No response" }];
496
+ }
497
+ }
454
498
  function runDaemon(agentName) {
455
499
  const wsUrl = process.env._BX_WS_URL;
456
500
  const claudePath = process.env._BX_CLAUDE_PATH || "claude";
457
501
  const agentDisplayName = process.env._BX_AGENT_NAME || agentName;
502
+ const runtime = process.env._BX_RUNTIME || "claude-code";
458
503
  if (!wsUrl) {
459
504
  console.error("Missing _BX_WS_URL");
460
505
  process.exit(1);
@@ -470,34 +515,71 @@ function runDaemon(agentName) {
470
515
  activeProcess = null;
471
516
  }
472
517
  const { messageId, message, sessionId } = msg;
473
- const args = ["-p", message, "--output-format", "stream-json", "--verbose"];
474
- if (sessionId) args.push("--resume", sessionId);
475
518
  const childEnv = { ...process.env };
476
519
  delete childEnv.CLAUDECODE;
477
- const child = spawn(claudePath, args, {
478
- stdio: ["ignore", "pipe", "pipe"],
520
+ let cmd;
521
+ let args;
522
+ if (runtime === "opencode") {
523
+ cmd = "opencode";
524
+ args = ["run", "--format", "json"];
525
+ } else if (runtime === "openclaw") {
526
+ cmd = "openclaw";
527
+ args = ["agent", "--message", message, "--json", "--agent", "main", "--timeout", "300"];
528
+ } else {
529
+ cmd = claudePath;
530
+ args = ["-p", message, "--output-format", "stream-json", "--verbose"];
531
+ if (sessionId) args.push("--resume", sessionId);
532
+ }
533
+ const child = spawn(cmd, args, {
534
+ stdio: [runtime === "opencode" ? "pipe" : "ignore", "pipe", "pipe"],
479
535
  env: childEnv
480
536
  });
481
537
  activeProcess = child;
482
- const rl = createInterface3({ input: child.stdout });
483
- rl.on("line", (line) => {
484
- const event = parseStreamJsonLine(line);
485
- if (event && sendToServer) {
486
- sendToServer({ type: "event", messageId, event });
487
- }
488
- });
489
- child.on("exit", (code) => {
490
- if (activeProcess === child) activeProcess = null;
491
- if (sendToServer) {
492
- sendToServer({ type: "event", messageId, event: { kind: "done" } });
493
- }
494
- if (code && code !== 0) {
495
- console.error(`Claude Code exited with code ${code}`);
496
- sendToServer?.({ type: "error", messageId, error: `Claude Code exited with code ${code}` });
497
- }
498
- });
538
+ if (runtime === "opencode" && child.stdin) {
539
+ child.stdin.write(message);
540
+ child.stdin.end();
541
+ }
542
+ if (runtime === "openclaw") {
543
+ let stdout = "";
544
+ child.stdout.on("data", (chunk) => {
545
+ stdout += chunk.toString();
546
+ });
547
+ child.on("exit", (code) => {
548
+ if (activeProcess === child) activeProcess = null;
549
+ if (sendToServer) {
550
+ const events = parseOpenClawOutput(stdout);
551
+ for (const event of events) {
552
+ sendToServer({ type: "event", messageId, event });
553
+ }
554
+ sendToServer({ type: "event", messageId, event: { kind: "done" } });
555
+ }
556
+ if (code && code !== 0) {
557
+ console.error(`OpenClaw exited with code ${code}`);
558
+ sendToServer?.({ type: "error", messageId, error: `OpenClaw exited with code ${code}` });
559
+ }
560
+ });
561
+ } else {
562
+ const parseLine = runtime === "opencode" ? parseOpenCodeLine : parseStreamJsonLine;
563
+ const rl = createInterface3({ input: child.stdout });
564
+ rl.on("line", (line) => {
565
+ const event = parseLine(line);
566
+ if (event && sendToServer) {
567
+ sendToServer({ type: "event", messageId, event });
568
+ }
569
+ });
570
+ child.on("exit", (code) => {
571
+ if (activeProcess === child) activeProcess = null;
572
+ if (sendToServer) {
573
+ sendToServer({ type: "event", messageId, event: { kind: "done" } });
574
+ }
575
+ if (code && code !== 0) {
576
+ console.error(`${runtime} exited with code ${code}`);
577
+ sendToServer?.({ type: "error", messageId, error: `${runtime} exited with code ${code}` });
578
+ }
579
+ });
580
+ }
499
581
  child.on("error", (err) => {
500
- console.error(`Failed to spawn Claude Code: ${err.message}`);
582
+ console.error(`Failed to spawn ${runtime}: ${err.message}`);
501
583
  sendToServer?.({ type: "error", messageId, error: `Failed to spawn: ${err.message}` });
502
584
  });
503
585
  };
@@ -538,12 +620,18 @@ function runDaemon(agentName) {
538
620
  handleChat(msg);
539
621
  }
540
622
  });
541
- ws.on("close", () => {
623
+ ws.on("close", (code, reason) => {
542
624
  sendToServer = null;
543
625
  if (activeProcess) {
544
626
  activeProcess.kill("SIGTERM");
545
627
  activeProcess = null;
546
628
  }
629
+ if (code === 4006) {
630
+ console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Agent was deleted. Shutting down.`);
631
+ cleanup();
632
+ process.exit(0);
633
+ return;
634
+ }
547
635
  if (shouldReconnect) {
548
636
  const delay = Math.min(
549
637
  RECONNECT_BASE_MS * Math.pow(2, reconnectAttempt),
@@ -573,7 +661,7 @@ function registerConnectCommand(program2) {
573
661
  program2.command("_daemon <agent-name>", { hidden: true }).action((agentName) => {
574
662
  runDaemon(agentName);
575
663
  });
576
- program2.command("connect <agent-name>").description("Connect a local Claude Code instance to a BoxCrew agent.").option("--claude-path <path>", "Path to claude CLI binary", "claude").action(async (agentName, options) => {
664
+ 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) => {
577
665
  const existingPid = readPid(agentName);
578
666
  if (existingPid) {
579
667
  console.log(`Agent "${agentName}" is already connected (PID ${existingPid}).`);
@@ -591,12 +679,29 @@ function registerConnectCommand(program2) {
591
679
  ...process.env,
592
680
  _BX_WS_URL: config2.websocket_url,
593
681
  _BX_CLAUDE_PATH: options.claudePath,
594
- _BX_AGENT_NAME: config2.agent_name
682
+ _BX_AGENT_NAME: config2.agent_name,
683
+ _BX_RUNTIME: config2.runtime
595
684
  }
596
685
  });
597
686
  child.unref();
598
- console.log(`Agent "${config2.agent_name}" is online.`);
599
- console.log(`Logs: ${logFile}`);
687
+ const runtimeNames = {
688
+ "claude-code": "Claude Code",
689
+ "opencode": "OpenCode",
690
+ "openclaw": "OpenClaw"
691
+ };
692
+ const runtimeDisplay = runtimeNames[config2.runtime] || config2.runtime;
693
+ const cwd = process.cwd();
694
+ const home = homedir();
695
+ const shortLog = logFile.startsWith(home) ? "~" + logFile.slice(home.length) : logFile;
696
+ console.log("");
697
+ console.log(` Agent "${config2.agent_name}" is online.`);
698
+ console.log("");
699
+ console.log(` Runtime: ${runtimeDisplay}`);
700
+ console.log(` Directory: ${cwd}`);
701
+ console.log(` Logs: ${shortLog}`);
702
+ console.log("");
703
+ console.log(` To disconnect: npx @boxcrew/cli disconnect ${agentName}`);
704
+ console.log("");
600
705
  });
601
706
  program2.command("disconnect <agent-name>").description("Disconnect a local agent.").action((agentName) => {
602
707
  const pid = readPid(agentName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcrew/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "BoxCrew CLI — manage your agents from the terminal",
5
5
  "type": "module",
6
6
  "bin": {