@sma1lboy/kobe 0.7.1 → 0.7.3
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 +966 -182
- 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.3",
|
|
74
74
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
75
75
|
type: "module",
|
|
76
76
|
packageManager: "bun@1.3.13",
|
|
@@ -748,7 +748,8 @@ function coerceTask(value) {
|
|
|
748
748
|
if (!isTaskStatus(v.status))
|
|
749
749
|
return null;
|
|
750
750
|
const archived = typeof v.archived === "boolean" ? v.archived : false;
|
|
751
|
-
const
|
|
751
|
+
const kind = v.kind === "main" ? "main" : "task";
|
|
752
|
+
const healedStatus = kind === "main" ? v.status === "in_progress" || v.status === "done" ? "backlog" : v.status : v.status === "done" && !archived ? "in_progress" : v.status;
|
|
752
753
|
return {
|
|
753
754
|
id: toTaskId(v.id),
|
|
754
755
|
title: v.title,
|
|
@@ -758,7 +759,7 @@ function coerceTask(value) {
|
|
|
758
759
|
status: healedStatus,
|
|
759
760
|
archived,
|
|
760
761
|
pinned: typeof v.pinned === "boolean" ? v.pinned : false,
|
|
761
|
-
kind
|
|
762
|
+
kind,
|
|
762
763
|
vendor: isVendorId(v.vendor) ? v.vendor : DEFAULT_TASK_VENDOR,
|
|
763
764
|
prStatus: coercePRStatus(v.prStatus),
|
|
764
765
|
createdAt: v.createdAt,
|
|
@@ -3227,6 +3228,111 @@ var init_protocol = __esm(() => {
|
|
|
3227
3228
|
CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
|
|
3228
3229
|
});
|
|
3229
3230
|
|
|
3231
|
+
// src/daemon/paths.ts
|
|
3232
|
+
import { createHash as createHash2 } from "crypto";
|
|
3233
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
3234
|
+
import { join as join3 } from "path";
|
|
3235
|
+
function shortHomeTag(homeDir2) {
|
|
3236
|
+
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
3237
|
+
}
|
|
3238
|
+
function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
3239
|
+
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3240
|
+
return naturalPath;
|
|
3241
|
+
const tag = shortHomeTag(homeDir2);
|
|
3242
|
+
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
3243
|
+
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
3244
|
+
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3245
|
+
return fallback;
|
|
3246
|
+
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
3247
|
+
}
|
|
3248
|
+
function defaultDaemonSocketPath(homeDir2) {
|
|
3249
|
+
const override = process.env.KOBE_DAEMON_SOCKET_PATH;
|
|
3250
|
+
if (override && override.length > 0)
|
|
3251
|
+
return override;
|
|
3252
|
+
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
3253
|
+
if (explicit && explicit.length > 0) {
|
|
3254
|
+
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
3255
|
+
}
|
|
3256
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
3257
|
+
if (runtimeDir && runtimeDir.length > 0) {
|
|
3258
|
+
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
3259
|
+
}
|
|
3260
|
+
const home = homedir3();
|
|
3261
|
+
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
3262
|
+
}
|
|
3263
|
+
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3264
|
+
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
3265
|
+
if (override && override.length > 0)
|
|
3266
|
+
return override;
|
|
3267
|
+
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
3268
|
+
}
|
|
3269
|
+
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3270
|
+
return join3(homeDir2, ".kobe", "daemon.log");
|
|
3271
|
+
}
|
|
3272
|
+
function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3273
|
+
return join3(homeDir2, ".kobe", "client.log");
|
|
3274
|
+
}
|
|
3275
|
+
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
3276
|
+
var init_paths2 = () => {};
|
|
3277
|
+
|
|
3278
|
+
// src/client/client-log.ts
|
|
3279
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
3280
|
+
import { dirname as dirname3 } from "path";
|
|
3281
|
+
function setClientLogContext(ctx) {
|
|
3282
|
+
context = ctx;
|
|
3283
|
+
}
|
|
3284
|
+
function coerceError(err) {
|
|
3285
|
+
if (err instanceof Error)
|
|
3286
|
+
return { message: err.message, stack: err.stack };
|
|
3287
|
+
let text;
|
|
3288
|
+
try {
|
|
3289
|
+
text = typeof err === "string" ? err : JSON.stringify(err);
|
|
3290
|
+
} catch {
|
|
3291
|
+
text = String(err);
|
|
3292
|
+
}
|
|
3293
|
+
return { message: text };
|
|
3294
|
+
}
|
|
3295
|
+
function formatClientEntry(subsystem, message, now = new Date) {
|
|
3296
|
+
return `[${now.toISOString()}] client ${context} [${subsystem}] pid=${process.pid}: ${message}
|
|
3297
|
+
`;
|
|
3298
|
+
}
|
|
3299
|
+
function warnOnce() {
|
|
3300
|
+
if (warnedOnce)
|
|
3301
|
+
return;
|
|
3302
|
+
warnedOnce = true;
|
|
3303
|
+
try {
|
|
3304
|
+
process.stderr.write(`[kobe] client log write failed; continuing without it
|
|
3305
|
+
`);
|
|
3306
|
+
} catch {}
|
|
3307
|
+
}
|
|
3308
|
+
function append(line) {
|
|
3309
|
+
const path3 = defaultClientLogPath();
|
|
3310
|
+
writeChain = writeChain.then(async () => {
|
|
3311
|
+
try {
|
|
3312
|
+
await appendFile(path3, line);
|
|
3313
|
+
} catch (err) {
|
|
3314
|
+
if (err?.code === "ENOENT") {
|
|
3315
|
+
await mkdir2(dirname3(path3), { recursive: true });
|
|
3316
|
+
await appendFile(path3, line);
|
|
3317
|
+
} else {
|
|
3318
|
+
throw err;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}).catch(() => warnOnce());
|
|
3322
|
+
}
|
|
3323
|
+
function logClient(subsystem, message) {
|
|
3324
|
+
append(formatClientEntry(subsystem, message));
|
|
3325
|
+
}
|
|
3326
|
+
function logClientError(subsystem, err) {
|
|
3327
|
+
const e = coerceError(err);
|
|
3328
|
+
append(formatClientEntry(subsystem, e.stack ?? e.message));
|
|
3329
|
+
}
|
|
3330
|
+
var context = "client", warnedOnce = false, writeChain;
|
|
3331
|
+
var init_client_log = __esm(() => {
|
|
3332
|
+
init_paths2();
|
|
3333
|
+
writeChain = Promise.resolve();
|
|
3334
|
+
});
|
|
3335
|
+
|
|
3230
3336
|
// src/client/index.ts
|
|
3231
3337
|
import { connect } from "net";
|
|
3232
3338
|
|
|
@@ -3259,6 +3365,9 @@ class KobeDaemonClient {
|
|
|
3259
3365
|
p.then(cleanup, cleanup);
|
|
3260
3366
|
return p;
|
|
3261
3367
|
}
|
|
3368
|
+
get isDisposed() {
|
|
3369
|
+
return this.disposed;
|
|
3370
|
+
}
|
|
3262
3371
|
close() {
|
|
3263
3372
|
this.disposed = true;
|
|
3264
3373
|
this.socket?.end();
|
|
@@ -3372,7 +3481,13 @@ class KobeDaemonClient {
|
|
|
3372
3481
|
}
|
|
3373
3482
|
}
|
|
3374
3483
|
onLine(line) {
|
|
3375
|
-
|
|
3484
|
+
let frame;
|
|
3485
|
+
try {
|
|
3486
|
+
frame = JSON.parse(line);
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
logClientError("client-frame", err);
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3376
3491
|
if (frame.type === "event") {
|
|
3377
3492
|
this.emit(frame);
|
|
3378
3493
|
return;
|
|
@@ -3397,6 +3512,7 @@ class KobeDaemonClient {
|
|
|
3397
3512
|
}
|
|
3398
3513
|
var init_client = __esm(() => {
|
|
3399
3514
|
init_protocol();
|
|
3515
|
+
init_client_log();
|
|
3400
3516
|
});
|
|
3401
3517
|
|
|
3402
3518
|
// src/engine/claude-code-local/normalize.ts
|
|
@@ -3446,8 +3562,8 @@ function normalizeClaudeContent(content) {
|
|
|
3446
3562
|
}
|
|
3447
3563
|
|
|
3448
3564
|
// src/engine/claude-code-local/history.ts
|
|
3449
|
-
import { appendFile, mkdir as
|
|
3450
|
-
import { homedir as
|
|
3565
|
+
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
3566
|
+
import { homedir as homedir4 } from "os";
|
|
3451
3567
|
import path3 from "path";
|
|
3452
3568
|
function encodeCwd(cwd) {
|
|
3453
3569
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -3455,7 +3571,7 @@ function encodeCwd(cwd) {
|
|
|
3455
3571
|
async function listSessionFilesForWorktree(worktree) {
|
|
3456
3572
|
if (!worktree)
|
|
3457
3573
|
return [];
|
|
3458
|
-
const dir = path3.join(
|
|
3574
|
+
const dir = path3.join(homedir4(), ".claude", "projects", encodeCwd(worktree));
|
|
3459
3575
|
let entries;
|
|
3460
3576
|
try {
|
|
3461
3577
|
entries = await readdir(dir);
|
|
@@ -3561,7 +3677,7 @@ var defaultDeps;
|
|
|
3561
3677
|
var init_history = __esm(() => {
|
|
3562
3678
|
defaultDeps = {
|
|
3563
3679
|
projectsDir() {
|
|
3564
|
-
return path3.join(
|
|
3680
|
+
return path3.join(homedir4(), ".claude", "projects");
|
|
3565
3681
|
},
|
|
3566
3682
|
async readdir(p) {
|
|
3567
3683
|
try {
|
|
@@ -3663,7 +3779,7 @@ function validPositive(v) {
|
|
|
3663
3779
|
|
|
3664
3780
|
// src/engine/codex-local/history.ts
|
|
3665
3781
|
import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
3666
|
-
import { homedir as
|
|
3782
|
+
import { homedir as homedir5 } from "os";
|
|
3667
3783
|
import path4 from "path";
|
|
3668
3784
|
async function listRolloutFiles(deps = defaultDeps2) {
|
|
3669
3785
|
const root = deps.sessionsDir();
|
|
@@ -3994,7 +4110,7 @@ var init_history2 = __esm(() => {
|
|
|
3994
4110
|
init_synthetic();
|
|
3995
4111
|
defaultDeps2 = {
|
|
3996
4112
|
sessionsDir() {
|
|
3997
|
-
return path4.join(
|
|
4113
|
+
return path4.join(homedir5(), ".codex", "sessions");
|
|
3998
4114
|
},
|
|
3999
4115
|
async readdir(p) {
|
|
4000
4116
|
try {
|
|
@@ -4028,14 +4144,14 @@ function copilotUsageToSnapshot(value) {
|
|
|
4028
4144
|
output += numberOr2(metrics.usage.outputTokens, 0);
|
|
4029
4145
|
cached += numberOr2(metrics.usage.cacheReadTokens, 0);
|
|
4030
4146
|
}
|
|
4031
|
-
const
|
|
4032
|
-
if (input === 0 && output === 0 && cached === 0 &&
|
|
4147
|
+
const context2 = numberOr2(value.currentTokens, 0);
|
|
4148
|
+
if (input === 0 && output === 0 && cached === 0 && context2 === 0)
|
|
4033
4149
|
return;
|
|
4034
4150
|
return {
|
|
4035
4151
|
input_tokens: input,
|
|
4036
4152
|
output_tokens: output,
|
|
4037
4153
|
...cached > 0 ? { cache_read_input_tokens: cached } : {},
|
|
4038
|
-
...
|
|
4154
|
+
...context2 > 0 ? { context_tokens: context2 } : {}
|
|
4039
4155
|
};
|
|
4040
4156
|
}
|
|
4041
4157
|
function isObject4(v) {
|
|
@@ -4047,7 +4163,7 @@ function numberOr2(value, fallback) {
|
|
|
4047
4163
|
|
|
4048
4164
|
// src/engine/copilot-local/history.ts
|
|
4049
4165
|
import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
4050
|
-
import { homedir as
|
|
4166
|
+
import { homedir as homedir6 } from "os";
|
|
4051
4167
|
import path5 from "path";
|
|
4052
4168
|
async function listSessionDirs(deps = defaultDeps3) {
|
|
4053
4169
|
const root = path5.join(deps.copilotDir(), "session-state");
|
|
@@ -4216,7 +4332,7 @@ var init_history3 = __esm(() => {
|
|
|
4216
4332
|
const override = process.env.COPILOT_HOME?.trim();
|
|
4217
4333
|
if (override)
|
|
4218
4334
|
return override;
|
|
4219
|
-
return path5.join(
|
|
4335
|
+
return path5.join(homedir6(), ".copilot");
|
|
4220
4336
|
},
|
|
4221
4337
|
async readdir(p) {
|
|
4222
4338
|
try {
|
|
@@ -4409,7 +4525,7 @@ __export(exports_client, {
|
|
|
4409
4525
|
CLAUDE_ROLE_OPTION: () => CLAUDE_ROLE_OPTION,
|
|
4410
4526
|
CHAT_TAB_SESSION_ID_OPTION: () => CHAT_TAB_SESSION_ID_OPTION
|
|
4411
4527
|
});
|
|
4412
|
-
import { homedir as
|
|
4528
|
+
import { homedir as homedir7 } from "os";
|
|
4413
4529
|
function tmuxArgs(...args) {
|
|
4414
4530
|
return ["tmux", "-L", KOBE_TMUX_SOCKET, ...args];
|
|
4415
4531
|
}
|
|
@@ -4670,7 +4786,7 @@ var KOBE_TMUX_SOCKET, SAFE_SPAWN_CWD, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_RO
|
|
|
4670
4786
|
var init_client2 = __esm(() => {
|
|
4671
4787
|
init_invocation();
|
|
4672
4788
|
KOBE_TMUX_SOCKET = process.env.KOBE_TMUX_SOCKET?.trim() || "kobe";
|
|
4673
|
-
SAFE_SPAWN_CWD =
|
|
4789
|
+
SAFE_SPAWN_CWD = homedir7() || "/";
|
|
4674
4790
|
CLAUDE_ROLE_OPTION = PANE_ROLE_OPTION;
|
|
4675
4791
|
});
|
|
4676
4792
|
|
|
@@ -4768,6 +4884,13 @@ function formatDaemonError(subsystem, err, now = new Date) {
|
|
|
4768
4884
|
function logDaemonError(subsystem, err) {
|
|
4769
4885
|
process.stderr.write(formatDaemonError(subsystem, err));
|
|
4770
4886
|
}
|
|
4887
|
+
function formatDaemonInfo(subsystem, message, now = new Date) {
|
|
4888
|
+
return `[${now.toISOString()}] daemon [${subsystem}]: ${message}
|
|
4889
|
+
`;
|
|
4890
|
+
}
|
|
4891
|
+
function logDaemonInfo(subsystem, message) {
|
|
4892
|
+
process.stderr.write(formatDaemonInfo(subsystem, message));
|
|
4893
|
+
}
|
|
4771
4894
|
function installDaemonCrashHandlers(log = (l) => process.stderr.write(l)) {
|
|
4772
4895
|
if (onRejection || onException)
|
|
4773
4896
|
return;
|
|
@@ -4843,54 +4966,10 @@ class DaemonEventBus {
|
|
|
4843
4966
|
}
|
|
4844
4967
|
}
|
|
4845
4968
|
|
|
4846
|
-
// src/daemon/paths.ts
|
|
4847
|
-
import { createHash as createHash2 } from "crypto";
|
|
4848
|
-
import { homedir as homedir7, tmpdir } from "os";
|
|
4849
|
-
import { join as join3 } from "path";
|
|
4850
|
-
function shortHomeTag(homeDir2) {
|
|
4851
|
-
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
4852
|
-
}
|
|
4853
|
-
function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
4854
|
-
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4855
|
-
return naturalPath;
|
|
4856
|
-
const tag = shortHomeTag(homeDir2);
|
|
4857
|
-
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
4858
|
-
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
4859
|
-
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4860
|
-
return fallback;
|
|
4861
|
-
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
4862
|
-
}
|
|
4863
|
-
function defaultDaemonSocketPath(homeDir2) {
|
|
4864
|
-
const override = process.env.KOBE_DAEMON_SOCKET_PATH;
|
|
4865
|
-
if (override && override.length > 0)
|
|
4866
|
-
return override;
|
|
4867
|
-
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
4868
|
-
if (explicit && explicit.length > 0) {
|
|
4869
|
-
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
4870
|
-
}
|
|
4871
|
-
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
4872
|
-
if (runtimeDir && runtimeDir.length > 0) {
|
|
4873
|
-
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
4874
|
-
}
|
|
4875
|
-
const home = homedir7();
|
|
4876
|
-
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
4877
|
-
}
|
|
4878
|
-
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
|
|
4879
|
-
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
4880
|
-
if (override && override.length > 0)
|
|
4881
|
-
return override;
|
|
4882
|
-
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
4883
|
-
}
|
|
4884
|
-
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
|
|
4885
|
-
return join3(homeDir2, ".kobe", "daemon.log");
|
|
4886
|
-
}
|
|
4887
|
-
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
4888
|
-
var init_paths2 = () => {};
|
|
4889
|
-
|
|
4890
4969
|
// src/daemon/server.ts
|
|
4891
|
-
import { mkdir as
|
|
4970
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
4892
4971
|
import { createServer } from "net";
|
|
4893
|
-
import { dirname as
|
|
4972
|
+
import { dirname as dirname4 } from "path";
|
|
4894
4973
|
function resolveIdleGraceMs() {
|
|
4895
4974
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
4896
4975
|
if (raw === undefined)
|
|
@@ -4924,10 +5003,12 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4924
5003
|
if (stopping || guiCount() > 0)
|
|
4925
5004
|
return;
|
|
4926
5005
|
cancelIdleTimer();
|
|
5006
|
+
logDaemonInfo("idle", `last gui gone \u2014 arming ${idleGraceMs}ms idle-stop grace`);
|
|
4927
5007
|
idleTimer = setTimeout(() => {
|
|
4928
5008
|
idleTimer = null;
|
|
4929
5009
|
if (stopping || guiCount() > 0)
|
|
4930
5010
|
return;
|
|
5011
|
+
logDaemonInfo("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
4931
5012
|
stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err));
|
|
4932
5013
|
}, idleGraceMs);
|
|
4933
5014
|
idleTimer.unref?.();
|
|
@@ -4936,8 +5017,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4936
5017
|
bus.onPublish((event) => {
|
|
4937
5018
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
4938
5019
|
});
|
|
4939
|
-
await
|
|
4940
|
-
await
|
|
5020
|
+
await mkdir4(dirname4(socketPath), { recursive: true });
|
|
5021
|
+
await mkdir4(dirname4(pidPath), { recursive: true });
|
|
4941
5022
|
await unlink4(socketPath).catch(() => {});
|
|
4942
5023
|
const server = createServer((socket) => {
|
|
4943
5024
|
const client = {
|
|
@@ -4956,6 +5037,9 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4956
5037
|
socket.on("error", () => {});
|
|
4957
5038
|
socket.on("close", () => {
|
|
4958
5039
|
clients.delete(client);
|
|
5040
|
+
if (client.subscribed) {
|
|
5041
|
+
logDaemonInfo("conn", `client #${client.id} (${client.holdsLifetime ? "gui" : "pane"}) disconnected \u2014 ${clients.size} client(s), ${guiCount()} gui left`);
|
|
5042
|
+
}
|
|
4959
5043
|
if (client.holdsLifetime)
|
|
4960
5044
|
maybeArmIdleShutdown();
|
|
4961
5045
|
});
|
|
@@ -5148,6 +5232,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5148
5232
|
client.holdsLifetime = role === "gui";
|
|
5149
5233
|
if (client.holdsLifetime)
|
|
5150
5234
|
cancelIdleTimer();
|
|
5235
|
+
logDaemonInfo("conn", `client #${client.id} subscribed as ${role} \u2014 ${clients.size} client(s), ${guiCount()} gui`);
|
|
5151
5236
|
for (const event of bus.snapshot()) {
|
|
5152
5237
|
writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
|
|
5153
5238
|
}
|
|
@@ -5327,13 +5412,13 @@ __export(exports_daemon_process, {
|
|
|
5327
5412
|
});
|
|
5328
5413
|
import { spawn } from "child_process";
|
|
5329
5414
|
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5330
|
-
import { dirname as
|
|
5415
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
5331
5416
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5332
5417
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5333
5418
|
let stdio = "ignore";
|
|
5334
5419
|
let logFd;
|
|
5335
5420
|
try {
|
|
5336
|
-
mkdirSync2(
|
|
5421
|
+
mkdirSync2(dirname5(logPath), { recursive: true });
|
|
5337
5422
|
logFd = openSync(logPath, "a");
|
|
5338
5423
|
stdio = ["ignore", logFd, logFd];
|
|
5339
5424
|
} catch {
|
|
@@ -5400,7 +5485,7 @@ function resolveKobeSpawn(subcommand) {
|
|
|
5400
5485
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5401
5486
|
return [process.execPath, ...subcommand];
|
|
5402
5487
|
}
|
|
5403
|
-
const dir =
|
|
5488
|
+
const dir = dirname5(here);
|
|
5404
5489
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5405
5490
|
if (existsSync(sourceEntry))
|
|
5406
5491
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
@@ -5946,7 +6031,7 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5946
6031
|
keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
|
|
5947
6032
|
], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
|
|
5948
6033
|
}
|
|
5949
|
-
if (opsPane
|
|
6034
|
+
if (opsPane) {
|
|
5950
6035
|
commands.push([
|
|
5951
6036
|
"respawn-pane",
|
|
5952
6037
|
"-k",
|
|
@@ -5957,7 +6042,7 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5957
6042
|
keepAlive(envPrefix + opsPaneCommand({
|
|
5958
6043
|
cwd,
|
|
5959
6044
|
taskId,
|
|
5960
|
-
claudePaneId: claudePane,
|
|
6045
|
+
claudePaneId: claudePane ?? null,
|
|
5961
6046
|
cliInvocation: inv,
|
|
5962
6047
|
vendor
|
|
5963
6048
|
}))
|
|
@@ -6242,32 +6327,74 @@ var init_worktree_changes = __esm(() => {
|
|
|
6242
6327
|
// src/cli/api-cmd.ts
|
|
6243
6328
|
var exports_api_cmd = {};
|
|
6244
6329
|
__export(exports_api_cmd, {
|
|
6330
|
+
verbSchema: () => verbSchema,
|
|
6331
|
+
verbHelp: () => verbHelp,
|
|
6332
|
+
validateAgainstSpec: () => validateAgainstSpec,
|
|
6333
|
+
schemaIndex: () => schemaIndex,
|
|
6245
6334
|
runApiSubcommand: () => runApiSubcommand,
|
|
6246
6335
|
parseFlags: () => parseFlags,
|
|
6247
6336
|
parseAgentsSpec: () => parseAgentsSpec,
|
|
6337
|
+
fullSchema: () => fullSchema,
|
|
6338
|
+
findVerb: () => findVerb,
|
|
6248
6339
|
apiUsage: () => apiUsage,
|
|
6340
|
+
VERB_GROUPS: () => VERB_GROUPS,
|
|
6341
|
+
VERBS: () => VERBS,
|
|
6249
6342
|
FANOUT_CAP: () => FANOUT_CAP,
|
|
6250
6343
|
ApiError: () => ApiError,
|
|
6251
|
-
API_VERBS: () => API_VERBS
|
|
6344
|
+
API_VERBS: () => API_VERBS,
|
|
6345
|
+
API_SCHEMA_VERSION: () => API_SCHEMA_VERSION
|
|
6252
6346
|
});
|
|
6253
6347
|
import { resolve as resolve4 } from "path";
|
|
6254
|
-
function
|
|
6348
|
+
function groupOf(verbName) {
|
|
6349
|
+
for (const [group, names] of Object.entries(VERB_GROUPS)) {
|
|
6350
|
+
if (names.includes(verbName))
|
|
6351
|
+
return group;
|
|
6352
|
+
}
|
|
6353
|
+
return "other";
|
|
6354
|
+
}
|
|
6355
|
+
async function handleSchema(_client, parsed) {
|
|
6356
|
+
const { flags } = parsed;
|
|
6357
|
+
const verbName = optional(flags, "verb");
|
|
6358
|
+
if (verbName) {
|
|
6359
|
+
const v = findVerb(verbName);
|
|
6360
|
+
if (!v)
|
|
6361
|
+
throw new ApiError(`unknown verb: ${verbName}`, "BAD_VERB");
|
|
6362
|
+
return verbSchema(v);
|
|
6363
|
+
}
|
|
6364
|
+
const group = optional(flags, "group");
|
|
6365
|
+
if (group)
|
|
6366
|
+
return groupSchema(group);
|
|
6367
|
+
if (optionalBool(flags, "all"))
|
|
6368
|
+
return fullSchema();
|
|
6369
|
+
return schemaIndex();
|
|
6370
|
+
}
|
|
6371
|
+
function findVerb(name) {
|
|
6372
|
+
const canonical = VERB_ALIASES[name] ?? name;
|
|
6373
|
+
return VERBS.find((v) => v.name === canonical);
|
|
6374
|
+
}
|
|
6375
|
+
function parseFlags(argv, booleanFlags = new Set) {
|
|
6255
6376
|
const flags = new Map;
|
|
6256
6377
|
let pretty = false;
|
|
6378
|
+
let help = false;
|
|
6257
6379
|
for (let i = 0;i < argv.length; i++) {
|
|
6258
6380
|
const arg = argv[i];
|
|
6259
|
-
if (!arg.startsWith("--")) {
|
|
6381
|
+
if (!arg.startsWith("--") && arg !== "-h") {
|
|
6260
6382
|
throw new ApiError(`unexpected positional arg: ${arg}`, "BAD_FLAG");
|
|
6261
6383
|
}
|
|
6384
|
+
if (arg === "-h") {
|
|
6385
|
+
help = true;
|
|
6386
|
+
continue;
|
|
6387
|
+
}
|
|
6262
6388
|
const eq = arg.indexOf("=");
|
|
6263
6389
|
if (eq !== -1) {
|
|
6264
6390
|
const key2 = arg.slice(2, eq);
|
|
6265
6391
|
const value = arg.slice(eq + 1);
|
|
6266
|
-
if (key2 === "pretty")
|
|
6392
|
+
if (key2 === "pretty")
|
|
6267
6393
|
pretty = value !== "false" && value !== "0";
|
|
6268
|
-
|
|
6394
|
+
else if (key2 === "help")
|
|
6395
|
+
help = value !== "false" && value !== "0";
|
|
6396
|
+
else
|
|
6269
6397
|
flags.set(key2, value);
|
|
6270
|
-
}
|
|
6271
6398
|
continue;
|
|
6272
6399
|
}
|
|
6273
6400
|
const key = arg.slice(2);
|
|
@@ -6275,6 +6402,14 @@ function parseFlags(argv) {
|
|
|
6275
6402
|
pretty = true;
|
|
6276
6403
|
continue;
|
|
6277
6404
|
}
|
|
6405
|
+
if (key === "help") {
|
|
6406
|
+
help = true;
|
|
6407
|
+
continue;
|
|
6408
|
+
}
|
|
6409
|
+
if (booleanFlags.has(key)) {
|
|
6410
|
+
flags.set(key, "true");
|
|
6411
|
+
continue;
|
|
6412
|
+
}
|
|
6278
6413
|
const next = argv[i + 1];
|
|
6279
6414
|
if (next === undefined || next.startsWith("--")) {
|
|
6280
6415
|
throw new ApiError(`flag --${key} requires a value`, "BAD_FLAG");
|
|
@@ -6282,19 +6417,50 @@ function parseFlags(argv) {
|
|
|
6282
6417
|
flags.set(key, next);
|
|
6283
6418
|
i += 1;
|
|
6284
6419
|
}
|
|
6285
|
-
return { flags, pretty };
|
|
6420
|
+
return { flags, pretty, help };
|
|
6421
|
+
}
|
|
6422
|
+
function validateAgainstSpec(verb, flags) {
|
|
6423
|
+
const known = new Set(verb.flags.map((f) => f.name));
|
|
6424
|
+
for (const key of flags.keys()) {
|
|
6425
|
+
if (!known.has(key)) {
|
|
6426
|
+
throw new ApiError(`unknown flag --${key} for "${verb.name}". Run \`kobe api ${verb.name} --help\``, "BAD_FLAG");
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
for (const f of verb.flags) {
|
|
6430
|
+
if (f.required && !flags.get(f.name))
|
|
6431
|
+
throw new ApiError(`--${f.name} is required for "${verb.name}"`, "MISSING_FLAG");
|
|
6432
|
+
if (f.type === "enum" && f.values) {
|
|
6433
|
+
const raw = flags.get(f.name);
|
|
6434
|
+
if (raw !== undefined && !f.values.includes(raw)) {
|
|
6435
|
+
throw new ApiError(`--${f.name} must be one of ${f.values.join(", ")}`, "BAD_FLAG");
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
if (f.type === "int") {
|
|
6439
|
+
const raw = flags.get(f.name);
|
|
6440
|
+
if (raw !== undefined) {
|
|
6441
|
+
const n = Number.parseInt(raw, 10);
|
|
6442
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
6443
|
+
throw new ApiError(`--${f.name} must be a positive integer`, "BAD_FLAG");
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
}
|
|
6286
6447
|
}
|
|
6287
6448
|
function required(flags, key) {
|
|
6288
6449
|
const v = flags.get(key);
|
|
6289
|
-
if (v === undefined || v.length === 0)
|
|
6450
|
+
if (v === undefined || v.length === 0)
|
|
6290
6451
|
throw new ApiError(`--${key} is required`, "MISSING_FLAG");
|
|
6291
|
-
}
|
|
6292
6452
|
return v;
|
|
6293
6453
|
}
|
|
6294
6454
|
function optional(flags, key) {
|
|
6295
6455
|
const v = flags.get(key);
|
|
6296
6456
|
return v && v.length > 0 ? v : undefined;
|
|
6297
6457
|
}
|
|
6458
|
+
function requireEnum(flags, key, values) {
|
|
6459
|
+
const v = required(flags, key);
|
|
6460
|
+
if (!values.includes(v))
|
|
6461
|
+
throw new ApiError(`--${key} must be one of ${values.join(", ")}`, "BAD_FLAG");
|
|
6462
|
+
return v;
|
|
6463
|
+
}
|
|
6298
6464
|
function optionalVendor2(flags) {
|
|
6299
6465
|
const raw = optional(flags, "vendor");
|
|
6300
6466
|
if (raw === undefined)
|
|
@@ -6304,16 +6470,28 @@ function optionalVendor2(flags) {
|
|
|
6304
6470
|
}
|
|
6305
6471
|
return raw;
|
|
6306
6472
|
}
|
|
6473
|
+
function optionalBool(flags, key) {
|
|
6474
|
+
const raw = optional(flags, key);
|
|
6475
|
+
if (raw === undefined)
|
|
6476
|
+
return;
|
|
6477
|
+
if (["true", "1", "yes"].includes(raw))
|
|
6478
|
+
return true;
|
|
6479
|
+
if (["false", "0", "no"].includes(raw))
|
|
6480
|
+
return false;
|
|
6481
|
+
throw new ApiError(`--${key} must be a boolean (true/false)`, "BAD_FLAG");
|
|
6482
|
+
}
|
|
6307
6483
|
function optionalPositiveInt(flags, key) {
|
|
6308
6484
|
const raw = optional(flags, key);
|
|
6309
6485
|
if (raw === undefined)
|
|
6310
6486
|
return;
|
|
6311
6487
|
const n = Number.parseInt(raw, 10);
|
|
6312
|
-
if (!Number.isInteger(n) || n <= 0)
|
|
6488
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
6313
6489
|
throw new ApiError(`--${key} must be a positive integer`, "BAD_FLAG");
|
|
6314
|
-
}
|
|
6315
6490
|
return n;
|
|
6316
6491
|
}
|
|
6492
|
+
function resolveRepoFlag(repo) {
|
|
6493
|
+
return resolve4(process.cwd(), repo);
|
|
6494
|
+
}
|
|
6317
6495
|
function parseAgentsSpec(spec) {
|
|
6318
6496
|
const out = [];
|
|
6319
6497
|
for (const part of spec.split(",")) {
|
|
@@ -6338,19 +6516,102 @@ function parseAgentsSpec(spec) {
|
|
|
6338
6516
|
throw new ApiError('--agents specified no agents (e.g. "claude:2,codex:1")', "BAD_FLAG");
|
|
6339
6517
|
return out;
|
|
6340
6518
|
}
|
|
6519
|
+
function flagJson(f) {
|
|
6520
|
+
return {
|
|
6521
|
+
name: f.name,
|
|
6522
|
+
type: f.type,
|
|
6523
|
+
required: f.required ?? false,
|
|
6524
|
+
...f.values ? { values: f.values } : {},
|
|
6525
|
+
...f.default !== undefined ? { default: f.default } : {},
|
|
6526
|
+
...f.placeholder ? { placeholder: f.placeholder } : {},
|
|
6527
|
+
description: f.description
|
|
6528
|
+
};
|
|
6529
|
+
}
|
|
6530
|
+
function verbSchema(v) {
|
|
6531
|
+
return {
|
|
6532
|
+
name: v.name,
|
|
6533
|
+
group: groupOf(v.name),
|
|
6534
|
+
summary: v.summary,
|
|
6535
|
+
offline: v.offline ?? false,
|
|
6536
|
+
flags: v.flags.map(flagJson)
|
|
6537
|
+
};
|
|
6538
|
+
}
|
|
6539
|
+
function schemaIndex() {
|
|
6540
|
+
return {
|
|
6541
|
+
apiVersion: API_SCHEMA_VERSION,
|
|
6542
|
+
kobeVersion: CURRENT_VERSION,
|
|
6543
|
+
hint: "Compact index. Drill into ONE verb: `kobe api schema --verb <name>` (or `kobe api <verb> --help`). One group: `--group <g>`. Whole spec: `--all`.",
|
|
6544
|
+
groups: VERB_GROUPS,
|
|
6545
|
+
verbs: VERBS.map((v) => ({ name: v.name, group: groupOf(v.name), summary: v.summary })),
|
|
6546
|
+
globalFlags: GLOBAL_FLAGS,
|
|
6547
|
+
aliases: VERB_ALIASES
|
|
6548
|
+
};
|
|
6549
|
+
}
|
|
6550
|
+
function groupSchema(group) {
|
|
6551
|
+
const names = VERB_GROUPS[group];
|
|
6552
|
+
if (!names) {
|
|
6553
|
+
throw new ApiError(`unknown group: ${group}. Groups: ${Object.keys(VERB_GROUPS).join(", ")}`, "BAD_FLAG");
|
|
6554
|
+
}
|
|
6555
|
+
return {
|
|
6556
|
+
group,
|
|
6557
|
+
verbs: names.map((n) => {
|
|
6558
|
+
const v = findVerb(n);
|
|
6559
|
+
return { name: n, summary: v?.summary ?? "" };
|
|
6560
|
+
})
|
|
6561
|
+
};
|
|
6562
|
+
}
|
|
6563
|
+
function fullSchema() {
|
|
6564
|
+
return {
|
|
6565
|
+
apiVersion: API_SCHEMA_VERSION,
|
|
6566
|
+
kobeVersion: CURRENT_VERSION,
|
|
6567
|
+
output: {
|
|
6568
|
+
success: "one JSON object on stdout, newline-terminated, exit 0",
|
|
6569
|
+
error: '{"error":{"message","code"}} on stderr, exit != 0',
|
|
6570
|
+
pretty: "--pretty indents stdout JSON"
|
|
6571
|
+
},
|
|
6572
|
+
globalFlags: GLOBAL_FLAGS,
|
|
6573
|
+
aliases: VERB_ALIASES,
|
|
6574
|
+
groups: VERB_GROUPS,
|
|
6575
|
+
verbs: VERBS.map(verbSchema)
|
|
6576
|
+
};
|
|
6577
|
+
}
|
|
6578
|
+
function flagSignature(verb) {
|
|
6579
|
+
return verb.flags.map((f) => {
|
|
6580
|
+
const meta = f.type === "enum" && f.values ? f.values.join("|") : f.placeholder ?? (f.type === "bool" ? "" : "X");
|
|
6581
|
+
const core = meta ? `--${f.name} ${meta}` : `--${f.name}`;
|
|
6582
|
+
return f.required ? core : `[${core}]`;
|
|
6583
|
+
}).join(" ");
|
|
6584
|
+
}
|
|
6585
|
+
function verbHelp(verb) {
|
|
6586
|
+
const lines = [`kobe api ${verb.name} ${flagSignature(verb)}`.trimEnd(), "", verb.summary, ""];
|
|
6587
|
+
const alias = Object.entries(VERB_ALIASES).find(([, canon]) => canon === verb.name)?.[0];
|
|
6588
|
+
if (alias)
|
|
6589
|
+
lines.push(`Alias: ${alias}`, "");
|
|
6590
|
+
if (verb.flags.length > 0) {
|
|
6591
|
+
lines.push("Flags:");
|
|
6592
|
+
for (const f of verb.flags) {
|
|
6593
|
+
const req = f.required ? " (required)" : "";
|
|
6594
|
+
const def = f.default !== undefined ? ` [default: ${f.default}]` : "";
|
|
6595
|
+
const vals = f.type === "enum" && f.values ? ` {${f.values.join("|")}}` : "";
|
|
6596
|
+
lines.push(` --${f.name}${vals}${req}${def} ${f.description}`);
|
|
6597
|
+
}
|
|
6598
|
+
lines.push("");
|
|
6599
|
+
}
|
|
6600
|
+
lines.push("Global: [--pretty] [--help]");
|
|
6601
|
+
return lines.join(`
|
|
6602
|
+
`);
|
|
6603
|
+
}
|
|
6341
6604
|
function apiUsage() {
|
|
6605
|
+
const rows = VERBS.map((v) => ` ${v.name.padEnd(18)} ${v.summary}`);
|
|
6342
6606
|
return [
|
|
6343
|
-
"usage: kobe api <verb> [flags] [--pretty]",
|
|
6607
|
+
"usage: kobe api <verb> [flags] [--pretty] [--help]",
|
|
6608
|
+
"",
|
|
6609
|
+
"Explore the full surface (names, flags, types) with: kobe api schema",
|
|
6344
6610
|
"",
|
|
6345
6611
|
"verbs:",
|
|
6346
|
-
|
|
6347
|
-
" fan-out --repo PATH --prompt TEXT [--count N | --agents claude:2,codex:1] [--base-branch B]",
|
|
6348
|
-
" send [--task-id ID] --prompt TEXT",
|
|
6349
|
-
" get-task --task-id ID",
|
|
6350
|
-
" collect --task-ids a,b,c | --repo PATH",
|
|
6351
|
-
" list",
|
|
6612
|
+
...rows,
|
|
6352
6613
|
"",
|
|
6353
|
-
"Output is one JSON object on stdout (exit 0); errors are JSON on stderr (exit
|
|
6614
|
+
"Output is one JSON object on stdout (exit 0); errors are JSON on stderr (exit != 0)."
|
|
6354
6615
|
].join(`
|
|
6355
6616
|
`);
|
|
6356
6617
|
}
|
|
@@ -6407,12 +6668,22 @@ async function resolveActiveTaskId(client) {
|
|
|
6407
6668
|
}
|
|
6408
6669
|
return activeId;
|
|
6409
6670
|
}
|
|
6410
|
-
async function
|
|
6671
|
+
async function simpleRpc(client, name, payload) {
|
|
6672
|
+
if (!client)
|
|
6673
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6674
|
+
return client.request(name, payload);
|
|
6675
|
+
}
|
|
6676
|
+
async function add(client, parsed) {
|
|
6677
|
+
if (!client)
|
|
6678
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6411
6679
|
const { flags } = parsed;
|
|
6412
|
-
const payload = { repo: required(flags, "repo") };
|
|
6680
|
+
const payload = { repo: resolveRepoFlag(required(flags, "repo")) };
|
|
6413
6681
|
const title = optional(flags, "title");
|
|
6414
6682
|
if (title)
|
|
6415
6683
|
payload.title = title;
|
|
6684
|
+
const branch = optional(flags, "branch");
|
|
6685
|
+
if (branch)
|
|
6686
|
+
payload.branch = branch;
|
|
6416
6687
|
const baseRef = optional(flags, "base-branch");
|
|
6417
6688
|
if (baseRef)
|
|
6418
6689
|
payload.baseRef = baseRef;
|
|
@@ -6420,25 +6691,26 @@ async function spawnTask(client, parsed) {
|
|
|
6420
6691
|
if (vendor)
|
|
6421
6692
|
payload.vendor = vendor;
|
|
6422
6693
|
const res = await client.request("task.create", payload);
|
|
6423
|
-
const
|
|
6424
|
-
|
|
6425
|
-
|
|
6694
|
+
const taskId = res.taskId;
|
|
6695
|
+
const status = optional(flags, "status");
|
|
6696
|
+
if (status)
|
|
6697
|
+
await client.request("task.status", { taskId, status: requireEnum(flags, "status", TASK_STATUSES) });
|
|
6698
|
+
const pin = optionalBool(flags, "pin");
|
|
6699
|
+
if (pin !== undefined)
|
|
6700
|
+
await client.request("task.pin", { taskId, pinned: pin });
|
|
6701
|
+
let task = res.task;
|
|
6702
|
+
if (status || pin !== undefined) {
|
|
6703
|
+
task = (await client.request("task.get", { taskId })).task;
|
|
6426
6704
|
}
|
|
6427
|
-
const
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
}, prompt);
|
|
6433
|
-
return {
|
|
6434
|
-
taskId: res.taskId,
|
|
6435
|
-
task: res.task,
|
|
6436
|
-
started: delivered.started,
|
|
6437
|
-
engineReady: delivered.engineReady,
|
|
6438
|
-
session: delivered.session
|
|
6439
|
-
};
|
|
6705
|
+
const prompt = optional(flags, "prompt");
|
|
6706
|
+
if (!prompt)
|
|
6707
|
+
return { taskId, task, started: false };
|
|
6708
|
+
const delivered = await deliverPrompt(client, { id: taskId, worktreePath: task.worktreePath, vendor: task.vendor, repo: task.repo }, prompt);
|
|
6709
|
+
return { taskId, task, started: delivered.started, engineReady: delivered.engineReady, session: delivered.session };
|
|
6440
6710
|
}
|
|
6441
6711
|
async function send(client, parsed) {
|
|
6712
|
+
if (!client)
|
|
6713
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6442
6714
|
const { flags } = parsed;
|
|
6443
6715
|
const prompt = required(flags, "prompt");
|
|
6444
6716
|
let taskId = optional(flags, "task-id");
|
|
@@ -6465,17 +6737,50 @@ async function send(client, parsed) {
|
|
|
6465
6737
|
};
|
|
6466
6738
|
}
|
|
6467
6739
|
async function getTask(client, parsed) {
|
|
6740
|
+
if (!client)
|
|
6741
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6468
6742
|
const taskId = required(parsed.flags, "task-id");
|
|
6469
6743
|
const res = await client.request("task.get", { taskId });
|
|
6470
6744
|
const running = await sessionExists(tmuxSessionName(taskId));
|
|
6471
6745
|
return { task: res.task, running };
|
|
6472
6746
|
}
|
|
6473
6747
|
async function list(client) {
|
|
6748
|
+
if (!client)
|
|
6749
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6474
6750
|
return client.request("task.list");
|
|
6475
6751
|
}
|
|
6752
|
+
async function setActive(client, parsed) {
|
|
6753
|
+
if (!client)
|
|
6754
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6755
|
+
const none = optionalBool(parsed.flags, "none");
|
|
6756
|
+
const taskId = none ? null : required(parsed.flags, "task-id");
|
|
6757
|
+
await client.request("task.setActive", { taskId });
|
|
6758
|
+
return { ok: true, activeTaskId: taskId };
|
|
6759
|
+
}
|
|
6760
|
+
async function adopt(client, parsed) {
|
|
6761
|
+
if (!client)
|
|
6762
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6763
|
+
const { flags } = parsed;
|
|
6764
|
+
const input = {
|
|
6765
|
+
repo: resolveRepoFlag(required(flags, "repo")),
|
|
6766
|
+
worktreePath: resolveRepoFlag(required(flags, "worktree"))
|
|
6767
|
+
};
|
|
6768
|
+
const branch = optional(flags, "branch");
|
|
6769
|
+
if (branch)
|
|
6770
|
+
input.branch = branch;
|
|
6771
|
+
const vendor = optionalVendor2(flags);
|
|
6772
|
+
if (vendor)
|
|
6773
|
+
input.vendor = vendor;
|
|
6774
|
+
const title = optional(flags, "title");
|
|
6775
|
+
if (title)
|
|
6776
|
+
input.title = title;
|
|
6777
|
+
return client.request("worktree.adopt", input);
|
|
6778
|
+
}
|
|
6476
6779
|
async function fanOut(client, parsed) {
|
|
6780
|
+
if (!client)
|
|
6781
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6477
6782
|
const { flags } = parsed;
|
|
6478
|
-
const repo = required(flags, "repo");
|
|
6783
|
+
const repo = resolveRepoFlag(required(flags, "repo"));
|
|
6479
6784
|
const prompt = required(flags, "prompt");
|
|
6480
6785
|
const title = optional(flags, "title");
|
|
6481
6786
|
const baseRef = optional(flags, "base-branch");
|
|
@@ -6504,6 +6809,8 @@ async function fanOut(client, parsed) {
|
|
|
6504
6809
|
return { count: tasks.length, tasks };
|
|
6505
6810
|
}
|
|
6506
6811
|
async function collect(client, parsed) {
|
|
6812
|
+
if (!client)
|
|
6813
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6507
6814
|
const { flags } = parsed;
|
|
6508
6815
|
const idsFlag = optional(flags, "task-ids");
|
|
6509
6816
|
const repoFlag = optional(flags, "repo");
|
|
@@ -6512,7 +6819,7 @@ async function collect(client, parsed) {
|
|
|
6512
6819
|
taskIds = idsFlag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6513
6820
|
} else if (repoFlag) {
|
|
6514
6821
|
const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
6515
|
-
const target = resolveRepoRoot2(
|
|
6822
|
+
const target = resolveRepoRoot2(resolveRepoFlag(repoFlag));
|
|
6516
6823
|
const { tasks } = await client.request("task.list");
|
|
6517
6824
|
taskIds = tasks.filter((t) => !t.archived && resolveRepoRoot2(t.repo) === target).map((t) => t.id);
|
|
6518
6825
|
} else {
|
|
@@ -6538,72 +6845,67 @@ async function collect(client, parsed) {
|
|
|
6538
6845
|
return { tasks: out };
|
|
6539
6846
|
}
|
|
6540
6847
|
async function runApiSubcommand(argv) {
|
|
6541
|
-
const [
|
|
6542
|
-
if (!
|
|
6543
|
-
if (!
|
|
6848
|
+
const [verbName, ...rest] = argv;
|
|
6849
|
+
if (!verbName || verbName === "--help" || verbName === "-h" || verbName === "help") {
|
|
6850
|
+
if (!verbName)
|
|
6544
6851
|
fail(apiUsage(), "MISSING_VERB", 2);
|
|
6545
|
-
}
|
|
6546
6852
|
process.stdout.write(`${apiUsage()}
|
|
6547
6853
|
`);
|
|
6548
6854
|
return;
|
|
6549
6855
|
}
|
|
6550
|
-
|
|
6551
|
-
|
|
6856
|
+
const verb = findVerb(verbName);
|
|
6857
|
+
if (!verb)
|
|
6858
|
+
fail(`unknown verb: ${verbName}
|
|
6552
6859
|
${apiUsage()}`, "BAD_VERB", 2);
|
|
6553
|
-
|
|
6860
|
+
const booleanFlags = new Set(verb.flags.filter((f) => f.type === "bool").map((f) => f.name));
|
|
6554
6861
|
let parsed;
|
|
6555
6862
|
try {
|
|
6556
|
-
parsed = parseFlags(rest);
|
|
6863
|
+
parsed = parseFlags(rest, booleanFlags);
|
|
6557
6864
|
} catch (err) {
|
|
6558
6865
|
if (err instanceof ApiError)
|
|
6559
6866
|
fail(err.message, err.code, 2);
|
|
6560
6867
|
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
6561
6868
|
}
|
|
6562
|
-
|
|
6869
|
+
if (parsed.help) {
|
|
6870
|
+
process.stdout.write(`${verbHelp(verb)}
|
|
6871
|
+
`);
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6563
6874
|
try {
|
|
6564
|
-
|
|
6875
|
+
validateAgainstSpec(verb, parsed.flags);
|
|
6565
6876
|
} catch (err) {
|
|
6566
|
-
|
|
6877
|
+
if (err instanceof ApiError)
|
|
6878
|
+
fail(err.message, err.code, 2);
|
|
6879
|
+
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
6567
6880
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
case "fan-out":
|
|
6575
|
-
result = await fanOut(client, parsed);
|
|
6576
|
-
break;
|
|
6577
|
-
case "send":
|
|
6578
|
-
result = await send(client, parsed);
|
|
6579
|
-
break;
|
|
6580
|
-
case "get-task":
|
|
6581
|
-
result = await getTask(client, parsed);
|
|
6582
|
-
break;
|
|
6583
|
-
case "collect":
|
|
6584
|
-
result = await collect(client, parsed);
|
|
6585
|
-
break;
|
|
6586
|
-
case "list":
|
|
6587
|
-
result = await list(client);
|
|
6588
|
-
break;
|
|
6881
|
+
let client = null;
|
|
6882
|
+
if (!verb.offline) {
|
|
6883
|
+
try {
|
|
6884
|
+
client = await connectOrStartDaemon();
|
|
6885
|
+
} catch (err) {
|
|
6886
|
+
fail(`could not reach or start the kobe daemon: ${err instanceof Error ? err.message : String(err)}`, "BAD_DAEMON", 2);
|
|
6589
6887
|
}
|
|
6888
|
+
}
|
|
6889
|
+
try {
|
|
6890
|
+
const result = await verb.handler(client, parsed);
|
|
6590
6891
|
emit(result, parsed.pretty);
|
|
6591
6892
|
} catch (err) {
|
|
6592
6893
|
if (err instanceof ApiError)
|
|
6593
6894
|
fail(err.message, err.code, 1);
|
|
6594
6895
|
fail(err instanceof Error ? err.message : String(err), "RPC_ERROR", 1);
|
|
6595
6896
|
} finally {
|
|
6596
|
-
client
|
|
6897
|
+
client?.close();
|
|
6597
6898
|
}
|
|
6598
6899
|
}
|
|
6599
|
-
var
|
|
6900
|
+
var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS;
|
|
6600
6901
|
var init_api_cmd = __esm(() => {
|
|
6601
6902
|
init_daemon_process();
|
|
6602
6903
|
init_interactive_command();
|
|
6603
6904
|
init_client2();
|
|
6604
6905
|
init_prompt_delivery();
|
|
6605
6906
|
init_vendor();
|
|
6606
|
-
|
|
6907
|
+
init_version();
|
|
6908
|
+
TASK_STATUSES = ["backlog", "in_progress", "in_review", "done", "canceled", "error"];
|
|
6607
6909
|
ApiError = class ApiError extends Error {
|
|
6608
6910
|
code;
|
|
6609
6911
|
constructor(message, code) {
|
|
@@ -6611,6 +6913,241 @@ var init_api_cmd = __esm(() => {
|
|
|
6611
6913
|
this.code = code;
|
|
6612
6914
|
}
|
|
6613
6915
|
};
|
|
6916
|
+
F = {
|
|
6917
|
+
repo: (required = true) => ({
|
|
6918
|
+
name: "repo",
|
|
6919
|
+
type: "string",
|
|
6920
|
+
required,
|
|
6921
|
+
placeholder: "PATH",
|
|
6922
|
+
description: "Repo root (git toplevel). Relative paths resolve against $PWD."
|
|
6923
|
+
}),
|
|
6924
|
+
taskId: (required = true) => ({
|
|
6925
|
+
name: "task-id",
|
|
6926
|
+
type: "string",
|
|
6927
|
+
required,
|
|
6928
|
+
placeholder: "ID",
|
|
6929
|
+
description: "Target task id (from `list` / `add`)."
|
|
6930
|
+
}),
|
|
6931
|
+
vendor: () => ({
|
|
6932
|
+
name: "vendor",
|
|
6933
|
+
type: "enum",
|
|
6934
|
+
values: ALL_VENDORS,
|
|
6935
|
+
placeholder: "V",
|
|
6936
|
+
description: "Engine vendor for the task."
|
|
6937
|
+
}),
|
|
6938
|
+
title: () => ({ name: "title", type: "string", placeholder: "T", description: "Human task title." }),
|
|
6939
|
+
prompt: (required, desc) => ({
|
|
6940
|
+
name: "prompt",
|
|
6941
|
+
type: "string",
|
|
6942
|
+
required,
|
|
6943
|
+
placeholder: "TEXT",
|
|
6944
|
+
description: desc
|
|
6945
|
+
})
|
|
6946
|
+
};
|
|
6947
|
+
VERB_ALIASES = { "spawn-task": "add" };
|
|
6948
|
+
VERB_GROUPS = {
|
|
6949
|
+
discover: ["schema"],
|
|
6950
|
+
read: ["list", "get-task", "collect"],
|
|
6951
|
+
create: ["add", "fan-out"],
|
|
6952
|
+
drive: ["send", "set-active"],
|
|
6953
|
+
edit: ["rename", "set-branch", "set-vendor", "set-status"],
|
|
6954
|
+
lifecycle: ["archive", "pin", "delete"],
|
|
6955
|
+
worktree: ["ensure-worktree", "adopt", "discover-adoptable"]
|
|
6956
|
+
};
|
|
6957
|
+
VERBS = [
|
|
6958
|
+
{
|
|
6959
|
+
name: "schema",
|
|
6960
|
+
summary: "Explore the API. Default = a COMPACT index (groups + verb summaries, no flags). Drill in with --verb / --group; --all for the full spec.",
|
|
6961
|
+
flags: [
|
|
6962
|
+
{ name: "verb", type: "string", placeholder: "NAME", description: "Full flag detail for ONE verb." },
|
|
6963
|
+
{ name: "group", type: "string", placeholder: "G", description: "List the verbs in one group (compact)." },
|
|
6964
|
+
{
|
|
6965
|
+
name: "all",
|
|
6966
|
+
type: "bool",
|
|
6967
|
+
description: "The COMPLETE spec \u2014 every verb AND every flag (large; avoid by default)."
|
|
6968
|
+
}
|
|
6969
|
+
],
|
|
6970
|
+
offline: true,
|
|
6971
|
+
handler: handleSchema
|
|
6972
|
+
},
|
|
6973
|
+
{ name: "list", summary: "List all tasks (incl. archived). Returns { tasks }.", flags: [], handler: list },
|
|
6974
|
+
{
|
|
6975
|
+
name: "get-task",
|
|
6976
|
+
summary: "Read one task's metadata. `.running` = its tmux session is live.",
|
|
6977
|
+
flags: [F.taskId()],
|
|
6978
|
+
handler: getTask
|
|
6979
|
+
},
|
|
6980
|
+
{
|
|
6981
|
+
name: "add",
|
|
6982
|
+
summary: "Create a task (shows in the sidebar immediately). With --prompt it also starts the engine and delivers it. Alias: spawn-task.",
|
|
6983
|
+
flags: [
|
|
6984
|
+
F.repo(),
|
|
6985
|
+
F.title(),
|
|
6986
|
+
{
|
|
6987
|
+
name: "branch",
|
|
6988
|
+
type: "string",
|
|
6989
|
+
placeholder: "B",
|
|
6990
|
+
description: "Explicit branch name (else auto kobe/<slug>-<id>)."
|
|
6991
|
+
},
|
|
6992
|
+
{ name: "base-branch", type: "string", placeholder: "B", description: "Base ref the worktree branches from." },
|
|
6993
|
+
F.vendor(),
|
|
6994
|
+
{
|
|
6995
|
+
name: "status",
|
|
6996
|
+
type: "enum",
|
|
6997
|
+
values: TASK_STATUSES,
|
|
6998
|
+
default: "backlog",
|
|
6999
|
+
description: "Initial lifecycle status."
|
|
7000
|
+
},
|
|
7001
|
+
{ name: "pin", type: "bool", description: "Pin the task to the top of the sidebar." },
|
|
7002
|
+
F.prompt(false, "Optional first message \u2014 when set, materializes the worktree, starts the engine, and pastes it.")
|
|
7003
|
+
],
|
|
7004
|
+
handler: add
|
|
7005
|
+
},
|
|
7006
|
+
{
|
|
7007
|
+
name: "fan-out",
|
|
7008
|
+
summary: `Spawn N tasks of ONE prompt in a single call (parallel attempts). Capped at ${FANOUT_CAP}.`,
|
|
7009
|
+
flags: [
|
|
7010
|
+
F.repo(),
|
|
7011
|
+
F.prompt(true, "Shared prompt delivered to every spawned task."),
|
|
7012
|
+
{ name: "count", type: "int", placeholder: "N", description: "Number of tasks of one vendor (with --vendor)." },
|
|
7013
|
+
{
|
|
7014
|
+
name: "agents",
|
|
7015
|
+
type: "string",
|
|
7016
|
+
placeholder: "claude:2,codex:1",
|
|
7017
|
+
description: "Per-vendor counts (alternative to --count)."
|
|
7018
|
+
},
|
|
7019
|
+
F.vendor(),
|
|
7020
|
+
F.title(),
|
|
7021
|
+
{ name: "base-branch", type: "string", placeholder: "B", description: "Base ref for every worktree." }
|
|
7022
|
+
],
|
|
7023
|
+
handler: fanOut
|
|
7024
|
+
},
|
|
7025
|
+
{
|
|
7026
|
+
name: "send",
|
|
7027
|
+
summary: "Paste a follow-up prompt into a task's running engine (one full turn). Defaults to the active task.",
|
|
7028
|
+
flags: [F.taskId(false), F.prompt(true, "Text pasted + submitted into the engine pane.")],
|
|
7029
|
+
handler: send
|
|
7030
|
+
},
|
|
7031
|
+
{
|
|
7032
|
+
name: "collect",
|
|
7033
|
+
summary: "Read-only comparison snapshot of several tasks (identity, branch, .running, uncommitted .changes).",
|
|
7034
|
+
flags: [
|
|
7035
|
+
{ name: "task-ids", type: "csv", placeholder: "a,b,c", description: "Comma-separated task ids." },
|
|
7036
|
+
F.repo(false)
|
|
7037
|
+
],
|
|
7038
|
+
handler: collect
|
|
7039
|
+
},
|
|
7040
|
+
{
|
|
7041
|
+
name: "rename",
|
|
7042
|
+
summary: "Set a task's title.",
|
|
7043
|
+
flags: [F.taskId(), { name: "title", type: "string", required: true, placeholder: "T", description: "New title." }],
|
|
7044
|
+
handler: (c, p) => simpleRpc(c, "task.rename", { taskId: required(p.flags, "task-id"), title: required(p.flags, "title") })
|
|
7045
|
+
},
|
|
7046
|
+
{
|
|
7047
|
+
name: "set-branch",
|
|
7048
|
+
summary: "Rename a task's branch (git branch -m if materialized, else recorded).",
|
|
7049
|
+
flags: [
|
|
7050
|
+
F.taskId(),
|
|
7051
|
+
{ name: "branch", type: "string", required: true, placeholder: "B", description: "New branch name." }
|
|
7052
|
+
],
|
|
7053
|
+
handler: (c, p) => simpleRpc(c, "task.setBranch", { taskId: required(p.flags, "task-id"), branch: required(p.flags, "branch") })
|
|
7054
|
+
},
|
|
7055
|
+
{
|
|
7056
|
+
name: "set-vendor",
|
|
7057
|
+
summary: "Change a task's engine vendor (takes effect on next session rebuild).",
|
|
7058
|
+
flags: [F.taskId(), { ...F.vendor(), required: true }],
|
|
7059
|
+
handler: (c, p) => simpleRpc(c, "task.setVendor", {
|
|
7060
|
+
taskId: required(p.flags, "task-id"),
|
|
7061
|
+
vendor: requireEnum(p.flags, "vendor", ALL_VENDORS)
|
|
7062
|
+
})
|
|
7063
|
+
},
|
|
7064
|
+
{
|
|
7065
|
+
name: "set-status",
|
|
7066
|
+
summary: "Set a task's lifecycle status.",
|
|
7067
|
+
flags: [
|
|
7068
|
+
F.taskId(),
|
|
7069
|
+
{ name: "status", type: "enum", required: true, values: TASK_STATUSES, description: "New status." }
|
|
7070
|
+
],
|
|
7071
|
+
handler: (c, p) => simpleRpc(c, "task.status", {
|
|
7072
|
+
taskId: required(p.flags, "task-id"),
|
|
7073
|
+
status: requireEnum(p.flags, "status", TASK_STATUSES)
|
|
7074
|
+
})
|
|
7075
|
+
},
|
|
7076
|
+
{
|
|
7077
|
+
name: "archive",
|
|
7078
|
+
summary: "Archive (or with --archived=false, unarchive) a task. Non-destructive: worktree/branch/history stay.",
|
|
7079
|
+
flags: [
|
|
7080
|
+
F.taskId(),
|
|
7081
|
+
{ name: "archived", type: "bool", default: "true", description: "true to archive, false to unarchive." }
|
|
7082
|
+
],
|
|
7083
|
+
handler: (c, p) => simpleRpc(c, "task.archive", {
|
|
7084
|
+
taskId: required(p.flags, "task-id"),
|
|
7085
|
+
archived: optionalBool(p.flags, "archived") ?? true
|
|
7086
|
+
})
|
|
7087
|
+
},
|
|
7088
|
+
{
|
|
7089
|
+
name: "pin",
|
|
7090
|
+
summary: "Pin (or with --pinned=false, unpin) a task to the top of the sidebar.",
|
|
7091
|
+
flags: [F.taskId(), { name: "pinned", type: "bool", default: "true", description: "true to pin, false to unpin." }],
|
|
7092
|
+
handler: (c, p) => simpleRpc(c, "task.pin", {
|
|
7093
|
+
taskId: required(p.flags, "task-id"),
|
|
7094
|
+
pinned: optionalBool(p.flags, "pinned") ?? true
|
|
7095
|
+
})
|
|
7096
|
+
},
|
|
7097
|
+
{
|
|
7098
|
+
name: "set-active",
|
|
7099
|
+
summary: "Set the shared active task (the focus every Tasks pane highlights). Pass --none to clear.",
|
|
7100
|
+
flags: [
|
|
7101
|
+
F.taskId(false),
|
|
7102
|
+
{ name: "none", type: "bool", description: "Clear the active task instead of setting one." }
|
|
7103
|
+
],
|
|
7104
|
+
handler: setActive
|
|
7105
|
+
},
|
|
7106
|
+
{
|
|
7107
|
+
name: "ensure-worktree",
|
|
7108
|
+
summary: "Materialize a task's git worktree on disk now (without starting an engine). Returns { worktreePath }.",
|
|
7109
|
+
flags: [F.taskId()],
|
|
7110
|
+
handler: (c, p) => simpleRpc(c, "task.ensureWorktree", { taskId: required(p.flags, "task-id") })
|
|
7111
|
+
},
|
|
7112
|
+
{
|
|
7113
|
+
name: "delete",
|
|
7114
|
+
summary: "Permanently remove a task (and its worktree). DESTRUCTIVE \u2014 prefer `archive`. Needs --force on a dirty worktree.",
|
|
7115
|
+
flags: [F.taskId(), { name: "force", type: "bool", description: "Delete even with uncommitted changes." }],
|
|
7116
|
+
handler: (c, p) => simpleRpc(c, "task.delete", {
|
|
7117
|
+
taskId: required(p.flags, "task-id"),
|
|
7118
|
+
force: optionalBool(p.flags, "force") ?? false
|
|
7119
|
+
})
|
|
7120
|
+
},
|
|
7121
|
+
{
|
|
7122
|
+
name: "discover-adoptable",
|
|
7123
|
+
summary: "List existing git worktrees in a repo not yet tracked as kobe tasks. Returns { worktrees }.",
|
|
7124
|
+
flags: [F.repo()],
|
|
7125
|
+
handler: (c, p) => simpleRpc(c, "worktree.discoverAdoptable", { repo: resolveRepoFlag(required(p.flags, "repo")) })
|
|
7126
|
+
},
|
|
7127
|
+
{
|
|
7128
|
+
name: "adopt",
|
|
7129
|
+
summary: "Import an existing git worktree as a kobe task. Returns { task }.",
|
|
7130
|
+
flags: [
|
|
7131
|
+
F.repo(),
|
|
7132
|
+
{
|
|
7133
|
+
name: "worktree",
|
|
7134
|
+
type: "string",
|
|
7135
|
+
required: true,
|
|
7136
|
+
placeholder: "PATH",
|
|
7137
|
+
description: "Path of the worktree to adopt."
|
|
7138
|
+
},
|
|
7139
|
+
{ name: "branch", type: "string", placeholder: "B", description: "Branch override (else the worktree's own)." },
|
|
7140
|
+
F.vendor(),
|
|
7141
|
+
F.title()
|
|
7142
|
+
],
|
|
7143
|
+
handler: adopt
|
|
7144
|
+
}
|
|
7145
|
+
];
|
|
7146
|
+
API_VERBS = VERBS.map((v) => v.name);
|
|
7147
|
+
GLOBAL_FLAGS = [
|
|
7148
|
+
{ name: "pretty", type: "bool", description: "Pretty-print stdout JSON." },
|
|
7149
|
+
{ name: "help", type: "bool", description: "Show usage for the verb and exit." }
|
|
7150
|
+
];
|
|
6614
7151
|
});
|
|
6615
7152
|
|
|
6616
7153
|
// src/cli/update.ts
|
|
@@ -7121,31 +7658,66 @@ var init_daemon_cmd = __esm(() => {
|
|
|
7121
7658
|
});
|
|
7122
7659
|
|
|
7123
7660
|
// src/lib/skill-install.ts
|
|
7124
|
-
import { existsSync as existsSync4 } from "fs";
|
|
7661
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
7125
7662
|
import { homedir as homedir9 } from "os";
|
|
7126
7663
|
import { join as join7 } from "path";
|
|
7664
|
+
function npxSkillsArgv(opts = {}) {
|
|
7665
|
+
return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
|
|
7666
|
+
}
|
|
7667
|
+
function npxSkillsCommand(opts = {}) {
|
|
7668
|
+
return `npx ${npxSkillsArgv(opts).join(" ")}`;
|
|
7669
|
+
}
|
|
7127
7670
|
function kobeSkillPaths(opts = {}) {
|
|
7128
7671
|
const home = opts.home ?? homedir9();
|
|
7129
7672
|
const cwd = opts.cwd ?? process.cwd();
|
|
7130
7673
|
return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
|
|
7131
7674
|
}
|
|
7132
|
-
function
|
|
7133
|
-
|
|
7675
|
+
function parseSkillVersion(content) {
|
|
7676
|
+
const m = content.match(/kobe-skill-version:\s*(\d+)/);
|
|
7677
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
7678
|
+
}
|
|
7679
|
+
function kobeSkillState(opts) {
|
|
7680
|
+
const path6 = kobeSkillPaths(opts).find((p) => existsSync4(p));
|
|
7681
|
+
if (!path6) {
|
|
7682
|
+
return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
|
|
7683
|
+
}
|
|
7684
|
+
let installedVersion = null;
|
|
7685
|
+
try {
|
|
7686
|
+
installedVersion = parseSkillVersion(readFileSync6(path6, "utf8"));
|
|
7687
|
+
} catch {
|
|
7688
|
+
installedVersion = null;
|
|
7689
|
+
}
|
|
7690
|
+
const stale = installedVersion === null || installedVersion < KOBE_SKILL_VERSION;
|
|
7691
|
+
return { installed: true, installedVersion, currentVersion: KOBE_SKILL_VERSION, stale };
|
|
7134
7692
|
}
|
|
7135
7693
|
function maybeHintSkillInstall() {
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7694
|
+
const state = kobeSkillState();
|
|
7695
|
+
if (!state.installed) {
|
|
7696
|
+
if (getPersistedString(HINT_SEEN_KEY) === "1")
|
|
7697
|
+
return;
|
|
7698
|
+
setPersistedString(HINT_SEEN_KEY, "1");
|
|
7699
|
+
process.stderr.write(`
|
|
7700
|
+
kobe: the kobe agent skill isn't installed \u2014 install it so your coding agent can drive kobe via \`kobe api\`:
|
|
7701
|
+
${SKILL_INSTALL_COMMAND}
|
|
7702
|
+
(wraps \`${npxSkillsCommand()}\`; check anytime with \`kobe doctor\`)
|
|
7703
|
+
|
|
7704
|
+
`);
|
|
7139
7705
|
return;
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7706
|
+
}
|
|
7707
|
+
if (state.stale) {
|
|
7708
|
+
const key = `${HINT_SEEN_KEY}:v${state.currentVersion}`;
|
|
7709
|
+
if (getPersistedString(key) === "1")
|
|
7710
|
+
return;
|
|
7711
|
+
setPersistedString(key, "1");
|
|
7712
|
+
const was = state.installedVersion === null ? "an older" : `v${state.installedVersion}`;
|
|
7713
|
+
process.stderr.write(`
|
|
7714
|
+
kobe: your kobe agent skill is out of date (${was}; this kobe wants v${state.currentVersion}) \u2014 refresh it so \`kobe api\` guidance matches:
|
|
7143
7715
|
${SKILL_INSTALL_COMMAND}
|
|
7144
|
-
(check anytime with \`kobe doctor\`)
|
|
7145
7716
|
|
|
7146
7717
|
`);
|
|
7718
|
+
}
|
|
7147
7719
|
}
|
|
7148
|
-
var SKILL_REL_PATH = ".claude/skills/kobe/SKILL.md", SKILL_INSTALL_COMMAND = "
|
|
7720
|
+
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";
|
|
7149
7721
|
var init_skill_install = __esm(() => {
|
|
7150
7722
|
init_repos();
|
|
7151
7723
|
});
|
|
@@ -7154,9 +7726,10 @@ var init_skill_install = __esm(() => {
|
|
|
7154
7726
|
var exports_maintenance = {};
|
|
7155
7727
|
__export(exports_maintenance, {
|
|
7156
7728
|
runResetSubcommand: () => runResetSubcommand,
|
|
7729
|
+
runReloadSubcommand: () => runReloadSubcommand,
|
|
7157
7730
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
7158
7731
|
});
|
|
7159
|
-
import { existsSync as existsSync5, readFileSync as
|
|
7732
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, statSync } from "fs";
|
|
7160
7733
|
import { unlink as unlink6 } from "fs/promises";
|
|
7161
7734
|
import { join as join8 } from "path";
|
|
7162
7735
|
import { createInterface } from "readline";
|
|
@@ -7205,7 +7778,7 @@ function describeFile(path6) {
|
|
|
7205
7778
|
}
|
|
7206
7779
|
function taskCount(tasksPath) {
|
|
7207
7780
|
try {
|
|
7208
|
-
const parsed = JSON.parse(
|
|
7781
|
+
const parsed = JSON.parse(readFileSync7(tasksPath, "utf8"));
|
|
7209
7782
|
return Array.isArray(parsed.tasks) ? parsed.tasks.length : null;
|
|
7210
7783
|
} catch {
|
|
7211
7784
|
return null;
|
|
@@ -7213,7 +7786,7 @@ function taskCount(tasksPath) {
|
|
|
7213
7786
|
}
|
|
7214
7787
|
function tailFile(path6, n) {
|
|
7215
7788
|
try {
|
|
7216
|
-
const lines =
|
|
7789
|
+
const lines = readFileSync7(path6, "utf8").split(`
|
|
7217
7790
|
`).filter((l) => l.trim().length > 0);
|
|
7218
7791
|
return lines.slice(-n).join(`
|
|
7219
7792
|
`);
|
|
@@ -7293,11 +7866,16 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
7293
7866
|
out.push("tmux: \u2717 not found on PATH (task sessions need tmux)");
|
|
7294
7867
|
}
|
|
7295
7868
|
out.push("");
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
out.push(
|
|
7869
|
+
const skill = kobeSkillState();
|
|
7870
|
+
if (!skill.installed) {
|
|
7871
|
+
out.push("skill: \u2717 kobe agent skill not installed (optional \u2014 lets a coding agent drive `kobe api`)");
|
|
7872
|
+
out.push(` \u2192 ${SKILL_INSTALL_COMMAND}`);
|
|
7873
|
+
} else if (skill.stale) {
|
|
7874
|
+
const was = skill.installedVersion === null ? "unstamped" : `v${skill.installedVersion}`;
|
|
7875
|
+
out.push(`skill: \u26A0 kobe agent skill out of date (${was}; this kobe wants v${skill.currentVersion})`);
|
|
7300
7876
|
out.push(` \u2192 ${SKILL_INSTALL_COMMAND}`);
|
|
7877
|
+
} else {
|
|
7878
|
+
out.push(`skill: \u2713 kobe agent skill installed (v${skill.installedVersion})`);
|
|
7301
7879
|
}
|
|
7302
7880
|
out.push("");
|
|
7303
7881
|
const count = taskCount(tasksPath);
|
|
@@ -7407,6 +7985,54 @@ Stop daemon and kill kobe sessions? [y/N] `);
|
|
|
7407
7985
|
console.log(`
|
|
7408
7986
|
kobe: reset complete. Relaunch kobe to start fresh.`);
|
|
7409
7987
|
}
|
|
7988
|
+
async function runReloadSubcommand(argv = []) {
|
|
7989
|
+
if (argv.includes("--help") || argv.includes("-h") || argv.includes("help")) {
|
|
7990
|
+
process.stdout.write([
|
|
7991
|
+
"Usage: kobe reload",
|
|
7992
|
+
"",
|
|
7993
|
+
"Restart kobe's Tasks + Ops panes in every live session, in place.",
|
|
7994
|
+
"Picks up new kobe code without `kobe reset` \u2014 the engine (claude)",
|
|
7995
|
+
"panes and your running turns are never touched. Takes no options.",
|
|
7996
|
+
""
|
|
7997
|
+
].join(`
|
|
7998
|
+
`));
|
|
7999
|
+
return;
|
|
8000
|
+
}
|
|
8001
|
+
const unknown = argv.find((a) => a.length > 0);
|
|
8002
|
+
if (unknown !== undefined) {
|
|
8003
|
+
process.stderr.write(`kobe reload: unexpected argument "${unknown}"
|
|
8004
|
+
|
|
8005
|
+
Usage: kobe reload (takes no options)
|
|
8006
|
+
`);
|
|
8007
|
+
process.exit(2);
|
|
8008
|
+
}
|
|
8009
|
+
if (!await tmuxAvailable()) {
|
|
8010
|
+
console.log("kobe reload: tmux is not installed \u2014 no panes to reload");
|
|
8011
|
+
return;
|
|
8012
|
+
}
|
|
8013
|
+
const { code, stdout } = await tmuxQuiet(["list-sessions", "-F", "#{session_name}"]);
|
|
8014
|
+
if (code !== 0) {
|
|
8015
|
+
console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
|
|
8016
|
+
return;
|
|
8017
|
+
}
|
|
8018
|
+
const sessions = stdout.split(`
|
|
8019
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
8020
|
+
if (sessions.length === 0) {
|
|
8021
|
+
console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
|
|
8022
|
+
return;
|
|
8023
|
+
}
|
|
8024
|
+
const { refreshKobeWorkspacePanes: refreshKobeWorkspacePanes2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
8025
|
+
let reloaded = 0;
|
|
8026
|
+
for (const session of sessions) {
|
|
8027
|
+
try {
|
|
8028
|
+
await refreshKobeWorkspacePanes2(session);
|
|
8029
|
+
reloaded++;
|
|
8030
|
+
} catch (err) {
|
|
8031
|
+
console.error(` failed to reload session "${session}": ${err instanceof Error ? err.message : String(err)}`);
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
console.log(`kobe: reloaded Tasks/Ops panes in ${reloaded}/${sessions.length} session(s) \u2014 engine panes untouched`);
|
|
8035
|
+
}
|
|
7410
8036
|
var init_maintenance = __esm(() => {
|
|
7411
8037
|
init_client();
|
|
7412
8038
|
init_lifecycle();
|
|
@@ -7417,6 +8043,106 @@ var init_maintenance = __esm(() => {
|
|
|
7417
8043
|
init_client2();
|
|
7418
8044
|
});
|
|
7419
8045
|
|
|
8046
|
+
// src/cli/skill-cmd.ts
|
|
8047
|
+
var exports_skill_cmd = {};
|
|
8048
|
+
__export(exports_skill_cmd, {
|
|
8049
|
+
runSkillSubcommand: () => runSkillSubcommand
|
|
8050
|
+
});
|
|
8051
|
+
function skillUsage() {
|
|
8052
|
+
return [
|
|
8053
|
+
"usage: kobe skill <verb>",
|
|
8054
|
+
"",
|
|
8055
|
+
"verbs:",
|
|
8056
|
+
" install [--agent NAME] Install the kobe agent skill (wraps `npx skills add`)",
|
|
8057
|
+
" status Show whether the skill is installed",
|
|
8058
|
+
" command [--agent NAME] Print the underlying npx command without running it",
|
|
8059
|
+
"",
|
|
8060
|
+
`The skill teaches a coding agent how to drive \`kobe api\`. Default agent: ${DEFAULT_SKILL_AGENT}.`
|
|
8061
|
+
].join(`
|
|
8062
|
+
`);
|
|
8063
|
+
}
|
|
8064
|
+
function parseAgent(rest) {
|
|
8065
|
+
let agent = DEFAULT_SKILL_AGENT;
|
|
8066
|
+
for (let i = 0;i < rest.length; i++) {
|
|
8067
|
+
const arg = rest[i];
|
|
8068
|
+
if (arg === "--agent") {
|
|
8069
|
+
const v = rest[i + 1];
|
|
8070
|
+
if (!v || v.startsWith("--")) {
|
|
8071
|
+
process.stderr.write(`kobe skill: --agent requires a value
|
|
8072
|
+
`);
|
|
8073
|
+
process.exit(2);
|
|
8074
|
+
}
|
|
8075
|
+
agent = v;
|
|
8076
|
+
i++;
|
|
8077
|
+
} else if (arg.startsWith("--agent=")) {
|
|
8078
|
+
agent = arg.slice("--agent=".length);
|
|
8079
|
+
} else {
|
|
8080
|
+
process.stderr.write(`kobe skill: unknown flag "${arg}"
|
|
8081
|
+
|
|
8082
|
+
${skillUsage()}
|
|
8083
|
+
`);
|
|
8084
|
+
process.exit(2);
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
return agent;
|
|
8088
|
+
}
|
|
8089
|
+
async function runSkillSubcommand(argv) {
|
|
8090
|
+
const [verb, ...rest] = argv;
|
|
8091
|
+
if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
|
|
8092
|
+
process.stdout.write(`${skillUsage()}
|
|
8093
|
+
`);
|
|
8094
|
+
if (!verb)
|
|
8095
|
+
process.exitCode = 2;
|
|
8096
|
+
return;
|
|
8097
|
+
}
|
|
8098
|
+
if (!SKILL_VERBS.includes(verb)) {
|
|
8099
|
+
process.stderr.write(`kobe skill: unknown verb "${verb}"
|
|
8100
|
+
|
|
8101
|
+
${skillUsage()}
|
|
8102
|
+
`);
|
|
8103
|
+
process.exit(2);
|
|
8104
|
+
}
|
|
8105
|
+
if (verb === "status") {
|
|
8106
|
+
const state = kobeSkillState();
|
|
8107
|
+
const [userPath, projectPath] = kobeSkillPaths();
|
|
8108
|
+
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})`;
|
|
8109
|
+
process.stdout.write([
|
|
8110
|
+
`kobe skill: ${head}`,
|
|
8111
|
+
` looked in: ${userPath}`,
|
|
8112
|
+
` ${projectPath}`,
|
|
8113
|
+
state.installed && !state.stale ? "" : " \u2192 run `kobe skill install` to install / refresh",
|
|
8114
|
+
""
|
|
8115
|
+
].join(`
|
|
8116
|
+
`));
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
8119
|
+
if (verb === "command") {
|
|
8120
|
+
process.stdout.write(`${npxSkillsCommand({ agent: parseAgent(rest) })}
|
|
8121
|
+
`);
|
|
8122
|
+
return;
|
|
8123
|
+
}
|
|
8124
|
+
const agent = parseAgent(rest);
|
|
8125
|
+
const args = npxSkillsArgv({ agent });
|
|
8126
|
+
process.stdout.write(`kobe skill: running \`npx ${args.join(" ")}\`
|
|
8127
|
+
`);
|
|
8128
|
+
const proc = Bun.spawn(["npx", ...args], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
|
|
8129
|
+
const code = await proc.exited;
|
|
8130
|
+
if (code !== 0) {
|
|
8131
|
+
process.stderr.write(`
|
|
8132
|
+
kobe skill install failed (npx exited ${code}). Is \`npx\` on PATH and are you online?
|
|
8133
|
+
` + `You can run it yourself: ${npxSkillsCommand({ agent })}
|
|
8134
|
+
`);
|
|
8135
|
+
process.exit(code || 1);
|
|
8136
|
+
}
|
|
8137
|
+
process.stdout.write(`kobe skill: installed.
|
|
8138
|
+
`);
|
|
8139
|
+
}
|
|
8140
|
+
var SKILL_VERBS;
|
|
8141
|
+
var init_skill_cmd = __esm(() => {
|
|
8142
|
+
init_skill_install();
|
|
8143
|
+
SKILL_VERBS = ["install", "status", "command"];
|
|
8144
|
+
});
|
|
8145
|
+
|
|
7420
8146
|
// ../../node_modules/.bun/entities@7.0.1/node_modules/entities/dist/esm/decode-codepoint.js
|
|
7421
8147
|
function replaceCodePoint(codePoint) {
|
|
7422
8148
|
var _a2;
|
|
@@ -8706,6 +9432,7 @@ class RemoteOrchestrator {
|
|
|
8706
9432
|
setConnectionState;
|
|
8707
9433
|
ensureReachable;
|
|
8708
9434
|
role;
|
|
9435
|
+
reconnecting = false;
|
|
8709
9436
|
constructor(client, options = {}) {
|
|
8710
9437
|
this.client = client;
|
|
8711
9438
|
const [tasks, setTasks] = createSignal([]);
|
|
@@ -8723,7 +9450,37 @@ class RemoteOrchestrator {
|
|
|
8723
9450
|
this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
|
|
8724
9451
|
this.role = options.role ?? "pane";
|
|
8725
9452
|
this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
|
|
8726
|
-
this.client.onLifecycle("close", () =>
|
|
9453
|
+
this.client.onLifecycle("close", () => {
|
|
9454
|
+
this.setConnectionState("disconnected");
|
|
9455
|
+
if (this.role === "pane") {
|
|
9456
|
+
logClient("orch", "daemon socket closed \u2014 starting non-spawning reconnect loop");
|
|
9457
|
+
this.reconnectLoop();
|
|
9458
|
+
}
|
|
9459
|
+
});
|
|
9460
|
+
}
|
|
9461
|
+
async reconnectLoop() {
|
|
9462
|
+
if (this.reconnecting)
|
|
9463
|
+
return;
|
|
9464
|
+
this.reconnecting = true;
|
|
9465
|
+
let delayMs = 500;
|
|
9466
|
+
let attempt = 0;
|
|
9467
|
+
while (!this.client.isDisposed) {
|
|
9468
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
9469
|
+
if (this.client.isDisposed)
|
|
9470
|
+
break;
|
|
9471
|
+
attempt++;
|
|
9472
|
+
try {
|
|
9473
|
+
await this.init();
|
|
9474
|
+
logClient("orch", `reconnected and re-subscribed after ${attempt} attempt(s) \u2014 task list re-synced`);
|
|
9475
|
+
this.reconnecting = false;
|
|
9476
|
+
return;
|
|
9477
|
+
} catch (err) {
|
|
9478
|
+
if (attempt === 1 || attempt % 10 === 0)
|
|
9479
|
+
logClientError("orch-reconnect", err);
|
|
9480
|
+
delayMs = Math.min(delayMs * 2, 3000);
|
|
9481
|
+
}
|
|
9482
|
+
}
|
|
9483
|
+
this.reconnecting = false;
|
|
8727
9484
|
}
|
|
8728
9485
|
async init() {
|
|
8729
9486
|
const hello = await this.client.request("hello", {
|
|
@@ -8744,6 +9501,7 @@ class RemoteOrchestrator {
|
|
|
8744
9501
|
this.setTasks(hello.tasks.map(deserializeTask));
|
|
8745
9502
|
await this.client.subscribe({ role: this.role });
|
|
8746
9503
|
this.setConnectionState("online");
|
|
9504
|
+
logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
|
|
8747
9505
|
}
|
|
8748
9506
|
connectionStateSignal() {
|
|
8749
9507
|
return this.connectionStateAcc;
|
|
@@ -8877,6 +9635,7 @@ function deserializeTask(s) {
|
|
|
8877
9635
|
var init_remote_orchestrator = __esm(() => {
|
|
8878
9636
|
init_dev();
|
|
8879
9637
|
init_protocol();
|
|
9638
|
+
init_client_log();
|
|
8880
9639
|
init_daemon_process();
|
|
8881
9640
|
});
|
|
8882
9641
|
|
|
@@ -12854,7 +13613,7 @@ var init_binary3 = __esm(() => {
|
|
|
12854
13613
|
});
|
|
12855
13614
|
|
|
12856
13615
|
// src/engine/account-detect.ts
|
|
12857
|
-
import { readFileSync as
|
|
13616
|
+
import { readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
12858
13617
|
import { homedir as homedir14 } from "os";
|
|
12859
13618
|
import path10 from "path";
|
|
12860
13619
|
function claudeGlobalConfigPath(env, home) {
|
|
@@ -13067,7 +13826,7 @@ var init_account_detect = __esm(() => {
|
|
|
13067
13826
|
return null;
|
|
13068
13827
|
throw err;
|
|
13069
13828
|
}
|
|
13070
|
-
return
|
|
13829
|
+
return readFileSync8(p, "utf8");
|
|
13071
13830
|
},
|
|
13072
13831
|
env(name) {
|
|
13073
13832
|
return process.env[name];
|
|
@@ -14618,12 +15377,12 @@ var init_focus = __esm(() => {
|
|
|
14618
15377
|
});
|
|
14619
15378
|
|
|
14620
15379
|
// src/tui/context/kv.tsx
|
|
14621
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
14622
|
-
import { dirname as
|
|
15380
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
15381
|
+
import { dirname as dirname6 } from "path";
|
|
14623
15382
|
function loadInitial() {
|
|
14624
15383
|
const statePath2 = kvStatePath();
|
|
14625
15384
|
try {
|
|
14626
|
-
const text =
|
|
15385
|
+
const text = readFileSync9(statePath2, "utf8");
|
|
14627
15386
|
const parsed = JSON.parse(text);
|
|
14628
15387
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14629
15388
|
return parsed;
|
|
@@ -14647,7 +15406,7 @@ var init_kv = __esm(() => {
|
|
|
14647
15406
|
function writeNow(label) {
|
|
14648
15407
|
const statePath2 = kvStatePath();
|
|
14649
15408
|
try {
|
|
14650
|
-
mkdirSync4(
|
|
15409
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14651
15410
|
recursive: true
|
|
14652
15411
|
});
|
|
14653
15412
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14702,7 +15461,7 @@ var init_kv = __esm(() => {
|
|
|
14702
15461
|
}
|
|
14703
15462
|
const statePath2 = kvStatePath();
|
|
14704
15463
|
try {
|
|
14705
|
-
mkdirSync4(
|
|
15464
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14706
15465
|
recursive: true
|
|
14707
15466
|
});
|
|
14708
15467
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14719,10 +15478,10 @@ var init_kv = __esm(() => {
|
|
|
14719
15478
|
});
|
|
14720
15479
|
|
|
14721
15480
|
// src/tui/lib/persisted-ui-prefs.ts
|
|
14722
|
-
import { readFileSync as
|
|
15481
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
14723
15482
|
function readPersistedUiPrefs(fallbackTheme) {
|
|
14724
15483
|
try {
|
|
14725
|
-
const parsed = JSON.parse(
|
|
15484
|
+
const parsed = JSON.parse(readFileSync10(kvStatePath(), "utf8"));
|
|
14726
15485
|
const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
|
|
14727
15486
|
const transparent = parsed.transparentBackground === true;
|
|
14728
15487
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
@@ -16102,7 +16861,7 @@ function Sidebar(props) {
|
|
|
16102
16861
|
return readWorktreeChanges(task.worktreePath);
|
|
16103
16862
|
});
|
|
16104
16863
|
const titleText = isMain ? repoBasename(task.repo) : task.title;
|
|
16105
|
-
const loading = () => isLive() || task.status === "in_progress";
|
|
16864
|
+
const loading = () => isLive() || !isMain && task.status === "in_progress";
|
|
16106
16865
|
const subtitleText = createMemo(() => {
|
|
16107
16866
|
if (task.branch.length > 0)
|
|
16108
16867
|
return truncateBranchLabel(task.branch, subtitleBudget());
|
|
@@ -17042,6 +17801,7 @@ function ShortcutHints() {
|
|
|
17042
17801
|
})();
|
|
17043
17802
|
}
|
|
17044
17803
|
async function startTasksPane(opts = {}) {
|
|
17804
|
+
setClientLogContext("tasks");
|
|
17045
17805
|
for (const {
|
|
17046
17806
|
name,
|
|
17047
17807
|
theme
|
|
@@ -17056,21 +17816,28 @@ async function startTasksPane(opts = {}) {
|
|
|
17056
17816
|
const [fileTasks, setFileTasks] = createSignal(store2.list());
|
|
17057
17817
|
let orch = null;
|
|
17058
17818
|
try {
|
|
17059
|
-
const client = await
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17819
|
+
const client = await connectIfRunning();
|
|
17820
|
+
if (client) {
|
|
17821
|
+
const remote = new RemoteOrchestrator(client);
|
|
17822
|
+
await remote.init();
|
|
17823
|
+
orch = remote;
|
|
17824
|
+
} else {
|
|
17825
|
+
logClient("tasks-boot", "no daemon running \u2014 polling tasks.json (a gui owns daemon lifecycle)");
|
|
17826
|
+
}
|
|
17063
17827
|
} catch (err) {
|
|
17064
|
-
|
|
17828
|
+
logClientError("tasks-boot", err);
|
|
17829
|
+
logClient("tasks-boot", "daemon subscribe failed \u2014 polling tasks.json");
|
|
17065
17830
|
}
|
|
17066
|
-
const tasks = orch ? orch.tasksSignal() : fileTasks;
|
|
17831
|
+
const tasks = () => orch && orch.connectionStateSignal()() === "online" ? orch.tasksSignal()() : fileTasks();
|
|
17067
17832
|
const reload = async () => {
|
|
17068
|
-
if (orch)
|
|
17069
|
-
return;
|
|
17070
17833
|
await store2.load();
|
|
17071
17834
|
setFileTasks(store2.list());
|
|
17072
17835
|
};
|
|
17073
|
-
const timer =
|
|
17836
|
+
const timer = setInterval(() => {
|
|
17837
|
+
if (orch && orch.connectionStateSignal()() === "online")
|
|
17838
|
+
return;
|
|
17839
|
+
reload();
|
|
17840
|
+
}, RELOAD_MS);
|
|
17074
17841
|
await render(() => createComponent2(ThemeProvider, {
|
|
17075
17842
|
mode: "dark",
|
|
17076
17843
|
get theme() {
|
|
@@ -17130,6 +17897,7 @@ var init_host = __esm(() => {
|
|
|
17130
17897
|
init_client2();
|
|
17131
17898
|
init_solid();
|
|
17132
17899
|
init_dev();
|
|
17900
|
+
init_client_log();
|
|
17133
17901
|
init_daemon_process();
|
|
17134
17902
|
init_remote_orchestrator();
|
|
17135
17903
|
init_interactive_command();
|
|
@@ -19387,6 +20155,7 @@ function OpsApp(props) {
|
|
|
19387
20155
|
});
|
|
19388
20156
|
}
|
|
19389
20157
|
async function startOpsHost(args) {
|
|
20158
|
+
setClientLogContext("ops");
|
|
19390
20159
|
for (const {
|
|
19391
20160
|
name,
|
|
19392
20161
|
theme
|
|
@@ -19658,6 +20427,7 @@ var init_host5 = __esm(() => {
|
|
|
19658
20427
|
init_solid();
|
|
19659
20428
|
init_solid();
|
|
19660
20429
|
init_invocation();
|
|
20430
|
+
init_client_log();
|
|
19661
20431
|
init_turn_detector();
|
|
19662
20432
|
init_activity();
|
|
19663
20433
|
init_client2();
|
|
@@ -19728,6 +20498,7 @@ async function ensureRepos(orchestrator) {
|
|
|
19728
20498
|
return repos[0] ?? resolve6(process.cwd());
|
|
19729
20499
|
}
|
|
19730
20500
|
async function startDirectTmux() {
|
|
20501
|
+
setClientLogContext("gui");
|
|
19731
20502
|
if (!await tmuxAvailable()) {
|
|
19732
20503
|
console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
|
|
19733
20504
|
process.exitCode = 1;
|
|
@@ -19797,6 +20568,7 @@ async function startDirectTmux() {
|
|
|
19797
20568
|
}
|
|
19798
20569
|
}
|
|
19799
20570
|
var init_direct = __esm(() => {
|
|
20571
|
+
init_client_log();
|
|
19800
20572
|
init_daemon_process();
|
|
19801
20573
|
init_remote_orchestrator();
|
|
19802
20574
|
init_interactive_command();
|
|
@@ -22080,9 +22852,11 @@ function topLevelUsage() {
|
|
|
22080
22852
|
" api <verb> Scriptable RPC surface for agents (see `kobe api --help`)",
|
|
22081
22853
|
" daemon <verb> Manage the daemon (start|stop|status|restart)",
|
|
22082
22854
|
" theme <verb> Manage user themes (list|add|remove)",
|
|
22855
|
+
" skill <verb> Install the kobe agent skill (install|status|command)",
|
|
22083
22856
|
" update [target] Self-update kobe",
|
|
22084
22857
|
" doctor Diagnose daemon / tmux / state (read-only)",
|
|
22085
22858
|
" reset [--hard] Recover a wedged install",
|
|
22859
|
+
" reload Restart Tasks/Ops panes in place (engine untouched)",
|
|
22086
22860
|
" kill-sessions Tear down kobe's tmux server (dev reset)",
|
|
22087
22861
|
"",
|
|
22088
22862
|
"Options:",
|
|
@@ -22335,6 +23109,16 @@ async function main() {
|
|
|
22335
23109
|
await runResetSubcommand2(rest);
|
|
22336
23110
|
return;
|
|
22337
23111
|
}
|
|
23112
|
+
if (subcommand === "reload") {
|
|
23113
|
+
const { runReloadSubcommand: runReloadSubcommand2 } = await Promise.resolve().then(() => (init_maintenance(), exports_maintenance));
|
|
23114
|
+
await runReloadSubcommand2(rest);
|
|
23115
|
+
return;
|
|
23116
|
+
}
|
|
23117
|
+
if (subcommand === "skill") {
|
|
23118
|
+
const { runSkillSubcommand: runSkillSubcommand2 } = await Promise.resolve().then(() => (init_skill_cmd(), exports_skill_cmd));
|
|
23119
|
+
await runSkillSubcommand2(rest);
|
|
23120
|
+
return;
|
|
23121
|
+
}
|
|
22338
23122
|
if (subcommand === "new-chattab") {
|
|
22339
23123
|
const flags = parseOpsFlags(rest);
|
|
22340
23124
|
const session = flags.session;
|