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/api/index.cjs
CHANGED
|
@@ -131,11 +131,11 @@ var ActionTracker = class {
|
|
|
131
131
|
var import_hono = require("hono");
|
|
132
132
|
|
|
133
133
|
// src/controller.ts
|
|
134
|
-
var
|
|
134
|
+
var import_node_events2 = require("events");
|
|
135
135
|
var import_node_child_process3 = require("child_process");
|
|
136
136
|
var import_node_crypto2 = require("crypto");
|
|
137
|
-
var
|
|
138
|
-
var
|
|
137
|
+
var import_node_fs5 = require("fs");
|
|
138
|
+
var import_node_path4 = require("path");
|
|
139
139
|
|
|
140
140
|
// src/team-manager.ts
|
|
141
141
|
var import_promises = require("fs/promises");
|
|
@@ -824,6 +824,135 @@ function createLogger(level = "info") {
|
|
|
824
824
|
};
|
|
825
825
|
}
|
|
826
826
|
|
|
827
|
+
// src/statusline-capture.ts
|
|
828
|
+
var import_node_events = require("events");
|
|
829
|
+
var import_node_fs4 = require("fs");
|
|
830
|
+
var import_node_path3 = require("path");
|
|
831
|
+
function statusLineDir(teamName) {
|
|
832
|
+
return (0, import_node_path3.join)(teamDir(teamName), "statusline");
|
|
833
|
+
}
|
|
834
|
+
function statusLineLogPath(teamName, agentName) {
|
|
835
|
+
return (0, import_node_path3.join)(statusLineDir(teamName), `${agentName}.jsonl`);
|
|
836
|
+
}
|
|
837
|
+
function buildStatusLineCommand(logFilePath) {
|
|
838
|
+
const escapedPath = logFilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
839
|
+
return [
|
|
840
|
+
"python3 -c",
|
|
841
|
+
`'import sys,json;`,
|
|
842
|
+
`d=json.load(sys.stdin);`,
|
|
843
|
+
`open("${escapedPath}","a").write(json.dumps(d)+"\\n");`,
|
|
844
|
+
`print(d.get("model",{}).get("display_name",""))'`
|
|
845
|
+
].join(" ");
|
|
846
|
+
}
|
|
847
|
+
function buildStatusLineSettings(logFilePath) {
|
|
848
|
+
return {
|
|
849
|
+
statusLine: {
|
|
850
|
+
type: "command",
|
|
851
|
+
command: buildStatusLineCommand(logFilePath)
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
var StatusLineWatcher = class extends import_node_events.EventEmitter {
|
|
856
|
+
teamName;
|
|
857
|
+
log;
|
|
858
|
+
watchers = /* @__PURE__ */ new Map();
|
|
859
|
+
fileOffsets = /* @__PURE__ */ new Map();
|
|
860
|
+
stopped = false;
|
|
861
|
+
constructor(teamName, logger) {
|
|
862
|
+
super();
|
|
863
|
+
this.teamName = teamName;
|
|
864
|
+
this.log = logger;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Ensure the statusline directory exists.
|
|
868
|
+
*/
|
|
869
|
+
ensureDir() {
|
|
870
|
+
const dir = statusLineDir(this.teamName);
|
|
871
|
+
if (!(0, import_node_fs4.existsSync)(dir)) {
|
|
872
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Start watching a specific agent's statusLine log file.
|
|
877
|
+
*/
|
|
878
|
+
watchAgent(agentName) {
|
|
879
|
+
if (this.stopped) return;
|
|
880
|
+
const filePath = statusLineLogPath(this.teamName, agentName);
|
|
881
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) {
|
|
882
|
+
(0, import_node_fs4.writeFileSync)(filePath, "");
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const stats = (0, import_node_fs4.readFileSync)(filePath);
|
|
886
|
+
this.fileOffsets.set(filePath, stats.length);
|
|
887
|
+
} catch {
|
|
888
|
+
this.fileOffsets.set(filePath, 0);
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
const watcher = (0, import_node_fs4.watch)(filePath, (eventType) => {
|
|
892
|
+
if (eventType === "change") {
|
|
893
|
+
this.readNewLines(agentName, filePath);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
this.watchers.set(agentName, watcher);
|
|
897
|
+
this.log.debug(`Watching statusLine for agent "${agentName}" at ${filePath}`);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
this.log.error(`Failed to watch statusLine for "${agentName}": ${err}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Stop watching a specific agent.
|
|
904
|
+
*/
|
|
905
|
+
unwatchAgent(agentName) {
|
|
906
|
+
const watcher = this.watchers.get(agentName);
|
|
907
|
+
if (watcher) {
|
|
908
|
+
watcher.close();
|
|
909
|
+
this.watchers.delete(agentName);
|
|
910
|
+
}
|
|
911
|
+
const filePath = statusLineLogPath(this.teamName, agentName);
|
|
912
|
+
this.fileOffsets.delete(filePath);
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Stop all watchers.
|
|
916
|
+
*/
|
|
917
|
+
stop() {
|
|
918
|
+
this.stopped = true;
|
|
919
|
+
for (const [, watcher] of this.watchers) {
|
|
920
|
+
watcher.close();
|
|
921
|
+
}
|
|
922
|
+
this.watchers.clear();
|
|
923
|
+
this.fileOffsets.clear();
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Read new lines appended to the log file since last read.
|
|
927
|
+
*/
|
|
928
|
+
readNewLines(agentName, filePath) {
|
|
929
|
+
try {
|
|
930
|
+
const content = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
|
|
931
|
+
const offset = this.fileOffsets.get(filePath) ?? 0;
|
|
932
|
+
const newContent = content.slice(offset);
|
|
933
|
+
this.fileOffsets.set(filePath, content.length);
|
|
934
|
+
if (!newContent.trim()) return;
|
|
935
|
+
const lines = newContent.trim().split("\n");
|
|
936
|
+
for (const line of lines) {
|
|
937
|
+
if (!line.trim()) continue;
|
|
938
|
+
try {
|
|
939
|
+
const data = JSON.parse(line);
|
|
940
|
+
const event = {
|
|
941
|
+
agentName,
|
|
942
|
+
data,
|
|
943
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
944
|
+
};
|
|
945
|
+
this.emit("update", event);
|
|
946
|
+
} catch (parseErr) {
|
|
947
|
+
this.log.debug(`Failed to parse statusLine JSON: ${line}`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
} catch (err) {
|
|
951
|
+
this.log.debug(`Error reading statusLine file: ${err}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
827
956
|
// src/controller.ts
|
|
828
957
|
var PROTOCOL_ONLY_TYPES = /* @__PURE__ */ new Set([
|
|
829
958
|
"shutdown_approved",
|
|
@@ -843,12 +972,13 @@ var AGENT_COLORS = [
|
|
|
843
972
|
"#FF69B4",
|
|
844
973
|
"#7B68EE"
|
|
845
974
|
];
|
|
846
|
-
var ClaudeCodeController = class extends
|
|
975
|
+
var ClaudeCodeController = class extends import_node_events2.EventEmitter {
|
|
847
976
|
teamName;
|
|
848
977
|
team;
|
|
849
978
|
tasks;
|
|
850
979
|
processes;
|
|
851
980
|
poller;
|
|
981
|
+
statusLineWatcher;
|
|
852
982
|
log;
|
|
853
983
|
cwd;
|
|
854
984
|
claudeBinary;
|
|
@@ -870,7 +1000,11 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
870
1000
|
"controller",
|
|
871
1001
|
this.log
|
|
872
1002
|
);
|
|
1003
|
+
this.statusLineWatcher = new StatusLineWatcher(this.teamName, this.log);
|
|
873
1004
|
this.poller.onMessages((events) => this.handlePollEvents(events));
|
|
1005
|
+
this.statusLineWatcher.on("update", (event) => {
|
|
1006
|
+
this.emit("agent:statusline", event.agentName, event.data);
|
|
1007
|
+
});
|
|
874
1008
|
}
|
|
875
1009
|
// ─── Lifecycle ───────────────────────────────────────────────────────
|
|
876
1010
|
/**
|
|
@@ -881,6 +1015,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
881
1015
|
if (this.initialized) return this;
|
|
882
1016
|
await this.team.create({ cwd: this.cwd });
|
|
883
1017
|
await this.tasks.init();
|
|
1018
|
+
this.statusLineWatcher.ensureDir();
|
|
884
1019
|
this.poller.start();
|
|
885
1020
|
this.initialized = true;
|
|
886
1021
|
this.log.info(
|
|
@@ -921,6 +1056,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
921
1056
|
}
|
|
922
1057
|
await this.processes.killAll();
|
|
923
1058
|
this.poller.stop();
|
|
1059
|
+
this.statusLineWatcher.stop();
|
|
924
1060
|
await this.team.destroy();
|
|
925
1061
|
this.initialized = false;
|
|
926
1062
|
this.log.info("Controller shut down");
|
|
@@ -949,7 +1085,8 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
949
1085
|
subscriptions: []
|
|
950
1086
|
};
|
|
951
1087
|
await this.team.addMember(member);
|
|
952
|
-
this.ensureWorkspaceTrusted(cwd);
|
|
1088
|
+
this.ensureWorkspaceTrusted(cwd, opts.name);
|
|
1089
|
+
this.statusLineWatcher.watchAgent(opts.name);
|
|
953
1090
|
const env = Object.keys(this.defaultEnv).length > 0 || opts.env ? { ...this.defaultEnv, ...opts.env } : void 0;
|
|
954
1091
|
const proc = this.processes.spawn({
|
|
955
1092
|
teamName: this.teamName,
|
|
@@ -1156,6 +1293,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
1156
1293
|
* Kill a specific agent.
|
|
1157
1294
|
*/
|
|
1158
1295
|
async killAgent(name) {
|
|
1296
|
+
this.statusLineWatcher.unwatchAgent(name);
|
|
1159
1297
|
await this.processes.kill(name);
|
|
1160
1298
|
await this.team.removeMember(name);
|
|
1161
1299
|
}
|
|
@@ -1220,15 +1358,28 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
|
|
|
1220
1358
|
/**
|
|
1221
1359
|
* Ensure the agent's cwd has a .claude/settings.local.json so the
|
|
1222
1360
|
* CLI skips the interactive workspace trust prompt.
|
|
1361
|
+
* Also injects statusLine capture configuration.
|
|
1223
1362
|
*/
|
|
1224
|
-
ensureWorkspaceTrusted(cwd) {
|
|
1225
|
-
const claudeDir = (0,
|
|
1226
|
-
const settingsPath = (0,
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1363
|
+
ensureWorkspaceTrusted(cwd, agentName) {
|
|
1364
|
+
const claudeDir = (0, import_node_path4.join)(cwd, ".claude");
|
|
1365
|
+
const settingsPath = (0, import_node_path4.join)(claudeDir, "settings.local.json");
|
|
1366
|
+
(0, import_node_fs5.mkdirSync)(claudeDir, { recursive: true });
|
|
1367
|
+
let settings = {};
|
|
1368
|
+
if ((0, import_node_fs5.existsSync)(settingsPath)) {
|
|
1369
|
+
try {
|
|
1370
|
+
settings = JSON.parse((0, import_node_fs5.readFileSync)(settingsPath, "utf-8"));
|
|
1371
|
+
} catch {
|
|
1372
|
+
settings = {};
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (agentName && !settings.statusLine) {
|
|
1376
|
+
const logPath = statusLineLogPath(this.teamName, agentName);
|
|
1377
|
+
const statusLineSettings = buildStatusLineSettings(logPath);
|
|
1378
|
+
settings = { ...settings, ...statusLineSettings };
|
|
1379
|
+
this.log.debug(`Injected statusLine capture for "${agentName}"`);
|
|
1231
1380
|
}
|
|
1381
|
+
(0, import_node_fs5.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1382
|
+
this.log.debug(`Updated ${settingsPath}`);
|
|
1232
1383
|
}
|
|
1233
1384
|
ensureInitialized() {
|
|
1234
1385
|
if (!this.initialized) {
|
|
@@ -1243,7 +1394,7 @@ function sleep2(ms) {
|
|
|
1243
1394
|
}
|
|
1244
1395
|
|
|
1245
1396
|
// src/claude.ts
|
|
1246
|
-
var
|
|
1397
|
+
var import_node_events3 = require("events");
|
|
1247
1398
|
var import_node_crypto3 = require("crypto");
|
|
1248
1399
|
Symbol.asyncDispose ??= /* @__PURE__ */ Symbol("Symbol.asyncDispose");
|
|
1249
1400
|
function buildEnv(opts) {
|
|
@@ -1316,7 +1467,7 @@ function waitForReady(controller, agentName, timeoutMs = 15e3) {
|
|
|
1316
1467
|
controller.on("agent:exited", onExit);
|
|
1317
1468
|
});
|
|
1318
1469
|
}
|
|
1319
|
-
var Agent = class _Agent extends
|
|
1470
|
+
var Agent = class _Agent extends import_node_events3.EventEmitter {
|
|
1320
1471
|
controller;
|
|
1321
1472
|
handle;
|
|
1322
1473
|
ownsController;
|
|
@@ -1516,6 +1667,9 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
|
|
|
1516
1667
|
const onIdle = (name, _details) => {
|
|
1517
1668
|
if (name === agentName) this.emit("idle");
|
|
1518
1669
|
};
|
|
1670
|
+
const onStatusLine = (name, data) => {
|
|
1671
|
+
if (name === agentName) this.emit("statusline", data);
|
|
1672
|
+
};
|
|
1519
1673
|
const onPermission = (name, parsed) => {
|
|
1520
1674
|
if (name !== agentName) return;
|
|
1521
1675
|
let handled = false;
|
|
@@ -1564,6 +1718,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
|
|
|
1564
1718
|
};
|
|
1565
1719
|
this.controller.on("message", onMessage);
|
|
1566
1720
|
this.controller.on("idle", onIdle);
|
|
1721
|
+
this.controller.on("agent:statusline", onStatusLine);
|
|
1567
1722
|
this.controller.on("permission:request", onPermission);
|
|
1568
1723
|
this.controller.on("plan:approval_request", onPlan);
|
|
1569
1724
|
this.controller.on("agent:exited", onExit);
|
|
@@ -1571,6 +1726,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
|
|
|
1571
1726
|
this.boundListeners = [
|
|
1572
1727
|
{ event: "message", fn: onMessage },
|
|
1573
1728
|
{ event: "idle", fn: onIdle },
|
|
1729
|
+
{ event: "agent:statusline", fn: onStatusLine },
|
|
1574
1730
|
{ event: "permission:request", fn: onPermission },
|
|
1575
1731
|
{ event: "plan:approval_request", fn: onPlan },
|
|
1576
1732
|
{ event: "agent:exited", fn: onExit },
|