@sma1lboy/kobe 0.7.5 → 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 +357 -328
- 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,254 +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
|
-
await mkdir2(dirname3(settingsFilePath), { recursive: true });
|
|
2481
|
-
await writeFile2(settingsFilePath, `${JSON.stringify(next, null, 2)}
|
|
2482
|
-
`);
|
|
2483
|
-
}
|
|
2484
|
-
async installTaskHooks(ctx) {
|
|
2485
|
-
try {
|
|
2486
|
-
const claudeDir = join3(ctx.worktreeDir, ".claude");
|
|
2487
|
-
const settingsPath = join3(claudeDir, "settings.local.json");
|
|
2488
|
-
await mkdir2(claudeDir, { recursive: true });
|
|
2489
|
-
let current = {};
|
|
2490
|
-
try {
|
|
2491
|
-
const raw = await readFile2(settingsPath, "utf8");
|
|
2492
|
-
const parsed = JSON.parse(raw);
|
|
2493
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
2494
|
-
current = parsed;
|
|
2495
|
-
} catch {}
|
|
2496
|
-
const existingHooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks) ? current.hooks : {};
|
|
2497
|
-
current.hooks = mergeClaudeHooks(existingHooks, buildClaudeHooks(ctx.taskId));
|
|
2498
|
-
await writeFile2(settingsPath, `${JSON.stringify(current, null, 2)}
|
|
2499
|
-
`);
|
|
2500
|
-
await hideFromGit(ctx.worktreeDir, ".claude/settings.local.json");
|
|
2501
|
-
} catch {}
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
async function hideFromGit(worktreeDir, relPath) {
|
|
2505
|
-
if (hiddenWorktrees.has(worktreeDir))
|
|
2506
|
-
return;
|
|
2507
|
-
try {
|
|
2508
|
-
const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
|
|
2509
|
-
stdout: "pipe",
|
|
2510
|
-
stderr: "ignore"
|
|
2511
|
-
});
|
|
2512
|
-
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
2513
|
-
if (code !== 0)
|
|
2514
|
-
return;
|
|
2515
|
-
let commonDir = out.trim();
|
|
2516
|
-
if (!commonDir)
|
|
2517
|
-
return;
|
|
2518
|
-
if (!commonDir.startsWith("/"))
|
|
2519
|
-
commonDir = join3(worktreeDir, commonDir);
|
|
2520
|
-
const excludePath = join3(commonDir, "info", "exclude");
|
|
2521
|
-
const existing = existsSync(excludePath) ? await readFile2(excludePath, "utf8") : "";
|
|
2522
|
-
if (existing.split(`
|
|
2523
|
-
`).some((l) => l.trim() === relPath)) {
|
|
2524
|
-
hiddenWorktrees.add(worktreeDir);
|
|
2525
|
-
return;
|
|
2526
|
-
}
|
|
2527
|
-
await mkdir2(join3(commonDir, "info"), { recursive: true });
|
|
2528
|
-
await appendFile(excludePath, `${existing.endsWith(`
|
|
2529
|
-
`) || existing === "" ? "" : `
|
|
2530
|
-
`}${relPath}
|
|
2531
|
-
`);
|
|
2532
|
-
hiddenWorktrees.add(worktreeDir);
|
|
2533
|
-
} catch {}
|
|
2534
|
-
}
|
|
2535
|
-
var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created", hiddenWorktrees;
|
|
2536
|
-
var init_hook_adapter = __esm(() => {
|
|
2537
|
-
init_invocation();
|
|
2538
|
-
EVENT_MAP = [
|
|
2539
|
-
{ event: "SessionStart", verb: "session-start" },
|
|
2540
|
-
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
2541
|
-
{ event: "Stop", verb: "turn-complete" },
|
|
2542
|
-
{ event: "StopFailure", verb: "turn-failed" },
|
|
2543
|
-
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
2544
|
-
{ event: "SessionEnd", verb: "session-end" }
|
|
2545
|
-
];
|
|
2546
|
-
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
2547
|
-
hiddenWorktrees = new Set;
|
|
2548
|
-
});
|
|
2549
|
-
|
|
2550
|
-
// src/engine/hook-adapter.ts
|
|
2551
|
-
function createEngineHookAdapter(vendor) {
|
|
2552
|
-
if (vendor === "claude")
|
|
2553
|
-
return new ClaudeHookAdapter;
|
|
2554
|
-
return new NoopHookAdapter(vendor);
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
class NoopHookAdapter {
|
|
2558
|
-
vendor;
|
|
2559
|
-
constructor(vendor) {
|
|
2560
|
-
this.vendor = vendor;
|
|
2561
|
-
}
|
|
2562
|
-
supportsHooks() {
|
|
2563
|
-
return false;
|
|
2564
|
-
}
|
|
2565
|
-
async installTaskHooks() {}
|
|
2566
|
-
supportsWorktreeSync() {
|
|
2567
|
-
return false;
|
|
2568
|
-
}
|
|
2569
|
-
async installWorktreeSyncHook() {}
|
|
2570
|
-
async removeWorktreeSyncHook() {}
|
|
2571
|
-
}
|
|
2572
|
-
var init_hook_adapter2 = __esm(() => {
|
|
2573
|
-
init_hook_adapter();
|
|
2574
|
-
});
|
|
2575
|
-
|
|
2576
2328
|
// src/orchestrator/errors.ts
|
|
2577
2329
|
var IllegalTransitionError, TaskNotFoundError, CannotDeleteMainTaskError, DIRTY_WORKTREE_CODE = "DIRTY_WORKTREE", DirtyWorktreeError, WorktreeRemoveFailedError;
|
|
2578
2330
|
var init_errors = __esm(() => {
|
|
@@ -3251,10 +3003,8 @@ class Orchestrator {
|
|
|
3251
3003
|
const task = this.requireTask(id);
|
|
3252
3004
|
if (task.kind === "main")
|
|
3253
3005
|
return task.repo;
|
|
3254
|
-
if (task.worktreePath)
|
|
3255
|
-
await this.installEngineHooks(task);
|
|
3006
|
+
if (task.worktreePath)
|
|
3256
3007
|
return task.worktreePath;
|
|
3257
|
-
}
|
|
3258
3008
|
const inflight = this.worktreeLocks.get(task.id);
|
|
3259
3009
|
if (inflight) {
|
|
3260
3010
|
await inflight;
|
|
@@ -3278,7 +3028,6 @@ class Orchestrator {
|
|
|
3278
3028
|
worktreePath: info.path,
|
|
3279
3029
|
branch
|
|
3280
3030
|
});
|
|
3281
|
-
await this.installEngineHooks(this.requireTask(task.id));
|
|
3282
3031
|
} catch (err) {
|
|
3283
3032
|
this.slugs.cancel(task.repo, slug);
|
|
3284
3033
|
throw err;
|
|
@@ -3438,14 +3187,6 @@ class Orchestrator {
|
|
|
3438
3187
|
vendor: input.vendor ?? DEFAULT_TASK_VENDOR
|
|
3439
3188
|
});
|
|
3440
3189
|
}
|
|
3441
|
-
async installEngineHooks(task) {
|
|
3442
|
-
if (task.kind === "main" || !task.worktreePath)
|
|
3443
|
-
return;
|
|
3444
|
-
await createEngineHookAdapter(task.vendor ?? DEFAULT_TASK_VENDOR).installTaskHooks({
|
|
3445
|
-
worktreeDir: task.worktreePath,
|
|
3446
|
-
taskId: task.id
|
|
3447
|
-
});
|
|
3448
|
-
}
|
|
3449
3190
|
pendingBaseRefs = new Map;
|
|
3450
3191
|
adoptLocks = new Map;
|
|
3451
3192
|
requireTask(id) {
|
|
@@ -3469,7 +3210,6 @@ function canonPath(p) {
|
|
|
3469
3210
|
var PLACEHOLDER_TASK_TITLE = "(new task)";
|
|
3470
3211
|
var init_core = __esm(() => {
|
|
3471
3212
|
init_dev();
|
|
3472
|
-
init_hook_adapter2();
|
|
3473
3213
|
init_errors();
|
|
3474
3214
|
init_slug_allocator();
|
|
3475
3215
|
});
|
|
@@ -3512,7 +3252,7 @@ var init_protocol = __esm(() => {
|
|
|
3512
3252
|
// src/daemon/paths.ts
|
|
3513
3253
|
import { createHash as createHash2 } from "crypto";
|
|
3514
3254
|
import { homedir as homedir3, tmpdir } from "os";
|
|
3515
|
-
import { join as
|
|
3255
|
+
import { join as join3 } from "path";
|
|
3516
3256
|
function shortHomeTag(homeDir2) {
|
|
3517
3257
|
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
3518
3258
|
}
|
|
@@ -3521,7 +3261,7 @@ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
|
3521
3261
|
return naturalPath;
|
|
3522
3262
|
const tag = shortHomeTag(homeDir2);
|
|
3523
3263
|
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
3524
|
-
const fallback =
|
|
3264
|
+
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
3525
3265
|
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3526
3266
|
return fallback;
|
|
3527
3267
|
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
@@ -3532,33 +3272,33 @@ function defaultDaemonSocketPath(homeDir2) {
|
|
|
3532
3272
|
return override;
|
|
3533
3273
|
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
3534
3274
|
if (explicit && explicit.length > 0) {
|
|
3535
|
-
return fitSocketPath(
|
|
3275
|
+
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
3536
3276
|
}
|
|
3537
3277
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
3538
3278
|
if (runtimeDir && runtimeDir.length > 0) {
|
|
3539
|
-
return fitSocketPath(
|
|
3279
|
+
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
3540
3280
|
}
|
|
3541
3281
|
const home = homedir3();
|
|
3542
|
-
return fitSocketPath(
|
|
3282
|
+
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
3543
3283
|
}
|
|
3544
3284
|
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3545
3285
|
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
3546
3286
|
if (override && override.length > 0)
|
|
3547
3287
|
return override;
|
|
3548
|
-
return
|
|
3288
|
+
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
3549
3289
|
}
|
|
3550
3290
|
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3551
|
-
return
|
|
3291
|
+
return join3(homeDir2, ".kobe", "daemon.log");
|
|
3552
3292
|
}
|
|
3553
3293
|
function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3554
|
-
return
|
|
3294
|
+
return join3(homeDir2, ".kobe", "client.log");
|
|
3555
3295
|
}
|
|
3556
3296
|
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
3557
3297
|
var init_paths2 = () => {};
|
|
3558
3298
|
|
|
3559
3299
|
// src/client/client-log.ts
|
|
3560
|
-
import { appendFile
|
|
3561
|
-
import { dirname as
|
|
3300
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
3301
|
+
import { dirname as dirname3 } from "path";
|
|
3562
3302
|
function setClientLogContext(ctx) {
|
|
3563
3303
|
context = ctx;
|
|
3564
3304
|
}
|
|
@@ -3590,11 +3330,11 @@ function append(line) {
|
|
|
3590
3330
|
const path3 = defaultClientLogPath();
|
|
3591
3331
|
writeChain = writeChain.then(async () => {
|
|
3592
3332
|
try {
|
|
3593
|
-
await
|
|
3333
|
+
await appendFile(path3, line);
|
|
3594
3334
|
} catch (err) {
|
|
3595
3335
|
if (err?.code === "ENOENT") {
|
|
3596
|
-
await
|
|
3597
|
-
await
|
|
3336
|
+
await mkdir2(dirname3(path3), { recursive: true });
|
|
3337
|
+
await appendFile(path3, line);
|
|
3598
3338
|
} else {
|
|
3599
3339
|
throw err;
|
|
3600
3340
|
}
|
|
@@ -3874,7 +3614,7 @@ function normalizeClaudeContent(content) {
|
|
|
3874
3614
|
}
|
|
3875
3615
|
|
|
3876
3616
|
// src/engine/claude-code-local/history.ts
|
|
3877
|
-
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";
|
|
3878
3618
|
import { homedir as homedir4 } from "os";
|
|
3879
3619
|
import path3 from "path";
|
|
3880
3620
|
function encodeCwd(cwd) {
|
|
@@ -3945,7 +3685,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3945
3685
|
} catch {
|
|
3946
3686
|
continue;
|
|
3947
3687
|
}
|
|
3948
|
-
if (!
|
|
3688
|
+
if (!isObject(parsed))
|
|
3949
3689
|
continue;
|
|
3950
3690
|
const msg = extractMessage(parsed, sessionId);
|
|
3951
3691
|
if (msg)
|
|
@@ -3954,7 +3694,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3954
3694
|
return out;
|
|
3955
3695
|
}
|
|
3956
3696
|
function extractMessage(record, fallbackSessionId) {
|
|
3957
|
-
const inner =
|
|
3697
|
+
const inner = isObject(record.message) ? record.message : record;
|
|
3958
3698
|
const role = inner.role;
|
|
3959
3699
|
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
3960
3700
|
return null;
|
|
@@ -3967,7 +3707,7 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
3967
3707
|
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
3968
3708
|
}
|
|
3969
3709
|
function extractUsage(v) {
|
|
3970
|
-
if (!
|
|
3710
|
+
if (!isObject(v))
|
|
3971
3711
|
return;
|
|
3972
3712
|
const inTok = typeof v.input_tokens === "number" ? v.input_tokens : undefined;
|
|
3973
3713
|
const outTok = typeof v.output_tokens === "number" ? v.output_tokens : undefined;
|
|
@@ -3982,7 +3722,7 @@ function extractUsage(v) {
|
|
|
3982
3722
|
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
3983
3723
|
};
|
|
3984
3724
|
}
|
|
3985
|
-
function
|
|
3725
|
+
function isObject(v) {
|
|
3986
3726
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3987
3727
|
}
|
|
3988
3728
|
var defaultDeps;
|
|
@@ -3999,7 +3739,7 @@ var init_history = __esm(() => {
|
|
|
3999
3739
|
}
|
|
4000
3740
|
},
|
|
4001
3741
|
async readFile(p) {
|
|
4002
|
-
return await
|
|
3742
|
+
return await readFile2(p, "utf8");
|
|
4003
3743
|
},
|
|
4004
3744
|
async pathExists(p) {
|
|
4005
3745
|
try {
|
|
@@ -4026,7 +3766,7 @@ function normalizeCodexContent(raw) {
|
|
|
4026
3766
|
blocks.push({ type: "text", text: item });
|
|
4027
3767
|
continue;
|
|
4028
3768
|
}
|
|
4029
|
-
if (!
|
|
3769
|
+
if (!isObject2(item))
|
|
4030
3770
|
continue;
|
|
4031
3771
|
const t = typeof item.type === "string" ? item.type : undefined;
|
|
4032
3772
|
if (t === "input_text" || t === "output_text") {
|
|
@@ -4040,7 +3780,7 @@ function normalizeCodexContent(raw) {
|
|
|
4040
3780
|
}
|
|
4041
3781
|
return blocks;
|
|
4042
3782
|
}
|
|
4043
|
-
function
|
|
3783
|
+
function isObject2(v) {
|
|
4044
3784
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4045
3785
|
}
|
|
4046
3786
|
|
|
@@ -4090,7 +3830,7 @@ function validPositive(v) {
|
|
|
4090
3830
|
}
|
|
4091
3831
|
|
|
4092
3832
|
// src/engine/codex-local/history.ts
|
|
4093
|
-
import { readFile as
|
|
3833
|
+
import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
4094
3834
|
import { homedir as homedir5 } from "os";
|
|
4095
3835
|
import path4 from "path";
|
|
4096
3836
|
async function listRolloutFiles(deps = defaultDeps2) {
|
|
@@ -4225,11 +3965,11 @@ function parseJsonl2(raw, sessionId) {
|
|
|
4225
3965
|
} catch {
|
|
4226
3966
|
continue;
|
|
4227
3967
|
}
|
|
4228
|
-
if (!
|
|
3968
|
+
if (!isObject3(parsed))
|
|
4229
3969
|
continue;
|
|
4230
3970
|
if (parsed.type !== "response_item")
|
|
4231
3971
|
continue;
|
|
4232
|
-
const payload =
|
|
3972
|
+
const payload = isObject3(parsed.payload) ? parsed.payload : undefined;
|
|
4233
3973
|
if (!payload)
|
|
4234
3974
|
continue;
|
|
4235
3975
|
const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
|
|
@@ -4346,7 +4086,7 @@ function textFromReasoningValue(value) {
|
|
|
4346
4086
|
parts.push(entry);
|
|
4347
4087
|
continue;
|
|
4348
4088
|
}
|
|
4349
|
-
if (!
|
|
4089
|
+
if (!isObject3(entry))
|
|
4350
4090
|
continue;
|
|
4351
4091
|
const text = typeof entry.text === "string" ? entry.text : "";
|
|
4352
4092
|
if (text.length > 0)
|
|
@@ -4388,14 +4128,14 @@ function deriveCodexUsageMetrics(raw) {
|
|
|
4388
4128
|
} catch {
|
|
4389
4129
|
continue;
|
|
4390
4130
|
}
|
|
4391
|
-
if (!
|
|
4131
|
+
if (!isObject3(parsed))
|
|
4392
4132
|
continue;
|
|
4393
4133
|
const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs(parsed.timestamp) : null;
|
|
4394
4134
|
if (parsed.type === "response_item")
|
|
4395
4135
|
continue;
|
|
4396
4136
|
if (parsed.type !== "turn.completed")
|
|
4397
4137
|
continue;
|
|
4398
|
-
const usage =
|
|
4138
|
+
const usage = isObject3(parsed.usage) ? parsed.usage : undefined;
|
|
4399
4139
|
if (!usage)
|
|
4400
4140
|
continue;
|
|
4401
4141
|
const snapshot = codexUsageToSnapshot(usage);
|
|
@@ -4414,7 +4154,7 @@ function parseTimestampMs(value) {
|
|
|
4414
4154
|
const ms = new Date(value).getTime();
|
|
4415
4155
|
return Number.isFinite(ms) ? ms : null;
|
|
4416
4156
|
}
|
|
4417
|
-
function
|
|
4157
|
+
function isObject3(v) {
|
|
4418
4158
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4419
4159
|
}
|
|
4420
4160
|
var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
|
|
@@ -4432,7 +4172,7 @@ var init_history2 = __esm(() => {
|
|
|
4432
4172
|
}
|
|
4433
4173
|
},
|
|
4434
4174
|
async readFile(p) {
|
|
4435
|
-
return await
|
|
4175
|
+
return await readFile3(p, "utf8");
|
|
4436
4176
|
},
|
|
4437
4177
|
stat: stat2
|
|
4438
4178
|
};
|
|
@@ -4441,16 +4181,16 @@ var init_history2 = __esm(() => {
|
|
|
4441
4181
|
|
|
4442
4182
|
// src/engine/copilot-local/usage.ts
|
|
4443
4183
|
function copilotUsageToSnapshot(value) {
|
|
4444
|
-
if (!
|
|
4184
|
+
if (!isObject4(value))
|
|
4445
4185
|
return;
|
|
4446
|
-
const modelMetrics =
|
|
4186
|
+
const modelMetrics = isObject4(value.modelMetrics) ? value.modelMetrics : undefined;
|
|
4447
4187
|
if (!modelMetrics)
|
|
4448
4188
|
return;
|
|
4449
4189
|
let input = 0;
|
|
4450
4190
|
let output = 0;
|
|
4451
4191
|
let cached = 0;
|
|
4452
4192
|
for (const metrics of Object.values(modelMetrics)) {
|
|
4453
|
-
if (!
|
|
4193
|
+
if (!isObject4(metrics) || !isObject4(metrics.usage))
|
|
4454
4194
|
continue;
|
|
4455
4195
|
input += numberOr2(metrics.usage.inputTokens, 0);
|
|
4456
4196
|
output += numberOr2(metrics.usage.outputTokens, 0);
|
|
@@ -4466,7 +4206,7 @@ function copilotUsageToSnapshot(value) {
|
|
|
4466
4206
|
...context2 > 0 ? { context_tokens: context2 } : {}
|
|
4467
4207
|
};
|
|
4468
4208
|
}
|
|
4469
|
-
function
|
|
4209
|
+
function isObject4(v) {
|
|
4470
4210
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4471
4211
|
}
|
|
4472
4212
|
function numberOr2(value, fallback) {
|
|
@@ -4474,7 +4214,7 @@ function numberOr2(value, fallback) {
|
|
|
4474
4214
|
}
|
|
4475
4215
|
|
|
4476
4216
|
// src/engine/copilot-local/history.ts
|
|
4477
|
-
import { readFile as
|
|
4217
|
+
import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
4478
4218
|
import { homedir as homedir6 } from "os";
|
|
4479
4219
|
import path5 from "path";
|
|
4480
4220
|
async function listSessionDirs(deps = defaultDeps3) {
|
|
@@ -4571,10 +4311,10 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4571
4311
|
} catch {
|
|
4572
4312
|
continue;
|
|
4573
4313
|
}
|
|
4574
|
-
if (!
|
|
4314
|
+
if (!isObject5(record) || typeof record.type !== "string")
|
|
4575
4315
|
continue;
|
|
4576
4316
|
const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
4577
|
-
const data =
|
|
4317
|
+
const data = isObject5(record.data) ? record.data : {};
|
|
4578
4318
|
if (record.type === "session.start") {
|
|
4579
4319
|
const sid = typeof data.sessionId === "string" ? data.sessionId : undefined;
|
|
4580
4320
|
if (sid)
|
|
@@ -4597,7 +4337,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4597
4337
|
blocks.push({ type: "text", text });
|
|
4598
4338
|
const toolRequests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
|
|
4599
4339
|
for (const req of toolRequests) {
|
|
4600
|
-
if (!
|
|
4340
|
+
if (!isObject5(req))
|
|
4601
4341
|
continue;
|
|
4602
4342
|
const callId = typeof req.id === "string" ? req.id : typeof req.toolCallId === "string" ? req.toolCallId : "tool";
|
|
4603
4343
|
const name = typeof req.name === "string" ? req.name : typeof req.toolName === "string" ? req.toolName : "tool";
|
|
@@ -4634,7 +4374,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4634
4374
|
}
|
|
4635
4375
|
return { messages, usageMetrics, firstUserMessage };
|
|
4636
4376
|
}
|
|
4637
|
-
function
|
|
4377
|
+
function isObject5(v) {
|
|
4638
4378
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4639
4379
|
}
|
|
4640
4380
|
var defaultDeps3, PREVIEW_CHAR_CAP = 200;
|
|
@@ -4654,7 +4394,7 @@ var init_history3 = __esm(() => {
|
|
|
4654
4394
|
}
|
|
4655
4395
|
},
|
|
4656
4396
|
async readFile(p) {
|
|
4657
|
-
return await
|
|
4397
|
+
return await readFile4(p, "utf8");
|
|
4658
4398
|
},
|
|
4659
4399
|
stat: stat3,
|
|
4660
4400
|
async rm(p) {
|
|
@@ -4716,6 +4456,90 @@ var init_auto_title = __esm(() => {
|
|
|
4716
4456
|
init_history3();
|
|
4717
4457
|
});
|
|
4718
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
|
+
|
|
4719
4543
|
// src/tmux/client.ts
|
|
4720
4544
|
var exports_client = {};
|
|
4721
4545
|
__export(exports_client, {
|
|
@@ -5195,9 +5019,9 @@ class DaemonEventBus {
|
|
|
5195
5019
|
}
|
|
5196
5020
|
|
|
5197
5021
|
// src/daemon/server.ts
|
|
5198
|
-
import { mkdir as
|
|
5022
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
5199
5023
|
import { createServer } from "net";
|
|
5200
|
-
import { dirname as
|
|
5024
|
+
import { dirname as dirname4 } from "path";
|
|
5201
5025
|
function resolveIdleGraceMs() {
|
|
5202
5026
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
5203
5027
|
if (raw === undefined)
|
|
@@ -5274,8 +5098,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5274
5098
|
activity.set(taskId, entry);
|
|
5275
5099
|
bus.publish("engine-state", { taskId, state, ...detail ? { detail } : {}, at });
|
|
5276
5100
|
}
|
|
5277
|
-
await
|
|
5278
|
-
await
|
|
5101
|
+
await mkdir4(dirname4(socketPath), { recursive: true });
|
|
5102
|
+
await mkdir4(dirname4(pidPath), { recursive: true });
|
|
5279
5103
|
await unlink4(socketPath).catch(() => {});
|
|
5280
5104
|
const server = createServer((socket) => {
|
|
5281
5105
|
const client = {
|
|
@@ -5345,7 +5169,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5345
5169
|
resolve2();
|
|
5346
5170
|
});
|
|
5347
5171
|
});
|
|
5348
|
-
await
|
|
5172
|
+
await writeFile3(pidPath, `${process.pid}
|
|
5349
5173
|
`, "utf8");
|
|
5350
5174
|
async function stopSoon() {
|
|
5351
5175
|
if (stopping)
|
|
@@ -5570,7 +5394,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5570
5394
|
}
|
|
5571
5395
|
async function readPidFile(pidPath) {
|
|
5572
5396
|
try {
|
|
5573
|
-
const raw = await
|
|
5397
|
+
const raw = await readFile5(pidPath, "utf8");
|
|
5574
5398
|
const pid = Number(raw.trim());
|
|
5575
5399
|
return Number.isFinite(pid) ? pid : null;
|
|
5576
5400
|
} catch {
|
|
@@ -5711,14 +5535,14 @@ __export(exports_daemon_process, {
|
|
|
5711
5535
|
connectIfRunning: () => connectIfRunning
|
|
5712
5536
|
});
|
|
5713
5537
|
import { spawn } from "child_process";
|
|
5714
|
-
import { closeSync, existsSync
|
|
5715
|
-
import { dirname as
|
|
5538
|
+
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5539
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
5716
5540
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5717
5541
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5718
5542
|
let stdio = "ignore";
|
|
5719
5543
|
let logFd;
|
|
5720
5544
|
try {
|
|
5721
|
-
mkdirSync2(
|
|
5545
|
+
mkdirSync2(dirname5(logPath), { recursive: true });
|
|
5722
5546
|
logFd = openSync(logPath, "a");
|
|
5723
5547
|
stdio = ["ignore", logFd, logFd];
|
|
5724
5548
|
} catch {
|
|
@@ -5785,12 +5609,12 @@ function resolveKobeSpawn(subcommand) {
|
|
|
5785
5609
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5786
5610
|
return [process.execPath, ...subcommand];
|
|
5787
5611
|
}
|
|
5788
|
-
const dir =
|
|
5612
|
+
const dir = dirname5(here);
|
|
5789
5613
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5790
|
-
if (
|
|
5614
|
+
if (existsSync(sourceEntry))
|
|
5791
5615
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
5792
5616
|
const distEntry = resolve2(dir, "../cli/index.js");
|
|
5793
|
-
if (
|
|
5617
|
+
if (existsSync(distEntry))
|
|
5794
5618
|
return [process.execPath, distEntry, ...subcommand];
|
|
5795
5619
|
throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
|
|
5796
5620
|
}
|
|
@@ -5881,14 +5705,14 @@ async function runRepoSubcommand(args) {
|
|
|
5881
5705
|
return;
|
|
5882
5706
|
}
|
|
5883
5707
|
const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
5884
|
-
const { existsSync:
|
|
5885
|
-
const { join:
|
|
5708
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
5709
|
+
const { join: join4 } = await import("path");
|
|
5886
5710
|
if (verb === "show") {
|
|
5887
5711
|
const [pathArg] = rest.filter((a) => !a.startsWith("-"));
|
|
5888
5712
|
const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
|
|
5889
5713
|
const override = getRepoInitOverride2(repo);
|
|
5890
|
-
const hasFileScript =
|
|
5891
|
-
const hasFilePrompt =
|
|
5714
|
+
const hasFileScript = existsSync2(join4(repo, ".kobe", "init.sh"));
|
|
5715
|
+
const hasFilePrompt = existsSync2(join4(repo, ".kobe", "init-prompt.md"));
|
|
5892
5716
|
console.log(`repo: ${repo}`);
|
|
5893
5717
|
console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
|
|
5894
5718
|
console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
|
|
@@ -6039,6 +5863,172 @@ var init_prompt_delivery = __esm(() => {
|
|
|
6039
5863
|
init_client2();
|
|
6040
5864
|
});
|
|
6041
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
|
+
|
|
6042
6032
|
// src/tui/panes/terminal/tmux.ts
|
|
6043
6033
|
var exports_tmux = {};
|
|
6044
6034
|
__export(exports_tmux, {
|
|
@@ -6089,6 +6079,12 @@ async function ensureSession(opts) {
|
|
|
6089
6079
|
}
|
|
6090
6080
|
}
|
|
6091
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
|
+
}
|
|
6092
6088
|
if (await sessionExists(opts.name)) {
|
|
6093
6089
|
const sessionOptions = await getSessionOptions(opts.name, ["@kobe_worktree", "@kobe_vendor"]);
|
|
6094
6090
|
const taggedWorktree = sessionOptions["@kobe_worktree"] ?? "";
|
|
@@ -6503,6 +6499,7 @@ async function quickCreate(session) {
|
|
|
6503
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;
|
|
6504
6500
|
var init_tmux = __esm(() => {
|
|
6505
6501
|
init_invocation();
|
|
6502
|
+
init_hook_adapter2();
|
|
6506
6503
|
init_interactive_command();
|
|
6507
6504
|
init_env();
|
|
6508
6505
|
init_client2();
|
|
@@ -8454,7 +8451,8 @@ var init_skill_cmd = __esm(() => {
|
|
|
8454
8451
|
// src/cli/hook-cmd.ts
|
|
8455
8452
|
var exports_hook_cmd = {};
|
|
8456
8453
|
__export(exports_hook_cmd, {
|
|
8457
|
-
runHookSubcommand: () => runHookSubcommand
|
|
8454
|
+
runHookSubcommand: () => runHookSubcommand,
|
|
8455
|
+
ensureDefaultWorktreeSync: () => ensureDefaultWorktreeSync
|
|
8458
8456
|
});
|
|
8459
8457
|
import { homedir as homedir10 } from "os";
|
|
8460
8458
|
import { dirname as dirname7, join as join10, resolve as resolve6 } from "path";
|
|
@@ -8572,13 +8570,29 @@ function persistedSyncPath(stored) {
|
|
|
8572
8570
|
return syncSettingsPath({ kind: "repo", path: stored.slice(5) });
|
|
8573
8571
|
return stored;
|
|
8574
8572
|
}
|
|
8573
|
+
async function ensureDefaultWorktreeSync() {
|
|
8574
|
+
try {
|
|
8575
|
+
const stored = getPersistedString(SYNC_SETTING_KEY);
|
|
8576
|
+
if (stored === "off")
|
|
8577
|
+
return;
|
|
8578
|
+
const adapters = worktreeSyncAdapters();
|
|
8579
|
+
if (adapters.length === 0)
|
|
8580
|
+
return;
|
|
8581
|
+
const path6 = persistedSyncPath(stored) ?? syncSettingsPath({ kind: "global" });
|
|
8582
|
+
for (const a of adapters)
|
|
8583
|
+
await a.installWorktreeSyncHook(path6);
|
|
8584
|
+
if (!stored)
|
|
8585
|
+
setPersistedString(SYNC_SETTING_KEY, path6);
|
|
8586
|
+
} catch {}
|
|
8587
|
+
}
|
|
8575
8588
|
async function runHookSetup(argv) {
|
|
8576
8589
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
8577
8590
|
process.stdout.write([
|
|
8578
8591
|
"Usage: kobe hook setup [--global | --repo <path> | --off]",
|
|
8579
8592
|
"",
|
|
8580
|
-
"
|
|
8581
|
-
"
|
|
8593
|
+
"Syncs an external `claude --worktree` into kobe as a task. This is ON",
|
|
8594
|
+
"by default (installed globally into ~/.claude on launch). Use this to",
|
|
8595
|
+
"move it to one repo (--repo <path>) or to turn it OFF (--off).",
|
|
8582
8596
|
""
|
|
8583
8597
|
].join(`
|
|
8584
8598
|
`));
|
|
@@ -14372,11 +14386,12 @@ var init_account_detect = __esm(() => {
|
|
|
14372
14386
|
|
|
14373
14387
|
// src/tui/lib/editor-prefs.ts
|
|
14374
14388
|
function normalizeEditorKind(value) {
|
|
14375
|
-
return value
|
|
14389
|
+
return EDITOR_KINDS.includes(value) ? value : DEFAULT_EDITOR_KIND;
|
|
14376
14390
|
}
|
|
14377
|
-
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";
|
|
14378
14392
|
var init_editor_prefs = __esm(() => {
|
|
14379
|
-
EDITOR_KINDS = ["vim", "nano", "custom"];
|
|
14393
|
+
EDITOR_KINDS = ["auto", "vim", "nvim", "nano", "emacs", "custom"];
|
|
14394
|
+
AUTO_EDITOR_CANDIDATES = ["nvim", "vim", "emacs", "nano"];
|
|
14380
14395
|
});
|
|
14381
14396
|
|
|
14382
14397
|
// src/tui/lib/settings-surface.ts
|
|
@@ -14883,7 +14898,7 @@ function GeneralSettingsSection(props) {
|
|
|
14883
14898
|
setProp(_el$42, "gap", 0);
|
|
14884
14899
|
setProp(_el$42, "paddingTop", 1);
|
|
14885
14900
|
insertNode(_el$43, createTextNode(`Editor`));
|
|
14886
|
-
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.`));
|
|
14887
14902
|
setProp(_el$45, "wrapMode", "word");
|
|
14888
14903
|
insertNode(_el$47, _el$48);
|
|
14889
14904
|
setProp(_el$47, "flexDirection", "row");
|
|
@@ -19421,8 +19436,12 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19421
19436
|
const file = shellQuote(absPath);
|
|
19422
19437
|
if (kind === "vim")
|
|
19423
19438
|
return { bin: "vim", command: `vim ${file}` };
|
|
19439
|
+
if (kind === "nvim")
|
|
19440
|
+
return { bin: "nvim", command: `nvim ${file}` };
|
|
19424
19441
|
if (kind === "nano")
|
|
19425
19442
|
return { bin: "nano", command: `nano ${file}` };
|
|
19443
|
+
if (kind === "emacs")
|
|
19444
|
+
return { bin: "emacs", command: `emacs ${file}` };
|
|
19426
19445
|
const tmpl = (customCommand.trim() || (envEditor ?? "").trim()).trim();
|
|
19427
19446
|
if (!tmpl)
|
|
19428
19447
|
return null;
|
|
@@ -19432,11 +19451,20 @@ function buildEditorCommand(kind, customCommand, absPath, envEditor) {
|
|
|
19432
19451
|
const command = tmpl.includes(FILE_PLACEHOLDER) ? tmpl.split(FILE_PLACEHOLDER).join(file) : `${tmpl} ${file}`;
|
|
19433
19452
|
return { bin, command };
|
|
19434
19453
|
}
|
|
19435
|
-
function resolveEditorCommand(absPath) {
|
|
19454
|
+
async function resolveEditorCommand(absPath) {
|
|
19436
19455
|
const kind = normalizeEditorKind(getPersistedString(EDITOR_KIND_KEY));
|
|
19437
19456
|
const custom = getPersistedString(EDITOR_CUSTOM_KEY) ?? "";
|
|
19438
|
-
const env = process.env.VISUAL ?? process.env.EDITOR ?? "";
|
|
19439
|
-
|
|
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;
|
|
19440
19468
|
}
|
|
19441
19469
|
async function binaryAvailable(bin) {
|
|
19442
19470
|
try {
|
|
@@ -19451,7 +19479,7 @@ async function binaryAvailable(bin) {
|
|
|
19451
19479
|
}
|
|
19452
19480
|
}
|
|
19453
19481
|
async function openInEditor(session, worktree, absPath) {
|
|
19454
|
-
const resolved = resolveEditorCommand(absPath);
|
|
19482
|
+
const resolved = await resolveEditorCommand(absPath);
|
|
19455
19483
|
if (!resolved)
|
|
19456
19484
|
return false;
|
|
19457
19485
|
if (!await binaryAvailable(resolved.bin))
|
|
@@ -21141,6 +21169,7 @@ async function ensureRepos(orchestrator) {
|
|
|
21141
21169
|
}
|
|
21142
21170
|
async function startDirectTmux() {
|
|
21143
21171
|
setClientLogContext("gui");
|
|
21172
|
+
Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd)).then((m) => m.ensureDefaultWorktreeSync());
|
|
21144
21173
|
if (!await tmuxAvailable()) {
|
|
21145
21174
|
console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
|
|
21146
21175
|
process.exitCode = 1;
|
package/package.json
CHANGED