@sma1lboy/kobe 0.7.6 → 0.7.7
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/cli/index.js +336 -327
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -70,7 +70,7 @@ var init_package = __esm(() => {
|
|
|
70
70
|
package_default = {
|
|
71
71
|
$schema: "https://json.schemastore.org/package.json",
|
|
72
72
|
name: "@sma1lboy/kobe",
|
|
73
|
-
version: "0.7.
|
|
73
|
+
version: "0.7.7",
|
|
74
74
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
75
75
|
type: "module",
|
|
76
76
|
packageManager: "bun@1.3.13",
|
|
@@ -2325,256 +2325,6 @@ var init_dev = __esm(() => {
|
|
|
2325
2325
|
}
|
|
2326
2326
|
});
|
|
2327
2327
|
|
|
2328
|
-
// src/cli/invocation.ts
|
|
2329
|
-
import { fileURLToPath } from "url";
|
|
2330
|
-
function kobeCliInvocation() {
|
|
2331
|
-
const isBuilt = import.meta.url.endsWith(".js");
|
|
2332
|
-
if (isBuilt)
|
|
2333
|
-
return ["kobe"];
|
|
2334
|
-
const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
|
|
2335
|
-
const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
|
|
2336
|
-
return [process.execPath, "--preload", preload, "--conditions=browser", entry];
|
|
2337
|
-
}
|
|
2338
|
-
var init_invocation = () => {};
|
|
2339
|
-
|
|
2340
|
-
// src/tmux/session-layout.ts
|
|
2341
|
-
function shellQuote(s) {
|
|
2342
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
2343
|
-
}
|
|
2344
|
-
function shellQuoteArgv(argv) {
|
|
2345
|
-
return argv.map(shellQuote).join(" ");
|
|
2346
|
-
}
|
|
2347
|
-
function keepAlive(cmd) {
|
|
2348
|
-
return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
|
|
2349
|
-
}
|
|
2350
|
-
function shQuote(s) {
|
|
2351
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
2352
|
-
}
|
|
2353
|
-
function homeWelcomeCommand() {
|
|
2354
|
-
const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
|
|
2355
|
-
return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
|
|
2356
|
-
}
|
|
2357
|
-
function engineLaunchLine(engineCmd, init) {
|
|
2358
|
-
const tail = keepAlive(engineCmd);
|
|
2359
|
-
const script = init?.initScript?.trim();
|
|
2360
|
-
if (!script)
|
|
2361
|
-
return tail;
|
|
2362
|
-
const group = ["{", script, "}"].join(`
|
|
2363
|
-
`);
|
|
2364
|
-
if (init?.markerPath) {
|
|
2365
|
-
const marker = shQuote(init.markerPath);
|
|
2366
|
-
const markerDir = shQuote(markerDirOf(init.markerPath));
|
|
2367
|
-
return [
|
|
2368
|
-
`if [ ! -f ${marker} ]; then`,
|
|
2369
|
-
group,
|
|
2370
|
-
`if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
|
|
2371
|
-
"fi",
|
|
2372
|
-
tail
|
|
2373
|
-
].join(`
|
|
2374
|
-
`);
|
|
2375
|
-
}
|
|
2376
|
-
return [group, tail].join(`
|
|
2377
|
-
`);
|
|
2378
|
-
}
|
|
2379
|
-
function markerDirOf(p) {
|
|
2380
|
-
const i = p.lastIndexOf("/");
|
|
2381
|
-
return i <= 0 ? "." : p.slice(0, i);
|
|
2382
|
-
}
|
|
2383
|
-
function fallbackOpsScript(cwd) {
|
|
2384
|
-
return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
|
|
2385
|
-
}
|
|
2386
|
-
function previewWindowCommand(args) {
|
|
2387
|
-
const wt = shellQuote(args.worktree);
|
|
2388
|
-
const file = shellQuote(args.relPath);
|
|
2389
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
2390
|
-
const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
|
|
2391
|
-
return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
|
|
2392
|
-
}
|
|
2393
|
-
function updatePageCommand(args) {
|
|
2394
|
-
return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
|
|
2395
|
-
}
|
|
2396
|
-
function tasksPaneCommand(cliInvocation, opts = {}) {
|
|
2397
|
-
const argv = [...cliInvocation, "tasks"];
|
|
2398
|
-
if (opts.initialTaskId)
|
|
2399
|
-
argv.push("--initial-task-id", opts.initialTaskId);
|
|
2400
|
-
return shellQuoteArgv(argv);
|
|
2401
|
-
}
|
|
2402
|
-
function opsPaneCommand(args) {
|
|
2403
|
-
if (args.taskId && args.claudePaneId) {
|
|
2404
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
2405
|
-
const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
|
|
2406
|
-
return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
|
|
2407
|
-
}
|
|
2408
|
-
return fallbackOpsScript(args.cwd);
|
|
2409
|
-
}
|
|
2410
|
-
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
2411
|
-
|
|
2412
|
-
// src/engine/claude-code-local/hook-adapter.ts
|
|
2413
|
-
import { existsSync } from "fs";
|
|
2414
|
-
import { appendFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
2415
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
2416
|
-
function isObject(v) {
|
|
2417
|
-
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
2418
|
-
}
|
|
2419
|
-
async function readJsonObject(path3) {
|
|
2420
|
-
try {
|
|
2421
|
-
const parsed = JSON.parse(await readFile2(path3, "utf8"));
|
|
2422
|
-
return isObject(parsed) ? parsed : {};
|
|
2423
|
-
} catch {
|
|
2424
|
-
return {};
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
function isKobeWorktreeSyncGroup(group) {
|
|
2428
|
-
if (!isObject(group) || !Array.isArray(group.hooks))
|
|
2429
|
-
return false;
|
|
2430
|
-
return group.hooks.some((h) => isObject(h) && typeof h.command === "string" && h.command.includes(WORKTREE_SYNC_MARKER));
|
|
2431
|
-
}
|
|
2432
|
-
function mergeWorktreeSyncHook(current, command) {
|
|
2433
|
-
const { hooks: rawHooks, ...restSettings } = current;
|
|
2434
|
-
const { WorktreeCreate, ...otherHooks } = isObject(rawHooks) ? rawHooks : {};
|
|
2435
|
-
const prior = Array.isArray(WorktreeCreate) ? WorktreeCreate : [];
|
|
2436
|
-
const kept = prior.filter((g) => !isKobeWorktreeSyncGroup(g));
|
|
2437
|
-
if (command !== null)
|
|
2438
|
-
kept.push({ hooks: [{ type: "command", command }] });
|
|
2439
|
-
const nextHooks = { ...otherHooks };
|
|
2440
|
-
if (kept.length > 0)
|
|
2441
|
-
nextHooks.WorktreeCreate = kept;
|
|
2442
|
-
return Object.keys(nextHooks).length > 0 ? { ...restSettings, hooks: nextHooks } : { ...restSettings };
|
|
2443
|
-
}
|
|
2444
|
-
function buildClaudeHooks(taskId, inv = kobeCliInvocation()) {
|
|
2445
|
-
const out = {};
|
|
2446
|
-
for (const { event, matcher, verb } of EVENT_MAP) {
|
|
2447
|
-
const command = shellQuoteArgv([...inv, "hook", verb, "--task-id", taskId]);
|
|
2448
|
-
const group = { hooks: [{ type: "command", command }] };
|
|
2449
|
-
if (matcher)
|
|
2450
|
-
group.matcher = matcher;
|
|
2451
|
-
out[event] = [group];
|
|
2452
|
-
}
|
|
2453
|
-
return out;
|
|
2454
|
-
}
|
|
2455
|
-
function mergeClaudeHooks(existing, kobeHooks) {
|
|
2456
|
-
const merged = { ...existing };
|
|
2457
|
-
for (const event of KOBE_HOOK_EVENTS)
|
|
2458
|
-
merged[event] = kobeHooks[event];
|
|
2459
|
-
return merged;
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
class ClaudeHookAdapter {
|
|
2463
|
-
vendor = "claude";
|
|
2464
|
-
supportsHooks() {
|
|
2465
|
-
return true;
|
|
2466
|
-
}
|
|
2467
|
-
supportsWorktreeSync() {
|
|
2468
|
-
return true;
|
|
2469
|
-
}
|
|
2470
|
-
async installWorktreeSyncHook(settingsFilePath) {
|
|
2471
|
-
await this.editWorktreeSyncHook(settingsFilePath, true);
|
|
2472
|
-
}
|
|
2473
|
-
async removeWorktreeSyncHook(settingsFilePath) {
|
|
2474
|
-
await this.editWorktreeSyncHook(settingsFilePath, false);
|
|
2475
|
-
}
|
|
2476
|
-
async editWorktreeSyncHook(settingsFilePath, install) {
|
|
2477
|
-
const current = await readJsonObject(settingsFilePath);
|
|
2478
|
-
const command = install ? shellQuoteArgv([...kobeCliInvocation(), "hook", "worktree-created"]) : null;
|
|
2479
|
-
const next = mergeWorktreeSyncHook(current, command);
|
|
2480
|
-
if (JSON.stringify(next) === JSON.stringify(current))
|
|
2481
|
-
return;
|
|
2482
|
-
await mkdir2(dirname3(settingsFilePath), { recursive: true });
|
|
2483
|
-
await writeFile2(settingsFilePath, `${JSON.stringify(next, null, 2)}
|
|
2484
|
-
`);
|
|
2485
|
-
}
|
|
2486
|
-
async installTaskHooks(ctx) {
|
|
2487
|
-
try {
|
|
2488
|
-
const claudeDir = join3(ctx.worktreeDir, ".claude");
|
|
2489
|
-
const settingsPath = join3(claudeDir, "settings.local.json");
|
|
2490
|
-
await mkdir2(claudeDir, { recursive: true });
|
|
2491
|
-
let current = {};
|
|
2492
|
-
try {
|
|
2493
|
-
const raw = await readFile2(settingsPath, "utf8");
|
|
2494
|
-
const parsed = JSON.parse(raw);
|
|
2495
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
2496
|
-
current = parsed;
|
|
2497
|
-
} catch {}
|
|
2498
|
-
const existingHooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks) ? current.hooks : {};
|
|
2499
|
-
current.hooks = mergeClaudeHooks(existingHooks, buildClaudeHooks(ctx.taskId));
|
|
2500
|
-
await writeFile2(settingsPath, `${JSON.stringify(current, null, 2)}
|
|
2501
|
-
`);
|
|
2502
|
-
await hideFromGit(ctx.worktreeDir, ".claude/settings.local.json");
|
|
2503
|
-
} catch {}
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
async function hideFromGit(worktreeDir, relPath) {
|
|
2507
|
-
if (hiddenWorktrees.has(worktreeDir))
|
|
2508
|
-
return;
|
|
2509
|
-
try {
|
|
2510
|
-
const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
|
|
2511
|
-
stdout: "pipe",
|
|
2512
|
-
stderr: "ignore"
|
|
2513
|
-
});
|
|
2514
|
-
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
2515
|
-
if (code !== 0)
|
|
2516
|
-
return;
|
|
2517
|
-
let commonDir = out.trim();
|
|
2518
|
-
if (!commonDir)
|
|
2519
|
-
return;
|
|
2520
|
-
if (!commonDir.startsWith("/"))
|
|
2521
|
-
commonDir = join3(worktreeDir, commonDir);
|
|
2522
|
-
const excludePath = join3(commonDir, "info", "exclude");
|
|
2523
|
-
const existing = existsSync(excludePath) ? await readFile2(excludePath, "utf8") : "";
|
|
2524
|
-
if (existing.split(`
|
|
2525
|
-
`).some((l) => l.trim() === relPath)) {
|
|
2526
|
-
hiddenWorktrees.add(worktreeDir);
|
|
2527
|
-
return;
|
|
2528
|
-
}
|
|
2529
|
-
await mkdir2(join3(commonDir, "info"), { recursive: true });
|
|
2530
|
-
await appendFile(excludePath, `${existing.endsWith(`
|
|
2531
|
-
`) || existing === "" ? "" : `
|
|
2532
|
-
`}${relPath}
|
|
2533
|
-
`);
|
|
2534
|
-
hiddenWorktrees.add(worktreeDir);
|
|
2535
|
-
} catch {}
|
|
2536
|
-
}
|
|
2537
|
-
var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created", hiddenWorktrees;
|
|
2538
|
-
var init_hook_adapter = __esm(() => {
|
|
2539
|
-
init_invocation();
|
|
2540
|
-
EVENT_MAP = [
|
|
2541
|
-
{ event: "SessionStart", verb: "session-start" },
|
|
2542
|
-
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
2543
|
-
{ event: "Stop", verb: "turn-complete" },
|
|
2544
|
-
{ event: "StopFailure", verb: "turn-failed" },
|
|
2545
|
-
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
2546
|
-
{ event: "SessionEnd", verb: "session-end" }
|
|
2547
|
-
];
|
|
2548
|
-
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
2549
|
-
hiddenWorktrees = new Set;
|
|
2550
|
-
});
|
|
2551
|
-
|
|
2552
|
-
// src/engine/hook-adapter.ts
|
|
2553
|
-
function createEngineHookAdapter(vendor) {
|
|
2554
|
-
if (vendor === "claude")
|
|
2555
|
-
return new ClaudeHookAdapter;
|
|
2556
|
-
return new NoopHookAdapter(vendor);
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
class NoopHookAdapter {
|
|
2560
|
-
vendor;
|
|
2561
|
-
constructor(vendor) {
|
|
2562
|
-
this.vendor = vendor;
|
|
2563
|
-
}
|
|
2564
|
-
supportsHooks() {
|
|
2565
|
-
return false;
|
|
2566
|
-
}
|
|
2567
|
-
async installTaskHooks() {}
|
|
2568
|
-
supportsWorktreeSync() {
|
|
2569
|
-
return false;
|
|
2570
|
-
}
|
|
2571
|
-
async installWorktreeSyncHook() {}
|
|
2572
|
-
async removeWorktreeSyncHook() {}
|
|
2573
|
-
}
|
|
2574
|
-
var init_hook_adapter2 = __esm(() => {
|
|
2575
|
-
init_hook_adapter();
|
|
2576
|
-
});
|
|
2577
|
-
|
|
2578
2328
|
// src/orchestrator/errors.ts
|
|
2579
2329
|
var IllegalTransitionError, TaskNotFoundError, CannotDeleteMainTaskError, DIRTY_WORKTREE_CODE = "DIRTY_WORKTREE", DirtyWorktreeError, WorktreeRemoveFailedError;
|
|
2580
2330
|
var init_errors = __esm(() => {
|
|
@@ -3253,10 +3003,8 @@ class Orchestrator {
|
|
|
3253
3003
|
const task = this.requireTask(id);
|
|
3254
3004
|
if (task.kind === "main")
|
|
3255
3005
|
return task.repo;
|
|
3256
|
-
if (task.worktreePath)
|
|
3257
|
-
await this.installEngineHooks(task);
|
|
3006
|
+
if (task.worktreePath)
|
|
3258
3007
|
return task.worktreePath;
|
|
3259
|
-
}
|
|
3260
3008
|
const inflight = this.worktreeLocks.get(task.id);
|
|
3261
3009
|
if (inflight) {
|
|
3262
3010
|
await inflight;
|
|
@@ -3280,7 +3028,6 @@ class Orchestrator {
|
|
|
3280
3028
|
worktreePath: info.path,
|
|
3281
3029
|
branch
|
|
3282
3030
|
});
|
|
3283
|
-
await this.installEngineHooks(this.requireTask(task.id));
|
|
3284
3031
|
} catch (err) {
|
|
3285
3032
|
this.slugs.cancel(task.repo, slug);
|
|
3286
3033
|
throw err;
|
|
@@ -3440,14 +3187,6 @@ class Orchestrator {
|
|
|
3440
3187
|
vendor: input.vendor ?? DEFAULT_TASK_VENDOR
|
|
3441
3188
|
});
|
|
3442
3189
|
}
|
|
3443
|
-
async installEngineHooks(task) {
|
|
3444
|
-
if (task.kind === "main" || !task.worktreePath)
|
|
3445
|
-
return;
|
|
3446
|
-
await createEngineHookAdapter(task.vendor ?? DEFAULT_TASK_VENDOR).installTaskHooks({
|
|
3447
|
-
worktreeDir: task.worktreePath,
|
|
3448
|
-
taskId: task.id
|
|
3449
|
-
});
|
|
3450
|
-
}
|
|
3451
3190
|
pendingBaseRefs = new Map;
|
|
3452
3191
|
adoptLocks = new Map;
|
|
3453
3192
|
requireTask(id) {
|
|
@@ -3471,7 +3210,6 @@ function canonPath(p) {
|
|
|
3471
3210
|
var PLACEHOLDER_TASK_TITLE = "(new task)";
|
|
3472
3211
|
var init_core = __esm(() => {
|
|
3473
3212
|
init_dev();
|
|
3474
|
-
init_hook_adapter2();
|
|
3475
3213
|
init_errors();
|
|
3476
3214
|
init_slug_allocator();
|
|
3477
3215
|
});
|
|
@@ -3514,7 +3252,7 @@ var init_protocol = __esm(() => {
|
|
|
3514
3252
|
// src/daemon/paths.ts
|
|
3515
3253
|
import { createHash as createHash2 } from "crypto";
|
|
3516
3254
|
import { homedir as homedir3, tmpdir } from "os";
|
|
3517
|
-
import { join as
|
|
3255
|
+
import { join as join3 } from "path";
|
|
3518
3256
|
function shortHomeTag(homeDir2) {
|
|
3519
3257
|
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
3520
3258
|
}
|
|
@@ -3523,7 +3261,7 @@ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
|
3523
3261
|
return naturalPath;
|
|
3524
3262
|
const tag = shortHomeTag(homeDir2);
|
|
3525
3263
|
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
3526
|
-
const fallback =
|
|
3264
|
+
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
3527
3265
|
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3528
3266
|
return fallback;
|
|
3529
3267
|
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
@@ -3534,33 +3272,33 @@ function defaultDaemonSocketPath(homeDir2) {
|
|
|
3534
3272
|
return override;
|
|
3535
3273
|
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
3536
3274
|
if (explicit && explicit.length > 0) {
|
|
3537
|
-
return fitSocketPath(
|
|
3275
|
+
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
3538
3276
|
}
|
|
3539
3277
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
3540
3278
|
if (runtimeDir && runtimeDir.length > 0) {
|
|
3541
|
-
return fitSocketPath(
|
|
3279
|
+
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
3542
3280
|
}
|
|
3543
3281
|
const home = homedir3();
|
|
3544
|
-
return fitSocketPath(
|
|
3282
|
+
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
3545
3283
|
}
|
|
3546
3284
|
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3547
3285
|
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
3548
3286
|
if (override && override.length > 0)
|
|
3549
3287
|
return override;
|
|
3550
|
-
return
|
|
3288
|
+
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
3551
3289
|
}
|
|
3552
3290
|
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3553
|
-
return
|
|
3291
|
+
return join3(homeDir2, ".kobe", "daemon.log");
|
|
3554
3292
|
}
|
|
3555
3293
|
function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3556
|
-
return
|
|
3294
|
+
return join3(homeDir2, ".kobe", "client.log");
|
|
3557
3295
|
}
|
|
3558
3296
|
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
3559
3297
|
var init_paths2 = () => {};
|
|
3560
3298
|
|
|
3561
3299
|
// src/client/client-log.ts
|
|
3562
|
-
import { appendFile
|
|
3563
|
-
import { dirname as
|
|
3300
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
3301
|
+
import { dirname as dirname3 } from "path";
|
|
3564
3302
|
function setClientLogContext(ctx) {
|
|
3565
3303
|
context = ctx;
|
|
3566
3304
|
}
|
|
@@ -3592,11 +3330,11 @@ function append(line) {
|
|
|
3592
3330
|
const path3 = defaultClientLogPath();
|
|
3593
3331
|
writeChain = writeChain.then(async () => {
|
|
3594
3332
|
try {
|
|
3595
|
-
await
|
|
3333
|
+
await appendFile(path3, line);
|
|
3596
3334
|
} catch (err) {
|
|
3597
3335
|
if (err?.code === "ENOENT") {
|
|
3598
|
-
await
|
|
3599
|
-
await
|
|
3336
|
+
await mkdir2(dirname3(path3), { recursive: true });
|
|
3337
|
+
await appendFile(path3, line);
|
|
3600
3338
|
} else {
|
|
3601
3339
|
throw err;
|
|
3602
3340
|
}
|
|
@@ -3876,7 +3614,7 @@ function normalizeClaudeContent(content) {
|
|
|
3876
3614
|
}
|
|
3877
3615
|
|
|
3878
3616
|
// src/engine/claude-code-local/history.ts
|
|
3879
|
-
import { appendFile as
|
|
3617
|
+
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
3880
3618
|
import { homedir as homedir4 } from "os";
|
|
3881
3619
|
import path3 from "path";
|
|
3882
3620
|
function encodeCwd(cwd) {
|
|
@@ -3947,7 +3685,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3947
3685
|
} catch {
|
|
3948
3686
|
continue;
|
|
3949
3687
|
}
|
|
3950
|
-
if (!
|
|
3688
|
+
if (!isObject(parsed))
|
|
3951
3689
|
continue;
|
|
3952
3690
|
const msg = extractMessage(parsed, sessionId);
|
|
3953
3691
|
if (msg)
|
|
@@ -3956,7 +3694,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3956
3694
|
return out;
|
|
3957
3695
|
}
|
|
3958
3696
|
function extractMessage(record, fallbackSessionId) {
|
|
3959
|
-
const inner =
|
|
3697
|
+
const inner = isObject(record.message) ? record.message : record;
|
|
3960
3698
|
const role = inner.role;
|
|
3961
3699
|
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
3962
3700
|
return null;
|
|
@@ -3969,7 +3707,7 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
3969
3707
|
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
3970
3708
|
}
|
|
3971
3709
|
function extractUsage(v) {
|
|
3972
|
-
if (!
|
|
3710
|
+
if (!isObject(v))
|
|
3973
3711
|
return;
|
|
3974
3712
|
const inTok = typeof v.input_tokens === "number" ? v.input_tokens : undefined;
|
|
3975
3713
|
const outTok = typeof v.output_tokens === "number" ? v.output_tokens : undefined;
|
|
@@ -3984,7 +3722,7 @@ function extractUsage(v) {
|
|
|
3984
3722
|
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
3985
3723
|
};
|
|
3986
3724
|
}
|
|
3987
|
-
function
|
|
3725
|
+
function isObject(v) {
|
|
3988
3726
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3989
3727
|
}
|
|
3990
3728
|
var defaultDeps;
|
|
@@ -4001,7 +3739,7 @@ var init_history = __esm(() => {
|
|
|
4001
3739
|
}
|
|
4002
3740
|
},
|
|
4003
3741
|
async readFile(p) {
|
|
4004
|
-
return await
|
|
3742
|
+
return await readFile2(p, "utf8");
|
|
4005
3743
|
},
|
|
4006
3744
|
async pathExists(p) {
|
|
4007
3745
|
try {
|
|
@@ -4028,7 +3766,7 @@ function normalizeCodexContent(raw) {
|
|
|
4028
3766
|
blocks.push({ type: "text", text: item });
|
|
4029
3767
|
continue;
|
|
4030
3768
|
}
|
|
4031
|
-
if (!
|
|
3769
|
+
if (!isObject2(item))
|
|
4032
3770
|
continue;
|
|
4033
3771
|
const t = typeof item.type === "string" ? item.type : undefined;
|
|
4034
3772
|
if (t === "input_text" || t === "output_text") {
|
|
@@ -4042,7 +3780,7 @@ function normalizeCodexContent(raw) {
|
|
|
4042
3780
|
}
|
|
4043
3781
|
return blocks;
|
|
4044
3782
|
}
|
|
4045
|
-
function
|
|
3783
|
+
function isObject2(v) {
|
|
4046
3784
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4047
3785
|
}
|
|
4048
3786
|
|
|
@@ -4092,7 +3830,7 @@ function validPositive(v) {
|
|
|
4092
3830
|
}
|
|
4093
3831
|
|
|
4094
3832
|
// src/engine/codex-local/history.ts
|
|
4095
|
-
import { readFile as
|
|
3833
|
+
import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
4096
3834
|
import { homedir as homedir5 } from "os";
|
|
4097
3835
|
import path4 from "path";
|
|
4098
3836
|
async function listRolloutFiles(deps = defaultDeps2) {
|
|
@@ -4227,11 +3965,11 @@ function parseJsonl2(raw, sessionId) {
|
|
|
4227
3965
|
} catch {
|
|
4228
3966
|
continue;
|
|
4229
3967
|
}
|
|
4230
|
-
if (!
|
|
3968
|
+
if (!isObject3(parsed))
|
|
4231
3969
|
continue;
|
|
4232
3970
|
if (parsed.type !== "response_item")
|
|
4233
3971
|
continue;
|
|
4234
|
-
const payload =
|
|
3972
|
+
const payload = isObject3(parsed.payload) ? parsed.payload : undefined;
|
|
4235
3973
|
if (!payload)
|
|
4236
3974
|
continue;
|
|
4237
3975
|
const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
|
|
@@ -4348,7 +4086,7 @@ function textFromReasoningValue(value) {
|
|
|
4348
4086
|
parts.push(entry);
|
|
4349
4087
|
continue;
|
|
4350
4088
|
}
|
|
4351
|
-
if (!
|
|
4089
|
+
if (!isObject3(entry))
|
|
4352
4090
|
continue;
|
|
4353
4091
|
const text = typeof entry.text === "string" ? entry.text : "";
|
|
4354
4092
|
if (text.length > 0)
|
|
@@ -4390,14 +4128,14 @@ function deriveCodexUsageMetrics(raw) {
|
|
|
4390
4128
|
} catch {
|
|
4391
4129
|
continue;
|
|
4392
4130
|
}
|
|
4393
|
-
if (!
|
|
4131
|
+
if (!isObject3(parsed))
|
|
4394
4132
|
continue;
|
|
4395
4133
|
const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs(parsed.timestamp) : null;
|
|
4396
4134
|
if (parsed.type === "response_item")
|
|
4397
4135
|
continue;
|
|
4398
4136
|
if (parsed.type !== "turn.completed")
|
|
4399
4137
|
continue;
|
|
4400
|
-
const usage =
|
|
4138
|
+
const usage = isObject3(parsed.usage) ? parsed.usage : undefined;
|
|
4401
4139
|
if (!usage)
|
|
4402
4140
|
continue;
|
|
4403
4141
|
const snapshot = codexUsageToSnapshot(usage);
|
|
@@ -4416,7 +4154,7 @@ function parseTimestampMs(value) {
|
|
|
4416
4154
|
const ms = new Date(value).getTime();
|
|
4417
4155
|
return Number.isFinite(ms) ? ms : null;
|
|
4418
4156
|
}
|
|
4419
|
-
function
|
|
4157
|
+
function isObject3(v) {
|
|
4420
4158
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4421
4159
|
}
|
|
4422
4160
|
var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
|
|
@@ -4434,7 +4172,7 @@ var init_history2 = __esm(() => {
|
|
|
4434
4172
|
}
|
|
4435
4173
|
},
|
|
4436
4174
|
async readFile(p) {
|
|
4437
|
-
return await
|
|
4175
|
+
return await readFile3(p, "utf8");
|
|
4438
4176
|
},
|
|
4439
4177
|
stat: stat2
|
|
4440
4178
|
};
|
|
@@ -4443,16 +4181,16 @@ var init_history2 = __esm(() => {
|
|
|
4443
4181
|
|
|
4444
4182
|
// src/engine/copilot-local/usage.ts
|
|
4445
4183
|
function copilotUsageToSnapshot(value) {
|
|
4446
|
-
if (!
|
|
4184
|
+
if (!isObject4(value))
|
|
4447
4185
|
return;
|
|
4448
|
-
const modelMetrics =
|
|
4186
|
+
const modelMetrics = isObject4(value.modelMetrics) ? value.modelMetrics : undefined;
|
|
4449
4187
|
if (!modelMetrics)
|
|
4450
4188
|
return;
|
|
4451
4189
|
let input = 0;
|
|
4452
4190
|
let output = 0;
|
|
4453
4191
|
let cached = 0;
|
|
4454
4192
|
for (const metrics of Object.values(modelMetrics)) {
|
|
4455
|
-
if (!
|
|
4193
|
+
if (!isObject4(metrics) || !isObject4(metrics.usage))
|
|
4456
4194
|
continue;
|
|
4457
4195
|
input += numberOr2(metrics.usage.inputTokens, 0);
|
|
4458
4196
|
output += numberOr2(metrics.usage.outputTokens, 0);
|
|
@@ -4468,7 +4206,7 @@ function copilotUsageToSnapshot(value) {
|
|
|
4468
4206
|
...context2 > 0 ? { context_tokens: context2 } : {}
|
|
4469
4207
|
};
|
|
4470
4208
|
}
|
|
4471
|
-
function
|
|
4209
|
+
function isObject4(v) {
|
|
4472
4210
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4473
4211
|
}
|
|
4474
4212
|
function numberOr2(value, fallback) {
|
|
@@ -4476,7 +4214,7 @@ function numberOr2(value, fallback) {
|
|
|
4476
4214
|
}
|
|
4477
4215
|
|
|
4478
4216
|
// src/engine/copilot-local/history.ts
|
|
4479
|
-
import { readFile as
|
|
4217
|
+
import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
4480
4218
|
import { homedir as homedir6 } from "os";
|
|
4481
4219
|
import path5 from "path";
|
|
4482
4220
|
async function listSessionDirs(deps = defaultDeps3) {
|
|
@@ -4573,10 +4311,10 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4573
4311
|
} catch {
|
|
4574
4312
|
continue;
|
|
4575
4313
|
}
|
|
4576
|
-
if (!
|
|
4314
|
+
if (!isObject5(record) || typeof record.type !== "string")
|
|
4577
4315
|
continue;
|
|
4578
4316
|
const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
4579
|
-
const data =
|
|
4317
|
+
const data = isObject5(record.data) ? record.data : {};
|
|
4580
4318
|
if (record.type === "session.start") {
|
|
4581
4319
|
const sid = typeof data.sessionId === "string" ? data.sessionId : undefined;
|
|
4582
4320
|
if (sid)
|
|
@@ -4599,7 +4337,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4599
4337
|
blocks.push({ type: "text", text });
|
|
4600
4338
|
const toolRequests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
|
|
4601
4339
|
for (const req of toolRequests) {
|
|
4602
|
-
if (!
|
|
4340
|
+
if (!isObject5(req))
|
|
4603
4341
|
continue;
|
|
4604
4342
|
const callId = typeof req.id === "string" ? req.id : typeof req.toolCallId === "string" ? req.toolCallId : "tool";
|
|
4605
4343
|
const name = typeof req.name === "string" ? req.name : typeof req.toolName === "string" ? req.toolName : "tool";
|
|
@@ -4636,7 +4374,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4636
4374
|
}
|
|
4637
4375
|
return { messages, usageMetrics, firstUserMessage };
|
|
4638
4376
|
}
|
|
4639
|
-
function
|
|
4377
|
+
function isObject5(v) {
|
|
4640
4378
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4641
4379
|
}
|
|
4642
4380
|
var defaultDeps3, PREVIEW_CHAR_CAP = 200;
|
|
@@ -4656,7 +4394,7 @@ var init_history3 = __esm(() => {
|
|
|
4656
4394
|
}
|
|
4657
4395
|
},
|
|
4658
4396
|
async readFile(p) {
|
|
4659
|
-
return await
|
|
4397
|
+
return await readFile4(p, "utf8");
|
|
4660
4398
|
},
|
|
4661
4399
|
stat: stat3,
|
|
4662
4400
|
async rm(p) {
|
|
@@ -4718,6 +4456,90 @@ var init_auto_title = __esm(() => {
|
|
|
4718
4456
|
init_history3();
|
|
4719
4457
|
});
|
|
4720
4458
|
|
|
4459
|
+
// src/cli/invocation.ts
|
|
4460
|
+
import { fileURLToPath } from "url";
|
|
4461
|
+
function kobeCliInvocation() {
|
|
4462
|
+
const isBuilt = import.meta.url.endsWith(".js");
|
|
4463
|
+
if (isBuilt)
|
|
4464
|
+
return ["kobe"];
|
|
4465
|
+
const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
|
|
4466
|
+
const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
|
|
4467
|
+
return [process.execPath, "--preload", preload, "--conditions=browser", entry];
|
|
4468
|
+
}
|
|
4469
|
+
var init_invocation = () => {};
|
|
4470
|
+
|
|
4471
|
+
// src/tmux/session-layout.ts
|
|
4472
|
+
function shellQuote(s) {
|
|
4473
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4474
|
+
}
|
|
4475
|
+
function shellQuoteArgv(argv) {
|
|
4476
|
+
return argv.map(shellQuote).join(" ");
|
|
4477
|
+
}
|
|
4478
|
+
function keepAlive(cmd) {
|
|
4479
|
+
return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
|
|
4480
|
+
}
|
|
4481
|
+
function shQuote(s) {
|
|
4482
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4483
|
+
}
|
|
4484
|
+
function homeWelcomeCommand() {
|
|
4485
|
+
const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
|
|
4486
|
+
return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
|
|
4487
|
+
}
|
|
4488
|
+
function engineLaunchLine(engineCmd, init) {
|
|
4489
|
+
const tail = keepAlive(engineCmd);
|
|
4490
|
+
const script = init?.initScript?.trim();
|
|
4491
|
+
if (!script)
|
|
4492
|
+
return tail;
|
|
4493
|
+
const group = ["{", script, "}"].join(`
|
|
4494
|
+
`);
|
|
4495
|
+
if (init?.markerPath) {
|
|
4496
|
+
const marker = shQuote(init.markerPath);
|
|
4497
|
+
const markerDir = shQuote(markerDirOf(init.markerPath));
|
|
4498
|
+
return [
|
|
4499
|
+
`if [ ! -f ${marker} ]; then`,
|
|
4500
|
+
group,
|
|
4501
|
+
`if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
|
|
4502
|
+
"fi",
|
|
4503
|
+
tail
|
|
4504
|
+
].join(`
|
|
4505
|
+
`);
|
|
4506
|
+
}
|
|
4507
|
+
return [group, tail].join(`
|
|
4508
|
+
`);
|
|
4509
|
+
}
|
|
4510
|
+
function markerDirOf(p) {
|
|
4511
|
+
const i = p.lastIndexOf("/");
|
|
4512
|
+
return i <= 0 ? "." : p.slice(0, i);
|
|
4513
|
+
}
|
|
4514
|
+
function fallbackOpsScript(cwd) {
|
|
4515
|
+
return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
|
|
4516
|
+
}
|
|
4517
|
+
function previewWindowCommand(args) {
|
|
4518
|
+
const wt = shellQuote(args.worktree);
|
|
4519
|
+
const file = shellQuote(args.relPath);
|
|
4520
|
+
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4521
|
+
const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
|
|
4522
|
+
return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
|
|
4523
|
+
}
|
|
4524
|
+
function updatePageCommand(args) {
|
|
4525
|
+
return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
|
|
4526
|
+
}
|
|
4527
|
+
function tasksPaneCommand(cliInvocation, opts = {}) {
|
|
4528
|
+
const argv = [...cliInvocation, "tasks"];
|
|
4529
|
+
if (opts.initialTaskId)
|
|
4530
|
+
argv.push("--initial-task-id", opts.initialTaskId);
|
|
4531
|
+
return shellQuoteArgv(argv);
|
|
4532
|
+
}
|
|
4533
|
+
function opsPaneCommand(args) {
|
|
4534
|
+
if (args.taskId && args.claudePaneId) {
|
|
4535
|
+
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4536
|
+
const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
|
|
4537
|
+
return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
|
|
4538
|
+
}
|
|
4539
|
+
return fallbackOpsScript(args.cwd);
|
|
4540
|
+
}
|
|
4541
|
+
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
4542
|
+
|
|
4721
4543
|
// src/tmux/client.ts
|
|
4722
4544
|
var exports_client = {};
|
|
4723
4545
|
__export(exports_client, {
|
|
@@ -5197,9 +5019,9 @@ class DaemonEventBus {
|
|
|
5197
5019
|
}
|
|
5198
5020
|
|
|
5199
5021
|
// src/daemon/server.ts
|
|
5200
|
-
import { mkdir as
|
|
5022
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
5201
5023
|
import { createServer } from "net";
|
|
5202
|
-
import { dirname as
|
|
5024
|
+
import { dirname as dirname4 } from "path";
|
|
5203
5025
|
function resolveIdleGraceMs() {
|
|
5204
5026
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
5205
5027
|
if (raw === undefined)
|
|
@@ -5276,8 +5098,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5276
5098
|
activity.set(taskId, entry);
|
|
5277
5099
|
bus.publish("engine-state", { taskId, state, ...detail ? { detail } : {}, at });
|
|
5278
5100
|
}
|
|
5279
|
-
await
|
|
5280
|
-
await
|
|
5101
|
+
await mkdir4(dirname4(socketPath), { recursive: true });
|
|
5102
|
+
await mkdir4(dirname4(pidPath), { recursive: true });
|
|
5281
5103
|
await unlink4(socketPath).catch(() => {});
|
|
5282
5104
|
const server = createServer((socket) => {
|
|
5283
5105
|
const client = {
|
|
@@ -5347,7 +5169,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5347
5169
|
resolve2();
|
|
5348
5170
|
});
|
|
5349
5171
|
});
|
|
5350
|
-
await
|
|
5172
|
+
await writeFile3(pidPath, `${process.pid}
|
|
5351
5173
|
`, "utf8");
|
|
5352
5174
|
async function stopSoon() {
|
|
5353
5175
|
if (stopping)
|
|
@@ -5572,7 +5394,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5572
5394
|
}
|
|
5573
5395
|
async function readPidFile(pidPath) {
|
|
5574
5396
|
try {
|
|
5575
|
-
const raw = await
|
|
5397
|
+
const raw = await readFile5(pidPath, "utf8");
|
|
5576
5398
|
const pid = Number(raw.trim());
|
|
5577
5399
|
return Number.isFinite(pid) ? pid : null;
|
|
5578
5400
|
} catch {
|
|
@@ -5713,14 +5535,14 @@ __export(exports_daemon_process, {
|
|
|
5713
5535
|
connectIfRunning: () => connectIfRunning
|
|
5714
5536
|
});
|
|
5715
5537
|
import { spawn } from "child_process";
|
|
5716
|
-
import { closeSync, existsSync
|
|
5717
|
-
import { dirname as
|
|
5538
|
+
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5539
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
5718
5540
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5719
5541
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5720
5542
|
let stdio = "ignore";
|
|
5721
5543
|
let logFd;
|
|
5722
5544
|
try {
|
|
5723
|
-
mkdirSync2(
|
|
5545
|
+
mkdirSync2(dirname5(logPath), { recursive: true });
|
|
5724
5546
|
logFd = openSync(logPath, "a");
|
|
5725
5547
|
stdio = ["ignore", logFd, logFd];
|
|
5726
5548
|
} catch {
|
|
@@ -5787,12 +5609,12 @@ function resolveKobeSpawn(subcommand) {
|
|
|
5787
5609
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5788
5610
|
return [process.execPath, ...subcommand];
|
|
5789
5611
|
}
|
|
5790
|
-
const dir =
|
|
5612
|
+
const dir = dirname5(here);
|
|
5791
5613
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5792
|
-
if (
|
|
5614
|
+
if (existsSync(sourceEntry))
|
|
5793
5615
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
5794
5616
|
const distEntry = resolve2(dir, "../cli/index.js");
|
|
5795
|
-
if (
|
|
5617
|
+
if (existsSync(distEntry))
|
|
5796
5618
|
return [process.execPath, distEntry, ...subcommand];
|
|
5797
5619
|
throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
|
|
5798
5620
|
}
|
|
@@ -5883,14 +5705,14 @@ async function runRepoSubcommand(args) {
|
|
|
5883
5705
|
return;
|
|
5884
5706
|
}
|
|
5885
5707
|
const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
5886
|
-
const { existsSync:
|
|
5887
|
-
const { join:
|
|
5708
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
5709
|
+
const { join: join4 } = await import("path");
|
|
5888
5710
|
if (verb === "show") {
|
|
5889
5711
|
const [pathArg] = rest.filter((a) => !a.startsWith("-"));
|
|
5890
5712
|
const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
|
|
5891
5713
|
const override = getRepoInitOverride2(repo);
|
|
5892
|
-
const hasFileScript =
|
|
5893
|
-
const hasFilePrompt =
|
|
5714
|
+
const hasFileScript = existsSync2(join4(repo, ".kobe", "init.sh"));
|
|
5715
|
+
const hasFilePrompt = existsSync2(join4(repo, ".kobe", "init-prompt.md"));
|
|
5894
5716
|
console.log(`repo: ${repo}`);
|
|
5895
5717
|
console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
|
|
5896
5718
|
console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
|
|
@@ -6041,6 +5863,172 @@ var init_prompt_delivery = __esm(() => {
|
|
|
6041
5863
|
init_client2();
|
|
6042
5864
|
});
|
|
6043
5865
|
|
|
5866
|
+
// src/engine/claude-code-local/hook-adapter.ts
|
|
5867
|
+
import { existsSync as existsSync2 } from "fs";
|
|
5868
|
+
import { appendFile as appendFile3, mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
5869
|
+
import { dirname as dirname6, join as join4 } from "path";
|
|
5870
|
+
function isObject6(v) {
|
|
5871
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
5872
|
+
}
|
|
5873
|
+
async function readJsonObject(path6) {
|
|
5874
|
+
try {
|
|
5875
|
+
const parsed = JSON.parse(await readFile6(path6, "utf8"));
|
|
5876
|
+
return isObject6(parsed) ? parsed : {};
|
|
5877
|
+
} catch {
|
|
5878
|
+
return {};
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
function isKobeWorktreeSyncGroup(group) {
|
|
5882
|
+
if (!isObject6(group) || !Array.isArray(group.hooks))
|
|
5883
|
+
return false;
|
|
5884
|
+
return group.hooks.some((h) => isObject6(h) && typeof h.command === "string" && h.command.includes(WORKTREE_SYNC_MARKER));
|
|
5885
|
+
}
|
|
5886
|
+
function mergeWorktreeSyncHook(current, command) {
|
|
5887
|
+
const { hooks: rawHooks, ...restSettings } = current;
|
|
5888
|
+
const { WorktreeCreate, ...otherHooks } = isObject6(rawHooks) ? rawHooks : {};
|
|
5889
|
+
const prior = Array.isArray(WorktreeCreate) ? WorktreeCreate : [];
|
|
5890
|
+
const kept = prior.filter((g) => !isKobeWorktreeSyncGroup(g));
|
|
5891
|
+
if (command !== null)
|
|
5892
|
+
kept.push({ hooks: [{ type: "command", command }] });
|
|
5893
|
+
const nextHooks = { ...otherHooks };
|
|
5894
|
+
if (kept.length > 0)
|
|
5895
|
+
nextHooks.WorktreeCreate = kept;
|
|
5896
|
+
return Object.keys(nextHooks).length > 0 ? { ...restSettings, hooks: nextHooks } : { ...restSettings };
|
|
5897
|
+
}
|
|
5898
|
+
function buildClaudeHooks(taskId, inv = kobeCliInvocation()) {
|
|
5899
|
+
const out = {};
|
|
5900
|
+
for (const { event, matcher, verb } of EVENT_MAP) {
|
|
5901
|
+
const command = shellQuoteArgv([...inv, "hook", verb, "--task-id", taskId]);
|
|
5902
|
+
const group = { hooks: [{ type: "command", command }] };
|
|
5903
|
+
if (matcher)
|
|
5904
|
+
group.matcher = matcher;
|
|
5905
|
+
out[event] = [group];
|
|
5906
|
+
}
|
|
5907
|
+
return out;
|
|
5908
|
+
}
|
|
5909
|
+
function mergeClaudeHooks(existing, kobeHooks) {
|
|
5910
|
+
const merged = { ...existing };
|
|
5911
|
+
for (const event of KOBE_HOOK_EVENTS)
|
|
5912
|
+
merged[event] = kobeHooks[event];
|
|
5913
|
+
return merged;
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
class ClaudeHookAdapter {
|
|
5917
|
+
vendor = "claude";
|
|
5918
|
+
supportsHooks() {
|
|
5919
|
+
return true;
|
|
5920
|
+
}
|
|
5921
|
+
supportsWorktreeSync() {
|
|
5922
|
+
return true;
|
|
5923
|
+
}
|
|
5924
|
+
async installWorktreeSyncHook(settingsFilePath) {
|
|
5925
|
+
await this.editWorktreeSyncHook(settingsFilePath, true);
|
|
5926
|
+
}
|
|
5927
|
+
async removeWorktreeSyncHook(settingsFilePath) {
|
|
5928
|
+
await this.editWorktreeSyncHook(settingsFilePath, false);
|
|
5929
|
+
}
|
|
5930
|
+
async editWorktreeSyncHook(settingsFilePath, install) {
|
|
5931
|
+
const current = await readJsonObject(settingsFilePath);
|
|
5932
|
+
const command = install ? shellQuoteArgv([...kobeCliInvocation(), "hook", "worktree-created"]) : null;
|
|
5933
|
+
const next = mergeWorktreeSyncHook(current, command);
|
|
5934
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
5935
|
+
return;
|
|
5936
|
+
await mkdir5(dirname6(settingsFilePath), { recursive: true });
|
|
5937
|
+
await writeFile4(settingsFilePath, `${JSON.stringify(next, null, 2)}
|
|
5938
|
+
`);
|
|
5939
|
+
}
|
|
5940
|
+
async installTaskHooks(ctx) {
|
|
5941
|
+
try {
|
|
5942
|
+
const claudeDir = join4(ctx.worktreeDir, ".claude");
|
|
5943
|
+
const settingsPath = join4(claudeDir, "settings.local.json");
|
|
5944
|
+
await mkdir5(claudeDir, { recursive: true });
|
|
5945
|
+
let current = {};
|
|
5946
|
+
try {
|
|
5947
|
+
const raw = await readFile6(settingsPath, "utf8");
|
|
5948
|
+
const parsed = JSON.parse(raw);
|
|
5949
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
5950
|
+
current = parsed;
|
|
5951
|
+
} catch {}
|
|
5952
|
+
const existingHooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks) ? current.hooks : {};
|
|
5953
|
+
current.hooks = mergeClaudeHooks(existingHooks, buildClaudeHooks(ctx.taskId));
|
|
5954
|
+
await writeFile4(settingsPath, `${JSON.stringify(current, null, 2)}
|
|
5955
|
+
`);
|
|
5956
|
+
await hideFromGit(ctx.worktreeDir, ".claude/settings.local.json");
|
|
5957
|
+
} catch {}
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
async function hideFromGit(worktreeDir, relPath) {
|
|
5961
|
+
if (hiddenWorktrees.has(worktreeDir))
|
|
5962
|
+
return;
|
|
5963
|
+
try {
|
|
5964
|
+
const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
|
|
5965
|
+
stdout: "pipe",
|
|
5966
|
+
stderr: "ignore"
|
|
5967
|
+
});
|
|
5968
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
5969
|
+
if (code !== 0)
|
|
5970
|
+
return;
|
|
5971
|
+
let commonDir = out.trim();
|
|
5972
|
+
if (!commonDir)
|
|
5973
|
+
return;
|
|
5974
|
+
if (!commonDir.startsWith("/"))
|
|
5975
|
+
commonDir = join4(worktreeDir, commonDir);
|
|
5976
|
+
const excludePath = join4(commonDir, "info", "exclude");
|
|
5977
|
+
const existing = existsSync2(excludePath) ? await readFile6(excludePath, "utf8") : "";
|
|
5978
|
+
if (existing.split(`
|
|
5979
|
+
`).some((l) => l.trim() === relPath)) {
|
|
5980
|
+
hiddenWorktrees.add(worktreeDir);
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
5983
|
+
await mkdir5(join4(commonDir, "info"), { recursive: true });
|
|
5984
|
+
await appendFile3(excludePath, `${existing.endsWith(`
|
|
5985
|
+
`) || existing === "" ? "" : `
|
|
5986
|
+
`}${relPath}
|
|
5987
|
+
`);
|
|
5988
|
+
hiddenWorktrees.add(worktreeDir);
|
|
5989
|
+
} catch {}
|
|
5990
|
+
}
|
|
5991
|
+
var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created", hiddenWorktrees;
|
|
5992
|
+
var init_hook_adapter = __esm(() => {
|
|
5993
|
+
init_invocation();
|
|
5994
|
+
EVENT_MAP = [
|
|
5995
|
+
{ event: "SessionStart", verb: "session-start" },
|
|
5996
|
+
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
5997
|
+
{ event: "Stop", verb: "turn-complete" },
|
|
5998
|
+
{ event: "StopFailure", verb: "turn-failed" },
|
|
5999
|
+
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
6000
|
+
{ event: "SessionEnd", verb: "session-end" }
|
|
6001
|
+
];
|
|
6002
|
+
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
6003
|
+
hiddenWorktrees = new Set;
|
|
6004
|
+
});
|
|
6005
|
+
|
|
6006
|
+
// src/engine/hook-adapter.ts
|
|
6007
|
+
function createEngineHookAdapter(vendor) {
|
|
6008
|
+
if (vendor === "claude")
|
|
6009
|
+
return new ClaudeHookAdapter;
|
|
6010
|
+
return new NoopHookAdapter(vendor);
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
class NoopHookAdapter {
|
|
6014
|
+
vendor;
|
|
6015
|
+
constructor(vendor) {
|
|
6016
|
+
this.vendor = vendor;
|
|
6017
|
+
}
|
|
6018
|
+
supportsHooks() {
|
|
6019
|
+
return false;
|
|
6020
|
+
}
|
|
6021
|
+
async installTaskHooks() {}
|
|
6022
|
+
supportsWorktreeSync() {
|
|
6023
|
+
return false;
|
|
6024
|
+
}
|
|
6025
|
+
async installWorktreeSyncHook() {}
|
|
6026
|
+
async removeWorktreeSyncHook() {}
|
|
6027
|
+
}
|
|
6028
|
+
var init_hook_adapter2 = __esm(() => {
|
|
6029
|
+
init_hook_adapter();
|
|
6030
|
+
});
|
|
6031
|
+
|
|
6044
6032
|
// src/tui/panes/terminal/tmux.ts
|
|
6045
6033
|
var exports_tmux = {};
|
|
6046
6034
|
__export(exports_tmux, {
|
|
@@ -6091,6 +6079,12 @@ async function ensureSession(opts) {
|
|
|
6091
6079
|
}
|
|
6092
6080
|
}
|
|
6093
6081
|
async function ensureSessionImpl(opts) {
|
|
6082
|
+
if (opts.taskId && opts.cwd) {
|
|
6083
|
+
await createEngineHookAdapter(coerceVendorId(opts.vendor)).installTaskHooks({
|
|
6084
|
+
worktreeDir: opts.cwd,
|
|
6085
|
+
taskId: opts.taskId
|
|
6086
|
+
});
|
|
6087
|
+
}
|
|
6094
6088
|
if (await sessionExists(opts.name)) {
|
|
6095
6089
|
const sessionOptions = await getSessionOptions(opts.name, ["@kobe_worktree", "@kobe_vendor"]);
|
|
6096
6090
|
const taggedWorktree = sessionOptions["@kobe_worktree"] ?? "";
|
|
@@ -6505,6 +6499,7 @@ async function quickCreate(session) {
|
|
|
6505
6499
|
var CHAT_TAB_SWITCH_BINDINGS, CHAT_TAB_CLOSE_BINDING, CHAT_TAB_RENAME_BINDING, CHAT_TAB_ENGINE_PROMPT, CHAT_TAB_CHOOSE_ENGINE_BINDINGS, CHAT_TAB_STATE_OPTION = "@kobe_tab_state", PANE_VERSION_OPTION = "@kobe_pane_version", CHAT_TAB_STATUS_FORMAT = "#{?#{==:#{@kobe_tab_state},running},\u25CF,#{?#{==:#{@kobe_tab_state},done},\u2713,#{?#{==:#{@kobe_tab_state},error},!,#{?#{==:#{@kobe_tab_state},unknown},?,\u25CB}}}} #I:#W", CHAT_TAB_STATUS_CURRENT_FORMAT, ensureSessionLocks;
|
|
6506
6500
|
var init_tmux = __esm(() => {
|
|
6507
6501
|
init_invocation();
|
|
6502
|
+
init_hook_adapter2();
|
|
6508
6503
|
init_interactive_command();
|
|
6509
6504
|
init_env();
|
|
6510
6505
|
init_client2();
|
|
@@ -14391,11 +14386,12 @@ var init_account_detect = __esm(() => {
|
|
|
14391
14386
|
|
|
14392
14387
|
// src/tui/lib/editor-prefs.ts
|
|
14393
14388
|
function normalizeEditorKind(value) {
|
|
14394
|
-
return value
|
|
14389
|
+
return EDITOR_KINDS.includes(value) ? value : DEFAULT_EDITOR_KIND;
|
|
14395
14390
|
}
|
|
14396
|
-
var EDITOR_KINDS, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "
|
|
14391
|
+
var EDITOR_KINDS, AUTO_EDITOR_CANDIDATES, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "auto";
|
|
14397
14392
|
var init_editor_prefs = __esm(() => {
|
|
14398
|
-
EDITOR_KINDS = ["vim", "nano", "custom"];
|
|
14393
|
+
EDITOR_KINDS = ["auto", "vim", "nvim", "nano", "emacs", "custom"];
|
|
14394
|
+
AUTO_EDITOR_CANDIDATES = ["nvim", "vim", "emacs", "nano"];
|
|
14399
14395
|
});
|
|
14400
14396
|
|
|
14401
14397
|
// src/tui/lib/settings-surface.ts
|
|
@@ -14902,7 +14898,7 @@ function GeneralSettingsSection(props) {
|
|
|
14902
14898
|
setProp(_el$42, "gap", 0);
|
|
14903
14899
|
setProp(_el$42, "paddingTop", 1);
|
|
14904
14900
|
insertNode(_el$43, createTextNode(`Editor`));
|
|
14905
|
-
insertNode(_el$45, createTextNode(`What \`e\` opens a file with in the file tree (enter stays the read-only preview). enter on the row below cycles vim / nano / custom; if the editor isn't installed it falls back to the preview.`));
|
|
14901
|
+
insertNode(_el$45, createTextNode(`What \`e\` opens a file with in the file tree (enter stays the read-only preview). \`auto\` (default) follows $VISUAL / $EDITOR, else auto-detects nvim / vim / emacs / nano. enter on the row below cycles auto / vim / nvim / nano / emacs / custom; if the editor isn't installed it falls back to the preview.`));
|
|
14906
14902
|
setProp(_el$45, "wrapMode", "word");
|
|
14907
14903
|
insertNode(_el$47, _el$48);
|
|
14908
14904
|
setProp(_el$47, "flexDirection", "row");
|
|
@@ -19440,8 +19436,12 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19440
19436
|
const file = shellQuote(absPath);
|
|
19441
19437
|
if (kind === "vim")
|
|
19442
19438
|
return { bin: "vim", command: `vim ${file}` };
|
|
19439
|
+
if (kind === "nvim")
|
|
19440
|
+
return { bin: "nvim", command: `nvim ${file}` };
|
|
19443
19441
|
if (kind === "nano")
|
|
19444
19442
|
return { bin: "nano", command: `nano ${file}` };
|
|
19443
|
+
if (kind === "emacs")
|
|
19444
|
+
return { bin: "emacs", command: `emacs ${file}` };
|
|
19445
19445
|
const tmpl = (customCommand.trim() || (envEditor ?? "").trim()).trim();
|
|
19446
19446
|
if (!tmpl)
|
|
19447
19447
|
return null;
|
|
@@ -19451,11 +19451,20 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19451
19451
|
const command = tmpl.includes(FILE_PLACEHOLDER) ? tmpl.split(FILE_PLACEHOLDER).join(file) : `${tmpl} ${file}`;
|
|
19452
19452
|
return { bin, command };
|
|
19453
19453
|
}
|
|
19454
|
-
function resolveEditorCommand(absPath) {
|
|
19454
|
+
async function resolveEditorCommand(absPath) {
|
|
19455
19455
|
const kind = normalizeEditorKind(getPersistedString(EDITOR_KIND_KEY));
|
|
19456
19456
|
const custom = getPersistedString(EDITOR_CUSTOM_KEY) ?? "";
|
|
19457
|
-
const env = process.env.VISUAL ?? process.env.EDITOR ?? "";
|
|
19458
|
-
|
|
19457
|
+
const env = (process.env.VISUAL ?? process.env.EDITOR ?? "").trim();
|
|
19458
|
+
if (kind !== "auto")
|
|
19459
|
+
return buildEditorCommand(kind, custom, absPath, env);
|
|
19460
|
+
if (env)
|
|
19461
|
+
return buildEditorCommand("custom", "", absPath, env);
|
|
19462
|
+
const file = shellQuote(absPath);
|
|
19463
|
+
for (const bin of AUTO_EDITOR_CANDIDATES) {
|
|
19464
|
+
if (await binaryAvailable(bin))
|
|
19465
|
+
return { bin, command: `${bin} ${file}` };
|
|
19466
|
+
}
|
|
19467
|
+
return null;
|
|
19459
19468
|
}
|
|
19460
19469
|
async function binaryAvailable(bin) {
|
|
19461
19470
|
try {
|
|
@@ -19470,7 +19479,7 @@ async function binaryAvailable(bin) {
|
|
|
19470
19479
|
}
|
|
19471
19480
|
}
|
|
19472
19481
|
async function openInEditor(session, worktree, absPath) {
|
|
19473
|
-
const resolved = resolveEditorCommand(absPath);
|
|
19482
|
+
const resolved = await resolveEditorCommand(absPath);
|
|
19474
19483
|
if (!resolved)
|
|
19475
19484
|
return false;
|
|
19476
19485
|
if (!await binaryAvailable(resolved.bin))
|
package/package.json
CHANGED