@sma1lboy/kobe 0.7.6 → 0.7.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/cli/index.js +357 -342
- 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.8",
|
|
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 && opts.cwd.includes(`/${KOBE_WORKTREE_ROOT_SUBPATH}/`)) {
|
|
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,8 +6499,10 @@ 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();
|
|
6505
|
+
init_paths();
|
|
6510
6506
|
init_client2();
|
|
6511
6507
|
init_prompt_delivery();
|
|
6512
6508
|
init_vendor();
|
|
@@ -14391,11 +14387,12 @@ var init_account_detect = __esm(() => {
|
|
|
14391
14387
|
|
|
14392
14388
|
// src/tui/lib/editor-prefs.ts
|
|
14393
14389
|
function normalizeEditorKind(value) {
|
|
14394
|
-
return value
|
|
14390
|
+
return EDITOR_KINDS.includes(value) ? value : DEFAULT_EDITOR_KIND;
|
|
14395
14391
|
}
|
|
14396
|
-
var EDITOR_KINDS, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "
|
|
14392
|
+
var EDITOR_KINDS, AUTO_EDITOR_CANDIDATES, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "auto";
|
|
14397
14393
|
var init_editor_prefs = __esm(() => {
|
|
14398
|
-
EDITOR_KINDS = ["vim", "nano", "custom"];
|
|
14394
|
+
EDITOR_KINDS = ["auto", "vim", "nvim", "nano", "emacs", "custom"];
|
|
14395
|
+
AUTO_EDITOR_CANDIDATES = ["nvim", "vim", "emacs", "nano"];
|
|
14399
14396
|
});
|
|
14400
14397
|
|
|
14401
14398
|
// src/tui/lib/settings-surface.ts
|
|
@@ -14902,7 +14899,7 @@ function GeneralSettingsSection(props) {
|
|
|
14902
14899
|
setProp(_el$42, "gap", 0);
|
|
14903
14900
|
setProp(_el$42, "paddingTop", 1);
|
|
14904
14901
|
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.`));
|
|
14902
|
+
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
14903
|
setProp(_el$45, "wrapMode", "word");
|
|
14907
14904
|
insertNode(_el$47, _el$48);
|
|
14908
14905
|
setProp(_el$47, "flexDirection", "row");
|
|
@@ -17105,13 +17102,7 @@ var init_keys = __esm(() => {
|
|
|
17105
17102
|
init_keymap();
|
|
17106
17103
|
});
|
|
17107
17104
|
|
|
17108
|
-
// src/tui/panes/sidebar/
|
|
17109
|
-
import { TextAttributes as TextAttributes9 } from "@opentui/core";
|
|
17110
|
-
function truncateBranchLabel(branch, max = BRANCH_LABEL_MAX) {
|
|
17111
|
-
if (branch.length <= max)
|
|
17112
|
-
return branch;
|
|
17113
|
-
return `${branch.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
17114
|
-
}
|
|
17105
|
+
// src/tui/panes/sidebar/labels.ts
|
|
17115
17106
|
function truncateTitle(title, max) {
|
|
17116
17107
|
if (max <= 0)
|
|
17117
17108
|
return "";
|
|
@@ -17119,6 +17110,17 @@ function truncateTitle(title, max) {
|
|
|
17119
17110
|
return title;
|
|
17120
17111
|
return `${title.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
17121
17112
|
}
|
|
17113
|
+
function spacedTitle(title, max) {
|
|
17114
|
+
return ` ${truncateTitle(title, Math.max(0, max))}`;
|
|
17115
|
+
}
|
|
17116
|
+
|
|
17117
|
+
// src/tui/panes/sidebar/Sidebar.tsx
|
|
17118
|
+
import { TextAttributes as TextAttributes9 } from "@opentui/core";
|
|
17119
|
+
function truncateBranchLabel(branch, max = BRANCH_LABEL_MAX) {
|
|
17120
|
+
if (branch.length <= max)
|
|
17121
|
+
return branch;
|
|
17122
|
+
return `${branch.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
17123
|
+
}
|
|
17122
17124
|
function approxCellWidth(s) {
|
|
17123
17125
|
let n = 0;
|
|
17124
17126
|
for (const ch of s)
|
|
@@ -17564,7 +17566,7 @@ function Sidebar(props) {
|
|
|
17564
17566
|
setProp(_el$32, "flexDirection", "row");
|
|
17565
17567
|
setProp(_el$32, "flexGrow", 1);
|
|
17566
17568
|
setProp(_el$32, "paddingRight", 1);
|
|
17567
|
-
setProp(_el$32, "gap",
|
|
17569
|
+
setProp(_el$32, "gap", 0);
|
|
17568
17570
|
setProp(_el$33, "wrapMode", "none");
|
|
17569
17571
|
insert(_el$33, (() => {
|
|
17570
17572
|
var _c$2 = memo2(() => !!loading());
|
|
@@ -17572,14 +17574,14 @@ function Sidebar(props) {
|
|
|
17572
17574
|
})());
|
|
17573
17575
|
setProp(_el$34, "wrapMode", "none");
|
|
17574
17576
|
setProp(_el$34, "flexGrow", 1);
|
|
17575
|
-
insert(_el$34, () =>
|
|
17577
|
+
insert(_el$34, () => spacedTitle(titleText, titleBudget()));
|
|
17576
17578
|
insert(_el$32, createComponent2(Show, {
|
|
17577
17579
|
get when() {
|
|
17578
17580
|
return loading();
|
|
17579
17581
|
},
|
|
17580
17582
|
get children() {
|
|
17581
17583
|
var _el$35 = createElement("text");
|
|
17582
|
-
insertNode(_el$35, createTextNode(`working`));
|
|
17584
|
+
insertNode(_el$35, createTextNode(` working`));
|
|
17583
17585
|
setProp(_el$35, "wrapMode", "none");
|
|
17584
17586
|
effect((_$p) => setProp(_el$35, "fg", theme.primary, _$p));
|
|
17585
17587
|
return _el$35;
|
|
@@ -17592,7 +17594,7 @@ function Sidebar(props) {
|
|
|
17592
17594
|
get children() {
|
|
17593
17595
|
var _el$37 = createElement("text");
|
|
17594
17596
|
setProp(_el$37, "wrapMode", "none");
|
|
17595
|
-
insert(_el$37, () => truncatePathTail(abbrevHome(task.repo), subtitleBudget()));
|
|
17597
|
+
insert(_el$37, () => ` ${truncatePathTail(abbrevHome(task.repo), subtitleBudget())}`);
|
|
17596
17598
|
effect((_p$) => {
|
|
17597
17599
|
var _v$15 = theme.textMuted, _v$16 = TextAttributes9.DIM;
|
|
17598
17600
|
_v$15 !== _p$.e && (_p$.e = setProp(_el$37, "fg", _v$15, _p$.e));
|
|
@@ -17639,7 +17641,7 @@ function Sidebar(props) {
|
|
|
17639
17641
|
setProp(_el$40, "flexDirection", "row");
|
|
17640
17642
|
setProp(_el$40, "flexGrow", 1);
|
|
17641
17643
|
setProp(_el$40, "paddingRight", 1);
|
|
17642
|
-
setProp(_el$40, "gap",
|
|
17644
|
+
setProp(_el$40, "gap", 0);
|
|
17643
17645
|
setProp(_el$41, "wrapMode", "none");
|
|
17644
17646
|
insert(_el$41, (() => {
|
|
17645
17647
|
var _c$3 = memo2(() => !!loading());
|
|
@@ -17647,14 +17649,14 @@ function Sidebar(props) {
|
|
|
17647
17649
|
})());
|
|
17648
17650
|
setProp(_el$42, "wrapMode", "none");
|
|
17649
17651
|
setProp(_el$42, "flexGrow", 1);
|
|
17650
|
-
insert(_el$42, () =>
|
|
17652
|
+
insert(_el$42, () => spacedTitle(titleText, titleBudget()));
|
|
17651
17653
|
insert(_el$40, createComponent2(Show, {
|
|
17652
17654
|
get when() {
|
|
17653
17655
|
return loading();
|
|
17654
17656
|
},
|
|
17655
17657
|
get children() {
|
|
17656
17658
|
var _el$43 = createElement("text");
|
|
17657
|
-
insertNode(_el$43, createTextNode(`working`));
|
|
17659
|
+
insertNode(_el$43, createTextNode(` working`));
|
|
17658
17660
|
setProp(_el$43, "wrapMode", "none");
|
|
17659
17661
|
effect((_$p) => setProp(_el$43, "fg", theme.primary, _$p));
|
|
17660
17662
|
return _el$43;
|
|
@@ -17667,7 +17669,7 @@ function Sidebar(props) {
|
|
|
17667
17669
|
children: (chip) => (() => {
|
|
17668
17670
|
var _el$55 = createElement("text");
|
|
17669
17671
|
setProp(_el$55, "wrapMode", "none");
|
|
17670
|
-
insert(_el$55, () => chip().text);
|
|
17672
|
+
insert(_el$55, () => ` ${chip().text}`);
|
|
17671
17673
|
effect((_$p) => setProp(_el$55, "fg", chip().tone === "error" ? theme.error : chip().tone === "warning" ? theme.warning : theme.primary, _$p));
|
|
17672
17674
|
return _el$55;
|
|
17673
17675
|
})()
|
|
@@ -19440,8 +19442,12 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19440
19442
|
const file = shellQuote(absPath);
|
|
19441
19443
|
if (kind === "vim")
|
|
19442
19444
|
return { bin: "vim", command: `vim ${file}` };
|
|
19445
|
+
if (kind === "nvim")
|
|
19446
|
+
return { bin: "nvim", command: `nvim ${file}` };
|
|
19443
19447
|
if (kind === "nano")
|
|
19444
19448
|
return { bin: "nano", command: `nano ${file}` };
|
|
19449
|
+
if (kind === "emacs")
|
|
19450
|
+
return { bin: "emacs", command: `emacs ${file}` };
|
|
19445
19451
|
const tmpl = (customCommand.trim() || (envEditor ?? "").trim()).trim();
|
|
19446
19452
|
if (!tmpl)
|
|
19447
19453
|
return null;
|
|
@@ -19451,11 +19457,20 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19451
19457
|
const command = tmpl.includes(FILE_PLACEHOLDER) ? tmpl.split(FILE_PLACEHOLDER).join(file) : `${tmpl} ${file}`;
|
|
19452
19458
|
return { bin, command };
|
|
19453
19459
|
}
|
|
19454
|
-
function resolveEditorCommand(absPath) {
|
|
19460
|
+
async function resolveEditorCommand(absPath) {
|
|
19455
19461
|
const kind = normalizeEditorKind(getPersistedString(EDITOR_KIND_KEY));
|
|
19456
19462
|
const custom = getPersistedString(EDITOR_CUSTOM_KEY) ?? "";
|
|
19457
|
-
const env = process.env.VISUAL ?? process.env.EDITOR ?? "";
|
|
19458
|
-
|
|
19463
|
+
const env = (process.env.VISUAL ?? process.env.EDITOR ?? "").trim();
|
|
19464
|
+
if (kind !== "auto")
|
|
19465
|
+
return buildEditorCommand(kind, custom, absPath, env);
|
|
19466
|
+
if (env)
|
|
19467
|
+
return buildEditorCommand("custom", "", absPath, env);
|
|
19468
|
+
const file = shellQuote(absPath);
|
|
19469
|
+
for (const bin of AUTO_EDITOR_CANDIDATES) {
|
|
19470
|
+
if (await binaryAvailable(bin))
|
|
19471
|
+
return { bin, command: `${bin} ${file}` };
|
|
19472
|
+
}
|
|
19473
|
+
return null;
|
|
19459
19474
|
}
|
|
19460
19475
|
async function binaryAvailable(bin) {
|
|
19461
19476
|
try {
|
|
@@ -19470,7 +19485,7 @@ async function binaryAvailable(bin) {
|
|
|
19470
19485
|
}
|
|
19471
19486
|
}
|
|
19472
19487
|
async function openInEditor(session, worktree, absPath) {
|
|
19473
|
-
const resolved = resolveEditorCommand(absPath);
|
|
19488
|
+
const resolved = await resolveEditorCommand(absPath);
|
|
19474
19489
|
if (!resolved)
|
|
19475
19490
|
return false;
|
|
19476
19491
|
if (!await binaryAvailable(resolved.bin))
|
package/package.json
CHANGED