claude-code-controller 0.5.1 → 0.6.0

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.
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import { C as ClaudeCodeController, j as PermissionPreset, E as TaskStatus, a as LogLevel, d as AgentType } from '../claude-0cg912ch.cjs';
2
+ import { C as ClaudeCodeController, k as PermissionPreset, F as TaskStatus, b as LogLevel, e as AgentType } from '../claude-B7-oBjuE.cjs';
3
3
  import 'node:events';
4
4
 
5
5
  interface PendingApproval {
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono';
2
- import { C as ClaudeCodeController, j as PermissionPreset, E as TaskStatus, a as LogLevel, d as AgentType } from '../claude-0cg912ch.js';
2
+ import { C as ClaudeCodeController, k as PermissionPreset, F as TaskStatus, b as LogLevel, e as AgentType } from '../claude-B7-oBjuE.js';
3
3
  import 'node:events';
4
4
 
5
5
  interface PendingApproval {
package/dist/api/index.js CHANGED
@@ -106,11 +106,11 @@ var ActionTracker = class {
106
106
  import { Hono } from "hono";
107
107
 
108
108
  // src/controller.ts
109
- import { EventEmitter } from "events";
109
+ import { EventEmitter as EventEmitter2 } from "events";
110
110
  import { execSync as execSync2 } from "child_process";
111
111
  import { randomUUID as randomUUID2 } from "crypto";
112
- import { mkdirSync, existsSync as existsSync4, writeFileSync } from "fs";
113
- import { join as join2 } from "path";
112
+ import { mkdirSync as mkdirSync2, existsSync as existsSync5, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
113
+ import { join as join3 } from "path";
114
114
 
115
115
  // src/team-manager.ts
116
116
  import { readFile, writeFile, mkdir, rm } from "fs/promises";
@@ -799,6 +799,135 @@ function createLogger(level = "info") {
799
799
  };
800
800
  }
801
801
 
802
+ // src/statusline-capture.ts
803
+ import { EventEmitter } from "events";
804
+ import { watch, existsSync as existsSync4, mkdirSync, readFileSync, writeFileSync } from "fs";
805
+ import { join as join2 } from "path";
806
+ function statusLineDir(teamName) {
807
+ return join2(teamDir(teamName), "statusline");
808
+ }
809
+ function statusLineLogPath(teamName, agentName) {
810
+ return join2(statusLineDir(teamName), `${agentName}.jsonl`);
811
+ }
812
+ function buildStatusLineCommand(logFilePath) {
813
+ const escapedPath = logFilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
814
+ return [
815
+ "python3 -c",
816
+ `'import sys,json;`,
817
+ `d=json.load(sys.stdin);`,
818
+ `open("${escapedPath}","a").write(json.dumps(d)+"\\n");`,
819
+ `print(d.get("model",{}).get("display_name",""))'`
820
+ ].join(" ");
821
+ }
822
+ function buildStatusLineSettings(logFilePath) {
823
+ return {
824
+ statusLine: {
825
+ type: "command",
826
+ command: buildStatusLineCommand(logFilePath)
827
+ }
828
+ };
829
+ }
830
+ var StatusLineWatcher = class extends EventEmitter {
831
+ teamName;
832
+ log;
833
+ watchers = /* @__PURE__ */ new Map();
834
+ fileOffsets = /* @__PURE__ */ new Map();
835
+ stopped = false;
836
+ constructor(teamName, logger) {
837
+ super();
838
+ this.teamName = teamName;
839
+ this.log = logger;
840
+ }
841
+ /**
842
+ * Ensure the statusline directory exists.
843
+ */
844
+ ensureDir() {
845
+ const dir = statusLineDir(this.teamName);
846
+ if (!existsSync4(dir)) {
847
+ mkdirSync(dir, { recursive: true });
848
+ }
849
+ }
850
+ /**
851
+ * Start watching a specific agent's statusLine log file.
852
+ */
853
+ watchAgent(agentName) {
854
+ if (this.stopped) return;
855
+ const filePath = statusLineLogPath(this.teamName, agentName);
856
+ if (!existsSync4(filePath)) {
857
+ writeFileSync(filePath, "");
858
+ }
859
+ try {
860
+ const stats = readFileSync(filePath);
861
+ this.fileOffsets.set(filePath, stats.length);
862
+ } catch {
863
+ this.fileOffsets.set(filePath, 0);
864
+ }
865
+ try {
866
+ const watcher = watch(filePath, (eventType) => {
867
+ if (eventType === "change") {
868
+ this.readNewLines(agentName, filePath);
869
+ }
870
+ });
871
+ this.watchers.set(agentName, watcher);
872
+ this.log.debug(`Watching statusLine for agent "${agentName}" at ${filePath}`);
873
+ } catch (err) {
874
+ this.log.error(`Failed to watch statusLine for "${agentName}": ${err}`);
875
+ }
876
+ }
877
+ /**
878
+ * Stop watching a specific agent.
879
+ */
880
+ unwatchAgent(agentName) {
881
+ const watcher = this.watchers.get(agentName);
882
+ if (watcher) {
883
+ watcher.close();
884
+ this.watchers.delete(agentName);
885
+ }
886
+ const filePath = statusLineLogPath(this.teamName, agentName);
887
+ this.fileOffsets.delete(filePath);
888
+ }
889
+ /**
890
+ * Stop all watchers.
891
+ */
892
+ stop() {
893
+ this.stopped = true;
894
+ for (const [, watcher] of this.watchers) {
895
+ watcher.close();
896
+ }
897
+ this.watchers.clear();
898
+ this.fileOffsets.clear();
899
+ }
900
+ /**
901
+ * Read new lines appended to the log file since last read.
902
+ */
903
+ readNewLines(agentName, filePath) {
904
+ try {
905
+ const content = readFileSync(filePath, "utf-8");
906
+ const offset = this.fileOffsets.get(filePath) ?? 0;
907
+ const newContent = content.slice(offset);
908
+ this.fileOffsets.set(filePath, content.length);
909
+ if (!newContent.trim()) return;
910
+ const lines = newContent.trim().split("\n");
911
+ for (const line of lines) {
912
+ if (!line.trim()) continue;
913
+ try {
914
+ const data = JSON.parse(line);
915
+ const event = {
916
+ agentName,
917
+ data,
918
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
919
+ };
920
+ this.emit("update", event);
921
+ } catch (parseErr) {
922
+ this.log.debug(`Failed to parse statusLine JSON: ${line}`);
923
+ }
924
+ }
925
+ } catch (err) {
926
+ this.log.debug(`Error reading statusLine file: ${err}`);
927
+ }
928
+ }
929
+ };
930
+
802
931
  // src/controller.ts
803
932
  var PROTOCOL_ONLY_TYPES = /* @__PURE__ */ new Set([
804
933
  "shutdown_approved",
@@ -818,12 +947,13 @@ var AGENT_COLORS = [
818
947
  "#FF69B4",
819
948
  "#7B68EE"
820
949
  ];
821
- var ClaudeCodeController = class extends EventEmitter {
950
+ var ClaudeCodeController = class extends EventEmitter2 {
822
951
  teamName;
823
952
  team;
824
953
  tasks;
825
954
  processes;
826
955
  poller;
956
+ statusLineWatcher;
827
957
  log;
828
958
  cwd;
829
959
  claudeBinary;
@@ -845,7 +975,11 @@ var ClaudeCodeController = class extends EventEmitter {
845
975
  "controller",
846
976
  this.log
847
977
  );
978
+ this.statusLineWatcher = new StatusLineWatcher(this.teamName, this.log);
848
979
  this.poller.onMessages((events) => this.handlePollEvents(events));
980
+ this.statusLineWatcher.on("update", (event) => {
981
+ this.emit("agent:statusline", event.agentName, event.data);
982
+ });
849
983
  }
850
984
  // ─── Lifecycle ───────────────────────────────────────────────────────
851
985
  /**
@@ -856,6 +990,7 @@ var ClaudeCodeController = class extends EventEmitter {
856
990
  if (this.initialized) return this;
857
991
  await this.team.create({ cwd: this.cwd });
858
992
  await this.tasks.init();
993
+ this.statusLineWatcher.ensureDir();
859
994
  this.poller.start();
860
995
  this.initialized = true;
861
996
  this.log.info(
@@ -896,6 +1031,7 @@ var ClaudeCodeController = class extends EventEmitter {
896
1031
  }
897
1032
  await this.processes.killAll();
898
1033
  this.poller.stop();
1034
+ this.statusLineWatcher.stop();
899
1035
  await this.team.destroy();
900
1036
  this.initialized = false;
901
1037
  this.log.info("Controller shut down");
@@ -924,7 +1060,8 @@ var ClaudeCodeController = class extends EventEmitter {
924
1060
  subscriptions: []
925
1061
  };
926
1062
  await this.team.addMember(member);
927
- this.ensureWorkspaceTrusted(cwd);
1063
+ this.ensureWorkspaceTrusted(cwd, opts.name);
1064
+ this.statusLineWatcher.watchAgent(opts.name);
928
1065
  const env = Object.keys(this.defaultEnv).length > 0 || opts.env ? { ...this.defaultEnv, ...opts.env } : void 0;
929
1066
  const proc = this.processes.spawn({
930
1067
  teamName: this.teamName,
@@ -1131,6 +1268,7 @@ var ClaudeCodeController = class extends EventEmitter {
1131
1268
  * Kill a specific agent.
1132
1269
  */
1133
1270
  async killAgent(name) {
1271
+ this.statusLineWatcher.unwatchAgent(name);
1134
1272
  await this.processes.kill(name);
1135
1273
  await this.team.removeMember(name);
1136
1274
  }
@@ -1195,15 +1333,28 @@ var ClaudeCodeController = class extends EventEmitter {
1195
1333
  /**
1196
1334
  * Ensure the agent's cwd has a .claude/settings.local.json so the
1197
1335
  * CLI skips the interactive workspace trust prompt.
1336
+ * Also injects statusLine capture configuration.
1198
1337
  */
1199
- ensureWorkspaceTrusted(cwd) {
1200
- const claudeDir = join2(cwd, ".claude");
1201
- const settingsPath = join2(claudeDir, "settings.local.json");
1202
- if (!existsSync4(settingsPath)) {
1203
- mkdirSync(claudeDir, { recursive: true });
1204
- writeFileSync(settingsPath, "{}\n");
1205
- this.log.debug(`Created ${settingsPath} for workspace trust`);
1338
+ ensureWorkspaceTrusted(cwd, agentName) {
1339
+ const claudeDir = join3(cwd, ".claude");
1340
+ const settingsPath = join3(claudeDir, "settings.local.json");
1341
+ mkdirSync2(claudeDir, { recursive: true });
1342
+ let settings = {};
1343
+ if (existsSync5(settingsPath)) {
1344
+ try {
1345
+ settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
1346
+ } catch {
1347
+ settings = {};
1348
+ }
1206
1349
  }
1350
+ if (agentName && !settings.statusLine) {
1351
+ const logPath = statusLineLogPath(this.teamName, agentName);
1352
+ const statusLineSettings = buildStatusLineSettings(logPath);
1353
+ settings = { ...settings, ...statusLineSettings };
1354
+ this.log.debug(`Injected statusLine capture for "${agentName}"`);
1355
+ }
1356
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1357
+ this.log.debug(`Updated ${settingsPath}`);
1207
1358
  }
1208
1359
  ensureInitialized() {
1209
1360
  if (!this.initialized) {
@@ -1218,7 +1369,7 @@ function sleep2(ms) {
1218
1369
  }
1219
1370
 
1220
1371
  // src/claude.ts
1221
- import { EventEmitter as EventEmitter2 } from "events";
1372
+ import { EventEmitter as EventEmitter3 } from "events";
1222
1373
  import { randomUUID as randomUUID3 } from "crypto";
1223
1374
  Symbol.asyncDispose ??= /* @__PURE__ */ Symbol("Symbol.asyncDispose");
1224
1375
  function buildEnv(opts) {
@@ -1291,7 +1442,7 @@ function waitForReady(controller, agentName, timeoutMs = 15e3) {
1291
1442
  controller.on("agent:exited", onExit);
1292
1443
  });
1293
1444
  }
1294
- var Agent = class _Agent extends EventEmitter2 {
1445
+ var Agent = class _Agent extends EventEmitter3 {
1295
1446
  controller;
1296
1447
  handle;
1297
1448
  ownsController;
@@ -1491,6 +1642,9 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1491
1642
  const onIdle = (name, _details) => {
1492
1643
  if (name === agentName) this.emit("idle");
1493
1644
  };
1645
+ const onStatusLine = (name, data) => {
1646
+ if (name === agentName) this.emit("statusline", data);
1647
+ };
1494
1648
  const onPermission = (name, parsed) => {
1495
1649
  if (name !== agentName) return;
1496
1650
  let handled = false;
@@ -1539,6 +1693,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1539
1693
  };
1540
1694
  this.controller.on("message", onMessage);
1541
1695
  this.controller.on("idle", onIdle);
1696
+ this.controller.on("agent:statusline", onStatusLine);
1542
1697
  this.controller.on("permission:request", onPermission);
1543
1698
  this.controller.on("plan:approval_request", onPlan);
1544
1699
  this.controller.on("agent:exited", onExit);
@@ -1546,6 +1701,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1546
1701
  this.boundListeners = [
1547
1702
  { event: "message", fn: onMessage },
1548
1703
  { event: "idle", fn: onIdle },
1704
+ { event: "agent:statusline", fn: onStatusLine },
1549
1705
  { event: "permission:request", fn: onPermission },
1550
1706
  { event: "plan:approval_request", fn: onPlan },
1551
1707
  { event: "agent:exited", fn: onExit },