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.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { C as ClaudeCodeController,
|
|
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 {
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { C as ClaudeCodeController,
|
|
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
|
|
113
|
-
import { join as
|
|
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
|
|
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 =
|
|
1201
|
-
const settingsPath =
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
|
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
|
|
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 },
|