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.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { L as Logger, I as InboxMessage, S as StructuredMessage, a as LogLevel } from './claude-0cg912ch.js';
2
- export { A as Agent, b as AgentEvents, c as AgentHandle, d as AgentType, e as AskOptions, C as ClaudeCodeController, f as ClaudeOptions, g as ControllerEvents, h as ControllerOptions, i as IdleNotificationMessage, P as PermissionMode, j as PermissionPreset, k as PermissionRequestInfo, l as PermissionRequestMessage, m as PermissionResponseMessage, n as PlainTextMessage, o as PlanApprovalRequestMessage, p as PlanApprovalResponseMessage, q as PlanRequestInfo, R as ReceiveOptions, r as SandboxPermissionRequestMessage, s as SandboxPermissionResponseMessage, t as Session, u as SessionAgentOptions, v as SessionOptions, w as ShutdownApprovedMessage, x as ShutdownRequestMessage, y as SpawnAgentOptions, T as TaskAssignmentMessage, z as TaskCompletedMessage, B as TaskFile, D as TaskManager, E as TaskStatus, F as TeamConfig, G as TeamManager, H as TeamMember, J as claude } from './claude-0cg912ch.js';
1
+ import { L as Logger, I as InboxMessage, S as StructuredMessage, a as StatusLineData, b as LogLevel } from './claude-B7-oBjuE.js';
2
+ export { A as Agent, c as AgentEvents, d as AgentHandle, e as AgentType, f as AskOptions, C as ClaudeCodeController, g as ClaudeOptions, h as ControllerEvents, i as ControllerOptions, j as IdleNotificationMessage, P as PermissionMode, k as PermissionPreset, l as PermissionRequestInfo, m as PermissionRequestMessage, n as PermissionResponseMessage, o as PlainTextMessage, p as PlanApprovalRequestMessage, q as PlanApprovalResponseMessage, r as PlanRequestInfo, R as ReceiveOptions, s as SandboxPermissionRequestMessage, t as SandboxPermissionResponseMessage, u as Session, v as SessionAgentOptions, w as SessionOptions, x as ShutdownApprovedMessage, y as ShutdownRequestMessage, z as SpawnAgentOptions, T as TaskAssignmentMessage, B as TaskCompletedMessage, D as TaskFile, E as TaskManager, F as TaskStatus, G as TeamConfig, H as TeamManager, J as TeamMember, K as claude } from './claude-B7-oBjuE.js';
3
3
  import { ChildProcess } from 'node:child_process';
4
- import 'node:events';
4
+ import { EventEmitter } from 'node:events';
5
5
 
