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/api/index.cjs +170 -14
- package/dist/api/index.cjs.map +1 -1
- package/dist/api/index.d.cts +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +170 -14
- package/dist/api/index.js.map +1 -1
- package/dist/{claude-0cg912ch.d.cts → claude-B7-oBjuE.d.cts} +47 -1
- package/dist/{claude-0cg912ch.d.ts → claude-B7-oBjuE.d.ts} +47 -1
- package/dist/index.cjs +180 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -4
- package/dist/index.d.ts +60 -4
- package/dist/index.js +175 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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-
|
|
2
|
-
export { A as Agent,
|
|
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
|
|
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
|
|
10
|
-
import { join as
|
|
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
|
|
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 =
|
|
1114
|
-
const settingsPath =
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
|
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,
|