@sma1lboy/kobe 0.7.2 → 0.7.4
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 +1482 -375
- 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.4",
|
|
74
74
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
75
75
|
type: "module",
|
|
76
76
|
packageManager: "bun@1.3.13",
|
|
@@ -2325,6 +2325,248 @@ 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
|
+
try {
|
|
2506
|
+
const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
|
|
2507
|
+
stdout: "pipe",
|
|
2508
|
+
stderr: "ignore"
|
|
2509
|
+
});
|
|
2510
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
2511
|
+
if (code !== 0)
|
|
2512
|
+
return;
|
|
2513
|
+
let commonDir = out.trim();
|
|
2514
|
+
if (!commonDir)
|
|
2515
|
+
return;
|
|
2516
|
+
if (!commonDir.startsWith("/"))
|
|
2517
|
+
commonDir = join3(worktreeDir, commonDir);
|
|
2518
|
+
const excludePath = join3(commonDir, "info", "exclude");
|
|
2519
|
+
const existing = existsSync(excludePath) ? await readFile2(excludePath, "utf8") : "";
|
|
2520
|
+
if (existing.split(`
|
|
2521
|
+
`).some((l) => l.trim() === relPath))
|
|
2522
|
+
return;
|
|
2523
|
+
await mkdir2(join3(commonDir, "info"), { recursive: true });
|
|
2524
|
+
await appendFile(excludePath, `${existing.endsWith(`
|
|
2525
|
+
`) || existing === "" ? "" : `
|
|
2526
|
+
`}${relPath}
|
|
2527
|
+
`);
|
|
2528
|
+
} catch {}
|
|
2529
|
+
}
|
|
2530
|
+
var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created";
|
|
2531
|
+
var init_hook_adapter = __esm(() => {
|
|
2532
|
+
init_invocation();
|
|
2533
|
+
EVENT_MAP = [
|
|
2534
|
+
{ event: "SessionStart", verb: "session-start" },
|
|
2535
|
+
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
2536
|
+
{ event: "Stop", verb: "turn-complete" },
|
|
2537
|
+
{ event: "StopFailure", verb: "turn-failed" },
|
|
2538
|
+
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
2539
|
+
{ event: "SessionEnd", verb: "session-end" }
|
|
2540
|
+
];
|
|
2541
|
+
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
// src/engine/hook-adapter.ts
|
|
2545
|
+
function createEngineHookAdapter(vendor) {
|
|
2546
|
+
if (vendor === "claude")
|
|
2547
|
+
return new ClaudeHookAdapter;
|
|
2548
|
+
return new NoopHookAdapter(vendor);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
class NoopHookAdapter {
|
|
2552
|
+
vendor;
|
|
2553
|
+
constructor(vendor) {
|
|
2554
|
+
this.vendor = vendor;
|
|
2555
|
+
}
|
|
2556
|
+
supportsHooks() {
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
async installTaskHooks() {}
|
|
2560
|
+
supportsWorktreeSync() {
|
|
2561
|
+
return false;
|
|
2562
|
+
}
|
|
2563
|
+
async installWorktreeSyncHook() {}
|
|
2564
|
+
async removeWorktreeSyncHook() {}
|
|
2565
|
+
}
|
|
2566
|
+
var init_hook_adapter2 = __esm(() => {
|
|
2567
|
+
init_hook_adapter();
|
|
2568
|
+
});
|
|
2569
|
+
|
|
2328
2570
|
// src/orchestrator/errors.ts
|
|
2329
2571
|
var IllegalTransitionError, TaskNotFoundError, CannotDeleteMainTaskError, DIRTY_WORKTREE_CODE = "DIRTY_WORKTREE", DirtyWorktreeError, WorktreeRemoveFailedError;
|
|
2330
2572
|
var init_errors = __esm(() => {
|
|
@@ -3003,8 +3245,10 @@ class Orchestrator {
|
|
|
3003
3245
|
const task = this.requireTask(id);
|
|
3004
3246
|
if (task.kind === "main")
|
|
3005
3247
|
return task.repo;
|
|
3006
|
-
if (task.worktreePath)
|
|
3248
|
+
if (task.worktreePath) {
|
|
3249
|
+
await this.installEngineHooks(task);
|
|
3007
3250
|
return task.worktreePath;
|
|
3251
|
+
}
|
|
3008
3252
|
const inflight = this.worktreeLocks.get(task.id);
|
|
3009
3253
|
if (inflight) {
|
|
3010
3254
|
await inflight;
|
|
@@ -3028,6 +3272,7 @@ class Orchestrator {
|
|
|
3028
3272
|
worktreePath: info.path,
|
|
3029
3273
|
branch
|
|
3030
3274
|
});
|
|
3275
|
+
await this.installEngineHooks(this.requireTask(task.id));
|
|
3031
3276
|
} catch (err) {
|
|
3032
3277
|
this.slugs.cancel(task.repo, slug);
|
|
3033
3278
|
throw err;
|
|
@@ -3152,14 +3397,17 @@ class Orchestrator {
|
|
|
3152
3397
|
if (!input.worktreePath)
|
|
3153
3398
|
throw new Error("adoptWorktree: worktreePath is required");
|
|
3154
3399
|
const target = canonPath(input.worktreePath);
|
|
3400
|
+
const existing = this.store.list().find((t) => t.worktreePath && canonPath(t.worktreePath) === target);
|
|
3401
|
+
if (existing) {
|
|
3402
|
+
if (input.ifExists === "return")
|
|
3403
|
+
return existing;
|
|
3404
|
+
throw new Error(`adoptWorktree: ${input.worktreePath} is already adopted as a task`);
|
|
3405
|
+
}
|
|
3155
3406
|
const candidates = await this.worktrees.listAll(input.repo);
|
|
3156
3407
|
const match = candidates.find((wt) => canonPath(wt.path) === target);
|
|
3157
3408
|
if (!match) {
|
|
3158
3409
|
throw new Error(`adoptWorktree: ${input.worktreePath} is not an adoptable git worktree of ${input.repo} (unknown, detached, or the main checkout)`);
|
|
3159
3410
|
}
|
|
3160
|
-
const alreadyLinked = this.store.list().some((t) => t.worktreePath && canonPath(t.worktreePath) === target);
|
|
3161
|
-
if (alreadyLinked)
|
|
3162
|
-
throw new Error(`adoptWorktree: ${input.worktreePath} is already adopted as a task`);
|
|
3163
3411
|
const branch = input.branch?.trim() || match.branch;
|
|
3164
3412
|
const title = (input.title ?? basename2(match.path)).trim() || PLACEHOLDER_TASK_TITLE;
|
|
3165
3413
|
return this.store.create({
|
|
@@ -3172,6 +3420,14 @@ class Orchestrator {
|
|
|
3172
3420
|
vendor: input.vendor ?? DEFAULT_TASK_VENDOR
|
|
3173
3421
|
});
|
|
3174
3422
|
}
|
|
3423
|
+
async installEngineHooks(task) {
|
|
3424
|
+
if (task.kind === "main" || !task.worktreePath)
|
|
3425
|
+
return;
|
|
3426
|
+
await createEngineHookAdapter(task.vendor ?? DEFAULT_TASK_VENDOR).installTaskHooks({
|
|
3427
|
+
worktreeDir: task.worktreePath,
|
|
3428
|
+
taskId: task.id
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3175
3431
|
pendingBaseRefs = new Map;
|
|
3176
3432
|
requireTask(id) {
|
|
3177
3433
|
const task = this.store.get(id);
|
|
@@ -3194,6 +3450,7 @@ function canonPath(p) {
|
|
|
3194
3450
|
var PLACEHOLDER_TASK_TITLE = "(new task)";
|
|
3195
3451
|
var init_core = __esm(() => {
|
|
3196
3452
|
init_dev();
|
|
3453
|
+
init_hook_adapter2();
|
|
3197
3454
|
init_errors();
|
|
3198
3455
|
init_slug_allocator();
|
|
3199
3456
|
});
|
|
@@ -3225,13 +3482,13 @@ function frameToLine(frame) {
|
|
|
3225
3482
|
}
|
|
3226
3483
|
var DAEMON_PROTOCOL_VERSION = 2, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NAMES;
|
|
3227
3484
|
var init_protocol = __esm(() => {
|
|
3228
|
-
CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
|
|
3485
|
+
CHANNEL_NAMES = ["task.snapshot", "active-task", "update", "engine-state"];
|
|
3229
3486
|
});
|
|
3230
3487
|
|
|
3231
3488
|
// src/daemon/paths.ts
|
|
3232
3489
|
import { createHash as createHash2 } from "crypto";
|
|
3233
3490
|
import { homedir as homedir3, tmpdir } from "os";
|
|
3234
|
-
import { join as
|
|
3491
|
+
import { join as join4 } from "path";
|
|
3235
3492
|
function shortHomeTag(homeDir2) {
|
|
3236
3493
|
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
3237
3494
|
}
|
|
@@ -3240,7 +3497,7 @@ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
|
3240
3497
|
return naturalPath;
|
|
3241
3498
|
const tag = shortHomeTag(homeDir2);
|
|
3242
3499
|
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
3243
|
-
const fallback =
|
|
3500
|
+
const fallback = join4(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
3244
3501
|
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3245
3502
|
return fallback;
|
|
3246
3503
|
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
@@ -3251,33 +3508,33 @@ function defaultDaemonSocketPath(homeDir2) {
|
|
|
3251
3508
|
return override;
|
|
3252
3509
|
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
3253
3510
|
if (explicit && explicit.length > 0) {
|
|
3254
|
-
return fitSocketPath(
|
|
3511
|
+
return fitSocketPath(join4(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
3255
3512
|
}
|
|
3256
3513
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
3257
3514
|
if (runtimeDir && runtimeDir.length > 0) {
|
|
3258
|
-
return fitSocketPath(
|
|
3515
|
+
return fitSocketPath(join4(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
3259
3516
|
}
|
|
3260
3517
|
const home = homedir3();
|
|
3261
|
-
return fitSocketPath(
|
|
3518
|
+
return fitSocketPath(join4(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
3262
3519
|
}
|
|
3263
3520
|
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3264
3521
|
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
3265
3522
|
if (override && override.length > 0)
|
|
3266
3523
|
return override;
|
|
3267
|
-
return
|
|
3524
|
+
return join4(homeDir2, ".kobe", "daemon.pid");
|
|
3268
3525
|
}
|
|
3269
3526
|
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3270
|
-
return
|
|
3527
|
+
return join4(homeDir2, ".kobe", "daemon.log");
|
|
3271
3528
|
}
|
|
3272
3529
|
function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3273
|
-
return
|
|
3530
|
+
return join4(homeDir2, ".kobe", "client.log");
|
|
3274
3531
|
}
|
|
3275
3532
|
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
3276
3533
|
var init_paths2 = () => {};
|
|
3277
3534
|
|
|
3278
3535
|
// src/client/client-log.ts
|
|
3279
|
-
import { appendFile, mkdir as
|
|
3280
|
-
import { dirname as
|
|
3536
|
+
import { appendFile as appendFile2, mkdir as mkdir3 } from "fs/promises";
|
|
3537
|
+
import { dirname as dirname4 } from "path";
|
|
3281
3538
|
function setClientLogContext(ctx) {
|
|
3282
3539
|
context = ctx;
|
|
3283
3540
|
}
|
|
@@ -3309,11 +3566,11 @@ function append(line) {
|
|
|
3309
3566
|
const path3 = defaultClientLogPath();
|
|
3310
3567
|
writeChain = writeChain.then(async () => {
|
|
3311
3568
|
try {
|
|
3312
|
-
await
|
|
3569
|
+
await appendFile2(path3, line);
|
|
3313
3570
|
} catch (err) {
|
|
3314
3571
|
if (err?.code === "ENOENT") {
|
|
3315
|
-
await
|
|
3316
|
-
await
|
|
3572
|
+
await mkdir3(dirname4(path3), { recursive: true });
|
|
3573
|
+
await appendFile2(path3, line);
|
|
3317
3574
|
} else {
|
|
3318
3575
|
throw err;
|
|
3319
3576
|
}
|
|
@@ -3515,6 +3772,37 @@ var init_client = __esm(() => {
|
|
|
3515
3772
|
init_client_log();
|
|
3516
3773
|
});
|
|
3517
3774
|
|
|
3775
|
+
// src/engine/hook-events.ts
|
|
3776
|
+
function isEngineActivityKind(v) {
|
|
3777
|
+
return ENGINE_ACTIVITY_KINDS.includes(v);
|
|
3778
|
+
}
|
|
3779
|
+
function reduceActivity(_prev, kind, detail) {
|
|
3780
|
+
switch (kind) {
|
|
3781
|
+
case "session-start":
|
|
3782
|
+
case "session-end":
|
|
3783
|
+
return "idle";
|
|
3784
|
+
case "turn-start":
|
|
3785
|
+
return "running";
|
|
3786
|
+
case "turn-complete":
|
|
3787
|
+
return "turn_complete";
|
|
3788
|
+
case "turn-failed":
|
|
3789
|
+
return detail?.failure === "rate_limit" || detail?.failure === "billing" ? "rate_limited" : "error";
|
|
3790
|
+
case "awaiting-input":
|
|
3791
|
+
return detail?.waiting === "permission" ? "permission_needed" : "running";
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
var ENGINE_ACTIVITY_KINDS;
|
|
3795
|
+
var init_hook_events = __esm(() => {
|
|
3796
|
+
ENGINE_ACTIVITY_KINDS = [
|
|
3797
|
+
"session-start",
|
|
3798
|
+
"turn-start",
|
|
3799
|
+
"turn-complete",
|
|
3800
|
+
"turn-failed",
|
|
3801
|
+
"awaiting-input",
|
|
3802
|
+
"session-end"
|
|
3803
|
+
];
|
|
3804
|
+
});
|
|
3805
|
+
|
|
3518
3806
|
// src/engine/claude-code-local/normalize.ts
|
|
3519
3807
|
function normalizeClaudeContent(content) {
|
|
3520
3808
|
if (typeof content === "string") {
|
|
@@ -3562,7 +3850,7 @@ function normalizeClaudeContent(content) {
|
|
|
3562
3850
|
}
|
|
3563
3851
|
|
|
3564
3852
|
// src/engine/claude-code-local/history.ts
|
|
3565
|
-
import { appendFile as
|
|
3853
|
+
import { appendFile as appendFile3, mkdir as mkdir4, readFile as readFile3, readdir, stat, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
|
|
3566
3854
|
import { homedir as homedir4 } from "os";
|
|
3567
3855
|
import path3 from "path";
|
|
3568
3856
|
function encodeCwd(cwd) {
|
|
@@ -3633,7 +3921,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3633
3921
|
} catch {
|
|
3634
3922
|
continue;
|
|
3635
3923
|
}
|
|
3636
|
-
if (!
|
|
3924
|
+
if (!isObject2(parsed))
|
|
3637
3925
|
continue;
|
|
3638
3926
|
const msg = extractMessage(parsed, sessionId);
|
|
3639
3927
|
if (msg)
|
|
@@ -3642,7 +3930,7 @@ function parseJsonl(raw, sessionId) {
|
|
|
3642
3930
|
return out;
|
|
3643
3931
|
}
|
|
3644
3932
|
function extractMessage(record, fallbackSessionId) {
|
|
3645
|
-
const inner =
|
|
3933
|
+
const inner = isObject2(record.message) ? record.message : record;
|
|
3646
3934
|
const role = inner.role;
|
|
3647
3935
|
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
3648
3936
|
return null;
|
|
@@ -3655,7 +3943,7 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
3655
3943
|
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
3656
3944
|
}
|
|
3657
3945
|
function extractUsage(v) {
|
|
3658
|
-
if (!
|
|
3946
|
+
if (!isObject2(v))
|
|
3659
3947
|
return;
|
|
3660
3948
|
const inTok = typeof v.input_tokens === "number" ? v.input_tokens : undefined;
|
|
3661
3949
|
const outTok = typeof v.output_tokens === "number" ? v.output_tokens : undefined;
|
|
@@ -3670,7 +3958,7 @@ function extractUsage(v) {
|
|
|
3670
3958
|
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
3671
3959
|
};
|
|
3672
3960
|
}
|
|
3673
|
-
function
|
|
3961
|
+
function isObject2(v) {
|
|
3674
3962
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3675
3963
|
}
|
|
3676
3964
|
var defaultDeps;
|
|
@@ -3687,7 +3975,7 @@ var init_history = __esm(() => {
|
|
|
3687
3975
|
}
|
|
3688
3976
|
},
|
|
3689
3977
|
async readFile(p) {
|
|
3690
|
-
return await
|
|
3978
|
+
return await readFile3(p, "utf8");
|
|
3691
3979
|
},
|
|
3692
3980
|
async pathExists(p) {
|
|
3693
3981
|
try {
|
|
@@ -3714,7 +4002,7 @@ function normalizeCodexContent(raw) {
|
|
|
3714
4002
|
blocks.push({ type: "text", text: item });
|
|
3715
4003
|
continue;
|
|
3716
4004
|
}
|
|
3717
|
-
if (!
|
|
4005
|
+
if (!isObject3(item))
|
|
3718
4006
|
continue;
|
|
3719
4007
|
const t = typeof item.type === "string" ? item.type : undefined;
|
|
3720
4008
|
if (t === "input_text" || t === "output_text") {
|
|
@@ -3728,7 +4016,7 @@ function normalizeCodexContent(raw) {
|
|
|
3728
4016
|
}
|
|
3729
4017
|
return blocks;
|
|
3730
4018
|
}
|
|
3731
|
-
function
|
|
4019
|
+
function isObject3(v) {
|
|
3732
4020
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3733
4021
|
}
|
|
3734
4022
|
|
|
@@ -3778,7 +4066,7 @@ function validPositive(v) {
|
|
|
3778
4066
|
}
|
|
3779
4067
|
|
|
3780
4068
|
// src/engine/codex-local/history.ts
|
|
3781
|
-
import { readFile as
|
|
4069
|
+
import { readFile as readFile4, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
3782
4070
|
import { homedir as homedir5 } from "os";
|
|
3783
4071
|
import path4 from "path";
|
|
3784
4072
|
async function listRolloutFiles(deps = defaultDeps2) {
|
|
@@ -3913,11 +4201,11 @@ function parseJsonl2(raw, sessionId) {
|
|
|
3913
4201
|
} catch {
|
|
3914
4202
|
continue;
|
|
3915
4203
|
}
|
|
3916
|
-
if (!
|
|
4204
|
+
if (!isObject4(parsed))
|
|
3917
4205
|
continue;
|
|
3918
4206
|
if (parsed.type !== "response_item")
|
|
3919
4207
|
continue;
|
|
3920
|
-
const payload =
|
|
4208
|
+
const payload = isObject4(parsed.payload) ? parsed.payload : undefined;
|
|
3921
4209
|
if (!payload)
|
|
3922
4210
|
continue;
|
|
3923
4211
|
const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
|
|
@@ -4034,7 +4322,7 @@ function textFromReasoningValue(value) {
|
|
|
4034
4322
|
parts.push(entry);
|
|
4035
4323
|
continue;
|
|
4036
4324
|
}
|
|
4037
|
-
if (!
|
|
4325
|
+
if (!isObject4(entry))
|
|
4038
4326
|
continue;
|
|
4039
4327
|
const text = typeof entry.text === "string" ? entry.text : "";
|
|
4040
4328
|
if (text.length > 0)
|
|
@@ -4076,14 +4364,14 @@ function deriveCodexUsageMetrics(raw) {
|
|
|
4076
4364
|
} catch {
|
|
4077
4365
|
continue;
|
|
4078
4366
|
}
|
|
4079
|
-
if (!
|
|
4367
|
+
if (!isObject4(parsed))
|
|
4080
4368
|
continue;
|
|
4081
4369
|
const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs(parsed.timestamp) : null;
|
|
4082
4370
|
if (parsed.type === "response_item")
|
|
4083
4371
|
continue;
|
|
4084
4372
|
if (parsed.type !== "turn.completed")
|
|
4085
4373
|
continue;
|
|
4086
|
-
const usage =
|
|
4374
|
+
const usage = isObject4(parsed.usage) ? parsed.usage : undefined;
|
|
4087
4375
|
if (!usage)
|
|
4088
4376
|
continue;
|
|
4089
4377
|
const snapshot = codexUsageToSnapshot(usage);
|
|
@@ -4102,7 +4390,7 @@ function parseTimestampMs(value) {
|
|
|
4102
4390
|
const ms = new Date(value).getTime();
|
|
4103
4391
|
return Number.isFinite(ms) ? ms : null;
|
|
4104
4392
|
}
|
|
4105
|
-
function
|
|
4393
|
+
function isObject4(v) {
|
|
4106
4394
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4107
4395
|
}
|
|
4108
4396
|
var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
|
|
@@ -4120,7 +4408,7 @@ var init_history2 = __esm(() => {
|
|
|
4120
4408
|
}
|
|
4121
4409
|
},
|
|
4122
4410
|
async readFile(p) {
|
|
4123
|
-
return await
|
|
4411
|
+
return await readFile4(p, "utf8");
|
|
4124
4412
|
},
|
|
4125
4413
|
stat: stat2
|
|
4126
4414
|
};
|
|
@@ -4129,16 +4417,16 @@ var init_history2 = __esm(() => {
|
|
|
4129
4417
|
|
|
4130
4418
|
// src/engine/copilot-local/usage.ts
|
|
4131
4419
|
function copilotUsageToSnapshot(value) {
|
|
4132
|
-
if (!
|
|
4420
|
+
if (!isObject5(value))
|
|
4133
4421
|
return;
|
|
4134
|
-
const modelMetrics =
|
|
4422
|
+
const modelMetrics = isObject5(value.modelMetrics) ? value.modelMetrics : undefined;
|
|
4135
4423
|
if (!modelMetrics)
|
|
4136
4424
|
return;
|
|
4137
4425
|
let input = 0;
|
|
4138
4426
|
let output = 0;
|
|
4139
4427
|
let cached = 0;
|
|
4140
4428
|
for (const metrics of Object.values(modelMetrics)) {
|
|
4141
|
-
if (!
|
|
4429
|
+
if (!isObject5(metrics) || !isObject5(metrics.usage))
|
|
4142
4430
|
continue;
|
|
4143
4431
|
input += numberOr2(metrics.usage.inputTokens, 0);
|
|
4144
4432
|
output += numberOr2(metrics.usage.outputTokens, 0);
|
|
@@ -4154,7 +4442,7 @@ function copilotUsageToSnapshot(value) {
|
|
|
4154
4442
|
...context2 > 0 ? { context_tokens: context2 } : {}
|
|
4155
4443
|
};
|
|
4156
4444
|
}
|
|
4157
|
-
function
|
|
4445
|
+
function isObject5(v) {
|
|
4158
4446
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4159
4447
|
}
|
|
4160
4448
|
function numberOr2(value, fallback) {
|
|
@@ -4162,7 +4450,7 @@ function numberOr2(value, fallback) {
|
|
|
4162
4450
|
}
|
|
4163
4451
|
|
|
4164
4452
|
// src/engine/copilot-local/history.ts
|
|
4165
|
-
import { readFile as
|
|
4453
|
+
import { readFile as readFile5, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
4166
4454
|
import { homedir as homedir6 } from "os";
|
|
4167
4455
|
import path5 from "path";
|
|
4168
4456
|
async function listSessionDirs(deps = defaultDeps3) {
|
|
@@ -4259,10 +4547,10 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4259
4547
|
} catch {
|
|
4260
4548
|
continue;
|
|
4261
4549
|
}
|
|
4262
|
-
if (!
|
|
4550
|
+
if (!isObject6(record) || typeof record.type !== "string")
|
|
4263
4551
|
continue;
|
|
4264
4552
|
const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
4265
|
-
const data =
|
|
4553
|
+
const data = isObject6(record.data) ? record.data : {};
|
|
4266
4554
|
if (record.type === "session.start") {
|
|
4267
4555
|
const sid = typeof data.sessionId === "string" ? data.sessionId : undefined;
|
|
4268
4556
|
if (sid)
|
|
@@ -4285,7 +4573,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4285
4573
|
blocks.push({ type: "text", text });
|
|
4286
4574
|
const toolRequests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
|
|
4287
4575
|
for (const req of toolRequests) {
|
|
4288
|
-
if (!
|
|
4576
|
+
if (!isObject6(req))
|
|
4289
4577
|
continue;
|
|
4290
4578
|
const callId = typeof req.id === "string" ? req.id : typeof req.toolCallId === "string" ? req.toolCallId : "tool";
|
|
4291
4579
|
const name = typeof req.name === "string" ? req.name : typeof req.toolName === "string" ? req.toolName : "tool";
|
|
@@ -4322,7 +4610,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
4322
4610
|
}
|
|
4323
4611
|
return { messages, usageMetrics, firstUserMessage };
|
|
4324
4612
|
}
|
|
4325
|
-
function
|
|
4613
|
+
function isObject6(v) {
|
|
4326
4614
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4327
4615
|
}
|
|
4328
4616
|
var defaultDeps3, PREVIEW_CHAR_CAP = 200;
|
|
@@ -4342,7 +4630,7 @@ var init_history3 = __esm(() => {
|
|
|
4342
4630
|
}
|
|
4343
4631
|
},
|
|
4344
4632
|
async readFile(p) {
|
|
4345
|
-
return await
|
|
4633
|
+
return await readFile5(p, "utf8");
|
|
4346
4634
|
},
|
|
4347
4635
|
stat: stat3,
|
|
4348
4636
|
async rm(p) {
|
|
@@ -4404,90 +4692,6 @@ var init_auto_title = __esm(() => {
|
|
|
4404
4692
|
init_history3();
|
|
4405
4693
|
});
|
|
4406
4694
|
|
|
4407
|
-
// src/cli/invocation.ts
|
|
4408
|
-
import { fileURLToPath } from "url";
|
|
4409
|
-
function kobeCliInvocation() {
|
|
4410
|
-
const isBuilt = import.meta.url.endsWith(".js");
|
|
4411
|
-
if (isBuilt)
|
|
4412
|
-
return ["kobe"];
|
|
4413
|
-
const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
|
|
4414
|
-
const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
|
|
4415
|
-
return [process.execPath, "--preload", preload, "--conditions=browser", entry];
|
|
4416
|
-
}
|
|
4417
|
-
var init_invocation = () => {};
|
|
4418
|
-
|
|
4419
|
-
// src/tmux/session-layout.ts
|
|
4420
|
-
function shellQuote(s) {
|
|
4421
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4422
|
-
}
|
|
4423
|
-
function shellQuoteArgv(argv) {
|
|
4424
|
-
return argv.map(shellQuote).join(" ");
|
|
4425
|
-
}
|
|
4426
|
-
function keepAlive(cmd) {
|
|
4427
|
-
return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
|
|
4428
|
-
}
|
|
4429
|
-
function shQuote(s) {
|
|
4430
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4431
|
-
}
|
|
4432
|
-
function homeWelcomeCommand() {
|
|
4433
|
-
const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
|
|
4434
|
-
return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
|
|
4435
|
-
}
|
|
4436
|
-
function engineLaunchLine(engineCmd, init) {
|
|
4437
|
-
const tail = keepAlive(engineCmd);
|
|
4438
|
-
const script = init?.initScript?.trim();
|
|
4439
|
-
if (!script)
|
|
4440
|
-
return tail;
|
|
4441
|
-
const group = ["{", script, "}"].join(`
|
|
4442
|
-
`);
|
|
4443
|
-
if (init?.markerPath) {
|
|
4444
|
-
const marker = shQuote(init.markerPath);
|
|
4445
|
-
const markerDir = shQuote(markerDirOf(init.markerPath));
|
|
4446
|
-
return [
|
|
4447
|
-
`if [ ! -f ${marker} ]; then`,
|
|
4448
|
-
group,
|
|
4449
|
-
`if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
|
|
4450
|
-
"fi",
|
|
4451
|
-
tail
|
|
4452
|
-
].join(`
|
|
4453
|
-
`);
|
|
4454
|
-
}
|
|
4455
|
-
return [group, tail].join(`
|
|
4456
|
-
`);
|
|
4457
|
-
}
|
|
4458
|
-
function markerDirOf(p) {
|
|
4459
|
-
const i = p.lastIndexOf("/");
|
|
4460
|
-
return i <= 0 ? "." : p.slice(0, i);
|
|
4461
|
-
}
|
|
4462
|
-
function fallbackOpsScript(cwd) {
|
|
4463
|
-
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`;
|
|
4464
|
-
}
|
|
4465
|
-
function previewWindowCommand(args) {
|
|
4466
|
-
const wt = shellQuote(args.worktree);
|
|
4467
|
-
const file = shellQuote(args.relPath);
|
|
4468
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4469
|
-
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`;
|
|
4470
|
-
return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
|
|
4471
|
-
}
|
|
4472
|
-
function updatePageCommand(args) {
|
|
4473
|
-
return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
|
|
4474
|
-
}
|
|
4475
|
-
function tasksPaneCommand(cliInvocation, opts = {}) {
|
|
4476
|
-
const argv = [...cliInvocation, "tasks"];
|
|
4477
|
-
if (opts.initialTaskId)
|
|
4478
|
-
argv.push("--initial-task-id", opts.initialTaskId);
|
|
4479
|
-
return shellQuoteArgv(argv);
|
|
4480
|
-
}
|
|
4481
|
-
function opsPaneCommand(args) {
|
|
4482
|
-
if (args.taskId && args.claudePaneId) {
|
|
4483
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4484
|
-
const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
|
|
4485
|
-
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)}; }`;
|
|
4486
|
-
}
|
|
4487
|
-
return fallbackOpsScript(args.cwd);
|
|
4488
|
-
}
|
|
4489
|
-
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
4490
|
-
|
|
4491
4695
|
// src/tmux/client.ts
|
|
4492
4696
|
var exports_client = {};
|
|
4493
4697
|
__export(exports_client, {
|
|
@@ -4967,9 +5171,9 @@ class DaemonEventBus {
|
|
|
4967
5171
|
}
|
|
4968
5172
|
|
|
4969
5173
|
// src/daemon/server.ts
|
|
4970
|
-
import { mkdir as
|
|
5174
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
4971
5175
|
import { createServer } from "net";
|
|
4972
|
-
import { dirname as
|
|
5176
|
+
import { dirname as dirname5 } from "path";
|
|
4973
5177
|
function resolveIdleGraceMs() {
|
|
4974
5178
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
4975
5179
|
if (raw === undefined)
|
|
@@ -4977,6 +5181,13 @@ function resolveIdleGraceMs() {
|
|
|
4977
5181
|
const n = Number(raw);
|
|
4978
5182
|
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_IDLE_GRACE_MS;
|
|
4979
5183
|
}
|
|
5184
|
+
function resolveEngineStateTtlMs() {
|
|
5185
|
+
const raw = process.env.KOBE_ENGINE_STATE_TTL_MS;
|
|
5186
|
+
if (raw === undefined)
|
|
5187
|
+
return DEFAULT_ENGINE_STATE_TTL_MS;
|
|
5188
|
+
const n = Number(raw);
|
|
5189
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_ENGINE_STATE_TTL_MS;
|
|
5190
|
+
}
|
|
4980
5191
|
async function startDaemonServer(orch, options = {}) {
|
|
4981
5192
|
const socketPath = options.socketPath ?? defaultDaemonSocketPath(options.homeDir);
|
|
4982
5193
|
const pidPath = options.pidPath ?? defaultDaemonPidPath(options.homeDir);
|
|
@@ -5017,8 +5228,30 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5017
5228
|
bus.onPublish((event) => {
|
|
5018
5229
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
5019
5230
|
});
|
|
5020
|
-
|
|
5021
|
-
|
|
5231
|
+
const activity = new Map;
|
|
5232
|
+
const ACTIVITY_STALE_MS = resolveEngineStateTtlMs();
|
|
5233
|
+
function reportActivity(taskId, kind, detail) {
|
|
5234
|
+
const prev = activity.get(taskId);
|
|
5235
|
+
if (prev?.lapse)
|
|
5236
|
+
clearTimeout(prev.lapse);
|
|
5237
|
+
const state = reduceActivity(prev?.state, kind, detail);
|
|
5238
|
+
const at = Date.now();
|
|
5239
|
+
const entry = { state, detail, at };
|
|
5240
|
+
if (state !== "idle") {
|
|
5241
|
+
entry.lapse = setTimeout(() => {
|
|
5242
|
+
const cur = activity.get(taskId);
|
|
5243
|
+
if (cur && cur.at === at) {
|
|
5244
|
+
activity.set(taskId, { state: "idle", at: Date.now() });
|
|
5245
|
+
bus.publish("engine-state", { taskId, state: "idle", at: Date.now() });
|
|
5246
|
+
}
|
|
5247
|
+
}, ACTIVITY_STALE_MS);
|
|
5248
|
+
entry.lapse.unref?.();
|
|
5249
|
+
}
|
|
5250
|
+
activity.set(taskId, entry);
|
|
5251
|
+
bus.publish("engine-state", { taskId, state, ...detail ? { detail } : {}, at });
|
|
5252
|
+
}
|
|
5253
|
+
await mkdir5(dirname5(socketPath), { recursive: true });
|
|
5254
|
+
await mkdir5(dirname5(pidPath), { recursive: true });
|
|
5022
5255
|
await unlink4(socketPath).catch(() => {});
|
|
5023
5256
|
const server = createServer((socket) => {
|
|
5024
5257
|
const client = {
|
|
@@ -5088,7 +5321,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5088
5321
|
resolve2();
|
|
5089
5322
|
});
|
|
5090
5323
|
});
|
|
5091
|
-
await
|
|
5324
|
+
await writeFile4(pidPath, `${process.pid}
|
|
5092
5325
|
`, "utf8");
|
|
5093
5326
|
async function stopSoon() {
|
|
5094
5327
|
if (stopping)
|
|
@@ -5181,6 +5414,10 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5181
5414
|
case "task.delete": {
|
|
5182
5415
|
const taskId = requireString(payload, "taskId");
|
|
5183
5416
|
await orch.deleteTask(taskId, { force: optionalBoolean(payload, "force") });
|
|
5417
|
+
const gone = activity.get(taskId);
|
|
5418
|
+
if (gone?.lapse)
|
|
5419
|
+
clearTimeout(gone.lapse);
|
|
5420
|
+
activity.delete(taskId);
|
|
5184
5421
|
return {};
|
|
5185
5422
|
}
|
|
5186
5423
|
case "task.pin": {
|
|
@@ -5218,7 +5455,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5218
5455
|
worktreePath: requireString(payload, "worktreePath"),
|
|
5219
5456
|
branch: optionalString(payload, "branch"),
|
|
5220
5457
|
vendor: optionalVendor(payload, "vendor"),
|
|
5221
|
-
title: optionalString(payload, "title")
|
|
5458
|
+
title: optionalString(payload, "title"),
|
|
5459
|
+
ifExists: optionalString(payload, "ifExists") === "return" ? "return" : "error"
|
|
5222
5460
|
});
|
|
5223
5461
|
return { task: serializeTask(task) };
|
|
5224
5462
|
}
|
|
@@ -5226,6 +5464,15 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5226
5464
|
bus.publish("active-task", { taskId: optionalString(payload, "taskId") ?? null });
|
|
5227
5465
|
return {};
|
|
5228
5466
|
}
|
|
5467
|
+
case "engine.reportEvent": {
|
|
5468
|
+
const taskId = requireString(payload, "taskId");
|
|
5469
|
+
const kind = requireString(payload, "kind");
|
|
5470
|
+
if (!isEngineActivityKind(kind))
|
|
5471
|
+
throw new Error(`unknown engine event kind: ${kind}`);
|
|
5472
|
+
const detail = optionalActivityDetail(payload);
|
|
5473
|
+
reportActivity(taskId, kind, detail);
|
|
5474
|
+
return {};
|
|
5475
|
+
}
|
|
5229
5476
|
case "subscribe": {
|
|
5230
5477
|
client.subscribed = true;
|
|
5231
5478
|
const role = payload.role === "gui" ? "gui" : "pane";
|
|
@@ -5236,6 +5483,15 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5236
5483
|
for (const event of bus.snapshot()) {
|
|
5237
5484
|
writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
|
|
5238
5485
|
}
|
|
5486
|
+
for (const [taskId, entry] of activity) {
|
|
5487
|
+
if (entry.state === "idle")
|
|
5488
|
+
continue;
|
|
5489
|
+
writeFrame(client, {
|
|
5490
|
+
type: "event",
|
|
5491
|
+
name: "engine-state",
|
|
5492
|
+
payload: { taskId, state: entry.state, ...entry.detail ? { detail: entry.detail } : {}, at: entry.at }
|
|
5493
|
+
});
|
|
5494
|
+
}
|
|
5239
5495
|
return {};
|
|
5240
5496
|
}
|
|
5241
5497
|
default:
|
|
@@ -5286,7 +5542,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5286
5542
|
}
|
|
5287
5543
|
async function readPidFile(pidPath) {
|
|
5288
5544
|
try {
|
|
5289
|
-
const raw = await
|
|
5545
|
+
const raw = await readFile6(pidPath, "utf8");
|
|
5290
5546
|
const pid = Number(raw.trim());
|
|
5291
5547
|
return Number.isFinite(pid) ? pid : null;
|
|
5292
5548
|
} catch {
|
|
@@ -5337,13 +5593,29 @@ function optionalVendor(payload, key) {
|
|
|
5337
5593
|
}
|
|
5338
5594
|
return value;
|
|
5339
5595
|
}
|
|
5340
|
-
|
|
5596
|
+
function optionalActivityDetail(payload) {
|
|
5597
|
+
const raw = payload.detail;
|
|
5598
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
5599
|
+
return;
|
|
5600
|
+
const d = raw;
|
|
5601
|
+
const out = {};
|
|
5602
|
+
if (d.failure === "rate_limit" || d.failure === "billing" || d.failure === "other")
|
|
5603
|
+
out.failure = d.failure;
|
|
5604
|
+
if (d.waiting === "permission" || d.waiting === "input")
|
|
5605
|
+
out.waiting = d.waiting;
|
|
5606
|
+
if (typeof d.note === "string")
|
|
5607
|
+
out.note = d.note;
|
|
5608
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
5609
|
+
}
|
|
5610
|
+
var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000, DEFAULT_ENGINE_STATE_TTL_MS;
|
|
5341
5611
|
var init_server = __esm(() => {
|
|
5612
|
+
init_hook_events();
|
|
5342
5613
|
init_version();
|
|
5343
5614
|
init_auto_title_poller();
|
|
5344
5615
|
init_paths2();
|
|
5345
5616
|
init_protocol();
|
|
5346
5617
|
DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
|
|
5618
|
+
DEFAULT_ENGINE_STATE_TTL_MS = 10 * 60 * 1000;
|
|
5347
5619
|
});
|
|
5348
5620
|
|
|
5349
5621
|
// src/daemon/lifecycle.ts
|
|
@@ -5411,14 +5683,14 @@ __export(exports_daemon_process, {
|
|
|
5411
5683
|
connectIfRunning: () => connectIfRunning
|
|
5412
5684
|
});
|
|
5413
5685
|
import { spawn } from "child_process";
|
|
5414
|
-
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5415
|
-
import { dirname as
|
|
5686
|
+
import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5687
|
+
import { dirname as dirname6, resolve as resolve2 } from "path";
|
|
5416
5688
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5417
5689
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5418
5690
|
let stdio = "ignore";
|
|
5419
5691
|
let logFd;
|
|
5420
5692
|
try {
|
|
5421
|
-
mkdirSync2(
|
|
5693
|
+
mkdirSync2(dirname6(logPath), { recursive: true });
|
|
5422
5694
|
logFd = openSync(logPath, "a");
|
|
5423
5695
|
stdio = ["ignore", logFd, logFd];
|
|
5424
5696
|
} catch {
|
|
@@ -5485,12 +5757,12 @@ function resolveKobeSpawn(subcommand) {
|
|
|
5485
5757
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5486
5758
|
return [process.execPath, ...subcommand];
|
|
5487
5759
|
}
|
|
5488
|
-
const dir =
|
|
5760
|
+
const dir = dirname6(here);
|
|
5489
5761
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5490
|
-
if (
|
|
5762
|
+
if (existsSync2(sourceEntry))
|
|
5491
5763
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
5492
5764
|
const distEntry = resolve2(dir, "../cli/index.js");
|
|
5493
|
-
if (
|
|
5765
|
+
if (existsSync2(distEntry))
|
|
5494
5766
|
return [process.execPath, distEntry, ...subcommand];
|
|
5495
5767
|
throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
|
|
5496
5768
|
}
|
|
@@ -5581,14 +5853,14 @@ async function runRepoSubcommand(args) {
|
|
|
5581
5853
|
return;
|
|
5582
5854
|
}
|
|
5583
5855
|
const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
5584
|
-
const { existsSync:
|
|
5585
|
-
const { join:
|
|
5856
|
+
const { existsSync: existsSync3 } = await import("fs");
|
|
5857
|
+
const { join: join5 } = await import("path");
|
|
5586
5858
|
if (verb === "show") {
|
|
5587
5859
|
const [pathArg] = rest.filter((a) => !a.startsWith("-"));
|
|
5588
5860
|
const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
|
|
5589
5861
|
const override = getRepoInitOverride2(repo);
|
|
5590
|
-
const hasFileScript =
|
|
5591
|
-
const hasFilePrompt =
|
|
5862
|
+
const hasFileScript = existsSync3(join5(repo, ".kobe", "init.sh"));
|
|
5863
|
+
const hasFilePrompt = existsSync3(join5(repo, ".kobe", "init-prompt.md"));
|
|
5592
5864
|
console.log(`repo: ${repo}`);
|
|
5593
5865
|
console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
|
|
5594
5866
|
console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
|
|
@@ -6247,14 +6519,14 @@ var exports_repo_init = {};
|
|
|
6247
6519
|
__export(exports_repo_init, {
|
|
6248
6520
|
resolveRepoInit: () => resolveRepoInit
|
|
6249
6521
|
});
|
|
6250
|
-
import { existsSync as
|
|
6251
|
-
import { join as
|
|
6522
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
6523
|
+
import { join as join5 } from "path";
|
|
6252
6524
|
function repoFileScript(worktreePath) {
|
|
6253
|
-
return
|
|
6525
|
+
return existsSync3(join5(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
|
|
6254
6526
|
}
|
|
6255
6527
|
function repoFilePrompt(worktreePath) {
|
|
6256
|
-
const p =
|
|
6257
|
-
if (!
|
|
6528
|
+
const p = join5(worktreePath, INIT_PROMPT_REL);
|
|
6529
|
+
if (!existsSync3(p))
|
|
6258
6530
|
return;
|
|
6259
6531
|
try {
|
|
6260
6532
|
const text = readFileSync3(p, "utf8");
|
|
@@ -6275,8 +6547,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
|
|
|
6275
6547
|
var INIT_SCRIPT_REL, INIT_PROMPT_REL;
|
|
6276
6548
|
var init_repo_init = __esm(() => {
|
|
6277
6549
|
init_repos();
|
|
6278
|
-
INIT_SCRIPT_REL =
|
|
6279
|
-
INIT_PROMPT_REL =
|
|
6550
|
+
INIT_SCRIPT_REL = join5(".kobe", "init.sh");
|
|
6551
|
+
INIT_PROMPT_REL = join5(".kobe", "init-prompt.md");
|
|
6280
6552
|
});
|
|
6281
6553
|
|
|
6282
6554
|
// src/tui/panes/sidebar/worktree-changes.ts
|
|
@@ -6327,32 +6599,74 @@ var init_worktree_changes = __esm(() => {
|
|
|
6327
6599
|
// src/cli/api-cmd.ts
|
|
6328
6600
|
var exports_api_cmd = {};
|
|
6329
6601
|
__export(exports_api_cmd, {
|
|
6602
|
+
verbSchema: () => verbSchema,
|
|
6603
|
+
verbHelp: () => verbHelp,
|
|
6604
|
+
validateAgainstSpec: () => validateAgainstSpec,
|
|
6605
|
+
schemaIndex: () => schemaIndex,
|
|
6330
6606
|
runApiSubcommand: () => runApiSubcommand,
|
|
6331
6607
|
parseFlags: () => parseFlags,
|
|
6332
6608
|
parseAgentsSpec: () => parseAgentsSpec,
|
|
6609
|
+
fullSchema: () => fullSchema,
|
|
6610
|
+
findVerb: () => findVerb,
|
|
6333
6611
|
apiUsage: () => apiUsage,
|
|
6612
|
+
VERB_GROUPS: () => VERB_GROUPS,
|
|
6613
|
+
VERBS: () => VERBS,
|
|
6334
6614
|
FANOUT_CAP: () => FANOUT_CAP,
|
|
6335
6615
|
ApiError: () => ApiError,
|
|
6336
|
-
API_VERBS: () => API_VERBS
|
|
6616
|
+
API_VERBS: () => API_VERBS,
|
|
6617
|
+
API_SCHEMA_VERSION: () => API_SCHEMA_VERSION
|
|
6337
6618
|
});
|
|
6338
6619
|
import { resolve as resolve4 } from "path";
|
|
6339
|
-
function
|
|
6620
|
+
function groupOf(verbName) {
|
|
6621
|
+
for (const [group, names] of Object.entries(VERB_GROUPS)) {
|
|
6622
|
+
if (names.includes(verbName))
|
|
6623
|
+
return group;
|
|
6624
|
+
}
|
|
6625
|
+
return "other";
|
|
6626
|
+
}
|
|
6627
|
+
async function handleSchema(_client, parsed) {
|
|
6628
|
+
const { flags } = parsed;
|
|
6629
|
+
const verbName = optional(flags, "verb");
|
|
6630
|
+
if (verbName) {
|
|
6631
|
+
const v = findVerb(verbName);
|
|
6632
|
+
if (!v)
|
|
6633
|
+
throw new ApiError(`unknown verb: ${verbName}`, "BAD_VERB");
|
|
6634
|
+
return verbSchema(v);
|
|
6635
|
+
}
|
|
6636
|
+
const group = optional(flags, "group");
|
|
6637
|
+
if (group)
|
|
6638
|
+
return groupSchema(group);
|
|
6639
|
+
if (optionalBool(flags, "all"))
|
|
6640
|
+
return fullSchema();
|
|
6641
|
+
return schemaIndex();
|
|
6642
|
+
}
|
|
6643
|
+
function findVerb(name) {
|
|
6644
|
+
const canonical = VERB_ALIASES[name] ?? name;
|
|
6645
|
+
return VERBS.find((v) => v.name === canonical);
|
|
6646
|
+
}
|
|
6647
|
+
function parseFlags(argv, booleanFlags = new Set) {
|
|
6340
6648
|
const flags = new Map;
|
|
6341
6649
|
let pretty = false;
|
|
6650
|
+
let help = false;
|
|
6342
6651
|
for (let i = 0;i < argv.length; i++) {
|
|
6343
6652
|
const arg = argv[i];
|
|
6344
|
-
if (!arg.startsWith("--")) {
|
|
6653
|
+
if (!arg.startsWith("--") && arg !== "-h") {
|
|
6345
6654
|
throw new ApiError(`unexpected positional arg: ${arg}`, "BAD_FLAG");
|
|
6346
6655
|
}
|
|
6656
|
+
if (arg === "-h") {
|
|
6657
|
+
help = true;
|
|
6658
|
+
continue;
|
|
6659
|
+
}
|
|
6347
6660
|
const eq = arg.indexOf("=");
|
|
6348
6661
|
if (eq !== -1) {
|
|
6349
6662
|
const key2 = arg.slice(2, eq);
|
|
6350
6663
|
const value = arg.slice(eq + 1);
|
|
6351
|
-
if (key2 === "pretty")
|
|
6664
|
+
if (key2 === "pretty")
|
|
6352
6665
|
pretty = value !== "false" && value !== "0";
|
|
6353
|
-
|
|
6666
|
+
else if (key2 === "help")
|
|
6667
|
+
help = value !== "false" && value !== "0";
|
|
6668
|
+
else
|
|
6354
6669
|
flags.set(key2, value);
|
|
6355
|
-
}
|
|
6356
6670
|
continue;
|
|
6357
6671
|
}
|
|
6358
6672
|
const key = arg.slice(2);
|
|
@@ -6360,6 +6674,14 @@ function parseFlags(argv) {
|
|
|
6360
6674
|
pretty = true;
|
|
6361
6675
|
continue;
|
|
6362
6676
|
}
|
|
6677
|
+
if (key === "help") {
|
|
6678
|
+
help = true;
|
|
6679
|
+
continue;
|
|
6680
|
+
}
|
|
6681
|
+
if (booleanFlags.has(key)) {
|
|
6682
|
+
flags.set(key, "true");
|
|
6683
|
+
continue;
|
|
6684
|
+
}
|
|
6363
6685
|
const next = argv[i + 1];
|
|
6364
6686
|
if (next === undefined || next.startsWith("--")) {
|
|
6365
6687
|
throw new ApiError(`flag --${key} requires a value`, "BAD_FLAG");
|
|
@@ -6367,19 +6689,50 @@ function parseFlags(argv) {
|
|
|
6367
6689
|
flags.set(key, next);
|
|
6368
6690
|
i += 1;
|
|
6369
6691
|
}
|
|
6370
|
-
return { flags, pretty };
|
|
6692
|
+
return { flags, pretty, help };
|
|
6693
|
+
}
|
|
6694
|
+
function validateAgainstSpec(verb, flags) {
|
|
6695
|
+
const known = new Set(verb.flags.map((f) => f.name));
|
|
6696
|
+
for (const key of flags.keys()) {
|
|
6697
|
+
if (!known.has(key)) {
|
|
6698
|
+
throw new ApiError(`unknown flag --${key} for "${verb.name}". Run \`kobe api ${verb.name} --help\``, "BAD_FLAG");
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
for (const f of verb.flags) {
|
|
6702
|
+
if (f.required && !flags.get(f.name))
|
|
6703
|
+
throw new ApiError(`--${f.name} is required for "${verb.name}"`, "MISSING_FLAG");
|
|
6704
|
+
if (f.type === "enum" && f.values) {
|
|
6705
|
+
const raw = flags.get(f.name);
|
|
6706
|
+
if (raw !== undefined && !f.values.includes(raw)) {
|
|
6707
|
+
throw new ApiError(`--${f.name} must be one of ${f.values.join(", ")}`, "BAD_FLAG");
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
if (f.type === "int") {
|
|
6711
|
+
const raw = flags.get(f.name);
|
|
6712
|
+
if (raw !== undefined) {
|
|
6713
|
+
const n = Number.parseInt(raw, 10);
|
|
6714
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
6715
|
+
throw new ApiError(`--${f.name} must be a positive integer`, "BAD_FLAG");
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6371
6719
|
}
|
|
6372
6720
|
function required(flags, key) {
|
|
6373
6721
|
const v = flags.get(key);
|
|
6374
|
-
if (v === undefined || v.length === 0)
|
|
6722
|
+
if (v === undefined || v.length === 0)
|
|
6375
6723
|
throw new ApiError(`--${key} is required`, "MISSING_FLAG");
|
|
6376
|
-
}
|
|
6377
6724
|
return v;
|
|
6378
6725
|
}
|
|
6379
6726
|
function optional(flags, key) {
|
|
6380
6727
|
const v = flags.get(key);
|
|
6381
6728
|
return v && v.length > 0 ? v : undefined;
|
|
6382
6729
|
}
|
|
6730
|
+
function requireEnum(flags, key, values) {
|
|
6731
|
+
const v = required(flags, key);
|
|
6732
|
+
if (!values.includes(v))
|
|
6733
|
+
throw new ApiError(`--${key} must be one of ${values.join(", ")}`, "BAD_FLAG");
|
|
6734
|
+
return v;
|
|
6735
|
+
}
|
|
6383
6736
|
function optionalVendor2(flags) {
|
|
6384
6737
|
const raw = optional(flags, "vendor");
|
|
6385
6738
|
if (raw === undefined)
|
|
@@ -6389,16 +6742,28 @@ function optionalVendor2(flags) {
|
|
|
6389
6742
|
}
|
|
6390
6743
|
return raw;
|
|
6391
6744
|
}
|
|
6745
|
+
function optionalBool(flags, key) {
|
|
6746
|
+
const raw = optional(flags, key);
|
|
6747
|
+
if (raw === undefined)
|
|
6748
|
+
return;
|
|
6749
|
+
if (["true", "1", "yes"].includes(raw))
|
|
6750
|
+
return true;
|
|
6751
|
+
if (["false", "0", "no"].includes(raw))
|
|
6752
|
+
return false;
|
|
6753
|
+
throw new ApiError(`--${key} must be a boolean (true/false)`, "BAD_FLAG");
|
|
6754
|
+
}
|
|
6392
6755
|
function optionalPositiveInt(flags, key) {
|
|
6393
6756
|
const raw = optional(flags, key);
|
|
6394
6757
|
if (raw === undefined)
|
|
6395
6758
|
return;
|
|
6396
6759
|
const n = Number.parseInt(raw, 10);
|
|
6397
|
-
if (!Number.isInteger(n) || n <= 0)
|
|
6760
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
6398
6761
|
throw new ApiError(`--${key} must be a positive integer`, "BAD_FLAG");
|
|
6399
|
-
}
|
|
6400
6762
|
return n;
|
|
6401
6763
|
}
|
|
6764
|
+
function resolveRepoFlag(repo) {
|
|
6765
|
+
return resolve4(process.cwd(), repo);
|
|
6766
|
+
}
|
|
6402
6767
|
function parseAgentsSpec(spec) {
|
|
6403
6768
|
const out = [];
|
|
6404
6769
|
for (const part of spec.split(",")) {
|
|
@@ -6423,19 +6788,102 @@ function parseAgentsSpec(spec) {
|
|
|
6423
6788
|
throw new ApiError('--agents specified no agents (e.g. "claude:2,codex:1")', "BAD_FLAG");
|
|
6424
6789
|
return out;
|
|
6425
6790
|
}
|
|
6791
|
+
function flagJson(f) {
|
|
6792
|
+
return {
|
|
6793
|
+
name: f.name,
|
|
6794
|
+
type: f.type,
|
|
6795
|
+
required: f.required ?? false,
|
|
6796
|
+
...f.values ? { values: f.values } : {},
|
|
6797
|
+
...f.default !== undefined ? { default: f.default } : {},
|
|
6798
|
+
...f.placeholder ? { placeholder: f.placeholder } : {},
|
|
6799
|
+
description: f.description
|
|
6800
|
+
};
|
|
6801
|
+
}
|
|
6802
|
+
function verbSchema(v) {
|
|
6803
|
+
return {
|
|
6804
|
+
name: v.name,
|
|
6805
|
+
group: groupOf(v.name),
|
|
6806
|
+
summary: v.summary,
|
|
6807
|
+
offline: v.offline ?? false,
|
|
6808
|
+
flags: v.flags.map(flagJson)
|
|
6809
|
+
};
|
|
6810
|
+
}
|
|
6811
|
+
function schemaIndex() {
|
|
6812
|
+
return {
|
|
6813
|
+
apiVersion: API_SCHEMA_VERSION,
|
|
6814
|
+
kobeVersion: CURRENT_VERSION,
|
|
6815
|
+
hint: "Compact index. Drill into ONE verb: `kobe api schema --verb <name>` (or `kobe api <verb> --help`). One group: `--group <g>`. Whole spec: `--all`.",
|
|
6816
|
+
groups: VERB_GROUPS,
|
|
6817
|
+
verbs: VERBS.map((v) => ({ name: v.name, group: groupOf(v.name), summary: v.summary })),
|
|
6818
|
+
globalFlags: GLOBAL_FLAGS,
|
|
6819
|
+
aliases: VERB_ALIASES
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
function groupSchema(group) {
|
|
6823
|
+
const names = VERB_GROUPS[group];
|
|
6824
|
+
if (!names) {
|
|
6825
|
+
throw new ApiError(`unknown group: ${group}. Groups: ${Object.keys(VERB_GROUPS).join(", ")}`, "BAD_FLAG");
|
|
6826
|
+
}
|
|
6827
|
+
return {
|
|
6828
|
+
group,
|
|
6829
|
+
verbs: names.map((n) => {
|
|
6830
|
+
const v = findVerb(n);
|
|
6831
|
+
return { name: n, summary: v?.summary ?? "" };
|
|
6832
|
+
})
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
function fullSchema() {
|
|
6836
|
+
return {
|
|
6837
|
+
apiVersion: API_SCHEMA_VERSION,
|
|
6838
|
+
kobeVersion: CURRENT_VERSION,
|
|
6839
|
+
output: {
|
|
6840
|
+
success: "one JSON object on stdout, newline-terminated, exit 0",
|
|
6841
|
+
error: '{"error":{"message","code"}} on stderr, exit != 0',
|
|
6842
|
+
pretty: "--pretty indents stdout JSON"
|
|
6843
|
+
},
|
|
6844
|
+
globalFlags: GLOBAL_FLAGS,
|
|
6845
|
+
aliases: VERB_ALIASES,
|
|
6846
|
+
groups: VERB_GROUPS,
|
|
6847
|
+
verbs: VERBS.map(verbSchema)
|
|
6848
|
+
};
|
|
6849
|
+
}
|
|
6850
|
+
function flagSignature(verb) {
|
|
6851
|
+
return verb.flags.map((f) => {
|
|
6852
|
+
const meta = f.type === "enum" && f.values ? f.values.join("|") : f.placeholder ?? (f.type === "bool" ? "" : "X");
|
|
6853
|
+
const core = meta ? `--${f.name} ${meta}` : `--${f.name}`;
|
|
6854
|
+
return f.required ? core : `[${core}]`;
|
|
6855
|
+
}).join(" ");
|
|
6856
|
+
}
|
|
6857
|
+
function verbHelp(verb) {
|
|
6858
|
+
const lines = [`kobe api ${verb.name} ${flagSignature(verb)}`.trimEnd(), "", verb.summary, ""];
|
|
6859
|
+
const alias = Object.entries(VERB_ALIASES).find(([, canon]) => canon === verb.name)?.[0];
|
|
6860
|
+
if (alias)
|
|
6861
|
+
lines.push(`Alias: ${alias}`, "");
|
|
6862
|
+
if (verb.flags.length > 0) {
|
|
6863
|
+
lines.push("Flags:");
|
|
6864
|
+
for (const f of verb.flags) {
|
|
6865
|
+
const req = f.required ? " (required)" : "";
|
|
6866
|
+
const def = f.default !== undefined ? ` [default: ${f.default}]` : "";
|
|
6867
|
+
const vals = f.type === "enum" && f.values ? ` {${f.values.join("|")}}` : "";
|
|
6868
|
+
lines.push(` --${f.name}${vals}${req}${def} ${f.description}`);
|
|
6869
|
+
}
|
|
6870
|
+
lines.push("");
|
|
6871
|
+
}
|
|
6872
|
+
lines.push("Global: [--pretty] [--help]");
|
|
6873
|
+
return lines.join(`
|
|
6874
|
+
`);
|
|
6875
|
+
}
|
|
6426
6876
|
function apiUsage() {
|
|
6877
|
+
const rows = VERBS.map((v) => ` ${v.name.padEnd(18)} ${v.summary}`);
|
|
6427
6878
|
return [
|
|
6428
|
-
"usage: kobe api <verb> [flags] [--pretty]",
|
|
6879
|
+
"usage: kobe api <verb> [flags] [--pretty] [--help]",
|
|
6880
|
+
"",
|
|
6881
|
+
"Explore the full surface (names, flags, types) with: kobe api schema",
|
|
6429
6882
|
"",
|
|
6430
6883
|
"verbs:",
|
|
6431
|
-
|
|
6432
|
-
" fan-out --repo PATH --prompt TEXT [--count N | --agents claude:2,codex:1] [--base-branch B]",
|
|
6433
|
-
" send [--task-id ID] --prompt TEXT",
|
|
6434
|
-
" get-task --task-id ID",
|
|
6435
|
-
" collect --task-ids a,b,c | --repo PATH",
|
|
6436
|
-
" list",
|
|
6884
|
+
...rows,
|
|
6437
6885
|
"",
|
|
6438
|
-
"Output is one JSON object on stdout (exit 0); errors are JSON on stderr (exit
|
|
6886
|
+
"Output is one JSON object on stdout (exit 0); errors are JSON on stderr (exit != 0)."
|
|
6439
6887
|
].join(`
|
|
6440
6888
|
`);
|
|
6441
6889
|
}
|
|
@@ -6492,12 +6940,22 @@ async function resolveActiveTaskId(client) {
|
|
|
6492
6940
|
}
|
|
6493
6941
|
return activeId;
|
|
6494
6942
|
}
|
|
6495
|
-
async function
|
|
6943
|
+
async function simpleRpc(client, name, payload) {
|
|
6944
|
+
if (!client)
|
|
6945
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6946
|
+
return client.request(name, payload);
|
|
6947
|
+
}
|
|
6948
|
+
async function add(client, parsed) {
|
|
6949
|
+
if (!client)
|
|
6950
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6496
6951
|
const { flags } = parsed;
|
|
6497
|
-
const payload = { repo: required(flags, "repo") };
|
|
6952
|
+
const payload = { repo: resolveRepoFlag(required(flags, "repo")) };
|
|
6498
6953
|
const title = optional(flags, "title");
|
|
6499
6954
|
if (title)
|
|
6500
6955
|
payload.title = title;
|
|
6956
|
+
const branch = optional(flags, "branch");
|
|
6957
|
+
if (branch)
|
|
6958
|
+
payload.branch = branch;
|
|
6501
6959
|
const baseRef = optional(flags, "base-branch");
|
|
6502
6960
|
if (baseRef)
|
|
6503
6961
|
payload.baseRef = baseRef;
|
|
@@ -6505,25 +6963,26 @@ async function spawnTask(client, parsed) {
|
|
|
6505
6963
|
if (vendor)
|
|
6506
6964
|
payload.vendor = vendor;
|
|
6507
6965
|
const res = await client.request("task.create", payload);
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
6966
|
+
const taskId = res.taskId;
|
|
6967
|
+
const status = optional(flags, "status");
|
|
6968
|
+
if (status)
|
|
6969
|
+
await client.request("task.status", { taskId, status: requireEnum(flags, "status", TASK_STATUSES) });
|
|
6970
|
+
const pin = optionalBool(flags, "pin");
|
|
6971
|
+
if (pin !== undefined)
|
|
6972
|
+
await client.request("task.pin", { taskId, pinned: pin });
|
|
6973
|
+
let task = res.task;
|
|
6974
|
+
if (status || pin !== undefined) {
|
|
6975
|
+
task = (await client.request("task.get", { taskId })).task;
|
|
6511
6976
|
}
|
|
6512
|
-
const
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
}, prompt);
|
|
6518
|
-
return {
|
|
6519
|
-
taskId: res.taskId,
|
|
6520
|
-
task: res.task,
|
|
6521
|
-
started: delivered.started,
|
|
6522
|
-
engineReady: delivered.engineReady,
|
|
6523
|
-
session: delivered.session
|
|
6524
|
-
};
|
|
6977
|
+
const prompt = optional(flags, "prompt");
|
|
6978
|
+
if (!prompt)
|
|
6979
|
+
return { taskId, task, started: false };
|
|
6980
|
+
const delivered = await deliverPrompt(client, { id: taskId, worktreePath: task.worktreePath, vendor: task.vendor, repo: task.repo }, prompt);
|
|
6981
|
+
return { taskId, task, started: delivered.started, engineReady: delivered.engineReady, session: delivered.session };
|
|
6525
6982
|
}
|
|
6526
6983
|
async function send(client, parsed) {
|
|
6984
|
+
if (!client)
|
|
6985
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6527
6986
|
const { flags } = parsed;
|
|
6528
6987
|
const prompt = required(flags, "prompt");
|
|
6529
6988
|
let taskId = optional(flags, "task-id");
|
|
@@ -6550,17 +7009,50 @@ async function send(client, parsed) {
|
|
|
6550
7009
|
};
|
|
6551
7010
|
}
|
|
6552
7011
|
async function getTask(client, parsed) {
|
|
7012
|
+
if (!client)
|
|
7013
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6553
7014
|
const taskId = required(parsed.flags, "task-id");
|
|
6554
7015
|
const res = await client.request("task.get", { taskId });
|
|
6555
7016
|
const running = await sessionExists(tmuxSessionName(taskId));
|
|
6556
7017
|
return { task: res.task, running };
|
|
6557
7018
|
}
|
|
6558
7019
|
async function list(client) {
|
|
7020
|
+
if (!client)
|
|
7021
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6559
7022
|
return client.request("task.list");
|
|
6560
7023
|
}
|
|
7024
|
+
async function setActive(client, parsed) {
|
|
7025
|
+
if (!client)
|
|
7026
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
7027
|
+
const none = optionalBool(parsed.flags, "none");
|
|
7028
|
+
const taskId = none ? null : required(parsed.flags, "task-id");
|
|
7029
|
+
await client.request("task.setActive", { taskId });
|
|
7030
|
+
return { ok: true, activeTaskId: taskId };
|
|
7031
|
+
}
|
|
7032
|
+
async function adopt(client, parsed) {
|
|
7033
|
+
if (!client)
|
|
7034
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
7035
|
+
const { flags } = parsed;
|
|
7036
|
+
const input = {
|
|
7037
|
+
repo: resolveRepoFlag(required(flags, "repo")),
|
|
7038
|
+
worktreePath: resolveRepoFlag(required(flags, "worktree"))
|
|
7039
|
+
};
|
|
7040
|
+
const branch = optional(flags, "branch");
|
|
7041
|
+
if (branch)
|
|
7042
|
+
input.branch = branch;
|
|
7043
|
+
const vendor = optionalVendor2(flags);
|
|
7044
|
+
if (vendor)
|
|
7045
|
+
input.vendor = vendor;
|
|
7046
|
+
const title = optional(flags, "title");
|
|
7047
|
+
if (title)
|
|
7048
|
+
input.title = title;
|
|
7049
|
+
return client.request("worktree.adopt", input);
|
|
7050
|
+
}
|
|
6561
7051
|
async function fanOut(client, parsed) {
|
|
7052
|
+
if (!client)
|
|
7053
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6562
7054
|
const { flags } = parsed;
|
|
6563
|
-
const repo = required(flags, "repo");
|
|
7055
|
+
const repo = resolveRepoFlag(required(flags, "repo"));
|
|
6564
7056
|
const prompt = required(flags, "prompt");
|
|
6565
7057
|
const title = optional(flags, "title");
|
|
6566
7058
|
const baseRef = optional(flags, "base-branch");
|
|
@@ -6589,6 +7081,8 @@ async function fanOut(client, parsed) {
|
|
|
6589
7081
|
return { count: tasks.length, tasks };
|
|
6590
7082
|
}
|
|
6591
7083
|
async function collect(client, parsed) {
|
|
7084
|
+
if (!client)
|
|
7085
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6592
7086
|
const { flags } = parsed;
|
|
6593
7087
|
const idsFlag = optional(flags, "task-ids");
|
|
6594
7088
|
const repoFlag = optional(flags, "repo");
|
|
@@ -6597,7 +7091,7 @@ async function collect(client, parsed) {
|
|
|
6597
7091
|
taskIds = idsFlag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6598
7092
|
} else if (repoFlag) {
|
|
6599
7093
|
const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
6600
|
-
const target = resolveRepoRoot2(
|
|
7094
|
+
const target = resolveRepoRoot2(resolveRepoFlag(repoFlag));
|
|
6601
7095
|
const { tasks } = await client.request("task.list");
|
|
6602
7096
|
taskIds = tasks.filter((t) => !t.archived && resolveRepoRoot2(t.repo) === target).map((t) => t.id);
|
|
6603
7097
|
} else {
|
|
@@ -6623,72 +7117,67 @@ async function collect(client, parsed) {
|
|
|
6623
7117
|
return { tasks: out };
|
|
6624
7118
|
}
|
|
6625
7119
|
async function runApiSubcommand(argv) {
|
|
6626
|
-
const [
|
|
6627
|
-
if (!
|
|
6628
|
-
if (!
|
|
7120
|
+
const [verbName, ...rest] = argv;
|
|
7121
|
+
if (!verbName || verbName === "--help" || verbName === "-h" || verbName === "help") {
|
|
7122
|
+
if (!verbName)
|
|
6629
7123
|
fail(apiUsage(), "MISSING_VERB", 2);
|
|
6630
|
-
}
|
|
6631
7124
|
process.stdout.write(`${apiUsage()}
|
|
6632
7125
|
`);
|
|
6633
7126
|
return;
|
|
6634
7127
|
}
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
${
|
|
6638
|
-
|
|
6639
|
-
|
|
7128
|
+
const verb = findVerb(verbName);
|
|
7129
|
+
if (!verb)
|
|
7130
|
+
fail(`unknown verb: ${verbName}
|
|
7131
|
+
${apiUsage()}`, "BAD_VERB", 2);
|
|
7132
|
+
const booleanFlags = new Set(verb.flags.filter((f) => f.type === "bool").map((f) => f.name));
|
|
7133
|
+
let parsed;
|
|
7134
|
+
try {
|
|
7135
|
+
parsed = parseFlags(rest, booleanFlags);
|
|
7136
|
+
} catch (err) {
|
|
7137
|
+
if (err instanceof ApiError)
|
|
7138
|
+
fail(err.message, err.code, 2);
|
|
7139
|
+
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
7140
|
+
}
|
|
7141
|
+
if (parsed.help) {
|
|
7142
|
+
process.stdout.write(`${verbHelp(verb)}
|
|
7143
|
+
`);
|
|
7144
|
+
return;
|
|
7145
|
+
}
|
|
6640
7146
|
try {
|
|
6641
|
-
parsed
|
|
7147
|
+
validateAgainstSpec(verb, parsed.flags);
|
|
6642
7148
|
} catch (err) {
|
|
6643
7149
|
if (err instanceof ApiError)
|
|
6644
7150
|
fail(err.message, err.code, 2);
|
|
6645
7151
|
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
6646
7152
|
}
|
|
6647
|
-
let client;
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
7153
|
+
let client = null;
|
|
7154
|
+
if (!verb.offline) {
|
|
7155
|
+
try {
|
|
7156
|
+
client = await connectOrStartDaemon();
|
|
7157
|
+
} catch (err) {
|
|
7158
|
+
fail(`could not reach or start the kobe daemon: ${err instanceof Error ? err.message : String(err)}`, "BAD_DAEMON", 2);
|
|
7159
|
+
}
|
|
6652
7160
|
}
|
|
6653
7161
|
try {
|
|
6654
|
-
|
|
6655
|
-
switch (verb) {
|
|
6656
|
-
case "spawn-task":
|
|
6657
|
-
result = await spawnTask(client, parsed);
|
|
6658
|
-
break;
|
|
6659
|
-
case "fan-out":
|
|
6660
|
-
result = await fanOut(client, parsed);
|
|
6661
|
-
break;
|
|
6662
|
-
case "send":
|
|
6663
|
-
result = await send(client, parsed);
|
|
6664
|
-
break;
|
|
6665
|
-
case "get-task":
|
|
6666
|
-
result = await getTask(client, parsed);
|
|
6667
|
-
break;
|
|
6668
|
-
case "collect":
|
|
6669
|
-
result = await collect(client, parsed);
|
|
6670
|
-
break;
|
|
6671
|
-
case "list":
|
|
6672
|
-
result = await list(client);
|
|
6673
|
-
break;
|
|
6674
|
-
}
|
|
7162
|
+
const result = await verb.handler(client, parsed);
|
|
6675
7163
|
emit(result, parsed.pretty);
|
|
6676
7164
|
} catch (err) {
|
|
6677
7165
|
if (err instanceof ApiError)
|
|
6678
7166
|
fail(err.message, err.code, 1);
|
|
6679
7167
|
fail(err instanceof Error ? err.message : String(err), "RPC_ERROR", 1);
|
|
6680
7168
|
} finally {
|
|
6681
|
-
client
|
|
7169
|
+
client?.close();
|
|
6682
7170
|
}
|
|
6683
7171
|
}
|
|
6684
|
-
var
|
|
7172
|
+
var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS;
|
|
6685
7173
|
var init_api_cmd = __esm(() => {
|
|
6686
7174
|
init_daemon_process();
|
|
6687
7175
|
init_interactive_command();
|
|
6688
7176
|
init_client2();
|
|
6689
7177
|
init_prompt_delivery();
|
|
6690
7178
|
init_vendor();
|
|
6691
|
-
|
|
7179
|
+
init_version();
|
|
7180
|
+
TASK_STATUSES = ["backlog", "in_progress", "in_review", "done", "canceled", "error"];
|
|
6692
7181
|
ApiError = class ApiError extends Error {
|
|
6693
7182
|
code;
|
|
6694
7183
|
constructor(message, code) {
|
|
@@ -6696,6 +7185,241 @@ var init_api_cmd = __esm(() => {
|
|
|
6696
7185
|
this.code = code;
|
|
6697
7186
|
}
|
|
6698
7187
|
};
|
|
7188
|
+
F = {
|
|
7189
|
+
repo: (required = true) => ({
|
|
7190
|
+
name: "repo",
|
|
7191
|
+
type: "string",
|
|
7192
|
+
required,
|
|
7193
|
+
placeholder: "PATH",
|
|
7194
|
+
description: "Repo root (git toplevel). Relative paths resolve against $PWD."
|
|
7195
|
+
}),
|
|
7196
|
+
taskId: (required = true) => ({
|
|
7197
|
+
name: "task-id",
|
|
7198
|
+
type: "string",
|
|
7199
|
+
required,
|
|
7200
|
+
placeholder: "ID",
|
|
7201
|
+
description: "Target task id (from `list` / `add`)."
|
|
7202
|
+
}),
|
|
7203
|
+
vendor: () => ({
|
|
7204
|
+
name: "vendor",
|
|
7205
|
+
type: "enum",
|
|
7206
|
+
values: ALL_VENDORS,
|
|
7207
|
+
placeholder: "V",
|
|
7208
|
+
description: "Engine vendor for the task."
|
|
7209
|
+
}),
|
|
7210
|
+
title: () => ({ name: "title", type: "string", placeholder: "T", description: "Human task title." }),
|
|
7211
|
+
prompt: (required, desc) => ({
|
|
7212
|
+
name: "prompt",
|
|
7213
|
+
type: "string",
|
|
7214
|
+
required,
|
|
7215
|
+
placeholder: "TEXT",
|
|
7216
|
+
description: desc
|
|
7217
|
+
})
|
|
7218
|
+
};
|
|
7219
|
+
VERB_ALIASES = { "spawn-task": "add" };
|
|
7220
|
+
VERB_GROUPS = {
|
|
7221
|
+
discover: ["schema"],
|
|
7222
|
+
read: ["list", "get-task", "collect"],
|
|
7223
|
+
create: ["add", "fan-out"],
|
|
7224
|
+
drive: ["send", "set-active"],
|
|
7225
|
+
edit: ["rename", "set-branch", "set-vendor", "set-status"],
|
|
7226
|
+
lifecycle: ["archive", "pin", "delete"],
|
|
7227
|
+
worktree: ["ensure-worktree", "adopt", "discover-adoptable"]
|
|
7228
|
+
};
|
|
7229
|
+
VERBS = [
|
|
7230
|
+
{
|
|
7231
|
+
name: "schema",
|
|
7232
|
+
summary: "Explore the API. Default = a COMPACT index (groups + verb summaries, no flags). Drill in with --verb / --group; --all for the full spec.",
|
|
7233
|
+
flags: [
|
|
7234
|
+
{ name: "verb", type: "string", placeholder: "NAME", description: "Full flag detail for ONE verb." },
|
|
7235
|
+
{ name: "group", type: "string", placeholder: "G", description: "List the verbs in one group (compact)." },
|
|
7236
|
+
{
|
|
7237
|
+
name: "all",
|
|
7238
|
+
type: "bool",
|
|
7239
|
+
description: "The COMPLETE spec \u2014 every verb AND every flag (large; avoid by default)."
|
|
7240
|
+
}
|
|
7241
|
+
],
|
|
7242
|
+
offline: true,
|
|
7243
|
+
handler: handleSchema
|
|
7244
|
+
},
|
|
7245
|
+
{ name: "list", summary: "List all tasks (incl. archived). Returns { tasks }.", flags: [], handler: list },
|
|
7246
|
+
{
|
|
7247
|
+
name: "get-task",
|
|
7248
|
+
summary: "Read one task's metadata. `.running` = its tmux session is live.",
|
|
7249
|
+
flags: [F.taskId()],
|
|
7250
|
+
handler: getTask
|
|
7251
|
+
},
|
|
7252
|
+
{
|
|
7253
|
+
name: "add",
|
|
7254
|
+
summary: "Create a task (shows in the sidebar immediately). With --prompt it also starts the engine and delivers it. Alias: spawn-task.",
|
|
7255
|
+
flags: [
|
|
7256
|
+
F.repo(),
|
|
7257
|
+
F.title(),
|
|
7258
|
+
{
|
|
7259
|
+
name: "branch",
|
|
7260
|
+
type: "string",
|
|
7261
|
+
placeholder: "B",
|
|
7262
|
+
description: "Explicit branch name (else auto kobe/<slug>-<id>)."
|
|
7263
|
+
},
|
|
7264
|
+
{ name: "base-branch", type: "string", placeholder: "B", description: "Base ref the worktree branches from." },
|
|
7265
|
+
F.vendor(),
|
|
7266
|
+
{
|
|
7267
|
+
name: "status",
|
|
7268
|
+
type: "enum",
|
|
7269
|
+
values: TASK_STATUSES,
|
|
7270
|
+
default: "backlog",
|
|
7271
|
+
description: "Initial lifecycle status."
|
|
7272
|
+
},
|
|
7273
|
+
{ name: "pin", type: "bool", description: "Pin the task to the top of the sidebar." },
|
|
7274
|
+
F.prompt(false, "Optional first message \u2014 when set, materializes the worktree, starts the engine, and pastes it.")
|
|
7275
|
+
],
|
|
7276
|
+
handler: add
|
|
7277
|
+
},
|
|
7278
|
+
{
|
|
7279
|
+
name: "fan-out",
|
|
7280
|
+
summary: `Spawn N tasks of ONE prompt in a single call (parallel attempts). Capped at ${FANOUT_CAP}.`,
|
|
7281
|
+
flags: [
|
|
7282
|
+
F.repo(),
|
|
7283
|
+
F.prompt(true, "Shared prompt delivered to every spawned task."),
|
|
7284
|
+
{ name: "count", type: "int", placeholder: "N", description: "Number of tasks of one vendor (with --vendor)." },
|
|
7285
|
+
{
|
|
7286
|
+
name: "agents",
|
|
7287
|
+
type: "string",
|
|
7288
|
+
placeholder: "claude:2,codex:1",
|
|
7289
|
+
description: "Per-vendor counts (alternative to --count)."
|
|
7290
|
+
},
|
|
7291
|
+
F.vendor(),
|
|
7292
|
+
F.title(),
|
|
7293
|
+
{ name: "base-branch", type: "string", placeholder: "B", description: "Base ref for every worktree." }
|
|
7294
|
+
],
|
|
7295
|
+
handler: fanOut
|
|
7296
|
+
},
|
|
7297
|
+
{
|
|
7298
|
+
name: "send",
|
|
7299
|
+
summary: "Paste a follow-up prompt into a task's running engine (one full turn). Defaults to the active task.",
|
|
7300
|
+
flags: [F.taskId(false), F.prompt(true, "Text pasted + submitted into the engine pane.")],
|
|
7301
|
+
handler: send
|
|
7302
|
+
},
|
|
7303
|
+
{
|
|
7304
|
+
name: "collect",
|
|
7305
|
+
summary: "Read-only comparison snapshot of several tasks (identity, branch, .running, uncommitted .changes).",
|
|
7306
|
+
flags: [
|
|
7307
|
+
{ name: "task-ids", type: "csv", placeholder: "a,b,c", description: "Comma-separated task ids." },
|
|
7308
|
+
F.repo(false)
|
|
7309
|
+
],
|
|
7310
|
+
handler: collect
|
|
7311
|
+
},
|
|
7312
|
+
{
|
|
7313
|
+
name: "rename",
|
|
7314
|
+
summary: "Set a task's title.",
|
|
7315
|
+
flags: [F.taskId(), { name: "title", type: "string", required: true, placeholder: "T", description: "New title." }],
|
|
7316
|
+
handler: (c, p) => simpleRpc(c, "task.rename", { taskId: required(p.flags, "task-id"), title: required(p.flags, "title") })
|
|
7317
|
+
},
|
|
7318
|
+
{
|
|
7319
|
+
name: "set-branch",
|
|
7320
|
+
summary: "Rename a task's branch (git branch -m if materialized, else recorded).",
|
|
7321
|
+
flags: [
|
|
7322
|
+
F.taskId(),
|
|
7323
|
+
{ name: "branch", type: "string", required: true, placeholder: "B", description: "New branch name." }
|
|
7324
|
+
],
|
|
7325
|
+
handler: (c, p) => simpleRpc(c, "task.setBranch", { taskId: required(p.flags, "task-id"), branch: required(p.flags, "branch") })
|
|
7326
|
+
},
|
|
7327
|
+
{
|
|
7328
|
+
name: "set-vendor",
|
|
7329
|
+
summary: "Change a task's engine vendor (takes effect on next session rebuild).",
|
|
7330
|
+
flags: [F.taskId(), { ...F.vendor(), required: true }],
|
|
7331
|
+
handler: (c, p) => simpleRpc(c, "task.setVendor", {
|
|
7332
|
+
taskId: required(p.flags, "task-id"),
|
|
7333
|
+
vendor: requireEnum(p.flags, "vendor", ALL_VENDORS)
|
|
7334
|
+
})
|
|
7335
|
+
},
|
|
7336
|
+
{
|
|
7337
|
+
name: "set-status",
|
|
7338
|
+
summary: "Set a task's lifecycle status.",
|
|
7339
|
+
flags: [
|
|
7340
|
+
F.taskId(),
|
|
7341
|
+
{ name: "status", type: "enum", required: true, values: TASK_STATUSES, description: "New status." }
|
|
7342
|
+
],
|
|
7343
|
+
handler: (c, p) => simpleRpc(c, "task.status", {
|
|
7344
|
+
taskId: required(p.flags, "task-id"),
|
|
7345
|
+
status: requireEnum(p.flags, "status", TASK_STATUSES)
|
|
7346
|
+
})
|
|
7347
|
+
},
|
|
7348
|
+
{
|
|
7349
|
+
name: "archive",
|
|
7350
|
+
summary: "Archive (or with --archived=false, unarchive) a task. Non-destructive: worktree/branch/history stay.",
|
|
7351
|
+
flags: [
|
|
7352
|
+
F.taskId(),
|
|
7353
|
+
{ name: "archived", type: "bool", default: "true", description: "true to archive, false to unarchive." }
|
|
7354
|
+
],
|
|
7355
|
+
handler: (c, p) => simpleRpc(c, "task.archive", {
|
|
7356
|
+
taskId: required(p.flags, "task-id"),
|
|
7357
|
+
archived: optionalBool(p.flags, "archived") ?? true
|
|
7358
|
+
})
|
|
7359
|
+
},
|
|
7360
|
+
{
|
|
7361
|
+
name: "pin",
|
|
7362
|
+
summary: "Pin (or with --pinned=false, unpin) a task to the top of the sidebar.",
|
|
7363
|
+
flags: [F.taskId(), { name: "pinned", type: "bool", default: "true", description: "true to pin, false to unpin." }],
|
|
7364
|
+
handler: (c, p) => simpleRpc(c, "task.pin", {
|
|
7365
|
+
taskId: required(p.flags, "task-id"),
|
|
7366
|
+
pinned: optionalBool(p.flags, "pinned") ?? true
|
|
7367
|
+
})
|
|
7368
|
+
},
|
|
7369
|
+
{
|
|
7370
|
+
name: "set-active",
|
|
7371
|
+
summary: "Set the shared active task (the focus every Tasks pane highlights). Pass --none to clear.",
|
|
7372
|
+
flags: [
|
|
7373
|
+
F.taskId(false),
|
|
7374
|
+
{ name: "none", type: "bool", description: "Clear the active task instead of setting one." }
|
|
7375
|
+
],
|
|
7376
|
+
handler: setActive
|
|
7377
|
+
},
|
|
7378
|
+
{
|
|
7379
|
+
name: "ensure-worktree",
|
|
7380
|
+
summary: "Materialize a task's git worktree on disk now (without starting an engine). Returns { worktreePath }.",
|
|
7381
|
+
flags: [F.taskId()],
|
|
7382
|
+
handler: (c, p) => simpleRpc(c, "task.ensureWorktree", { taskId: required(p.flags, "task-id") })
|
|
7383
|
+
},
|
|
7384
|
+
{
|
|
7385
|
+
name: "delete",
|
|
7386
|
+
summary: "Permanently remove a task (and its worktree). DESTRUCTIVE \u2014 prefer `archive`. Needs --force on a dirty worktree.",
|
|
7387
|
+
flags: [F.taskId(), { name: "force", type: "bool", description: "Delete even with uncommitted changes." }],
|
|
7388
|
+
handler: (c, p) => simpleRpc(c, "task.delete", {
|
|
7389
|
+
taskId: required(p.flags, "task-id"),
|
|
7390
|
+
force: optionalBool(p.flags, "force") ?? false
|
|
7391
|
+
})
|
|
7392
|
+
},
|
|
7393
|
+
{
|
|
7394
|
+
name: "discover-adoptable",
|
|
7395
|
+
summary: "List existing git worktrees in a repo not yet tracked as kobe tasks. Returns { worktrees }.",
|
|
7396
|
+
flags: [F.repo()],
|
|
7397
|
+
handler: (c, p) => simpleRpc(c, "worktree.discoverAdoptable", { repo: resolveRepoFlag(required(p.flags, "repo")) })
|
|
7398
|
+
},
|
|
7399
|
+
{
|
|
7400
|
+
name: "adopt",
|
|
7401
|
+
summary: "Import an existing git worktree as a kobe task. Returns { task }.",
|
|
7402
|
+
flags: [
|
|
7403
|
+
F.repo(),
|
|
7404
|
+
{
|
|
7405
|
+
name: "worktree",
|
|
7406
|
+
type: "string",
|
|
7407
|
+
required: true,
|
|
7408
|
+
placeholder: "PATH",
|
|
7409
|
+
description: "Path of the worktree to adopt."
|
|
7410
|
+
},
|
|
7411
|
+
{ name: "branch", type: "string", placeholder: "B", description: "Branch override (else the worktree's own)." },
|
|
7412
|
+
F.vendor(),
|
|
7413
|
+
F.title()
|
|
7414
|
+
],
|
|
7415
|
+
handler: adopt
|
|
7416
|
+
}
|
|
7417
|
+
];
|
|
7418
|
+
API_VERBS = VERBS.map((v) => v.name);
|
|
7419
|
+
GLOBAL_FLAGS = [
|
|
7420
|
+
{ name: "pretty", type: "bool", description: "Pretty-print stdout JSON." },
|
|
7421
|
+
{ name: "help", type: "bool", description: "Show usage for the verb and exit." }
|
|
7422
|
+
];
|
|
6699
7423
|
});
|
|
6700
7424
|
|
|
6701
7425
|
// src/cli/update.ts
|
|
@@ -6836,9 +7560,9 @@ var init_schema = () => {};
|
|
|
6836
7560
|
|
|
6837
7561
|
// src/tui/context/theme/loader.ts
|
|
6838
7562
|
import { readFileSync as readFileSync4, readdirSync } from "fs";
|
|
6839
|
-
import { join as
|
|
7563
|
+
import { join as join6 } from "path";
|
|
6840
7564
|
function userThemesDir() {
|
|
6841
|
-
return
|
|
7565
|
+
return join6(kobeStateDir(), "themes");
|
|
6842
7566
|
}
|
|
6843
7567
|
function loadUserThemes() {
|
|
6844
7568
|
const dir = userThemesDir();
|
|
@@ -6852,7 +7576,7 @@ function loadUserThemes() {
|
|
|
6852
7576
|
for (const file of entries) {
|
|
6853
7577
|
if (!file.endsWith(".json"))
|
|
6854
7578
|
continue;
|
|
6855
|
-
const path6 =
|
|
7579
|
+
const path6 = join6(dir, file);
|
|
6856
7580
|
let parsed;
|
|
6857
7581
|
try {
|
|
6858
7582
|
const text = readFileSync4(path6, "utf8");
|
|
@@ -6882,8 +7606,8 @@ var exports_theme = {};
|
|
|
6882
7606
|
__export(exports_theme, {
|
|
6883
7607
|
runThemeSubcommand: () => runThemeSubcommand
|
|
6884
7608
|
});
|
|
6885
|
-
import { existsSync as
|
|
6886
|
-
import { basename as basename3, join as
|
|
7609
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
7610
|
+
import { basename as basename3, join as join7, resolve as resolve5 } from "path";
|
|
6887
7611
|
function fail2(message) {
|
|
6888
7612
|
process.stderr.write(`kobe theme: ${message}
|
|
6889
7613
|
`);
|
|
@@ -6914,7 +7638,7 @@ function listThemes() {
|
|
|
6914
7638
|
} else {
|
|
6915
7639
|
for (const f of userFiles) {
|
|
6916
7640
|
const name = f.slice(0, -".json".length);
|
|
6917
|
-
const path6 =
|
|
7641
|
+
const path6 = join7(dir, f);
|
|
6918
7642
|
const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
|
|
6919
7643
|
lines.push(` ${name}${overridesBundled} ${path6}`);
|
|
6920
7644
|
}
|
|
@@ -7004,8 +7728,8 @@ async function addTheme(args) {
|
|
|
7004
7728
|
}
|
|
7005
7729
|
const dir = userThemesDir();
|
|
7006
7730
|
mkdirSync3(dir, { recursive: true });
|
|
7007
|
-
const dest =
|
|
7008
|
-
if (
|
|
7731
|
+
const dest = join7(dir, `${name}.json`);
|
|
7732
|
+
if (existsSync4(dest) && !opts.force) {
|
|
7009
7733
|
fail2(`${dest} already exists (pass --force to overwrite)`);
|
|
7010
7734
|
}
|
|
7011
7735
|
writeFileSync2(dest, `${JSON.stringify(result.theme, null, 2)}
|
|
@@ -7022,8 +7746,8 @@ function removeTheme(args) {
|
|
|
7022
7746
|
if (BUNDLED_NAMES.includes(name)) {
|
|
7023
7747
|
fail2(`"${name}" is a built-in theme and cannot be removed`);
|
|
7024
7748
|
}
|
|
7025
|
-
const dest =
|
|
7026
|
-
if (!
|
|
7749
|
+
const dest = join7(userThemesDir(), `${name}.json`);
|
|
7750
|
+
if (!existsSync4(dest)) {
|
|
7027
7751
|
fail2(`no user theme named "${name}" (looked for ${dest})`);
|
|
7028
7752
|
}
|
|
7029
7753
|
unlinkSync(dest);
|
|
@@ -7206,31 +7930,66 @@ var init_daemon_cmd = __esm(() => {
|
|
|
7206
7930
|
});
|
|
7207
7931
|
|
|
7208
7932
|
// src/lib/skill-install.ts
|
|
7209
|
-
import { existsSync as
|
|
7933
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
7210
7934
|
import { homedir as homedir9 } from "os";
|
|
7211
|
-
import { join as
|
|
7935
|
+
import { join as join8 } from "path";
|
|
7936
|
+
function npxSkillsArgv(opts = {}) {
|
|
7937
|
+
return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
|
|
7938
|
+
}
|
|
7939
|
+
function npxSkillsCommand(opts = {}) {
|
|
7940
|
+
return `npx ${npxSkillsArgv(opts).join(" ")}`;
|
|
7941
|
+
}
|
|
7212
7942
|
function kobeSkillPaths(opts = {}) {
|
|
7213
7943
|
const home = opts.home ?? homedir9();
|
|
7214
7944
|
const cwd = opts.cwd ?? process.cwd();
|
|
7215
|
-
return [
|
|
7945
|
+
return [join8(home, SKILL_REL_PATH), join8(cwd, SKILL_REL_PATH)];
|
|
7216
7946
|
}
|
|
7217
|
-
function
|
|
7218
|
-
|
|
7947
|
+
function parseSkillVersion(content) {
|
|
7948
|
+
const m = content.match(/kobe-skill-version:\s*(\d+)/);
|
|
7949
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
7950
|
+
}
|
|
7951
|
+
function kobeSkillState(opts) {
|
|
7952
|
+
const path6 = kobeSkillPaths(opts).find((p) => existsSync5(p));
|
|
7953
|
+
if (!path6) {
|
|
7954
|
+
return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
|
|
7955
|
+
}
|
|
7956
|
+
let installedVersion = null;
|
|
7957
|
+
try {
|
|
7958
|
+
installedVersion = parseSkillVersion(readFileSync6(path6, "utf8"));
|
|
7959
|
+
} catch {
|
|
7960
|
+
installedVersion = null;
|
|
7961
|
+
}
|
|
7962
|
+
const stale = installedVersion === null || installedVersion < KOBE_SKILL_VERSION;
|
|
7963
|
+
return { installed: true, installedVersion, currentVersion: KOBE_SKILL_VERSION, stale };
|
|
7219
7964
|
}
|
|
7220
7965
|
function maybeHintSkillInstall() {
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7966
|
+
const state = kobeSkillState();
|
|
7967
|
+
if (!state.installed) {
|
|
7968
|
+
if (getPersistedString(HINT_SEEN_KEY) === "1")
|
|
7969
|
+
return;
|
|
7970
|
+
setPersistedString(HINT_SEEN_KEY, "1");
|
|
7971
|
+
process.stderr.write(`
|
|
7972
|
+
kobe: the kobe agent skill isn't installed \u2014 install it so your coding agent can drive kobe via \`kobe api\`:
|
|
7973
|
+
${SKILL_INSTALL_COMMAND}
|
|
7974
|
+
(wraps \`${npxSkillsCommand()}\`; check anytime with \`kobe doctor\`)
|
|
7975
|
+
|
|
7976
|
+
`);
|
|
7224
7977
|
return;
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7978
|
+
}
|
|
7979
|
+
if (state.stale) {
|
|
7980
|
+
const key = `${HINT_SEEN_KEY}:v${state.currentVersion}`;
|
|
7981
|
+
if (getPersistedString(key) === "1")
|
|
7982
|
+
return;
|
|
7983
|
+
setPersistedString(key, "1");
|
|
7984
|
+
const was = state.installedVersion === null ? "an older" : `v${state.installedVersion}`;
|
|
7985
|
+
process.stderr.write(`
|
|
7986
|
+
kobe: your kobe agent skill is out of date (${was}; this kobe wants v${state.currentVersion}) \u2014 refresh it so \`kobe api\` guidance matches:
|
|
7228
7987
|
${SKILL_INSTALL_COMMAND}
|
|
7229
|
-
(check anytime with \`kobe doctor\`)
|
|
7230
7988
|
|
|
7231
7989
|
`);
|
|
7990
|
+
}
|
|
7232
7991
|
}
|
|
7233
|
-
var SKILL_REL_PATH = ".claude/skills/kobe/SKILL.md", SKILL_INSTALL_COMMAND = "
|
|
7992
|
+
var KOBE_SKILL_VERSION = 1, SKILL_REL_PATH = ".claude/skills/kobe/SKILL.md", SKILL_INSTALL_COMMAND = "kobe skill install", SKILL_SOURCE_SLUG = "Sma1lboy/kobe", DEFAULT_SKILL_AGENT = "claude-code", HINT_SEEN_KEY = "skillHintSeen";
|
|
7234
7993
|
var init_skill_install = __esm(() => {
|
|
7235
7994
|
init_repos();
|
|
7236
7995
|
});
|
|
@@ -7242,9 +8001,9 @@ __export(exports_maintenance, {
|
|
|
7242
8001
|
runReloadSubcommand: () => runReloadSubcommand,
|
|
7243
8002
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
7244
8003
|
});
|
|
7245
|
-
import { existsSync as
|
|
8004
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
|
|
7246
8005
|
import { unlink as unlink6 } from "fs/promises";
|
|
7247
|
-
import { join as
|
|
8006
|
+
import { join as join9 } from "path";
|
|
7248
8007
|
import { createInterface } from "readline";
|
|
7249
8008
|
function isProcessAlive2(pid) {
|
|
7250
8009
|
try {
|
|
@@ -7291,7 +8050,7 @@ function describeFile(path6) {
|
|
|
7291
8050
|
}
|
|
7292
8051
|
function taskCount(tasksPath) {
|
|
7293
8052
|
try {
|
|
7294
|
-
const parsed = JSON.parse(
|
|
8053
|
+
const parsed = JSON.parse(readFileSync7(tasksPath, "utf8"));
|
|
7295
8054
|
return Array.isArray(parsed.tasks) ? parsed.tasks.length : null;
|
|
7296
8055
|
} catch {
|
|
7297
8056
|
return null;
|
|
@@ -7299,7 +8058,7 @@ function taskCount(tasksPath) {
|
|
|
7299
8058
|
}
|
|
7300
8059
|
function tailFile(path6, n) {
|
|
7301
8060
|
try {
|
|
7302
|
-
const lines =
|
|
8061
|
+
const lines = readFileSync7(path6, "utf8").split(`
|
|
7303
8062
|
`).filter((l) => l.trim().length > 0);
|
|
7304
8063
|
return lines.slice(-n).join(`
|
|
7305
8064
|
`);
|
|
@@ -7341,7 +8100,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
7341
8100
|
const socketPath = defaultDaemonSocketPath();
|
|
7342
8101
|
const pidPath = defaultDaemonPidPath();
|
|
7343
8102
|
const logPath = defaultDaemonLogPath();
|
|
7344
|
-
const tasksPath =
|
|
8103
|
+
const tasksPath = join9(kobeStateDir(), "tasks.json");
|
|
7345
8104
|
const statePath2 = kvStatePath();
|
|
7346
8105
|
const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
|
|
7347
8106
|
const status = await probeDaemonStatus(socketPath);
|
|
@@ -7362,7 +8121,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
7362
8121
|
} else {
|
|
7363
8122
|
out.push("daemon: \u2717 not running (no pidfile)");
|
|
7364
8123
|
}
|
|
7365
|
-
if (
|
|
8124
|
+
if (existsSync6(socketPath))
|
|
7366
8125
|
out.push(` orphan socket file present: ${socketPath}`);
|
|
7367
8126
|
const tail = tailFile(logPath, 8);
|
|
7368
8127
|
if (tail) {
|
|
@@ -7379,11 +8138,16 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
7379
8138
|
out.push("tmux: \u2717 not found on PATH (task sessions need tmux)");
|
|
7380
8139
|
}
|
|
7381
8140
|
out.push("");
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
out.push(
|
|
8141
|
+
const skill = kobeSkillState();
|
|
8142
|
+
if (!skill.installed) {
|
|
8143
|
+
out.push("skill: \u2717 kobe agent skill not installed (optional \u2014 lets a coding agent drive `kobe api`)");
|
|
8144
|
+
out.push(` \u2192 ${SKILL_INSTALL_COMMAND}`);
|
|
8145
|
+
} else if (skill.stale) {
|
|
8146
|
+
const was = skill.installedVersion === null ? "unstamped" : `v${skill.installedVersion}`;
|
|
8147
|
+
out.push(`skill: \u26A0 kobe agent skill out of date (${was}; this kobe wants v${skill.currentVersion})`);
|
|
7386
8148
|
out.push(` \u2192 ${SKILL_INSTALL_COMMAND}`);
|
|
8149
|
+
} else {
|
|
8150
|
+
out.push(`skill: \u2713 kobe agent skill installed (v${skill.installedVersion})`);
|
|
7387
8151
|
}
|
|
7388
8152
|
out.push("");
|
|
7389
8153
|
const count = taskCount(tasksPath);
|
|
@@ -7448,7 +8212,7 @@ async function runResetSubcommand(argv) {
|
|
|
7448
8212
|
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
7449
8213
|
const socketPath = defaultDaemonSocketPath();
|
|
7450
8214
|
const pidPath = defaultDaemonPidPath();
|
|
7451
|
-
const tasksPath =
|
|
8215
|
+
const tasksPath = join9(kobeStateDir(), "tasks.json");
|
|
7452
8216
|
const statePath2 = kvStatePath();
|
|
7453
8217
|
console.log("kobe reset will:");
|
|
7454
8218
|
console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
|
|
@@ -7551,6 +8315,275 @@ var init_maintenance = __esm(() => {
|
|
|
7551
8315
|
init_client2();
|
|
7552
8316
|
});
|
|
7553
8317
|
|
|
8318
|
+
// src/cli/skill-cmd.ts
|
|
8319
|
+
var exports_skill_cmd = {};
|
|
8320
|
+
__export(exports_skill_cmd, {
|
|
8321
|
+
runSkillSubcommand: () => runSkillSubcommand
|
|
8322
|
+
});
|
|
8323
|
+
function skillUsage() {
|
|
8324
|
+
return [
|
|
8325
|
+
"usage: kobe skill <verb>",
|
|
8326
|
+
"",
|
|
8327
|
+
"verbs:",
|
|
8328
|
+
" install [--agent NAME] Install the kobe agent skill (wraps `npx skills add`)",
|
|
8329
|
+
" status Show whether the skill is installed",
|
|
8330
|
+
" command [--agent NAME] Print the underlying npx command without running it",
|
|
8331
|
+
"",
|
|
8332
|
+
`The skill teaches a coding agent how to drive \`kobe api\`. Default agent: ${DEFAULT_SKILL_AGENT}.`
|
|
8333
|
+
].join(`
|
|
8334
|
+
`);
|
|
8335
|
+
}
|
|
8336
|
+
function parseAgent(rest) {
|
|
8337
|
+
let agent = DEFAULT_SKILL_AGENT;
|
|
8338
|
+
for (let i = 0;i < rest.length; i++) {
|
|
8339
|
+
const arg = rest[i];
|
|
8340
|
+
if (arg === "--agent") {
|
|
8341
|
+
const v = rest[i + 1];
|
|
8342
|
+
if (!v || v.startsWith("--")) {
|
|
8343
|
+
process.stderr.write(`kobe skill: --agent requires a value
|
|
8344
|
+
`);
|
|
8345
|
+
process.exit(2);
|
|
8346
|
+
}
|
|
8347
|
+
agent = v;
|
|
8348
|
+
i++;
|
|
8349
|
+
} else if (arg.startsWith("--agent=")) {
|
|
8350
|
+
agent = arg.slice("--agent=".length);
|
|
8351
|
+
} else {
|
|
8352
|
+
process.stderr.write(`kobe skill: unknown flag "${arg}"
|
|
8353
|
+
|
|
8354
|
+
${skillUsage()}
|
|
8355
|
+
`);
|
|
8356
|
+
process.exit(2);
|
|
8357
|
+
}
|
|
8358
|
+
}
|
|
8359
|
+
return agent;
|
|
8360
|
+
}
|
|
8361
|
+
async function runSkillSubcommand(argv) {
|
|
8362
|
+
const [verb, ...rest] = argv;
|
|
8363
|
+
if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
|
|
8364
|
+
process.stdout.write(`${skillUsage()}
|
|
8365
|
+
`);
|
|
8366
|
+
if (!verb)
|
|
8367
|
+
process.exitCode = 2;
|
|
8368
|
+
return;
|
|
8369
|
+
}
|
|
8370
|
+
if (!SKILL_VERBS.includes(verb)) {
|
|
8371
|
+
process.stderr.write(`kobe skill: unknown verb "${verb}"
|
|
8372
|
+
|
|
8373
|
+
${skillUsage()}
|
|
8374
|
+
`);
|
|
8375
|
+
process.exit(2);
|
|
8376
|
+
}
|
|
8377
|
+
if (verb === "status") {
|
|
8378
|
+
const state = kobeSkillState();
|
|
8379
|
+
const [userPath, projectPath] = kobeSkillPaths();
|
|
8380
|
+
const head = !state.installed ? "\u2717 not installed" : state.stale ? `\u26A0 out of date (installed ${state.installedVersion === null ? "unstamped" : `v${state.installedVersion}`}, this kobe wants v${state.currentVersion})` : `\u2713 installed (v${state.installedVersion})`;
|
|
8381
|
+
process.stdout.write([
|
|
8382
|
+
`kobe skill: ${head}`,
|
|
8383
|
+
` looked in: ${userPath}`,
|
|
8384
|
+
` ${projectPath}`,
|
|
8385
|
+
state.installed && !state.stale ? "" : " \u2192 run `kobe skill install` to install / refresh",
|
|
8386
|
+
""
|
|
8387
|
+
].join(`
|
|
8388
|
+
`));
|
|
8389
|
+
return;
|
|
8390
|
+
}
|
|
8391
|
+
if (verb === "command") {
|
|
8392
|
+
process.stdout.write(`${npxSkillsCommand({ agent: parseAgent(rest) })}
|
|
8393
|
+
`);
|
|
8394
|
+
return;
|
|
8395
|
+
}
|
|
8396
|
+
const agent = parseAgent(rest);
|
|
8397
|
+
const args = npxSkillsArgv({ agent });
|
|
8398
|
+
process.stdout.write(`kobe skill: running \`npx ${args.join(" ")}\`
|
|
8399
|
+
`);
|
|
8400
|
+
const proc = Bun.spawn(["npx", ...args], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
|
|
8401
|
+
const code = await proc.exited;
|
|
8402
|
+
if (code !== 0) {
|
|
8403
|
+
process.stderr.write(`
|
|
8404
|
+
kobe skill install failed (npx exited ${code}). Is \`npx\` on PATH and are you online?
|
|
8405
|
+
` + `You can run it yourself: ${npxSkillsCommand({ agent })}
|
|
8406
|
+
`);
|
|
8407
|
+
process.exit(code || 1);
|
|
8408
|
+
}
|
|
8409
|
+
process.stdout.write(`kobe skill: installed.
|
|
8410
|
+
`);
|
|
8411
|
+
}
|
|
8412
|
+
var SKILL_VERBS;
|
|
8413
|
+
var init_skill_cmd = __esm(() => {
|
|
8414
|
+
init_skill_install();
|
|
8415
|
+
SKILL_VERBS = ["install", "status", "command"];
|
|
8416
|
+
});
|
|
8417
|
+
|
|
8418
|
+
// src/cli/hook-cmd.ts
|
|
8419
|
+
var exports_hook_cmd = {};
|
|
8420
|
+
__export(exports_hook_cmd, {
|
|
8421
|
+
runHookSubcommand: () => runHookSubcommand
|
|
8422
|
+
});
|
|
8423
|
+
import { homedir as homedir10 } from "os";
|
|
8424
|
+
import { dirname as dirname7, join as join10, resolve as resolve6 } from "path";
|
|
8425
|
+
async function readStdinPayload() {
|
|
8426
|
+
try {
|
|
8427
|
+
const text = await Promise.race([
|
|
8428
|
+
Bun.stdin.text(),
|
|
8429
|
+
new Promise((resolve7) => setTimeout(() => resolve7(""), 500))
|
|
8430
|
+
]);
|
|
8431
|
+
if (!text.trim())
|
|
8432
|
+
return {};
|
|
8433
|
+
const parsed = JSON.parse(text);
|
|
8434
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
8435
|
+
} catch {
|
|
8436
|
+
return {};
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
function failureFromErrorType(errorType) {
|
|
8440
|
+
if (typeof errorType !== "string")
|
|
8441
|
+
return "other";
|
|
8442
|
+
if (errorType === "rate_limit" || errorType === "overloaded")
|
|
8443
|
+
return "rate_limit";
|
|
8444
|
+
if (errorType === "billing_error")
|
|
8445
|
+
return "billing";
|
|
8446
|
+
return "other";
|
|
8447
|
+
}
|
|
8448
|
+
function flagValue(argv, name) {
|
|
8449
|
+
for (let i = 0;i < argv.length; i++) {
|
|
8450
|
+
if (argv[i] === name)
|
|
8451
|
+
return argv[i + 1];
|
|
8452
|
+
if (argv[i].startsWith(`${name}=`))
|
|
8453
|
+
return argv[i].slice(name.length + 1);
|
|
8454
|
+
}
|
|
8455
|
+
return;
|
|
8456
|
+
}
|
|
8457
|
+
async function runHookSubcommand(argv) {
|
|
8458
|
+
const [verb, ...rest] = argv;
|
|
8459
|
+
if (verb === "setup") {
|
|
8460
|
+
await runHookSetup(rest);
|
|
8461
|
+
return;
|
|
8462
|
+
}
|
|
8463
|
+
try {
|
|
8464
|
+
if (verb === "worktree-created") {
|
|
8465
|
+
await reportWorktreeCreated();
|
|
8466
|
+
return;
|
|
8467
|
+
}
|
|
8468
|
+
if (!verb || !isEngineActivityKind(verb))
|
|
8469
|
+
return;
|
|
8470
|
+
const taskId = flagValue(rest, "--task-id");
|
|
8471
|
+
if (!taskId)
|
|
8472
|
+
return;
|
|
8473
|
+
const payload = await readStdinPayload();
|
|
8474
|
+
let detail;
|
|
8475
|
+
if (verb === "turn-failed") {
|
|
8476
|
+
detail = { failure: failureFromErrorType(payload.error_type) };
|
|
8477
|
+
} else if (verb === "awaiting-input") {
|
|
8478
|
+
detail = { waiting: "permission" };
|
|
8479
|
+
}
|
|
8480
|
+
const client = await connectIfRunning();
|
|
8481
|
+
if (!client)
|
|
8482
|
+
return;
|
|
8483
|
+
try {
|
|
8484
|
+
await client.request("engine.reportEvent", { taskId, kind: verb, ...detail ? { detail } : {} });
|
|
8485
|
+
} finally {
|
|
8486
|
+
client.close();
|
|
8487
|
+
}
|
|
8488
|
+
} catch {}
|
|
8489
|
+
}
|
|
8490
|
+
async function reportWorktreeCreated() {
|
|
8491
|
+
const payload = await readStdinPayload();
|
|
8492
|
+
const worktreePath = typeof payload.worktree_path === "string" ? payload.worktree_path : undefined;
|
|
8493
|
+
if (!worktreePath)
|
|
8494
|
+
return;
|
|
8495
|
+
const repo = await deriveRepoRoot(worktreePath);
|
|
8496
|
+
if (!repo)
|
|
8497
|
+
return;
|
|
8498
|
+
const client = await connectIfRunning();
|
|
8499
|
+
if (!client)
|
|
8500
|
+
return;
|
|
8501
|
+
try {
|
|
8502
|
+
await client.request("worktree.adopt", { repo, worktreePath, ifExists: "return" });
|
|
8503
|
+
} finally {
|
|
8504
|
+
client.close();
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
async function deriveRepoRoot(worktreePath) {
|
|
8508
|
+
try {
|
|
8509
|
+
const proc = Bun.spawn(["git", "-C", worktreePath, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
8510
|
+
stdout: "pipe",
|
|
8511
|
+
stderr: "ignore"
|
|
8512
|
+
});
|
|
8513
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
8514
|
+
if (code !== 0)
|
|
8515
|
+
return;
|
|
8516
|
+
const commonDir = out.trim();
|
|
8517
|
+
return commonDir ? dirname7(commonDir) : undefined;
|
|
8518
|
+
} catch {
|
|
8519
|
+
return;
|
|
8520
|
+
}
|
|
8521
|
+
}
|
|
8522
|
+
function syncSettingsPath(scope) {
|
|
8523
|
+
if (scope.kind === "repo")
|
|
8524
|
+
return join10(resolve6(scope.path), ".claude", "settings.json");
|
|
8525
|
+
return join10(homedir10(), ".claude", "settings.json");
|
|
8526
|
+
}
|
|
8527
|
+
function worktreeSyncAdapters() {
|
|
8528
|
+
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsWorktreeSync());
|
|
8529
|
+
}
|
|
8530
|
+
async function runHookSetup(argv) {
|
|
8531
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
8532
|
+
process.stdout.write([
|
|
8533
|
+
"Usage: kobe hook setup [--global | --repo <path> | --off]",
|
|
8534
|
+
"",
|
|
8535
|
+
"Install (or remove with --off) the hook that syncs an external",
|
|
8536
|
+
"`claude --worktree` into kobe as a task. Default: --global (~/.claude).",
|
|
8537
|
+
""
|
|
8538
|
+
].join(`
|
|
8539
|
+
`));
|
|
8540
|
+
return;
|
|
8541
|
+
}
|
|
8542
|
+
const off = argv.includes("--off");
|
|
8543
|
+
const repoIdx = argv.indexOf("--repo");
|
|
8544
|
+
const repoPath = repoIdx !== -1 ? argv[repoIdx + 1] : undefined;
|
|
8545
|
+
if (repoIdx !== -1 && !repoPath) {
|
|
8546
|
+
process.stderr.write(`kobe hook setup: --repo requires a path
|
|
8547
|
+
`);
|
|
8548
|
+
process.exit(2);
|
|
8549
|
+
}
|
|
8550
|
+
const adapters = worktreeSyncAdapters();
|
|
8551
|
+
if (adapters.length === 0) {
|
|
8552
|
+
process.stdout.write(`kobe hook setup: no engine supports external worktree sync \u2014 nothing to do
|
|
8553
|
+
`);
|
|
8554
|
+
return;
|
|
8555
|
+
}
|
|
8556
|
+
if (off) {
|
|
8557
|
+
const prev = getPersistedString(SYNC_SETTING_KEY);
|
|
8558
|
+
const path7 = prev?.startsWith("repo:") ? syncSettingsPath({ kind: "repo", path: prev.slice(5) }) : syncSettingsPath({ kind: "global" });
|
|
8559
|
+
for (const a of adapters)
|
|
8560
|
+
await a.removeWorktreeSyncHook(path7);
|
|
8561
|
+
setPersistedString(SYNC_SETTING_KEY, "off");
|
|
8562
|
+
process.stdout.write(`kobe hook setup: external worktree sync disabled (removed from ${path7})
|
|
8563
|
+
`);
|
|
8564
|
+
return;
|
|
8565
|
+
}
|
|
8566
|
+
const scope = repoPath ? { kind: "repo", path: repoPath } : { kind: "global" };
|
|
8567
|
+
const path6 = syncSettingsPath(scope);
|
|
8568
|
+
for (const a of adapters)
|
|
8569
|
+
await a.installWorktreeSyncHook(path6);
|
|
8570
|
+
setPersistedString(SYNC_SETTING_KEY, scope.kind === "repo" ? `repo:${resolve6(scope.path)}` : "global");
|
|
8571
|
+
process.stdout.write([
|
|
8572
|
+
`kobe hook setup: external worktree sync enabled (${scope.kind}) \u2014 wrote ${path6}`,
|
|
8573
|
+
"New `claude --worktree` worktrees will appear as kobe tasks.",
|
|
8574
|
+
""
|
|
8575
|
+
].join(`
|
|
8576
|
+
`));
|
|
8577
|
+
}
|
|
8578
|
+
var SYNC_SETTING_KEY = "externalWorktreeSync";
|
|
8579
|
+
var init_hook_cmd = __esm(() => {
|
|
8580
|
+
init_daemon_process();
|
|
8581
|
+
init_hook_adapter2();
|
|
8582
|
+
init_hook_events();
|
|
8583
|
+
init_repos();
|
|
8584
|
+
init_vendor();
|
|
8585
|
+
});
|
|
8586
|
+
|
|
7554
8587
|
// ../../node_modules/.bun/entities@7.0.1/node_modules/entities/dist/esm/decode-codepoint.js
|
|
7555
8588
|
function replaceCodePoint(codePoint) {
|
|
7556
8589
|
var _a2;
|
|
@@ -8836,6 +9869,8 @@ class RemoteOrchestrator {
|
|
|
8836
9869
|
setActiveTaskSig;
|
|
8837
9870
|
updateAcc;
|
|
8838
9871
|
setUpdateSig;
|
|
9872
|
+
engineStateAcc;
|
|
9873
|
+
setEngineStateSig;
|
|
8839
9874
|
connectionStateAcc;
|
|
8840
9875
|
setConnectionState;
|
|
8841
9876
|
ensureReachable;
|
|
@@ -8846,6 +9881,7 @@ class RemoteOrchestrator {
|
|
|
8846
9881
|
const [tasks, setTasks] = createSignal([]);
|
|
8847
9882
|
const [activeTask, setActiveTask] = createSignal(null);
|
|
8848
9883
|
const [update, setUpdate] = createSignal(null);
|
|
9884
|
+
const [engineState, setEngineState] = createSignal(new Map);
|
|
8849
9885
|
const [connectionState, setConnectionState] = createSignal("online");
|
|
8850
9886
|
this.tasksAcc = tasks;
|
|
8851
9887
|
this.setTasks = (next) => setTasks(() => next);
|
|
@@ -8853,6 +9889,8 @@ class RemoteOrchestrator {
|
|
|
8853
9889
|
this.setActiveTaskSig = (next) => setActiveTask(() => next);
|
|
8854
9890
|
this.updateAcc = update;
|
|
8855
9891
|
this.setUpdateSig = (next) => setUpdate(() => next);
|
|
9892
|
+
this.engineStateAcc = engineState;
|
|
9893
|
+
this.setEngineStateSig = (next) => setEngineState(() => next);
|
|
8856
9894
|
this.connectionStateAcc = connectionState;
|
|
8857
9895
|
this.setConnectionState = (next) => setConnectionState(() => next);
|
|
8858
9896
|
this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
|
|
@@ -8931,6 +9969,9 @@ class RemoteOrchestrator {
|
|
|
8931
9969
|
updateSignal() {
|
|
8932
9970
|
return this.updateAcc;
|
|
8933
9971
|
}
|
|
9972
|
+
engineStateSignal() {
|
|
9973
|
+
return this.engineStateAcc;
|
|
9974
|
+
}
|
|
8934
9975
|
listTasks() {
|
|
8935
9976
|
return this.tasksAcc();
|
|
8936
9977
|
}
|
|
@@ -9021,6 +10062,18 @@ class RemoteOrchestrator {
|
|
|
9021
10062
|
this.setUpdateSig(info ?? null);
|
|
9022
10063
|
return;
|
|
9023
10064
|
}
|
|
10065
|
+
if (name === "engine-state") {
|
|
10066
|
+
const p = payload;
|
|
10067
|
+
if (typeof p?.taskId !== "string" || typeof p.state !== "string")
|
|
10068
|
+
return;
|
|
10069
|
+
const next = new Map(this.engineStateAcc());
|
|
10070
|
+
if (p.state === "idle")
|
|
10071
|
+
next.delete(p.taskId);
|
|
10072
|
+
else
|
|
10073
|
+
next.set(p.taskId, { state: p.state, detail: p.detail, at: typeof p.at === "number" ? p.at : 0 });
|
|
10074
|
+
this.setEngineStateSig(next);
|
|
10075
|
+
return;
|
|
10076
|
+
}
|
|
9024
10077
|
}
|
|
9025
10078
|
}
|
|
9026
10079
|
function deserializeTask(s) {
|
|
@@ -10706,7 +11759,7 @@ function addTheme2(name, theme) {
|
|
|
10706
11759
|
}
|
|
10707
11760
|
function resolveTheme(theme, mode = "dark") {
|
|
10708
11761
|
const defs = theme.defs ?? {};
|
|
10709
|
-
function
|
|
11762
|
+
function resolve7(c, chain = []) {
|
|
10710
11763
|
if (typeof c === "string") {
|
|
10711
11764
|
if (c === "transparent" || c === "none")
|
|
10712
11765
|
return RGBA.fromInts(0, 0, 0, 0);
|
|
@@ -10718,13 +11771,13 @@ function resolveTheme(theme, mode = "dark") {
|
|
|
10718
11771
|
const next = defs[c] ?? theme.theme[c];
|
|
10719
11772
|
if (next === undefined)
|
|
10720
11773
|
return RGBA.fromInts(0, 0, 0);
|
|
10721
|
-
return
|
|
11774
|
+
return resolve7(next, [...chain, c]);
|
|
10722
11775
|
}
|
|
10723
|
-
return
|
|
11776
|
+
return resolve7(c[mode], chain);
|
|
10724
11777
|
}
|
|
10725
11778
|
const out = {};
|
|
10726
11779
|
for (const [k, v] of Object.entries(theme.theme)) {
|
|
10727
|
-
out[k] =
|
|
11780
|
+
out[k] = resolve7(v);
|
|
10728
11781
|
}
|
|
10729
11782
|
const text = out.text ?? RGBA.fromHex("#ffffff");
|
|
10730
11783
|
const background = out.background ?? RGBA.fromHex("#000000");
|
|
@@ -11503,7 +12556,7 @@ function findAvailableFolderName(parentDir, base) {
|
|
|
11503
12556
|
return trimmed;
|
|
11504
12557
|
}
|
|
11505
12558
|
function cloneRepo(url, target, onProgress) {
|
|
11506
|
-
return new Promise((
|
|
12559
|
+
return new Promise((resolve7) => {
|
|
11507
12560
|
let stderrBuf = "";
|
|
11508
12561
|
try {
|
|
11509
12562
|
const child = spawn2("git", ["clone", "--progress", url, target], {
|
|
@@ -11520,18 +12573,18 @@ function cloneRepo(url, target, onProgress) {
|
|
|
11520
12573
|
}
|
|
11521
12574
|
});
|
|
11522
12575
|
child.on("error", (err) => {
|
|
11523
|
-
|
|
12576
|
+
resolve7({ ok: false, error: err.message });
|
|
11524
12577
|
});
|
|
11525
12578
|
child.on("close", (code) => {
|
|
11526
12579
|
if (code === 0) {
|
|
11527
|
-
|
|
12580
|
+
resolve7({ ok: true, path: target });
|
|
11528
12581
|
return;
|
|
11529
12582
|
}
|
|
11530
12583
|
const tail = stderrBuf.split(/[\r\n]+/).filter((s) => s.trim().length > 0).pop() ?? `git clone exited with ${code}`;
|
|
11531
|
-
|
|
12584
|
+
resolve7({ ok: false, error: tail });
|
|
11532
12585
|
});
|
|
11533
12586
|
} catch (err) {
|
|
11534
|
-
|
|
12587
|
+
resolve7({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
11535
12588
|
}
|
|
11536
12589
|
});
|
|
11537
12590
|
}
|
|
@@ -12594,7 +13647,7 @@ var init_dialog2 = __esm(() => {
|
|
|
12594
13647
|
|
|
12595
13648
|
// src/tui/component/new-task-dialog/index.tsx
|
|
12596
13649
|
function show(dialog, defaultRepo, savedRepos, options) {
|
|
12597
|
-
return new Promise((
|
|
13650
|
+
return new Promise((resolve7) => {
|
|
12598
13651
|
dialog.replace(() => createComponent2(NewTaskDialogView, {
|
|
12599
13652
|
defaultRepo,
|
|
12600
13653
|
savedRepos,
|
|
@@ -12607,9 +13660,9 @@ function show(dialog, defaultRepo, savedRepos, options) {
|
|
|
12607
13660
|
get discoverAdoptable() {
|
|
12608
13661
|
return options?.discoverAdoptable;
|
|
12609
13662
|
},
|
|
12610
|
-
onSubmit: (v) =>
|
|
12611
|
-
onCancel: () =>
|
|
12612
|
-
}), () =>
|
|
13663
|
+
onSubmit: (v) => resolve7(v),
|
|
13664
|
+
onCancel: () => resolve7(undefined)
|
|
13665
|
+
}), () => resolve7(undefined));
|
|
12613
13666
|
dialog.setSize("medium");
|
|
12614
13667
|
});
|
|
12615
13668
|
}
|
|
@@ -12701,15 +13754,15 @@ var init_dialog3 = __esm(() => {
|
|
|
12701
13754
|
|
|
12702
13755
|
// src/tui/component/rename-task-dialog/index.tsx
|
|
12703
13756
|
function show2(dialog, currentTitle, opts = {}) {
|
|
12704
|
-
return new Promise((
|
|
13757
|
+
return new Promise((resolve7) => {
|
|
12705
13758
|
dialog.replace(() => createComponent2(RenameTaskDialogView, {
|
|
12706
13759
|
currentTitle,
|
|
12707
13760
|
get dialogTitle() {
|
|
12708
13761
|
return opts.dialogTitle;
|
|
12709
13762
|
},
|
|
12710
|
-
onSubmit: (v) =>
|
|
12711
|
-
onCancel: () =>
|
|
12712
|
-
}), () =>
|
|
13763
|
+
onSubmit: (v) => resolve7(v),
|
|
13764
|
+
onCancel: () => resolve7(undefined)
|
|
13765
|
+
}), () => resolve7(undefined));
|
|
12713
13766
|
});
|
|
12714
13767
|
}
|
|
12715
13768
|
var RenameTaskDialog;
|
|
@@ -12723,8 +13776,8 @@ var init_rename_task_dialog = __esm(() => {
|
|
|
12723
13776
|
|
|
12724
13777
|
// src/engine/claude-code-local/binary.ts
|
|
12725
13778
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
12726
|
-
import { existsSync as
|
|
12727
|
-
import { homedir as
|
|
13779
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
13780
|
+
import { homedir as homedir12 } from "os";
|
|
12728
13781
|
import path7 from "path";
|
|
12729
13782
|
async function findClaudeBinary(deps = defaultDeps4) {
|
|
12730
13783
|
const checked = [];
|
|
@@ -12797,7 +13850,7 @@ var init_binary = __esm(() => {
|
|
|
12797
13850
|
return process.env[name];
|
|
12798
13851
|
},
|
|
12799
13852
|
home() {
|
|
12800
|
-
return
|
|
13853
|
+
return homedir12();
|
|
12801
13854
|
},
|
|
12802
13855
|
which(name) {
|
|
12803
13856
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -12810,7 +13863,7 @@ var init_binary = __esm(() => {
|
|
|
12810
13863
|
return;
|
|
12811
13864
|
if (first.startsWith("claude:") && first.includes("aliased to")) {
|
|
12812
13865
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
12813
|
-
return aliasTarget &&
|
|
13866
|
+
return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
|
|
12814
13867
|
}
|
|
12815
13868
|
return first;
|
|
12816
13869
|
},
|
|
@@ -12827,8 +13880,8 @@ var init_binary = __esm(() => {
|
|
|
12827
13880
|
|
|
12828
13881
|
// src/engine/codex-local/binary.ts
|
|
12829
13882
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
12830
|
-
import { existsSync as
|
|
12831
|
-
import { homedir as
|
|
13883
|
+
import { existsSync as existsSync9, statSync as statSync4 } from "fs";
|
|
13884
|
+
import { homedir as homedir13 } from "os";
|
|
12832
13885
|
import path8 from "path";
|
|
12833
13886
|
async function findCodexBinary(deps = defaultDeps5) {
|
|
12834
13887
|
const checked = [];
|
|
@@ -12885,7 +13938,7 @@ var init_binary2 = __esm(() => {
|
|
|
12885
13938
|
return process.env[name];
|
|
12886
13939
|
},
|
|
12887
13940
|
home() {
|
|
12888
|
-
return
|
|
13941
|
+
return homedir13();
|
|
12889
13942
|
},
|
|
12890
13943
|
which(name) {
|
|
12891
13944
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -12898,7 +13951,7 @@ var init_binary2 = __esm(() => {
|
|
|
12898
13951
|
return;
|
|
12899
13952
|
if (first.startsWith("codex:") && first.includes("aliased to")) {
|
|
12900
13953
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
12901
|
-
return aliasTarget &&
|
|
13954
|
+
return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
|
|
12902
13955
|
}
|
|
12903
13956
|
return first;
|
|
12904
13957
|
},
|
|
@@ -12915,8 +13968,8 @@ var init_binary2 = __esm(() => {
|
|
|
12915
13968
|
|
|
12916
13969
|
// src/engine/copilot-local/binary.ts
|
|
12917
13970
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
12918
|
-
import { existsSync as
|
|
12919
|
-
import { homedir as
|
|
13971
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "fs";
|
|
13972
|
+
import { homedir as homedir14 } from "os";
|
|
12920
13973
|
import path9 from "path";
|
|
12921
13974
|
async function findCopilotBinary(deps = defaultDeps6) {
|
|
12922
13975
|
const checked = [];
|
|
@@ -12997,7 +14050,7 @@ var init_binary3 = __esm(() => {
|
|
|
12997
14050
|
return process.env[name];
|
|
12998
14051
|
},
|
|
12999
14052
|
home() {
|
|
13000
|
-
return
|
|
14053
|
+
return homedir14();
|
|
13001
14054
|
},
|
|
13002
14055
|
which(name) {
|
|
13003
14056
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -13010,7 +14063,7 @@ var init_binary3 = __esm(() => {
|
|
|
13010
14063
|
return;
|
|
13011
14064
|
if (first.startsWith("copilot:") && first.includes("aliased to")) {
|
|
13012
14065
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
13013
|
-
return aliasTarget &&
|
|
14066
|
+
return aliasTarget && existsSync10(aliasTarget) ? aliasTarget : undefined;
|
|
13014
14067
|
}
|
|
13015
14068
|
return first;
|
|
13016
14069
|
},
|
|
@@ -13021,8 +14074,8 @@ var init_binary3 = __esm(() => {
|
|
|
13021
14074
|
});
|
|
13022
14075
|
|
|
13023
14076
|
// src/engine/account-detect.ts
|
|
13024
|
-
import { readFileSync as
|
|
13025
|
-
import { homedir as
|
|
14077
|
+
import { readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
14078
|
+
import { homedir as homedir15 } from "os";
|
|
13026
14079
|
import path10 from "path";
|
|
13027
14080
|
function claudeGlobalConfigPath(env, home) {
|
|
13028
14081
|
const override = env("CLAUDE_CONFIG_DIR")?.trim();
|
|
@@ -13234,13 +14287,13 @@ var init_account_detect = __esm(() => {
|
|
|
13234
14287
|
return null;
|
|
13235
14288
|
throw err;
|
|
13236
14289
|
}
|
|
13237
|
-
return
|
|
14290
|
+
return readFileSync8(p, "utf8");
|
|
13238
14291
|
},
|
|
13239
14292
|
env(name) {
|
|
13240
14293
|
return process.env[name];
|
|
13241
14294
|
},
|
|
13242
14295
|
home() {
|
|
13243
|
-
return
|
|
14296
|
+
return homedir15();
|
|
13244
14297
|
},
|
|
13245
14298
|
findClaudeBinary() {
|
|
13246
14299
|
return findClaudeBinary();
|
|
@@ -13379,18 +14432,18 @@ var init_dialog_confirm = __esm(() => {
|
|
|
13379
14432
|
init_keymap();
|
|
13380
14433
|
init_dialog();
|
|
13381
14434
|
DialogConfirm.show = (dialog, title, message, label, confirmLabel, options) => {
|
|
13382
|
-
return new Promise((
|
|
14435
|
+
return new Promise((resolve7) => {
|
|
13383
14436
|
dialog.replace(() => createComponent2(DialogConfirm, {
|
|
13384
14437
|
title,
|
|
13385
14438
|
message,
|
|
13386
|
-
onConfirm: () =>
|
|
13387
|
-
onCancel: () =>
|
|
14439
|
+
onConfirm: () => resolve7(true),
|
|
14440
|
+
onCancel: () => resolve7(false),
|
|
13388
14441
|
label,
|
|
13389
14442
|
confirmLabel,
|
|
13390
14443
|
get initialActive() {
|
|
13391
14444
|
return options?.initialActive;
|
|
13392
14445
|
}
|
|
13393
|
-
}), () =>
|
|
14446
|
+
}), () => resolve7(undefined));
|
|
13394
14447
|
dialog.setSize("small");
|
|
13395
14448
|
});
|
|
13396
14449
|
};
|
|
@@ -13398,7 +14451,7 @@ var init_dialog_confirm = __esm(() => {
|
|
|
13398
14451
|
|
|
13399
14452
|
// src/tui/component/settings-dialog/actions.ts
|
|
13400
14453
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
13401
|
-
import { join as
|
|
14454
|
+
import { join as join12 } from "path";
|
|
13402
14455
|
function hasRestartableDaemon(orchestrator) {
|
|
13403
14456
|
return orchestrator instanceof RemoteOrchestrator;
|
|
13404
14457
|
}
|
|
@@ -13415,7 +14468,7 @@ async function confirmResetState(dialog, kv, renderer) {
|
|
|
13415
14468
|
return;
|
|
13416
14469
|
kv.clear();
|
|
13417
14470
|
try {
|
|
13418
|
-
unlinkSync2(
|
|
14471
|
+
unlinkSync2(join12(homeDir(), ".kobe", "tasks.json"));
|
|
13419
14472
|
} catch (err) {
|
|
13420
14473
|
if (err.code !== "ENOENT") {
|
|
13421
14474
|
console.error("kobe: failed to delete tasks.json during reset:", err);
|
|
@@ -14706,17 +15759,17 @@ var init_settings_dialog = __esm(() => {
|
|
|
14706
15759
|
init_sections();
|
|
14707
15760
|
SettingsDialog.show = (dialog, kv, orchestrator) => {
|
|
14708
15761
|
let visualPrefsChanged = false;
|
|
14709
|
-
return new Promise((
|
|
15762
|
+
return new Promise((resolve7) => {
|
|
14710
15763
|
dialog.replace(() => createComponent2(SettingsDialog, {
|
|
14711
15764
|
kv,
|
|
14712
15765
|
orchestrator,
|
|
14713
15766
|
onVisualPrefsChange: () => {
|
|
14714
15767
|
visualPrefsChanged = true;
|
|
14715
15768
|
},
|
|
14716
|
-
onClose: () =>
|
|
15769
|
+
onClose: () => resolve7({
|
|
14717
15770
|
visualPrefsChanged
|
|
14718
15771
|
})
|
|
14719
|
-
}), () =>
|
|
15772
|
+
}), () => resolve7({
|
|
14720
15773
|
visualPrefsChanged
|
|
14721
15774
|
}));
|
|
14722
15775
|
});
|
|
@@ -14785,12 +15838,12 @@ var init_focus = __esm(() => {
|
|
|
14785
15838
|
});
|
|
14786
15839
|
|
|
14787
15840
|
// src/tui/context/kv.tsx
|
|
14788
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
14789
|
-
import { dirname as
|
|
15841
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
15842
|
+
import { dirname as dirname8 } from "path";
|
|
14790
15843
|
function loadInitial() {
|
|
14791
15844
|
const statePath2 = kvStatePath();
|
|
14792
15845
|
try {
|
|
14793
|
-
const text =
|
|
15846
|
+
const text = readFileSync9(statePath2, "utf8");
|
|
14794
15847
|
const parsed = JSON.parse(text);
|
|
14795
15848
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14796
15849
|
return parsed;
|
|
@@ -14814,7 +15867,7 @@ var init_kv = __esm(() => {
|
|
|
14814
15867
|
function writeNow(label) {
|
|
14815
15868
|
const statePath2 = kvStatePath();
|
|
14816
15869
|
try {
|
|
14817
|
-
mkdirSync4(
|
|
15870
|
+
mkdirSync4(dirname8(statePath2), {
|
|
14818
15871
|
recursive: true
|
|
14819
15872
|
});
|
|
14820
15873
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14869,7 +15922,7 @@ var init_kv = __esm(() => {
|
|
|
14869
15922
|
}
|
|
14870
15923
|
const statePath2 = kvStatePath();
|
|
14871
15924
|
try {
|
|
14872
|
-
mkdirSync4(
|
|
15925
|
+
mkdirSync4(dirname8(statePath2), {
|
|
14873
15926
|
recursive: true
|
|
14874
15927
|
});
|
|
14875
15928
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14886,10 +15939,10 @@ var init_kv = __esm(() => {
|
|
|
14886
15939
|
});
|
|
14887
15940
|
|
|
14888
15941
|
// src/tui/lib/persisted-ui-prefs.ts
|
|
14889
|
-
import { readFileSync as
|
|
15942
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
14890
15943
|
function readPersistedUiPrefs(fallbackTheme) {
|
|
14891
15944
|
try {
|
|
14892
|
-
const parsed = JSON.parse(
|
|
15945
|
+
const parsed = JSON.parse(readFileSync10(kvStatePath(), "utf8"));
|
|
14893
15946
|
const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
|
|
14894
15947
|
const transparent = parsed.transparentBackground === true;
|
|
14895
15948
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
@@ -14905,8 +15958,8 @@ var init_persisted_ui_prefs = __esm(() => {
|
|
|
14905
15958
|
|
|
14906
15959
|
// src/tui/lib/worktree-opener.ts
|
|
14907
15960
|
import { spawn as spawn3 } from "child_process";
|
|
14908
|
-
import { existsSync as
|
|
14909
|
-
import { basename as basename4, delimiter, isAbsolute, join as
|
|
15961
|
+
import { existsSync as existsSync11 } from "fs";
|
|
15962
|
+
import { basename as basename4, delimiter, isAbsolute, join as join13 } from "path";
|
|
14910
15963
|
function executableOnPath(command, env, exists) {
|
|
14911
15964
|
if (isAbsolute(command))
|
|
14912
15965
|
return exists(command);
|
|
@@ -14914,7 +15967,7 @@ function executableOnPath(command, env, exists) {
|
|
|
14914
15967
|
for (const dir of pathEnv.split(delimiter)) {
|
|
14915
15968
|
if (!dir)
|
|
14916
15969
|
continue;
|
|
14917
|
-
if (exists(
|
|
15970
|
+
if (exists(join13(dir, command)))
|
|
14918
15971
|
return true;
|
|
14919
15972
|
}
|
|
14920
15973
|
return false;
|
|
@@ -14934,7 +15987,7 @@ function labelForOverride(command) {
|
|
|
14934
15987
|
function detectWorktreeOpener(deps = {}) {
|
|
14935
15988
|
const env = deps.env ?? process.env;
|
|
14936
15989
|
const platform = deps.platform ?? process.platform;
|
|
14937
|
-
const exists = deps.exists ??
|
|
15990
|
+
const exists = deps.exists ?? existsSync11;
|
|
14938
15991
|
const override = env.KOBE_OPEN_EDITOR?.trim();
|
|
14939
15992
|
if (override) {
|
|
14940
15993
|
return { id: "env", label: labelForOverride(override), command: override, args: [] };
|
|
@@ -16269,7 +17322,34 @@ function Sidebar(props) {
|
|
|
16269
17322
|
return readWorktreeChanges(task.worktreePath);
|
|
16270
17323
|
});
|
|
16271
17324
|
const titleText = isMain ? repoBasename(task.repo) : task.title;
|
|
16272
|
-
const
|
|
17325
|
+
const activity = () => props.engineState?.().get(task.id)?.state;
|
|
17326
|
+
const loading = () => activity() === "running" || isLive() || !isMain && task.status === "in_progress";
|
|
17327
|
+
const activityChip = () => {
|
|
17328
|
+
switch (activity()) {
|
|
17329
|
+
case "rate_limited":
|
|
17330
|
+
return {
|
|
17331
|
+
text: "limited",
|
|
17332
|
+
tone: "warning"
|
|
17333
|
+
};
|
|
17334
|
+
case "permission_needed":
|
|
17335
|
+
return {
|
|
17336
|
+
text: "approve?",
|
|
17337
|
+
tone: "warning"
|
|
17338
|
+
};
|
|
17339
|
+
case "error":
|
|
17340
|
+
return {
|
|
17341
|
+
text: "error",
|
|
17342
|
+
tone: "error"
|
|
17343
|
+
};
|
|
17344
|
+
case "turn_complete":
|
|
17345
|
+
return {
|
|
17346
|
+
text: "done",
|
|
17347
|
+
tone: "primary"
|
|
17348
|
+
};
|
|
17349
|
+
default:
|
|
17350
|
+
return null;
|
|
17351
|
+
}
|
|
17352
|
+
};
|
|
16273
17353
|
const subtitleText = createMemo(() => {
|
|
16274
17354
|
if (task.branch.length > 0)
|
|
16275
17355
|
return truncateBranchLabel(task.branch, subtitleBudget());
|
|
@@ -16432,6 +17512,18 @@ function Sidebar(props) {
|
|
|
16432
17512
|
return _el$43;
|
|
16433
17513
|
}
|
|
16434
17514
|
}), null);
|
|
17515
|
+
insert(_el$40, createComponent2(Show, {
|
|
17516
|
+
get when() {
|
|
17517
|
+
return memo2(() => !!!loading())() && activityChip();
|
|
17518
|
+
},
|
|
17519
|
+
children: (chip) => (() => {
|
|
17520
|
+
var _el$55 = createElement("text");
|
|
17521
|
+
setProp(_el$55, "wrapMode", "none");
|
|
17522
|
+
insert(_el$55, () => chip().text);
|
|
17523
|
+
effect((_$p) => setProp(_el$55, "fg", chip().tone === "error" ? theme.error : chip().tone === "warning" ? theme.warning : theme.primary, _$p));
|
|
17524
|
+
return _el$55;
|
|
17525
|
+
})()
|
|
17526
|
+
}), null);
|
|
16435
17527
|
effect((_p$) => {
|
|
16436
17528
|
var _v$22 = barColor(), _v$23 = badgeColor(), _v$24 = TextAttributes8.BOLD, _v$25 = theme.text, _v$26 = isSelected() || isCursor() ? TextAttributes8.BOLD : undefined;
|
|
16437
17529
|
_v$22 !== _p$.e && (_p$.e = setProp(_el$39, "fg", _v$22, _p$.e));
|
|
@@ -16577,43 +17669,43 @@ function Sidebar(props) {
|
|
|
16577
17669
|
const left = createMemo(() => Math.max(0, Math.min(h().x + 2, dims().width - boxW() - 1)));
|
|
16578
17670
|
const top = createMemo(() => Math.max(0, Math.min(h().y + 1, dims().height - boxH() - 1)));
|
|
16579
17671
|
return (() => {
|
|
16580
|
-
var _el$
|
|
16581
|
-
setProp(_el$
|
|
16582
|
-
setProp(_el$
|
|
16583
|
-
setProp(_el$
|
|
16584
|
-
setProp(_el$
|
|
16585
|
-
setProp(_el$
|
|
16586
|
-
setProp(_el$
|
|
16587
|
-
insert(_el$
|
|
17672
|
+
var _el$56 = createElement("box");
|
|
17673
|
+
setProp(_el$56, "position", "absolute");
|
|
17674
|
+
setProp(_el$56, "zIndex", 2600);
|
|
17675
|
+
setProp(_el$56, "flexDirection", "column");
|
|
17676
|
+
setProp(_el$56, "border", true);
|
|
17677
|
+
setProp(_el$56, "paddingLeft", 1);
|
|
17678
|
+
setProp(_el$56, "paddingRight", 1);
|
|
17679
|
+
insert(_el$56, createComponent2(For, {
|
|
16588
17680
|
get each() {
|
|
16589
17681
|
return lines();
|
|
16590
17682
|
},
|
|
16591
17683
|
children: (l) => (() => {
|
|
16592
|
-
var _el$
|
|
16593
|
-
setProp(_el$
|
|
16594
|
-
insert(_el$
|
|
17684
|
+
var _el$57 = createElement("text");
|
|
17685
|
+
setProp(_el$57, "wrapMode", "none");
|
|
17686
|
+
insert(_el$57, (() => {
|
|
16595
17687
|
var _c$4 = memo2(() => !!l.dim);
|
|
16596
17688
|
return () => _c$4() ? truncatePathTail(l.text, innerW()) : truncateTitle(l.text, innerW());
|
|
16597
17689
|
})());
|
|
16598
17690
|
effect((_p$) => {
|
|
16599
17691
|
var _v$35 = l.dim ? theme.textMuted : theme.text, _v$36 = l.bold ? TextAttributes8.BOLD : l.dim ? TextAttributes8.DIM : undefined;
|
|
16600
|
-
_v$35 !== _p$.e && (_p$.e = setProp(_el$
|
|
16601
|
-
_v$36 !== _p$.t && (_p$.t = setProp(_el$
|
|
17692
|
+
_v$35 !== _p$.e && (_p$.e = setProp(_el$57, "fg", _v$35, _p$.e));
|
|
17693
|
+
_v$36 !== _p$.t && (_p$.t = setProp(_el$57, "attributes", _v$36, _p$.t));
|
|
16602
17694
|
return _p$;
|
|
16603
17695
|
}, {
|
|
16604
17696
|
e: undefined,
|
|
16605
17697
|
t: undefined
|
|
16606
17698
|
});
|
|
16607
|
-
return _el$
|
|
17699
|
+
return _el$57;
|
|
16608
17700
|
})()
|
|
16609
17701
|
}));
|
|
16610
17702
|
effect((_p$) => {
|
|
16611
17703
|
var _v$30 = left(), _v$31 = top(), _v$32 = boxW(), _v$33 = theme.focusAccent, _v$34 = theme.backgroundElement;
|
|
16612
|
-
_v$30 !== _p$.e && (_p$.e = setProp(_el$
|
|
16613
|
-
_v$31 !== _p$.t && (_p$.t = setProp(_el$
|
|
16614
|
-
_v$32 !== _p$.a && (_p$.a = setProp(_el$
|
|
16615
|
-
_v$33 !== _p$.o && (_p$.o = setProp(_el$
|
|
16616
|
-
_v$34 !== _p$.i && (_p$.i = setProp(_el$
|
|
17704
|
+
_v$30 !== _p$.e && (_p$.e = setProp(_el$56, "left", _v$30, _p$.e));
|
|
17705
|
+
_v$31 !== _p$.t && (_p$.t = setProp(_el$56, "top", _v$31, _p$.t));
|
|
17706
|
+
_v$32 !== _p$.a && (_p$.a = setProp(_el$56, "width", _v$32, _p$.a));
|
|
17707
|
+
_v$33 !== _p$.o && (_p$.o = setProp(_el$56, "borderColor", _v$33, _p$.o));
|
|
17708
|
+
_v$34 !== _p$.i && (_p$.i = setProp(_el$56, "backgroundColor", _v$34, _p$.i));
|
|
16617
17709
|
return _p$;
|
|
16618
17710
|
}, {
|
|
16619
17711
|
e: undefined,
|
|
@@ -16622,7 +17714,7 @@ function Sidebar(props) {
|
|
|
16622
17714
|
o: undefined,
|
|
16623
17715
|
i: undefined
|
|
16624
17716
|
});
|
|
16625
|
-
return _el$
|
|
17717
|
+
return _el$56;
|
|
16626
17718
|
})();
|
|
16627
17719
|
}
|
|
16628
17720
|
}), null);
|
|
@@ -16708,7 +17800,7 @@ var exports_host = {};
|
|
|
16708
17800
|
__export(exports_host, {
|
|
16709
17801
|
startTasksPane: () => startTasksPane
|
|
16710
17802
|
});
|
|
16711
|
-
import { existsSync as
|
|
17803
|
+
import { existsSync as existsSync12 } from "fs";
|
|
16712
17804
|
import { TextAttributes as TextAttributes9 } from "@opentui/core";
|
|
16713
17805
|
function TasksShell(props) {
|
|
16714
17806
|
const themeCtx = useTheme();
|
|
@@ -16943,7 +18035,7 @@ function TasksShell(props) {
|
|
|
16943
18035
|
async function openSelectedWorktree(id) {
|
|
16944
18036
|
const task = props.tasks().find((t) => t.id === id);
|
|
16945
18037
|
let worktree = task?.worktreePath;
|
|
16946
|
-
if (!worktree || !
|
|
18038
|
+
if (!worktree || !existsSync12(worktree)) {
|
|
16947
18039
|
if (!props.orch) {
|
|
16948
18040
|
console.error("[kobe tasks] no daemon; cannot materialise worktree");
|
|
16949
18041
|
return;
|
|
@@ -16956,7 +18048,7 @@ function TasksShell(props) {
|
|
|
16956
18048
|
}
|
|
16957
18049
|
await props.reload();
|
|
16958
18050
|
}
|
|
16959
|
-
if (!worktree || !
|
|
18051
|
+
if (!worktree || !existsSync12(worktree))
|
|
16960
18052
|
return;
|
|
16961
18053
|
const opener = detectWorktreeOpener();
|
|
16962
18054
|
if (!opener) {
|
|
@@ -17007,7 +18099,7 @@ function TasksShell(props) {
|
|
|
17007
18099
|
const exists = await sessionExists(name);
|
|
17008
18100
|
if (exists) {
|
|
17009
18101
|
const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
|
|
17010
|
-
if (cwd2 &&
|
|
18102
|
+
if (cwd2 && existsSync12(cwd2)) {
|
|
17011
18103
|
await ensureSession({
|
|
17012
18104
|
name,
|
|
17013
18105
|
cwd: cwd2,
|
|
@@ -17021,7 +18113,7 @@ function TasksShell(props) {
|
|
|
17021
18113
|
return;
|
|
17022
18114
|
}
|
|
17023
18115
|
let cwd = task?.worktreePath;
|
|
17024
|
-
if (!cwd || !
|
|
18116
|
+
if (!cwd || !existsSync12(cwd)) {
|
|
17025
18117
|
if (!props.orch) {
|
|
17026
18118
|
console.error("[kobe tasks] no daemon; cannot materialise worktree");
|
|
17027
18119
|
return;
|
|
@@ -17034,7 +18126,7 @@ function TasksShell(props) {
|
|
|
17034
18126
|
}
|
|
17035
18127
|
await props.reload();
|
|
17036
18128
|
}
|
|
17037
|
-
if (!cwd || !
|
|
18129
|
+
if (!cwd || !existsSync12(cwd))
|
|
17038
18130
|
return;
|
|
17039
18131
|
const init2 = task?.repo ? resolveRepoInit(task.repo, cwd) : {};
|
|
17040
18132
|
const ready = await ensureSession({
|
|
@@ -17083,6 +18175,9 @@ function TasksShell(props) {
|
|
|
17083
18175
|
headerStatus,
|
|
17084
18176
|
onHeaderStatusClick: () => void openUpdate(),
|
|
17085
18177
|
width: () => dimensions().width,
|
|
18178
|
+
get engineState() {
|
|
18179
|
+
return memo2(() => !!props.orch)() ? props.orch.engineStateSignal() : undefined;
|
|
18180
|
+
},
|
|
17086
18181
|
onAddTask: () => void createTask(),
|
|
17087
18182
|
onRenameRequest: (id) => void renameTask(id),
|
|
17088
18183
|
onDeleteRequest: (id) => void deleteTask(id),
|
|
@@ -17302,6 +18397,7 @@ var init_host = __esm(() => {
|
|
|
17302
18397
|
init_solid();
|
|
17303
18398
|
init_solid();
|
|
17304
18399
|
init_solid();
|
|
18400
|
+
init_solid();
|
|
17305
18401
|
init_client2();
|
|
17306
18402
|
init_solid();
|
|
17307
18403
|
init_dev();
|
|
@@ -17652,13 +18748,13 @@ function releaseBodyLines(body) {
|
|
|
17652
18748
|
function waitForKeypress() {
|
|
17653
18749
|
if (!process.stdin.isTTY)
|
|
17654
18750
|
return Promise.resolve();
|
|
17655
|
-
return new Promise((
|
|
18751
|
+
return new Promise((resolve7) => {
|
|
17656
18752
|
const stdin = process.stdin;
|
|
17657
18753
|
const done = () => {
|
|
17658
18754
|
stdin.off("data", done);
|
|
17659
18755
|
stdin.setRawMode?.(false);
|
|
17660
18756
|
stdin.pause();
|
|
17661
|
-
|
|
18757
|
+
resolve7();
|
|
17662
18758
|
};
|
|
17663
18759
|
stdin.setRawMode?.(true);
|
|
17664
18760
|
stdin.resume();
|
|
@@ -17724,7 +18820,7 @@ function UpdatePage() {
|
|
|
17724
18820
|
}
|
|
17725
18821
|
async function runUpdater() {
|
|
17726
18822
|
setStatus("Leaving the TUI page and running the updater in this tmux window...");
|
|
17727
|
-
await new Promise((
|
|
18823
|
+
await new Promise((resolve7) => setTimeout(resolve7, 30));
|
|
17728
18824
|
renderer?.destroy();
|
|
17729
18825
|
process.stdout.write(`
|
|
17730
18826
|
kobe ${CURRENT_VERSION} -> latest
|
|
@@ -18031,7 +19127,7 @@ var init_host4 = __esm(() => {
|
|
|
18031
19127
|
});
|
|
18032
19128
|
|
|
18033
19129
|
// src/engine/turn-detector.ts
|
|
18034
|
-
import { readFile as
|
|
19130
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
18035
19131
|
|
|
18036
19132
|
class EngineTurnDetector {
|
|
18037
19133
|
supportsCompletionMarkers() {
|
|
@@ -18054,7 +19150,7 @@ function latestClaudeCompletionMarkerFromJsonl(raw, sourceId = "claude", fallbac
|
|
|
18054
19150
|
const record = parseJsonLine(line);
|
|
18055
19151
|
if (!record)
|
|
18056
19152
|
continue;
|
|
18057
|
-
const inner =
|
|
19153
|
+
const inner = isObject7(record.message) ? record.message : record;
|
|
18058
19154
|
if (inner.role !== "assistant")
|
|
18059
19155
|
continue;
|
|
18060
19156
|
if (!("content" in inner))
|
|
@@ -18096,7 +19192,7 @@ function parseJsonLine(line) {
|
|
|
18096
19192
|
return null;
|
|
18097
19193
|
try {
|
|
18098
19194
|
const parsed = JSON.parse(trimmed);
|
|
18099
|
-
return
|
|
19195
|
+
return isObject7(parsed) ? parsed : null;
|
|
18100
19196
|
} catch {
|
|
18101
19197
|
return null;
|
|
18102
19198
|
}
|
|
@@ -18105,7 +19201,7 @@ function timestampFromRecord(record, fallback) {
|
|
|
18105
19201
|
const ts = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : Number.NaN;
|
|
18106
19202
|
return Number.isFinite(ts) ? ts : fallback;
|
|
18107
19203
|
}
|
|
18108
|
-
function
|
|
19204
|
+
function isObject7(v) {
|
|
18109
19205
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18110
19206
|
}
|
|
18111
19207
|
var ClaudeTurnDetector, CodexTurnDetector, UnknownTurnDetector;
|
|
@@ -18118,7 +19214,7 @@ var init_turn_detector = __esm(() => {
|
|
|
18118
19214
|
const files = await listSessionFilesForWorktree(worktree);
|
|
18119
19215
|
let latest = null;
|
|
18120
19216
|
for (const file of files.slice(0, 4)) {
|
|
18121
|
-
const raw = await
|
|
19217
|
+
const raw = await readFile7(file.path, "utf8").catch(() => "");
|
|
18122
19218
|
const marker = latestClaudeCompletionMarkerFromJsonl(raw, file.path, file.mtimeMs);
|
|
18123
19219
|
if (marker && (!latest || marker.timestampMs > latest.timestampMs))
|
|
18124
19220
|
latest = marker;
|
|
@@ -18137,7 +19233,7 @@ var init_turn_detector = __esm(() => {
|
|
|
18137
19233
|
if (scanned >= 12)
|
|
18138
19234
|
break;
|
|
18139
19235
|
scanned++;
|
|
18140
|
-
const raw = await
|
|
19236
|
+
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
18141
19237
|
if (!raw || rolloutCwd(raw) !== worktree)
|
|
18142
19238
|
continue;
|
|
18143
19239
|
return latestCodexCompletionMarkerFromJsonl(raw, file);
|
|
@@ -18386,7 +19482,7 @@ var gitWrapper;
|
|
|
18386
19482
|
var init_git2 = __esm(() => {
|
|
18387
19483
|
gitWrapper = {
|
|
18388
19484
|
spawn(args, cwd) {
|
|
18389
|
-
return new Promise((
|
|
19485
|
+
return new Promise((resolve7, reject) => {
|
|
18390
19486
|
const child = nodeSpawn("git", [...args], {
|
|
18391
19487
|
cwd,
|
|
18392
19488
|
shell: false,
|
|
@@ -18405,7 +19501,7 @@ var init_git2 = __esm(() => {
|
|
|
18405
19501
|
});
|
|
18406
19502
|
child.on("error", reject);
|
|
18407
19503
|
child.on("close", (status, signal) => {
|
|
18408
|
-
|
|
19504
|
+
resolve7({ stdout, stderr, status, signal });
|
|
18409
19505
|
});
|
|
18410
19506
|
});
|
|
18411
19507
|
}
|
|
@@ -18459,14 +19555,14 @@ var init_keys2 = __esm(() => {
|
|
|
18459
19555
|
|
|
18460
19556
|
// src/tui/panes/filetree/open-external.ts
|
|
18461
19557
|
import { spawn as spawn5 } from "child_process";
|
|
18462
|
-
import { existsSync as
|
|
19558
|
+
import { existsSync as existsSync13 } from "fs";
|
|
18463
19559
|
import { platform } from "os";
|
|
18464
19560
|
function openExternally(absPath) {
|
|
18465
19561
|
if (!absPath)
|
|
18466
19562
|
return;
|
|
18467
19563
|
const plat = platform();
|
|
18468
19564
|
if (plat === "linux") {
|
|
18469
|
-
if (
|
|
19565
|
+
if (existsSync13("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
|
|
18470
19566
|
spawnDetached("wslview", [absPath], () => {
|
|
18471
19567
|
const child = spawn5("wslpath", ["-w", absPath], { stdio: ["ignore", "pipe", "ignore"] });
|
|
18472
19568
|
let out = "";
|
|
@@ -19857,7 +20953,7 @@ __export(exports_direct, {
|
|
|
19857
20953
|
startDirectTmux: () => startDirectTmux,
|
|
19858
20954
|
chooseInitialTask: () => chooseInitialTask
|
|
19859
20955
|
});
|
|
19860
|
-
import { resolve as
|
|
20956
|
+
import { resolve as resolve7 } from "path";
|
|
19861
20957
|
function chooseInitialTask(tasks, choice = {}) {
|
|
19862
20958
|
const byId = (id) => id ? tasks.find((t) => t.id === id) : undefined;
|
|
19863
20959
|
const active = byId(choice.activeTaskId);
|
|
@@ -19893,7 +20989,7 @@ async function ensureRepos(orchestrator) {
|
|
|
19893
20989
|
normalizeSavedRepos();
|
|
19894
20990
|
let repos = [...getSavedRepos()];
|
|
19895
20991
|
if (repos.length === 0) {
|
|
19896
|
-
const added = addSavedRepo(
|
|
20992
|
+
const added = addSavedRepo(resolve7(process.cwd()));
|
|
19897
20993
|
repos = [added.path];
|
|
19898
20994
|
}
|
|
19899
20995
|
for (const repo of repos) {
|
|
@@ -19903,7 +20999,7 @@ async function ensureRepos(orchestrator) {
|
|
|
19903
20999
|
console.error(`[kobe] ensureMainTask failed for ${repo}:`, err);
|
|
19904
21000
|
}
|
|
19905
21001
|
}
|
|
19906
|
-
return repos[0] ??
|
|
21002
|
+
return repos[0] ?? resolve7(process.cwd());
|
|
19907
21003
|
}
|
|
19908
21004
|
async function startDirectTmux() {
|
|
19909
21005
|
setClientLogContext("gui");
|
|
@@ -20500,9 +21596,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
|
|
|
20500
21596
|
var init_pulse = () => {};
|
|
20501
21597
|
|
|
20502
21598
|
// src/tui/lib/sound.ts
|
|
20503
|
-
import { existsSync as
|
|
21599
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
|
|
20504
21600
|
import { tmpdir as tmpdir2 } from "os";
|
|
20505
|
-
import { basename as basename6, isAbsolute as isAbsolute2, join as
|
|
21601
|
+
import { basename as basename6, isAbsolute as isAbsolute2, join as join14, resolve as resolve8 } from "path";
|
|
20506
21602
|
function args(player, file, volume) {
|
|
20507
21603
|
if (player === "ffplay")
|
|
20508
21604
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -20525,13 +21621,13 @@ function pickPlayer() {
|
|
|
20525
21621
|
return cachedPlayer;
|
|
20526
21622
|
const path12 = process.env.PATH ?? "";
|
|
20527
21623
|
const segments = path12.split(":").filter(Boolean);
|
|
20528
|
-
cachedPlayer = PLAYERS.find((p) => segments.some((dir) =>
|
|
21624
|
+
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join14(dir, p)))) ?? null;
|
|
20529
21625
|
return cachedPlayer;
|
|
20530
21626
|
}
|
|
20531
21627
|
async function ensureAsset() {
|
|
20532
21628
|
cachedPath ??= (async () => {
|
|
20533
21629
|
mkdirSync5(DIR, { recursive: true });
|
|
20534
|
-
const dest =
|
|
21630
|
+
const dest = join14(DIR, basename6(pulseAsset));
|
|
20535
21631
|
const out = Bun.file(dest);
|
|
20536
21632
|
if (await out.exists())
|
|
20537
21633
|
return dest;
|
|
@@ -20560,8 +21656,8 @@ function pulse(volume = 0.4) {
|
|
|
20560
21656
|
var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
20561
21657
|
var init_sound = __esm(() => {
|
|
20562
21658
|
init_pulse();
|
|
20563
|
-
pulseAsset = isAbsolute2(pulse_default) ? pulse_default :
|
|
20564
|
-
DIR =
|
|
21659
|
+
pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
|
|
21660
|
+
DIR = join14(tmpdir2(), "kobe-sfx");
|
|
20565
21661
|
PLAYERS = [
|
|
20566
21662
|
"ffplay",
|
|
20567
21663
|
"mpv",
|
|
@@ -21087,7 +22183,7 @@ var init_use_theme_persistence = __esm(() => {
|
|
|
21087
22183
|
});
|
|
21088
22184
|
|
|
21089
22185
|
// src/monitor/cost.ts
|
|
21090
|
-
import { readFile as
|
|
22186
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
21091
22187
|
async function summarizeTaskCost(opts) {
|
|
21092
22188
|
const files = await listSessionFilesForWorktree(opts.worktree);
|
|
21093
22189
|
const base = {
|
|
@@ -21109,7 +22205,7 @@ async function summarizeTaskCost(opts) {
|
|
|
21109
22205
|
for (const file of files) {
|
|
21110
22206
|
let raw;
|
|
21111
22207
|
try {
|
|
21112
|
-
raw = await
|
|
22208
|
+
raw = await readFile8(file.path, "utf8");
|
|
21113
22209
|
} catch {
|
|
21114
22210
|
continue;
|
|
21115
22211
|
}
|
|
@@ -21620,7 +22716,7 @@ var exports_app = {};
|
|
|
21620
22716
|
__export(exports_app, {
|
|
21621
22717
|
startApp: () => startApp
|
|
21622
22718
|
});
|
|
21623
|
-
import { homedir as
|
|
22719
|
+
import { homedir as homedir16 } from "os";
|
|
21624
22720
|
function Shell(props) {
|
|
21625
22721
|
const themeCtx = useTheme();
|
|
21626
22722
|
const {
|
|
@@ -22128,7 +23224,7 @@ async function startApp() {
|
|
|
22128
23224
|
} of loadUserThemes()) {
|
|
22129
23225
|
addTheme2(name, theme);
|
|
22130
23226
|
}
|
|
22131
|
-
const homeDir2 = process.env.KOBE_HOME_DIR ??
|
|
23227
|
+
const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir16();
|
|
22132
23228
|
let orchestrator;
|
|
22133
23229
|
if (process.env.KOBE_NO_DAEMON === "1") {
|
|
22134
23230
|
const store2 = new TaskIndexStore({
|
|
@@ -22241,7 +23337,7 @@ var init_tui = __esm(() => {
|
|
|
22241
23337
|
// src/cli/index.ts
|
|
22242
23338
|
init_path_glob();
|
|
22243
23339
|
init_vendor();
|
|
22244
|
-
import { resolve as
|
|
23340
|
+
import { resolve as resolve9 } from "path";
|
|
22245
23341
|
|
|
22246
23342
|
// src/cli/usage.ts
|
|
22247
23343
|
init_version();
|
|
@@ -22260,6 +23356,7 @@ function topLevelUsage() {
|
|
|
22260
23356
|
" api <verb> Scriptable RPC surface for agents (see `kobe api --help`)",
|
|
22261
23357
|
" daemon <verb> Manage the daemon (start|stop|status|restart)",
|
|
22262
23358
|
" theme <verb> Manage user themes (list|add|remove)",
|
|
23359
|
+
" skill <verb> Install the kobe agent skill (install|status|command)",
|
|
22263
23360
|
" update [target] Self-update kobe",
|
|
22264
23361
|
" doctor Diagnose daemon / tmux / state (read-only)",
|
|
22265
23362
|
" reset [--hard] Recover a wedged install",
|
|
@@ -22289,7 +23386,7 @@ Usage: kobe add [path]
|
|
|
22289
23386
|
`);
|
|
22290
23387
|
process.exit(2);
|
|
22291
23388
|
}
|
|
22292
|
-
const target =
|
|
23389
|
+
const target = resolve9(process.cwd(), arg && arg.length > 0 ? arg : ".");
|
|
22293
23390
|
const { addSavedRepo: addSavedRepo2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
22294
23391
|
const result = addSavedRepo2(target);
|
|
22295
23392
|
if (result.added) {
|
|
@@ -22388,7 +23485,7 @@ async function runAdoptSubcommand(args2) {
|
|
|
22388
23485
|
}
|
|
22389
23486
|
}
|
|
22390
23487
|
const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
22391
|
-
const repo = resolveRepoRoot2(
|
|
23488
|
+
const repo = resolveRepoRoot2(resolve9(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
|
|
22392
23489
|
const vendor = coerceVendorId(vendorArg);
|
|
22393
23490
|
const orch = await openLocalOrchestrator();
|
|
22394
23491
|
const worktrees = await orch.discoverAdoptableWorktrees(repo);
|
|
@@ -22521,6 +23618,16 @@ async function main() {
|
|
|
22521
23618
|
await runReloadSubcommand2(rest);
|
|
22522
23619
|
return;
|
|
22523
23620
|
}
|
|
23621
|
+
if (subcommand === "skill") {
|
|
23622
|
+
const { runSkillSubcommand: runSkillSubcommand2 } = await Promise.resolve().then(() => (init_skill_cmd(), exports_skill_cmd));
|
|
23623
|
+
await runSkillSubcommand2(rest);
|
|
23624
|
+
return;
|
|
23625
|
+
}
|
|
23626
|
+
if (subcommand === "hook") {
|
|
23627
|
+
const { runHookSubcommand: runHookSubcommand2 } = await Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd));
|
|
23628
|
+
await runHookSubcommand2(rest);
|
|
23629
|
+
return;
|
|
23630
|
+
}
|
|
22524
23631
|
if (subcommand === "new-chattab") {
|
|
22525
23632
|
const flags = parseOpsFlags(rest);
|
|
22526
23633
|
const session = flags.session;
|