6
6
  interface SpawnOptions {
7
7
  teamName: string;
@@ -117,6 +117,62 @@ declare function readUnread(teamName: string, agentName: string): Promise<InboxM
117
117
  */
118
118
  declare function parseMessage(msg: InboxMessage): StructuredMessage;
119
119
 
120
+ /**
121
+ * Directory where statusLine JSON logs are written per agent.
122
+ */
123
+ declare function statusLineDir(teamName: string): string;
124
+ declare function statusLineLogPath(teamName: string, agentName: string): string;
125
+ interface StatusLineEvent {
126
+ agentName: string;
127
+ data: StatusLineData;
128
+ timestamp: string;
129
+ }
130
+ interface StatusLineCaptureEvents {
131
+ update: [event: StatusLineEvent];
132
+ error: [error: Error];
133
+ }
134
+ /**
135
+ * Generates the statusLine capture script.
136
+ * This script receives JSON from Claude Code via stdin and appends it to a log file.
137
+ * It also outputs a minimal display for the TUI statusline bar.
138
+ */
139
+ declare function buildStatusLineCommand(logFilePath: string): string;
140
+ /**
141
+ * Generates the settings.local.json content with statusLine configuration.
142
+ */
143
+ declare function buildStatusLineSettings(logFilePath: string): Record<string, unknown>;
144
+ /**
145
+ * Watches statusLine log files for a team and emits parsed events.
146
+ */
147
+ declare class StatusLineWatcher extends EventEmitter<StatusLineCaptureEvents> {
148
+ private teamName;
149
+ private log;
150
+ private watchers;
151
+ private fileOffsets;
152
+ private stopped;
153
+ constructor(teamName: string, logger: Logger);
154
+ /**
155
+ * Ensure the statusline directory exists.
156
+ */
157
+ ensureDir(): void;
158
+ /**
159
+ * Start watching a specific agent's statusLine log file.
160
+ */
161
+ watchAgent(agentName: string): void;
162
+ /**
163
+ * Stop watching a specific agent.
164
+ */
165
+ unwatchAgent(agentName: string): void;
166
+ /**
167
+ * Stop all watchers.
168
+ */
169
+ stop(): void;
170
+ /**
171
+ * Read new lines appended to the log file since last read.
172
+ */
173
+ private readNewLines;
174
+ }
175
+
120
176
  declare function teamsDir(): string;
121
177
  declare function teamDir(teamName: string): string;
122
178
  declare function teamConfigPath(teamName: string): string;
@@ -129,4 +185,4 @@ declare function taskPath(teamName: string, taskId: string): string;
129
185
  declare function createLogger(level?: LogLevel): Logger;
130
186
  declare const silentLogger: Logger;
131
187
 
132
- export { InboxMessage, InboxPoller, LogLevel, Logger, ProcessManager, StructuredMessage, createLogger, inboxPath, inboxesDir, parseMessage, readInbox, readUnread, silentLogger, taskPath, tasksBaseDir, tasksDir, teamConfigPath, teamDir, teamsDir, writeInbox };
188
+ export { InboxMessage, InboxPoller, LogLevel, Logger, ProcessManager, type StatusLineCaptureEvents, StatusLineData, type StatusLineEvent, StatusLineWatcher, StructuredMessage, buildStatusLineCommand, buildStatusLineSettings, createLogger, inboxPath, inboxesDir, parseMessage, readInbox, readUnread, silentLogger, statusLineDir, statusLineLogPath, taskPath, tasksBaseDir, tasksDir, teamConfigPath, teamDir, teamsDir, writeInbox };
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // src/claude.ts
2
- import { EventEmitter as EventEmitter2 } from "events";
2
+ import { EventEmitter as EventEmitter3 } from "events";
3
3
  import { randomUUID as randomUUID3 } from "crypto";
4
4
 
5
5
  // src/controller.ts
6
- import { EventEmitter } from "events";
6
+ import { EventEmitter as EventEmitter2 } from "events";
7
7
  import { execSync as execSync2 } from "child_process";
8
8
  import { randomUUID as randomUUID2 } from "crypto";
9
- import { mkdirSync, existsSync as existsSync4, writeFileSync } from "fs";
10
- import { join as join2 } from "path";
9
+ import { mkdirSync as mkdirSync2, existsSync as existsSync5, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
10
+ import { join as join3 } from "path";
11
11
 
12
12
  // src/team-manager.ts
13
13
  import { readFile, writeFile, mkdir, rm } from "fs/promises";
@@ -712,6 +712,135 @@ var silentLogger = {
712
712
  }
713
713
  };
714
714
 
715
+ // src/statusline-capture.ts
716
+ import { EventEmitter } from "events";
717
+ import { watch, existsSync as existsSync4, mkdirSync, readFileSync, writeFileSync } from "fs";
718
+ import { join as join2 } from "path";
719
+ function statusLineDir(teamName) {
720
+ return join2(teamDir(teamName), "statusline");
721
+ }
722
+ function statusLineLogPath(teamName, agentName) {
723
+ return join2(statusLineDir(teamName), `${agentName}.jsonl`);
724
+ }
725
+ function buildStatusLineCommand(logFilePath) {
726
+ const escapedPath = logFilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
727
+ return [
728
+ "python3 -c",
729
+ `'import sys,json;`,
730
+ `d=json.load(sys.stdin);`,
731
+ `open("${escapedPath}","a").write(json.dumps(d)+"\\n");`,
732
+ `print(d.get("model",{}).get("display_name",""))'`
733
+ ].join(" ");
734
+ }
735
+ function buildStatusLineSettings(logFilePath) {
736
+ return {
737
+ statusLine: {
738
+ type: "command",
739
+ command: buildStatusLineCommand(logFilePath)
740
+ }
741
+ };
742
+ }
743
+ var StatusLineWatcher = class extends EventEmitter {
744
+ teamName;
745
+ log;
746
+ watchers = /* @__PURE__ */ new Map();
747
+ fileOffsets = /* @__PURE__ */ new Map();
748
+ stopped = false;
749
+ constructor(teamName, logger) {
750
+ super();
751
+ this.teamName = teamName;
752
+ this.log = logger;
753
+ }
754
+ /**
755
+ * Ensure the statusline directory exists.
756
+ */
757
+ ensureDir() {
758
+ const dir = statusLineDir(this.teamName);
759
+ if (!existsSync4(dir)) {
760
+ mkdirSync(dir, { recursive: true });
761
+ }
762
+ }
763
+ /**
764
+ * Start watching a specific agent's statusLine log file.
765
+ */
766
+ watchAgent(agentName) {
767
+ if (this.stopped) return;
768
+ const filePath = statusLineLogPath(this.teamName, agentName);
769
+ if (!existsSync4(filePath)) {
770
+ writeFileSync(filePath, "");
771
+ }
772
+ try {
773
+ const stats = readFileSync(filePath);
774
+ this.fileOffsets.set(filePath, stats.length);
775
+ } catch {
776
+ this.fileOffsets.set(filePath, 0);
777
+ }
778
+ try {
779
+ const watcher = watch(filePath, (eventType) => {
780
+ if (eventType === "change") {
781
+ this.readNewLines(agentName, filePath);
782
+ }
783
+ });
784
+ this.watchers.set(agentName, watcher);
785
+ this.log.debug(`Watching statusLine for agent "${agentName}" at ${filePath}`);
786
+ } catch (err) {
787
+ this.log.error(`Failed to watch statusLine for "${agentName}": ${err}`);
788
+ }
789
+ }
790
+ /**
791
+ * Stop watching a specific agent.
792
+ */
793
+ unwatchAgent(agentName) {
794
+ const watcher = this.watchers.get(agentName);
795
+ if (watcher) {
796
+ watcher.close();
797
+ this.watchers.delete(agentName);
798
+ }
799
+ const filePath = statusLineLogPath(this.teamName, agentName);
800
+ this.fileOffsets.delete(filePath);
801
+ }
802
+ /**
803
+ * Stop all watchers.
804
+ */
805
+ stop() {
806
+ this.stopped = true;
807
+ for (const [, watcher] of this.watchers) {
808
+ watcher.close();
809
+ }
810
+ this.watchers.clear();
811
+ this.fileOffsets.clear();
812
+ }
813
+ /**
814
+ * Read new lines appended to the log file since last read.
815
+ */
816
+ readNewLines(agentName, filePath) {
817
+ try {
818
+ const content = readFileSync(filePath, "utf-8");
819
+ const offset = this.fileOffsets.get(filePath) ?? 0;
820
+ const newContent = content.slice(offset);
821
+ this.fileOffsets.set(filePath, content.length);
822
+ if (!newContent.trim()) return;
823
+ const lines = newContent.trim().split("\n");
824
+ for (const line of lines) {
825
+ if (!line.trim()) continue;
826
+ try {
827
+ const data = JSON.parse(line);
828
+ const event = {
829
+ agentName,
830
+ data,
831
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
832
+ };
833
+ this.emit("update", event);
834
+ } catch (parseErr) {
835
+ this.log.debug(`Failed to parse statusLine JSON: ${line}`);
836
+ }
837
+ }
838
+ } catch (err) {
839
+ this.log.debug(`Error reading statusLine file: ${err}`);
840
+ }
841
+ }
842
+ };
843
+
715
844
  // src/controller.ts
716
845
  var PROTOCOL_ONLY_TYPES = /* @__PURE__ */ new Set([
717
846
  "shutdown_approved",
@@ -731,12 +860,13 @@ var AGENT_COLORS = [
731
860
  "#FF69B4",
732
861
  "#7B68EE"
733
862
  ];
734
- var ClaudeCodeController = class extends EventEmitter {
863
+ var ClaudeCodeController = class extends EventEmitter2 {
735
864
  teamName;
736
865
  team;
737
866
  tasks;
738
867
  processes;
739
868
  poller;
869
+ statusLineWatcher;
740
870
  log;
741
871
  cwd;
742
872
  claudeBinary;
@@ -758,7 +888,11 @@ var ClaudeCodeController = class extends EventEmitter {
758
888
  "controller",
759
889
  this.log
760
890
  );
891
+ this.statusLineWatcher = new StatusLineWatcher(this.teamName, this.log);
761
892
  this.poller.onMessages((events) => this.handlePollEvents(events));
893
+ this.statusLineWatcher.on("update", (event) => {
894
+ this.emit("agent:statusline", event.agentName, event.data);
895
+ });
762
896
  }
763
897
  // ─── Lifecycle ───────────────────────────────────────────────────────
764
898
  /**
@@ -769,6 +903,7 @@ var ClaudeCodeController = class extends EventEmitter {
769
903
  if (this.initialized) return this;
770
904
  await this.team.create({ cwd: this.cwd });
771
905
  await this.tasks.init();
906
+ this.statusLineWatcher.ensureDir();
772
907
  this.poller.start();
773
908
  this.initialized = true;
774
909
  this.log.info(
@@ -809,6 +944,7 @@ var ClaudeCodeController = class extends EventEmitter {
809
944
  }
810
945
  await this.processes.killAll();
811
946
  this.poller.stop();
947
+ this.statusLineWatcher.stop();
812
948
  await this.team.destroy();
813
949
  this.initialized = false;
814
950
  this.log.info("Controller shut down");
@@ -837,7 +973,8 @@ var ClaudeCodeController = class extends EventEmitter {
837
973
  subscriptions: []
838
974
  };
839
975
  await this.team.addMember(member);
840
- this.ensureWorkspaceTrusted(cwd);
976
+ this.ensureWorkspaceTrusted(cwd, opts.name);
977
+ this.statusLineWatcher.watchAgent(opts.name);
841
978
  const env = Object.keys(this.defaultEnv).length > 0 || opts.env ? { ...this.defaultEnv, ...opts.env } : void 0;
842
979
  const proc = this.processes.spawn({
843
980
  teamName: this.teamName,
@@ -1044,6 +1181,7 @@ var ClaudeCodeController = class extends EventEmitter {
1044
1181
  * Kill a specific agent.
1045
1182
  */
1046
1183
  async killAgent(name) {
1184
+ this.statusLineWatcher.unwatchAgent(name);
1047
1185
  await this.processes.kill(name);
1048
1186
  await this.team.removeMember(name);
1049
1187
  }
@@ -1108,15 +1246,28 @@ var ClaudeCodeController = class extends EventEmitter {
1108
1246
  /**
1109
1247
  * Ensure the agent's cwd has a .claude/settings.local.json so the
1110
1248
  * CLI skips the interactive workspace trust prompt.
1249
+ * Also injects statusLine capture configuration.
1111
1250
  */
1112
- ensureWorkspaceTrusted(cwd) {
1113
- const claudeDir = join2(cwd, ".claude");
1114
- const settingsPath = join2(claudeDir, "settings.local.json");
1115
- if (!existsSync4(settingsPath)) {
1116
- mkdirSync(claudeDir, { recursive: true });
1117
- writeFileSync(settingsPath, "{}\n");
1118
- this.log.debug(`Created ${settingsPath} for workspace trust`);
1251
+ ensureWorkspaceTrusted(cwd, agentName) {
1252
+ const claudeDir = join3(cwd, ".claude");
1253
+ const settingsPath = join3(claudeDir, "settings.local.json");
1254
+ mkdirSync2(claudeDir, { recursive: true });
1255
+ let settings = {};
1256
+ if (existsSync5(settingsPath)) {
1257
+ try {
1258
+ settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
1259
+ } catch {
1260
+ settings = {};
1261
+ }
1262
+ }
1263
+ if (agentName && !settings.statusLine) {
1264
+ const logPath = statusLineLogPath(this.teamName, agentName);
1265
+ const statusLineSettings = buildStatusLineSettings(logPath);
1266
+ settings = { ...settings, ...statusLineSettings };
1267
+ this.log.debug(`Injected statusLine capture for "${agentName}"`);
1119
1268
  }
1269
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1270
+ this.log.debug(`Updated ${settingsPath}`);
1120
1271
  }
1121
1272
  ensureInitialized() {
1122
1273
  if (!this.initialized) {
@@ -1202,7 +1353,7 @@ function waitForReady(controller, agentName, timeoutMs = 15e3) {
1202
1353
  controller.on("agent:exited", onExit);
1203
1354
  });
1204
1355
  }
1205
- var Agent = class _Agent extends EventEmitter2 {
1356
+ var Agent = class _Agent extends EventEmitter3 {
1206
1357
  controller;
1207
1358
  handle;
1208
1359
  ownsController;
@@ -1402,6 +1553,9 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1402
1553
  const onIdle = (name, _details) => {
1403
1554
  if (name === agentName) this.emit("idle");
1404
1555
  };
1556
+ const onStatusLine = (name, data) => {
1557
+ if (name === agentName) this.emit("statusline", data);
1558
+ };
1405
1559
  const onPermission = (name, parsed) => {
1406
1560
  if (name !== agentName) return;
1407
1561
  let handled = false;
@@ -1450,6 +1604,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1450
1604
  };
1451
1605
  this.controller.on("message", onMessage);
1452
1606
  this.controller.on("idle", onIdle);
1607
+ this.controller.on("agent:statusline", onStatusLine);
1453
1608
  this.controller.on("permission:request", onPermission);
1454
1609
  this.controller.on("plan:approval_request", onPlan);
1455
1610
  this.controller.on("agent:exited", onExit);
@@ -1457,6 +1612,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1457
1612
  this.boundListeners = [
1458
1613
  { event: "message", fn: onMessage },
1459
1614
  { event: "idle", fn: onIdle },
1615
+ { event: "agent:statusline", fn: onStatusLine },
1460
1616
  { event: "permission:request", fn: onPermission },
1461
1617
  { event: "plan:approval_request", fn: onPlan },
1462
1618
  { event: "agent:exited", fn: onExit },
@@ -1602,8 +1758,11 @@ export {
1602
1758
  InboxPoller,
1603
1759
  ProcessManager,
1604
1760
  Session,
1761
+ StatusLineWatcher,
1605
1762
  TaskManager,
1606
1763
  TeamManager,
1764
+ buildStatusLineCommand,
1765
+ buildStatusLineSettings,
1607
1766
  claude,
1608
1767
  createLogger,
1609
1768
  inboxPath,
@@ -1612,6 +1771,8 @@ export {
1612
1771
  readInbox,
1613
1772
  readUnread,
1614
1773
  silentLogger,
1774
+ statusLineDir,
1775
+ statusLineLogPath,
1615
1776
  taskPath,
1616
1777
  tasksBaseDir,
1617
1778
  tasksDir,