@sma1lboy/kobe 0.5.7 → 0.5.8
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/bin/kobed.js +404 -88
- package/dist/cli/index.js +878 -279
- package/package.json +1 -1
package/dist/bin/kobed.js
CHANGED
|
@@ -17,6 +17,20 @@ var __export = (target, all) => {
|
|
|
17
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
18
|
var __require = import.meta.require;
|
|
19
19
|
|
|
20
|
+
// src/daemon/paths.ts
|
|
21
|
+
import { homedir, tmpdir } from "os";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
function defaultDaemonSocketPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
24
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
25
|
+
if (runtimeDir && runtimeDir.length > 0)
|
|
26
|
+
return join(runtimeDir, "kobe.sock");
|
|
27
|
+
return join(homeDir, ".kobe", "daemon.sock");
|
|
28
|
+
}
|
|
29
|
+
function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
30
|
+
return join(homeDir, ".kobe", "daemon.pid");
|
|
31
|
+
}
|
|
32
|
+
var init_paths = () => {};
|
|
33
|
+
|
|
20
34
|
// src/daemon/protocol.ts
|
|
21
35
|
function serializeTask(task) {
|
|
22
36
|
return {
|
|
@@ -173,10 +187,80 @@ class KobeDaemonClient {
|
|
|
173
187
|
}
|
|
174
188
|
var init_client = () => {};
|
|
175
189
|
|
|
190
|
+
// src/client/daemon-process.ts
|
|
191
|
+
import { spawn } from "child_process";
|
|
192
|
+
import { existsSync } from "fs";
|
|
193
|
+
import { dirname, join as join2, resolve } from "path";
|
|
194
|
+
import { fileURLToPath } from "url";
|
|
195
|
+
async function connectOrStartDaemon() {
|
|
196
|
+
const socketPath = defaultDaemonSocketPath();
|
|
197
|
+
const client = new KobeDaemonClient(socketPath);
|
|
198
|
+
if (await canConnect(client))
|
|
199
|
+
return client;
|
|
200
|
+
const { entry, runWithBun } = resolveKobedEntry();
|
|
201
|
+
const child = runWithBun ? spawn(process.execPath, [entry, "start"], {
|
|
202
|
+
detached: true,
|
|
203
|
+
stdio: "ignore",
|
|
204
|
+
env: process.env
|
|
205
|
+
}) : spawn(entry, ["start"], {
|
|
206
|
+
detached: true,
|
|
207
|
+
stdio: "ignore",
|
|
208
|
+
env: process.env
|
|
209
|
+
});
|
|
210
|
+
child.unref();
|
|
211
|
+
const deadline = Date.now() + 5000;
|
|
212
|
+
let lastErr;
|
|
213
|
+
while (Date.now() < deadline) {
|
|
214
|
+
const next = new KobeDaemonClient(socketPath);
|
|
215
|
+
try {
|
|
216
|
+
await next.connect();
|
|
217
|
+
return next;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
lastErr = err;
|
|
220
|
+
next.close();
|
|
221
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
225
|
+
}
|
|
226
|
+
async function canConnect(client) {
|
|
227
|
+
try {
|
|
228
|
+
await client.connect();
|
|
229
|
+
return true;
|
|
230
|
+
} catch {
|
|
231
|
+
client.close();
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resolveKobedEntry() {
|
|
236
|
+
const here = fileURLToPath(import.meta.url);
|
|
237
|
+
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
238
|
+
const exeDir = dirname(process.execPath);
|
|
239
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
240
|
+
const sibling = join2(exeDir, `kobed${ext}`);
|
|
241
|
+
if (!existsSync(sibling)) {
|
|
242
|
+
throw new Error(`kobe: standalone build expected sibling kobed binary at ${sibling}; extract the full release tarball.`);
|
|
243
|
+
}
|
|
244
|
+
return { entry: sibling, runWithBun: false };
|
|
245
|
+
}
|
|
246
|
+
const dir = dirname(here);
|
|
247
|
+
const sourceEntry = resolve(dir, "../bin/kobed.ts");
|
|
248
|
+
if (existsSync(sourceEntry))
|
|
249
|
+
return { entry: sourceEntry, runWithBun: true };
|
|
250
|
+
const distEntry = resolve(dir, "../bin/kobed.js");
|
|
251
|
+
if (existsSync(distEntry))
|
|
252
|
+
return { entry: distEntry, runWithBun: true };
|
|
253
|
+
throw new Error(`kobe: could not locate kobed entry near ${dir}; expected ../bin/kobed.{ts,js}`);
|
|
254
|
+
}
|
|
255
|
+
var init_daemon_process = __esm(() => {
|
|
256
|
+
init_paths();
|
|
257
|
+
init_client();
|
|
258
|
+
});
|
|
259
|
+
|
|
176
260
|
// src/engine/claude-code-local/binary.ts
|
|
177
261
|
import { spawnSync } from "child_process";
|
|
178
|
-
import { existsSync, statSync } from "fs";
|
|
179
|
-
import { homedir } from "os";
|
|
262
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
263
|
+
import { homedir as homedir2 } from "os";
|
|
180
264
|
import path from "path";
|
|
181
265
|
async function findClaudeBinary(deps = defaultDeps) {
|
|
182
266
|
const checked = [];
|
|
@@ -249,7 +333,7 @@ var init_binary = __esm(() => {
|
|
|
249
333
|
return process.env[name];
|
|
250
334
|
},
|
|
251
335
|
home() {
|
|
252
|
-
return
|
|
336
|
+
return homedir2();
|
|
253
337
|
},
|
|
254
338
|
which(name) {
|
|
255
339
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -262,7 +346,7 @@ var init_binary = __esm(() => {
|
|
|
262
346
|
return;
|
|
263
347
|
if (first.startsWith("claude:") && first.includes("aliased to")) {
|
|
264
348
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
265
|
-
return aliasTarget &&
|
|
349
|
+
return aliasTarget && existsSync2(aliasTarget) ? aliasTarget : undefined;
|
|
266
350
|
}
|
|
267
351
|
return first;
|
|
268
352
|
},
|
|
@@ -279,7 +363,7 @@ var init_binary = __esm(() => {
|
|
|
279
363
|
|
|
280
364
|
// src/engine/claude-code-local/history.ts
|
|
281
365
|
import { readFile, readdir, unlink } from "fs/promises";
|
|
282
|
-
import { homedir as
|
|
366
|
+
import { homedir as homedir3 } from "os";
|
|
283
367
|
import path2 from "path";
|
|
284
368
|
function encodeCwd(cwd) {
|
|
285
369
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -379,7 +463,7 @@ var defaultDeps2;
|
|
|
379
463
|
var init_history = __esm(() => {
|
|
380
464
|
defaultDeps2 = {
|
|
381
465
|
projectsDir() {
|
|
382
|
-
return path2.join(
|
|
466
|
+
return path2.join(homedir3(), ".claude", "projects");
|
|
383
467
|
},
|
|
384
468
|
async readdir(p) {
|
|
385
469
|
try {
|
|
@@ -448,22 +532,22 @@ class SessionRegistry {
|
|
|
448
532
|
}
|
|
449
533
|
}
|
|
450
534
|
function waitForExit(proc) {
|
|
451
|
-
return new Promise((
|
|
535
|
+
return new Promise((resolve2) => {
|
|
452
536
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
453
|
-
|
|
537
|
+
resolve2();
|
|
454
538
|
return;
|
|
455
539
|
}
|
|
456
|
-
proc.once("close", () =>
|
|
457
|
-
proc.once("exit", () =>
|
|
540
|
+
proc.once("close", () => resolve2());
|
|
541
|
+
proc.once("exit", () => resolve2());
|
|
458
542
|
});
|
|
459
543
|
}
|
|
460
544
|
function delay(ms) {
|
|
461
|
-
return new Promise((
|
|
545
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
462
546
|
}
|
|
463
547
|
|
|
464
548
|
// src/engine/claude-code-local/sessions.ts
|
|
465
549
|
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
466
|
-
import { homedir as
|
|
550
|
+
import { homedir as homedir4 } from "os";
|
|
467
551
|
import path3 from "path";
|
|
468
552
|
async function listSessionsForCwd(cwd, deps = defaultDeps3) {
|
|
469
553
|
const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
|
|
@@ -537,7 +621,7 @@ var init_sessions = __esm(() => {
|
|
|
537
621
|
init_history();
|
|
538
622
|
defaultDeps3 = {
|
|
539
623
|
projectsDir() {
|
|
540
|
-
return path3.join(
|
|
624
|
+
return path3.join(homedir4(), ".claude", "projects");
|
|
541
625
|
},
|
|
542
626
|
async readdir(p) {
|
|
543
627
|
try {
|
|
@@ -557,10 +641,10 @@ var init_sessions = __esm(() => {
|
|
|
557
641
|
});
|
|
558
642
|
|
|
559
643
|
// src/engine/claude-code-local/spawn.ts
|
|
560
|
-
import { spawn } from "child_process";
|
|
644
|
+
import { spawn as spawn2 } from "child_process";
|
|
561
645
|
function spawnClaudeProcess(opts) {
|
|
562
646
|
const args = buildArgs(opts);
|
|
563
|
-
const proc =
|
|
647
|
+
const proc = spawn2(opts.binaryPath, args, {
|
|
564
648
|
cwd: opts.cwd,
|
|
565
649
|
env: { ...process.env, ...opts.env ?? {} },
|
|
566
650
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -766,7 +850,7 @@ class ClaudeCodeLocal {
|
|
|
766
850
|
}
|
|
767
851
|
if (session.closed)
|
|
768
852
|
return;
|
|
769
|
-
await new Promise((
|
|
853
|
+
await new Promise((resolve2) => session.waiters.push(resolve2));
|
|
770
854
|
}
|
|
771
855
|
}
|
|
772
856
|
};
|
|
@@ -906,11 +990,14 @@ var init_claude_code_local = __esm(() => {
|
|
|
906
990
|
// src/orchestrator/bridge/server.ts
|
|
907
991
|
import { mkdir, unlink as unlink2 } from "fs/promises";
|
|
908
992
|
import { createServer } from "net";
|
|
909
|
-
import { dirname } from "path";
|
|
993
|
+
import { dirname as dirname2 } from "path";
|
|
910
994
|
async function startBridgeServer(orch, socketPath) {
|
|
911
|
-
await mkdir(
|
|
995
|
+
await mkdir(dirname2(socketPath), { recursive: true });
|
|
912
996
|
await unlink2(socketPath).catch(() => {});
|
|
997
|
+
const conns = new Set;
|
|
913
998
|
const server = createServer((conn) => {
|
|
999
|
+
conns.add(conn);
|
|
1000
|
+
conn.on("close", () => conns.delete(conn));
|
|
914
1001
|
let buffer = "";
|
|
915
1002
|
conn.on("data", (chunk) => {
|
|
916
1003
|
buffer += chunk.toString("utf8");
|
|
@@ -933,17 +1020,20 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
933
1020
|
});
|
|
934
1021
|
conn.on("error", () => {});
|
|
935
1022
|
});
|
|
936
|
-
await new Promise((
|
|
1023
|
+
await new Promise((resolve2, reject) => {
|
|
937
1024
|
server.once("error", reject);
|
|
938
1025
|
server.listen(socketPath, () => {
|
|
939
1026
|
server.removeListener("error", reject);
|
|
940
|
-
|
|
1027
|
+
resolve2();
|
|
941
1028
|
});
|
|
942
1029
|
});
|
|
943
1030
|
return {
|
|
944
1031
|
socketPath,
|
|
945
1032
|
async close() {
|
|
946
|
-
|
|
1033
|
+
for (const conn of conns)
|
|
1034
|
+
conn.destroy();
|
|
1035
|
+
conns.clear();
|
|
1036
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
947
1037
|
await unlink2(socketPath).catch(() => {});
|
|
948
1038
|
}
|
|
949
1039
|
};
|
|
@@ -1036,17 +1126,17 @@ __export(exports_bridge, {
|
|
|
1036
1126
|
startBridge: () => startBridge
|
|
1037
1127
|
});
|
|
1038
1128
|
import { writeFile } from "fs/promises";
|
|
1039
|
-
import { homedir as
|
|
1040
|
-
import { join } from "path";
|
|
1041
|
-
import { fileURLToPath } from "url";
|
|
1129
|
+
import { homedir as homedir5 } from "os";
|
|
1130
|
+
import { join as join3 } from "path";
|
|
1131
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1042
1132
|
async function startBridge(orch, opts = {}) {
|
|
1043
|
-
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
1044
|
-
const runDir =
|
|
1045
|
-
const socketPath =
|
|
1046
|
-
const mcpConfigPath =
|
|
1133
|
+
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir5();
|
|
1134
|
+
const runDir = join3(home, ".kobe", "run");
|
|
1135
|
+
const socketPath = join3(runDir, `bridge-${process.pid}.sock`);
|
|
1136
|
+
const mcpConfigPath = join3(runDir, `mcp-${process.pid}.json`);
|
|
1047
1137
|
const server = await startBridgeServer(orch, socketPath);
|
|
1048
1138
|
const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
1049
|
-
const entry =
|
|
1139
|
+
const entry = fileURLToPath2(new URL(`../../cli/index${moduleExt}`, import.meta.url));
|
|
1050
1140
|
const mcpConfig = {
|
|
1051
1141
|
mcpServers: {
|
|
1052
1142
|
kobe: {
|
|
@@ -2299,8 +2389,8 @@ var init_dev = __esm(() => {
|
|
|
2299
2389
|
|
|
2300
2390
|
// src/engine/claude-settings.ts
|
|
2301
2391
|
import { readFileSync } from "fs";
|
|
2302
|
-
import { homedir as
|
|
2303
|
-
import { join as
|
|
2392
|
+
import { homedir as homedir6 } from "os";
|
|
2393
|
+
import { join as join4 } from "path";
|
|
2304
2394
|
function readClaudeSettings() {
|
|
2305
2395
|
if (cached !== undefined)
|
|
2306
2396
|
return cached;
|
|
@@ -2328,23 +2418,23 @@ function resolveDefaultModelId() {
|
|
|
2328
2418
|
}
|
|
2329
2419
|
var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
2330
2420
|
var init_claude_settings = __esm(() => {
|
|
2331
|
-
SETTINGS_PATH =
|
|
2421
|
+
SETTINGS_PATH = join4(homedir6(), ".claude", "settings.json");
|
|
2332
2422
|
});
|
|
2333
2423
|
|
|
2334
2424
|
// src/env.ts
|
|
2335
|
-
import { homedir as
|
|
2336
|
-
import { join as
|
|
2425
|
+
import { homedir as homedir7 } from "os";
|
|
2426
|
+
import { join as join5 } from "path";
|
|
2337
2427
|
function isDev() {
|
|
2338
2428
|
return process.env.KOBE_DEV === "1";
|
|
2339
2429
|
}
|
|
2340
2430
|
function homeDir() {
|
|
2341
|
-
return process.env.KOBE_HOME_DIR ??
|
|
2431
|
+
return process.env.KOBE_HOME_DIR ?? homedir7();
|
|
2342
2432
|
}
|
|
2343
2433
|
function kobeStateDir() {
|
|
2344
|
-
return
|
|
2434
|
+
return join5(homeDir(), ".kobe");
|
|
2345
2435
|
}
|
|
2346
2436
|
function kvStatePath() {
|
|
2347
|
-
return
|
|
2437
|
+
return join5(homeDir(), ".config", "kobe", "state.json");
|
|
2348
2438
|
}
|
|
2349
2439
|
function tmuxBin() {
|
|
2350
2440
|
return process.env.KOBE_TMUX_BIN ?? "tmux";
|
|
@@ -2364,7 +2454,7 @@ __export(exports_repos, {
|
|
|
2364
2454
|
});
|
|
2365
2455
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2366
2456
|
import { mkdirSync, readFileSync as readFileSync2, realpathSync, renameSync, writeFileSync } from "fs";
|
|
2367
|
-
import { dirname as
|
|
2457
|
+
import { dirname as dirname3 } from "path";
|
|
2368
2458
|
function resolveRepoRoot(absPath) {
|
|
2369
2459
|
const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
2370
2460
|
cwd: absPath,
|
|
@@ -2410,7 +2500,7 @@ function load() {
|
|
|
2410
2500
|
}
|
|
2411
2501
|
function save(state) {
|
|
2412
2502
|
const path4 = statePath();
|
|
2413
|
-
mkdirSync(
|
|
2503
|
+
mkdirSync(dirname3(path4), { recursive: true });
|
|
2414
2504
|
const tmp = `${path4}.tmp`;
|
|
2415
2505
|
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2416
2506
|
renameSync(tmp, path4);
|
|
@@ -2539,7 +2629,7 @@ var init_ulid = __esm(() => {
|
|
|
2539
2629
|
});
|
|
2540
2630
|
|
|
2541
2631
|
// src/orchestrator/metadata-suggester.ts
|
|
2542
|
-
import { spawn as
|
|
2632
|
+
import { spawn as spawn3 } from "child_process";
|
|
2543
2633
|
|
|
2544
2634
|
class MetadataSuggester {
|
|
2545
2635
|
binaryPromise = null;
|
|
@@ -2565,15 +2655,15 @@ class MetadataSuggester {
|
|
|
2565
2655
|
const binary = await this.resolveBinary();
|
|
2566
2656
|
if (!binary)
|
|
2567
2657
|
return null;
|
|
2568
|
-
return new Promise((
|
|
2658
|
+
return new Promise((resolve2) => {
|
|
2569
2659
|
let proc;
|
|
2570
2660
|
try {
|
|
2571
|
-
proc =
|
|
2661
|
+
proc = spawn3(binary, ["-p", builder(trimmed)], {
|
|
2572
2662
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2573
2663
|
env: process.env
|
|
2574
2664
|
});
|
|
2575
2665
|
} catch {
|
|
2576
|
-
|
|
2666
|
+
resolve2(null);
|
|
2577
2667
|
return;
|
|
2578
2668
|
}
|
|
2579
2669
|
let buf = "";
|
|
@@ -2585,7 +2675,7 @@ class MetadataSuggester {
|
|
|
2585
2675
|
try {
|
|
2586
2676
|
proc.kill();
|
|
2587
2677
|
} catch {}
|
|
2588
|
-
|
|
2678
|
+
resolve2(v);
|
|
2589
2679
|
};
|
|
2590
2680
|
const timer = setTimeout(() => settle(null), SUGGESTION_TIMEOUT_MS);
|
|
2591
2681
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -2997,6 +3087,9 @@ class Orchestrator {
|
|
|
2997
3087
|
planUsageSignal() {
|
|
2998
3088
|
return () => null;
|
|
2999
3089
|
}
|
|
3090
|
+
rcBridgeSignal() {
|
|
3091
|
+
return () => ({ state: "off" });
|
|
3092
|
+
}
|
|
3000
3093
|
subscribeTasks(listener) {
|
|
3001
3094
|
return this.store.subscribe(listener);
|
|
3002
3095
|
}
|
|
@@ -3704,8 +3797,8 @@ var init_core = __esm(() => {
|
|
|
3704
3797
|
|
|
3705
3798
|
// src/orchestrator/index/store.ts
|
|
3706
3799
|
import { mkdir as mkdir2, open, readFile as readFile3, rename, unlink as unlink3 } from "fs/promises";
|
|
3707
|
-
import { homedir as
|
|
3708
|
-
import { dirname as
|
|
3800
|
+
import { homedir as homedir8 } from "os";
|
|
3801
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
3709
3802
|
|
|
3710
3803
|
class TaskIndexStore {
|
|
3711
3804
|
homeDir;
|
|
@@ -3717,9 +3810,9 @@ class TaskIndexStore {
|
|
|
3717
3810
|
listeners = new Set;
|
|
3718
3811
|
saveChain = Promise.resolve();
|
|
3719
3812
|
constructor(options = {}) {
|
|
3720
|
-
this.homeDir = options.homeDir ??
|
|
3721
|
-
this.kobeDir =
|
|
3722
|
-
this.path =
|
|
3813
|
+
this.homeDir = options.homeDir ?? homedir8();
|
|
3814
|
+
this.kobeDir = join6(this.homeDir, ".kobe");
|
|
3815
|
+
this.path = join6(this.kobeDir, "tasks.json");
|
|
3723
3816
|
this.tmpPath = `${this.path}.tmp`;
|
|
3724
3817
|
}
|
|
3725
3818
|
subscribe(listener) {
|
|
@@ -3777,7 +3870,7 @@ class TaskIndexStore {
|
|
|
3777
3870
|
return next;
|
|
3778
3871
|
}
|
|
3779
3872
|
async doSave() {
|
|
3780
|
-
await mkdir2(
|
|
3873
|
+
await mkdir2(dirname4(this.path), { recursive: true });
|
|
3781
3874
|
const payload = this.snapshot();
|
|
3782
3875
|
const json = `${JSON.stringify(payload, null, 2)}
|
|
3783
3876
|
`;
|
|
@@ -4092,7 +4185,7 @@ function canonicalize(p) {
|
|
|
4092
4185
|
}
|
|
4093
4186
|
}
|
|
4094
4187
|
var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
|
|
4095
|
-
var
|
|
4188
|
+
var init_paths2 = () => {};
|
|
4096
4189
|
|
|
4097
4190
|
// src/orchestrator/worktree/manager.ts
|
|
4098
4191
|
import fs3 from "fs";
|
|
@@ -4286,25 +4379,13 @@ function canonicalize2(p) {
|
|
|
4286
4379
|
}
|
|
4287
4380
|
var init_manager = __esm(() => {
|
|
4288
4381
|
init_git();
|
|
4289
|
-
|
|
4382
|
+
init_paths2();
|
|
4290
4383
|
});
|
|
4291
4384
|
|
|
4292
|
-
// src/daemon/paths.ts
|
|
4293
|
-
import { homedir as homedir9, tmpdir } from "os";
|
|
4294
|
-
import { join as join5 } from "path";
|
|
4295
|
-
function defaultDaemonSocketPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir9()) {
|
|
4296
|
-
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
4297
|
-
if (runtimeDir && runtimeDir.length > 0)
|
|
4298
|
-
return join5(runtimeDir, "kobe.sock");
|
|
4299
|
-
return join5(homeDir2, ".kobe", "daemon.sock");
|
|
4300
|
-
}
|
|
4301
|
-
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir9()) {
|
|
4302
|
-
return join5(homeDir2, ".kobe", "daemon.pid");
|
|
4303
|
-
}
|
|
4304
|
-
var init_paths2 = () => {};
|
|
4305
|
-
|
|
4306
4385
|
// src/bin/kobed.ts
|
|
4386
|
+
init_daemon_process();
|
|
4307
4387
|
init_client();
|
|
4388
|
+
import { unlink as unlink5 } from "fs/promises";
|
|
4308
4389
|
|
|
4309
4390
|
// src/core/index.ts
|
|
4310
4391
|
init_claude_code_local();
|
|
@@ -4312,9 +4393,9 @@ init_bridge();
|
|
|
4312
4393
|
init_core();
|
|
4313
4394
|
init_store();
|
|
4314
4395
|
init_manager();
|
|
4315
|
-
import { homedir as
|
|
4396
|
+
import { homedir as homedir9 } from "os";
|
|
4316
4397
|
async function createKobeCore(options = {}) {
|
|
4317
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
4398
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
|
|
4318
4399
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
4319
4400
|
await store.load();
|
|
4320
4401
|
const worktrees = new GitWorktreeManager;
|
|
@@ -4335,20 +4416,21 @@ async function createKobeCore(options = {}) {
|
|
|
4335
4416
|
}
|
|
4336
4417
|
|
|
4337
4418
|
// src/bin/kobed.ts
|
|
4338
|
-
|
|
4419
|
+
init_paths();
|
|
4339
4420
|
|
|
4340
4421
|
// src/daemon/server.ts
|
|
4341
|
-
|
|
4422
|
+
init_repos();
|
|
4423
|
+
init_paths();
|
|
4342
4424
|
import { mkdir as mkdir3, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
4343
4425
|
import { createServer as createServer2 } from "net";
|
|
4344
|
-
import { dirname as
|
|
4426
|
+
import { dirname as dirname5 } from "path";
|
|
4345
4427
|
|
|
4346
4428
|
// src/engine/claude-code-local/plan-usage.ts
|
|
4347
4429
|
import { execFile } from "child_process";
|
|
4348
4430
|
import { createHash } from "crypto";
|
|
4349
4431
|
import { readFile as readFile4 } from "fs/promises";
|
|
4350
4432
|
import { homedir as homedir10, userInfo } from "os";
|
|
4351
|
-
import { join as
|
|
4433
|
+
import { join as join7 } from "path";
|
|
4352
4434
|
import { promisify } from "util";
|
|
4353
4435
|
var execFileAsync = promisify(execFile);
|
|
4354
4436
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -4383,8 +4465,8 @@ async function readKeychainToken() {
|
|
|
4383
4465
|
}
|
|
4384
4466
|
}
|
|
4385
4467
|
async function readPlainTextToken() {
|
|
4386
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
4387
|
-
const path7 =
|
|
4468
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir10(), ".claude");
|
|
4469
|
+
const path7 = join7(configDir, ".credentials.json");
|
|
4388
4470
|
try {
|
|
4389
4471
|
const raw = await readFile4(path7, "utf8");
|
|
4390
4472
|
return parseStoredOAuth(raw);
|
|
@@ -4497,6 +4579,180 @@ function createPlanUsagePoller(options) {
|
|
|
4497
4579
|
}
|
|
4498
4580
|
};
|
|
4499
4581
|
}
|
|
4582
|
+
// src/daemon/rc-bridge.ts
|
|
4583
|
+
init_binary();
|
|
4584
|
+
import { spawn as spawn4 } from "child_process";
|
|
4585
|
+
var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
|
|
4586
|
+
var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
|
|
4587
|
+
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
4588
|
+
function createRcBridge(options = {}) {
|
|
4589
|
+
const stopGraceMs = options.stopGraceMs ?? 5000;
|
|
4590
|
+
const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
|
|
4591
|
+
const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
|
|
4592
|
+
const spawner = options.spawner ?? ((cmd, args, cwd) => spawn4(cmd, [...args], {
|
|
4593
|
+
cwd,
|
|
4594
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4595
|
+
env: { ...process.env }
|
|
4596
|
+
}));
|
|
4597
|
+
const subscribers = new Set;
|
|
4598
|
+
let snapshot = { state: "off" };
|
|
4599
|
+
let proc = null;
|
|
4600
|
+
let stdoutBuffer = "";
|
|
4601
|
+
let stderrBuffer = "";
|
|
4602
|
+
let readyResolve = null;
|
|
4603
|
+
let readyReject = null;
|
|
4604
|
+
let readyTimer = null;
|
|
4605
|
+
function update(next) {
|
|
4606
|
+
snapshot = next;
|
|
4607
|
+
for (const cb of subscribers) {
|
|
4608
|
+
try {
|
|
4609
|
+
cb(snapshot);
|
|
4610
|
+
} catch {}
|
|
4611
|
+
}
|
|
4612
|
+
return snapshot;
|
|
4613
|
+
}
|
|
4614
|
+
function clearReadyTimer() {
|
|
4615
|
+
if (readyTimer) {
|
|
4616
|
+
clearTimeout(readyTimer);
|
|
4617
|
+
readyTimer = null;
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
function settleReady(status) {
|
|
4621
|
+
clearReadyTimer();
|
|
4622
|
+
const resolve2 = readyResolve;
|
|
4623
|
+
readyResolve = null;
|
|
4624
|
+
readyReject = null;
|
|
4625
|
+
if (resolve2)
|
|
4626
|
+
resolve2(status);
|
|
4627
|
+
}
|
|
4628
|
+
function failReady(err) {
|
|
4629
|
+
clearReadyTimer();
|
|
4630
|
+
const reject = readyReject;
|
|
4631
|
+
readyResolve = null;
|
|
4632
|
+
readyReject = null;
|
|
4633
|
+
if (reject)
|
|
4634
|
+
reject(err);
|
|
4635
|
+
}
|
|
4636
|
+
function onStdout(chunk) {
|
|
4637
|
+
const text = chunk.toString("utf8").replace(ANSI_RE, "");
|
|
4638
|
+
stdoutBuffer = (stdoutBuffer + text).slice(-4096);
|
|
4639
|
+
if (snapshot.state !== "starting" && snapshot.state !== "running")
|
|
4640
|
+
return;
|
|
4641
|
+
const envMatch = stdoutBuffer.match(ENV_ID_RE);
|
|
4642
|
+
const linkMatch = stdoutBuffer.match(DEEPLINK_RE);
|
|
4643
|
+
if (envMatch && snapshot.envId !== envMatch[1]) {
|
|
4644
|
+
const envId = envMatch[1];
|
|
4645
|
+
const deeplink = linkMatch ? `https://claude.ai/code?environment=${linkMatch[1]}` : `https://claude.ai/code?environment=${envId}`;
|
|
4646
|
+
const ready = update({
|
|
4647
|
+
...snapshot,
|
|
4648
|
+
state: "running",
|
|
4649
|
+
envId,
|
|
4650
|
+
deeplink
|
|
4651
|
+
});
|
|
4652
|
+
settleReady(ready);
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
function onStderr(chunk) {
|
|
4656
|
+
stderrBuffer = (stderrBuffer + chunk.toString("utf8")).slice(-4096);
|
|
4657
|
+
}
|
|
4658
|
+
function onExit(code, signal) {
|
|
4659
|
+
proc = null;
|
|
4660
|
+
const wasStopping = snapshot.state === "stopping";
|
|
4661
|
+
if (wasStopping) {
|
|
4662
|
+
update({ state: "off" });
|
|
4663
|
+
settleReady(snapshot);
|
|
4664
|
+
return;
|
|
4665
|
+
}
|
|
4666
|
+
const tail = (text) => text.trim().split(`
|
|
4667
|
+
`).slice(-3).join(`
|
|
4668
|
+
`);
|
|
4669
|
+
const msg = tail(stderrBuffer) || tail(stdoutBuffer) || `claude remote-control exited (code=${code}, signal=${signal})`;
|
|
4670
|
+
const errored = update({ state: "error", errorMessage: msg });
|
|
4671
|
+
failReady(new Error(errored.errorMessage ?? "claude remote-control exited"));
|
|
4672
|
+
}
|
|
4673
|
+
return {
|
|
4674
|
+
status() {
|
|
4675
|
+
return snapshot;
|
|
4676
|
+
},
|
|
4677
|
+
onChange(cb) {
|
|
4678
|
+
subscribers.add(cb);
|
|
4679
|
+
return () => subscribers.delete(cb);
|
|
4680
|
+
},
|
|
4681
|
+
async start(opts) {
|
|
4682
|
+
if (snapshot.state === "running" || snapshot.state === "starting")
|
|
4683
|
+
return snapshot;
|
|
4684
|
+
const binary = await binaryPathResolver();
|
|
4685
|
+
stdoutBuffer = "";
|
|
4686
|
+
stderrBuffer = "";
|
|
4687
|
+
update({
|
|
4688
|
+
state: "starting",
|
|
4689
|
+
cwd: opts.cwd,
|
|
4690
|
+
startedAt: new Date().toISOString(),
|
|
4691
|
+
bound: opts.bound
|
|
4692
|
+
});
|
|
4693
|
+
const args = ["remote-control", "--verbose", "--remote-control-session-name-prefix", "kobe"];
|
|
4694
|
+
let child;
|
|
4695
|
+
try {
|
|
4696
|
+
child = spawner(binary, args, opts.cwd);
|
|
4697
|
+
} catch (err) {
|
|
4698
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4699
|
+
update({ state: "error", errorMessage: `failed to spawn: ${msg}` });
|
|
4700
|
+
throw err;
|
|
4701
|
+
}
|
|
4702
|
+
proc = child;
|
|
4703
|
+
update({ ...snapshot, pid: child.pid ?? undefined });
|
|
4704
|
+
child.stdout.on("data", onStdout);
|
|
4705
|
+
child.stderr.on("data", onStderr);
|
|
4706
|
+
child.once("exit", onExit);
|
|
4707
|
+
child.once("error", (err) => {
|
|
4708
|
+
proc = null;
|
|
4709
|
+
update({ state: "error", errorMessage: err.message });
|
|
4710
|
+
failReady(err);
|
|
4711
|
+
});
|
|
4712
|
+
return new Promise((resolve2, reject) => {
|
|
4713
|
+
readyResolve = resolve2;
|
|
4714
|
+
readyReject = reject;
|
|
4715
|
+
readyTimer = setTimeout(() => {
|
|
4716
|
+
if (proc) {
|
|
4717
|
+
try {
|
|
4718
|
+
proc.kill("SIGTERM");
|
|
4719
|
+
} catch {}
|
|
4720
|
+
}
|
|
4721
|
+
update({ state: "error", errorMessage: `timed out waiting for environment id (${readyTimeoutMs}ms)` });
|
|
4722
|
+
failReady(new Error(`claude remote-control did not become ready within ${readyTimeoutMs}ms`));
|
|
4723
|
+
}, readyTimeoutMs);
|
|
4724
|
+
readyTimer.unref?.();
|
|
4725
|
+
});
|
|
4726
|
+
},
|
|
4727
|
+
async stop() {
|
|
4728
|
+
const child = proc;
|
|
4729
|
+
if (!child || snapshot.state !== "running" && snapshot.state !== "starting") {
|
|
4730
|
+
if (snapshot.state !== "off")
|
|
4731
|
+
update({ state: "off" });
|
|
4732
|
+
return snapshot;
|
|
4733
|
+
}
|
|
4734
|
+
update({ ...snapshot, state: "stopping" });
|
|
4735
|
+
const onExitPromise = new Promise((resolve2) => {
|
|
4736
|
+
const onceExit = () => resolve2();
|
|
4737
|
+
child.once("exit", onceExit);
|
|
4738
|
+
});
|
|
4739
|
+
try {
|
|
4740
|
+
child.kill("SIGTERM");
|
|
4741
|
+
} catch {}
|
|
4742
|
+
const killTimer = setTimeout(() => {
|
|
4743
|
+
if (proc === child) {
|
|
4744
|
+
try {
|
|
4745
|
+
child.kill("SIGKILL");
|
|
4746
|
+
} catch {}
|
|
4747
|
+
}
|
|
4748
|
+
}, stopGraceMs);
|
|
4749
|
+
killTimer.unref?.();
|
|
4750
|
+
await onExitPromise;
|
|
4751
|
+
clearTimeout(killTimer);
|
|
4752
|
+
return snapshot;
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4500
4756
|
|
|
4501
4757
|
// src/daemon/server.ts
|
|
4502
4758
|
async function startDaemonServer(orch, options = {}) {
|
|
@@ -4505,8 +4761,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4505
4761
|
const startedAt = options.startedAt ?? new Date;
|
|
4506
4762
|
const clients = new Set;
|
|
4507
4763
|
let nextClientId = 1;
|
|
4508
|
-
await mkdir3(
|
|
4509
|
-
await mkdir3(
|
|
4764
|
+
await mkdir3(dirname5(socketPath), { recursive: true });
|
|
4765
|
+
await mkdir3(dirname5(pidPath), { recursive: true });
|
|
4510
4766
|
await unlink4(socketPath).catch(() => {});
|
|
4511
4767
|
const server = createServer2((socket) => {
|
|
4512
4768
|
const client = {
|
|
@@ -4532,6 +4788,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4532
4788
|
const planUsagePoller = options.planUsagePoller ?? createPlanUsagePoller({
|
|
4533
4789
|
onUpdate: (usage) => broadcast(clients, { type: "event", name: "plan.usage", payload: { usage } })
|
|
4534
4790
|
});
|
|
4791
|
+
const rcBridge = options.rcBridge ?? createRcBridge();
|
|
4792
|
+
rcBridge.onChange((status) => broadcast(clients, { type: "event", name: "rcBridge.changed", payload: { status } }));
|
|
4535
4793
|
const serverApi = {
|
|
4536
4794
|
socketPath,
|
|
4537
4795
|
pidPath,
|
|
@@ -4539,24 +4797,27 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4539
4797
|
clients,
|
|
4540
4798
|
async close() {
|
|
4541
4799
|
planUsagePoller.stop();
|
|
4800
|
+
try {
|
|
4801
|
+
await rcBridge.stop();
|
|
4802
|
+
} catch {}
|
|
4542
4803
|
broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
|
|
4543
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
4544
4804
|
for (const client of Array.from(clients)) {
|
|
4545
4805
|
for (const unsub of client.subscriptions.values())
|
|
4546
4806
|
unsub();
|
|
4547
4807
|
client.subscriptions.clear();
|
|
4548
|
-
client.socket.
|
|
4808
|
+
client.socket.destroy();
|
|
4549
4809
|
}
|
|
4810
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
4550
4811
|
await unlink4(socketPath).catch(() => {});
|
|
4551
4812
|
await unlink4(pidPath).catch(() => {});
|
|
4552
4813
|
}
|
|
4553
4814
|
};
|
|
4554
4815
|
planUsagePoller.start();
|
|
4555
|
-
await new Promise((
|
|
4816
|
+
await new Promise((resolve2, reject) => {
|
|
4556
4817
|
server.once("error", reject);
|
|
4557
4818
|
server.listen(socketPath, () => {
|
|
4558
4819
|
server.removeListener("error", reject);
|
|
4559
|
-
|
|
4820
|
+
resolve2();
|
|
4560
4821
|
});
|
|
4561
4822
|
});
|
|
4562
4823
|
await writeFile3(pidPath, `${process.pid}
|
|
@@ -4588,7 +4849,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4588
4849
|
tasks: tasks.map(serializeTask),
|
|
4589
4850
|
pending,
|
|
4590
4851
|
runState,
|
|
4591
|
-
planUsage: planUsagePoller.current()
|
|
4852
|
+
planUsage: planUsagePoller.current(),
|
|
4853
|
+
rcBridge: rcBridge.status()
|
|
4592
4854
|
};
|
|
4593
4855
|
}
|
|
4594
4856
|
case "daemon.status":
|
|
@@ -4772,6 +5034,41 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4772
5034
|
subscribeClientToTask(orch, client, task);
|
|
4773
5035
|
return {};
|
|
4774
5036
|
}
|
|
5037
|
+
case "rcBridge.start": {
|
|
5038
|
+
const taskId = optionalString2(payload, "taskId");
|
|
5039
|
+
const tabId = optionalString2(payload, "tabId");
|
|
5040
|
+
let cwd;
|
|
5041
|
+
let bound;
|
|
5042
|
+
if (taskId) {
|
|
5043
|
+
const task = orch.getTask(taskId);
|
|
5044
|
+
if (!task)
|
|
5045
|
+
throw new Error(`rcBridge.start: unknown taskId ${taskId}`);
|
|
5046
|
+
const resolvedTabId = tabId ?? task.activeTabId;
|
|
5047
|
+
const tab = task.tabs.find((t) => t.id === resolvedTabId);
|
|
5048
|
+
if (!tab)
|
|
5049
|
+
throw new Error(`rcBridge.start: unknown tabId ${resolvedTabId} on task ${taskId}`);
|
|
5050
|
+
cwd = task.worktreePath;
|
|
5051
|
+
bound = {
|
|
5052
|
+
taskId: task.id,
|
|
5053
|
+
tabId: tab.id,
|
|
5054
|
+
sessionId: tab.sessionId,
|
|
5055
|
+
taskTitle: task.title
|
|
5056
|
+
};
|
|
5057
|
+
} else {
|
|
5058
|
+
cwd = optionalString2(payload, "cwd") ?? resolveRepoRoot(process.cwd());
|
|
5059
|
+
}
|
|
5060
|
+
if (!cwd)
|
|
5061
|
+
throw new Error("rcBridge.start requires a non-empty cwd");
|
|
5062
|
+
const status = await rcBridge.start({ cwd, bound });
|
|
5063
|
+
return { status };
|
|
5064
|
+
}
|
|
5065
|
+
case "rcBridge.stop": {
|
|
5066
|
+
const status = await rcBridge.stop();
|
|
5067
|
+
return { status };
|
|
5068
|
+
}
|
|
5069
|
+
case "rcBridge.status": {
|
|
5070
|
+
return { status: rcBridge.status() };
|
|
5071
|
+
}
|
|
4775
5072
|
default:
|
|
4776
5073
|
throw new Error(`unknown daemon request: ${req.name}`);
|
|
4777
5074
|
}
|
|
@@ -4975,23 +5272,42 @@ async function main() {
|
|
|
4975
5272
|
if (command === "restart") {
|
|
4976
5273
|
const oldPid = await readPidFile(pidPath);
|
|
4977
5274
|
const client = new KobeDaemonClient(socketPath);
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
}
|
|
4981
|
-
|
|
4982
|
-
|
|
5275
|
+
const stopRequest = client.request("daemon.stop").catch(() => {
|
|
5276
|
+
return;
|
|
5277
|
+
});
|
|
5278
|
+
const stopTimeout = new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
5279
|
+
await Promise.race([stopRequest, stopTimeout]);
|
|
5280
|
+
client.close();
|
|
4983
5281
|
if (oldPid && oldPid !== process.pid) {
|
|
4984
5282
|
const deadline = Date.now() + 5000;
|
|
5283
|
+
let escalated = false;
|
|
4985
5284
|
while (Date.now() < deadline) {
|
|
4986
5285
|
try {
|
|
4987
5286
|
process.kill(oldPid, 0);
|
|
4988
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
4989
5287
|
} catch {
|
|
4990
5288
|
break;
|
|
4991
5289
|
}
|
|
5290
|
+
if (!escalated && Date.now() - (deadline - 5000) > 2000) {
|
|
5291
|
+
try {
|
|
5292
|
+
process.kill(oldPid, "SIGTERM");
|
|
5293
|
+
} catch {}
|
|
5294
|
+
escalated = true;
|
|
5295
|
+
}
|
|
5296
|
+
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
4992
5297
|
}
|
|
5298
|
+
try {
|
|
5299
|
+
process.kill(oldPid, 0);
|
|
5300
|
+
process.kill(oldPid, "SIGKILL");
|
|
5301
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
5302
|
+
} catch {}
|
|
4993
5303
|
}
|
|
4994
|
-
|
|
5304
|
+
await unlink5(socketPath).catch(() => {});
|
|
5305
|
+
const next = await connectOrStartDaemon();
|
|
5306
|
+
next.close();
|
|
5307
|
+
console.log(`kobed: restarted, listening on ${socketPath}`);
|
|
5308
|
+
return;
|
|
5309
|
+
}
|
|
5310
|
+
if (command !== "start") {
|
|
4995
5311
|
console.error("usage: kobed start|stop|status|restart");
|
|
4996
5312
|
process.exit(2);
|
|
4997
5313
|
}
|