@sma1lboy/kobe 0.7.2 → 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 +697 -99
- 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",
|
|
@@ -6327,32 +6327,74 @@ var init_worktree_changes = __esm(() => {
|
|
|
6327
6327
|
// src/cli/api-cmd.ts
|
|
6328
6328
|
var exports_api_cmd = {};
|
|
6329
6329
|
__export(exports_api_cmd, {
|
|
6330
|
+
verbSchema: () => verbSchema,
|
|
6331
|
+
verbHelp: () => verbHelp,
|
|
6332
|
+
validateAgainstSpec: () => validateAgainstSpec,
|
|
6333
|
+
schemaIndex: () => schemaIndex,
|
|
6330
6334
|
runApiSubcommand: () => runApiSubcommand,
|
|
6331
6335
|
parseFlags: () => parseFlags,
|
|
6332
6336
|
parseAgentsSpec: () => parseAgentsSpec,
|
|
6337
|
+
fullSchema: () => fullSchema,
|
|
6338
|
+
findVerb: () => findVerb,
|
|
6333
6339
|
apiUsage: () => apiUsage,
|
|
6340
|
+
VERB_GROUPS: () => VERB_GROUPS,
|
|
6341
|
+
VERBS: () => VERBS,
|
|
6334
6342
|
FANOUT_CAP: () => FANOUT_CAP,
|
|
6335
6343
|
ApiError: () => ApiError,
|
|
6336
|
-
API_VERBS: () => API_VERBS
|
|
6344
|
+
API_VERBS: () => API_VERBS,
|
|
6345
|
+
API_SCHEMA_VERSION: () => API_SCHEMA_VERSION
|
|
6337
6346
|
});
|
|
6338
6347
|
import { resolve as resolve4 } from "path";
|
|
6339
|
-
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) {
|
|
6340
6376
|
const flags = new Map;
|
|
6341
6377
|
let pretty = false;
|
|
6378
|
+
let help = false;
|
|
6342
6379
|
for (let i = 0;i < argv.length; i++) {
|
|
6343
6380
|
const arg = argv[i];
|
|
6344
|
-
if (!arg.startsWith("--")) {
|
|
6381
|
+
if (!arg.startsWith("--") && arg !== "-h") {
|
|
6345
6382
|
throw new ApiError(`unexpected positional arg: ${arg}`, "BAD_FLAG");
|
|
6346
6383
|
}
|
|
6384
|
+
if (arg === "-h") {
|
|
6385
|
+
help = true;
|
|
6386
|
+
continue;
|
|
6387
|
+
}
|
|
6347
6388
|
const eq = arg.indexOf("=");
|
|
6348
6389
|
if (eq !== -1) {
|
|
6349
6390
|
const key2 = arg.slice(2, eq);
|
|
6350
6391
|
const value = arg.slice(eq + 1);
|
|
6351
|
-
if (key2 === "pretty")
|
|
6392
|
+
if (key2 === "pretty")
|
|
6352
6393
|
pretty = value !== "false" && value !== "0";
|
|
6353
|
-
|
|
6394
|
+
else if (key2 === "help")
|
|
6395
|
+
help = value !== "false" && value !== "0";
|
|
6396
|
+
else
|
|
6354
6397
|
flags.set(key2, value);
|
|
6355
|
-
}
|
|
6356
6398
|
continue;
|
|
6357
6399
|
}
|
|
6358
6400
|
const key = arg.slice(2);
|
|
@@ -6360,6 +6402,14 @@ function parseFlags(argv) {
|
|
|
6360
6402
|
pretty = true;
|
|
6361
6403
|
continue;
|
|
6362
6404
|
}
|
|
6405
|
+
if (key === "help") {
|
|
6406
|
+
help = true;
|
|
6407
|
+
continue;
|
|
6408
|
+
}
|
|
6409
|
+
if (booleanFlags.has(key)) {
|
|
6410
|
+
flags.set(key, "true");
|
|
6411
|
+
continue;
|
|
6412
|
+
}
|
|
6363
6413
|
const next = argv[i + 1];
|
|
6364
6414
|
if (next === undefined || next.startsWith("--")) {
|
|
6365
6415
|
throw new ApiError(`flag --${key} requires a value`, "BAD_FLAG");
|
|
@@ -6367,19 +6417,50 @@ function parseFlags(argv) {
|
|
|
6367
6417
|
flags.set(key, next);
|
|
6368
6418
|
i += 1;
|
|
6369
6419
|
}
|
|
6370
|
-
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
|
+
}
|
|
6371
6447
|
}
|
|
6372
6448
|
function required(flags, key) {
|
|
6373
6449
|
const v = flags.get(key);
|
|
6374
|
-
if (v === undefined || v.length === 0)
|
|
6450
|
+
if (v === undefined || v.length === 0)
|
|
6375
6451
|
throw new ApiError(`--${key} is required`, "MISSING_FLAG");
|
|
6376
|
-
}
|
|
6377
6452
|
return v;
|
|
6378
6453
|
}
|
|
6379
6454
|
function optional(flags, key) {
|
|
6380
6455
|
const v = flags.get(key);
|
|
6381
6456
|
return v && v.length > 0 ? v : undefined;
|
|
6382
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
|
+
}
|
|
6383
6464
|
function optionalVendor2(flags) {
|
|
6384
6465
|
const raw = optional(flags, "vendor");
|
|
6385
6466
|
if (raw === undefined)
|
|
@@ -6389,16 +6470,28 @@ function optionalVendor2(flags) {
|
|
|
6389
6470
|
}
|
|
6390
6471
|
return raw;
|
|
6391
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
|
+
}
|
|
6392
6483
|
function optionalPositiveInt(flags, key) {
|
|
6393
6484
|
const raw = optional(flags, key);
|
|
6394
6485
|
if (raw === undefined)
|
|
6395
6486
|
return;
|
|
6396
6487
|
const n = Number.parseInt(raw, 10);
|
|
6397
|
-
if (!Number.isInteger(n) || n <= 0)
|
|
6488
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
6398
6489
|
throw new ApiError(`--${key} must be a positive integer`, "BAD_FLAG");
|
|
6399
|
-
}
|
|
6400
6490
|
return n;
|
|
6401
6491
|
}
|
|
6492
|
+
function resolveRepoFlag(repo) {
|
|
6493
|
+
return resolve4(process.cwd(), repo);
|
|
6494
|
+
}
|
|
6402
6495
|
function parseAgentsSpec(spec) {
|
|
6403
6496
|
const out = [];
|
|
6404
6497
|
for (const part of spec.split(",")) {
|
|
@@ -6423,19 +6516,102 @@ function parseAgentsSpec(spec) {
|
|
|
6423
6516
|
throw new ApiError('--agents specified no agents (e.g. "claude:2,codex:1")', "BAD_FLAG");
|
|
6424
6517
|
return out;
|
|
6425
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
|
+
}
|
|
6426
6604
|
function apiUsage() {
|
|
6605
|
+
const rows = VERBS.map((v) => ` ${v.name.padEnd(18)} ${v.summary}`);
|
|
6427
6606
|
return [
|
|
6428
|
-
"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",
|
|
6429
6610
|
"",
|
|
6430
6611
|
"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",
|
|
6612
|
+
...rows,
|
|
6437
6613
|
"",
|
|
6438
|
-
"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)."
|
|
6439
6615
|
].join(`
|
|
6440
6616
|
`);
|
|
6441
6617
|
}
|
|
@@ -6492,12 +6668,22 @@ async function resolveActiveTaskId(client) {
|
|
|
6492
6668
|
}
|
|
6493
6669
|
return activeId;
|
|
6494
6670
|
}
|
|
6495
|
-
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");
|
|
6496
6679
|
const { flags } = parsed;
|
|
6497
|
-
const payload = { repo: required(flags, "repo") };
|
|
6680
|
+
const payload = { repo: resolveRepoFlag(required(flags, "repo")) };
|
|
6498
6681
|
const title = optional(flags, "title");
|
|
6499
6682
|
if (title)
|
|
6500
6683
|
payload.title = title;
|
|
6684
|
+
const branch = optional(flags, "branch");
|
|
6685
|
+
if (branch)
|
|
6686
|
+
payload.branch = branch;
|
|
6501
6687
|
const baseRef = optional(flags, "base-branch");
|
|
6502
6688
|
if (baseRef)
|
|
6503
6689
|
payload.baseRef = baseRef;
|
|
@@ -6505,25 +6691,26 @@ async function spawnTask(client, parsed) {
|
|
|
6505
6691
|
if (vendor)
|
|
6506
6692
|
payload.vendor = vendor;
|
|
6507
6693
|
const res = await client.request("task.create", payload);
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
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;
|
|
6511
6704
|
}
|
|
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
|
-
};
|
|
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 };
|
|
6525
6710
|
}
|
|
6526
6711
|
async function send(client, parsed) {
|
|
6712
|
+
if (!client)
|
|
6713
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6527
6714
|
const { flags } = parsed;
|
|
6528
6715
|
const prompt = required(flags, "prompt");
|
|
6529
6716
|
let taskId = optional(flags, "task-id");
|
|
@@ -6550,17 +6737,50 @@ async function send(client, parsed) {
|
|
|
6550
6737
|
};
|
|
6551
6738
|
}
|
|
6552
6739
|
async function getTask(client, parsed) {
|
|
6740
|
+
if (!client)
|
|
6741
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6553
6742
|
const taskId = required(parsed.flags, "task-id");
|
|
6554
6743
|
const res = await client.request("task.get", { taskId });
|
|
6555
6744
|
const running = await sessionExists(tmuxSessionName(taskId));
|
|
6556
6745
|
return { task: res.task, running };
|
|
6557
6746
|
}
|
|
6558
6747
|
async function list(client) {
|
|
6748
|
+
if (!client)
|
|
6749
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6559
6750
|
return client.request("task.list");
|
|
6560
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
|
+
}
|
|
6561
6779
|
async function fanOut(client, parsed) {
|
|
6780
|
+
if (!client)
|
|
6781
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6562
6782
|
const { flags } = parsed;
|
|
6563
|
-
const repo = required(flags, "repo");
|
|
6783
|
+
const repo = resolveRepoFlag(required(flags, "repo"));
|
|
6564
6784
|
const prompt = required(flags, "prompt");
|
|
6565
6785
|
const title = optional(flags, "title");
|
|
6566
6786
|
const baseRef = optional(flags, "base-branch");
|
|
@@ -6589,6 +6809,8 @@ async function fanOut(client, parsed) {
|
|
|
6589
6809
|
return { count: tasks.length, tasks };
|
|
6590
6810
|
}
|
|
6591
6811
|
async function collect(client, parsed) {
|
|
6812
|
+
if (!client)
|
|
6813
|
+
throw new ApiError("daemon required", "BAD_DAEMON");
|
|
6592
6814
|
const { flags } = parsed;
|
|
6593
6815
|
const idsFlag = optional(flags, "task-ids");
|
|
6594
6816
|
const repoFlag = optional(flags, "repo");
|
|
@@ -6597,7 +6819,7 @@ async function collect(client, parsed) {
|
|
|
6597
6819
|
taskIds = idsFlag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6598
6820
|
} else if (repoFlag) {
|
|
6599
6821
|
const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
6600
|
-
const target = resolveRepoRoot2(
|
|
6822
|
+
const target = resolveRepoRoot2(resolveRepoFlag(repoFlag));
|
|
6601
6823
|
const { tasks } = await client.request("task.list");
|
|
6602
6824
|
taskIds = tasks.filter((t) => !t.archived && resolveRepoRoot2(t.repo) === target).map((t) => t.id);
|
|
6603
6825
|
} else {
|
|
@@ -6623,72 +6845,67 @@ async function collect(client, parsed) {
|
|
|
6623
6845
|
return { tasks: out };
|
|
6624
6846
|
}
|
|
6625
6847
|
async function runApiSubcommand(argv) {
|
|
6626
|
-
const [
|
|
6627
|
-
if (!
|
|
6628
|
-
if (!
|
|
6848
|
+
const [verbName, ...rest] = argv;
|
|
6849
|
+
if (!verbName || verbName === "--help" || verbName === "-h" || verbName === "help") {
|
|
6850
|
+
if (!verbName)
|
|
6629
6851
|
fail(apiUsage(), "MISSING_VERB", 2);
|
|
6630
|
-
}
|
|
6631
6852
|
process.stdout.write(`${apiUsage()}
|
|
6632
6853
|
`);
|
|
6633
6854
|
return;
|
|
6634
6855
|
}
|
|
6635
|
-
|
|
6636
|
-
|
|
6856
|
+
const verb = findVerb(verbName);
|
|
6857
|
+
if (!verb)
|
|
6858
|
+
fail(`unknown verb: ${verbName}
|
|
6637
6859
|
${apiUsage()}`, "BAD_VERB", 2);
|
|
6638
|
-
|
|
6860
|
+
const booleanFlags = new Set(verb.flags.filter((f) => f.type === "bool").map((f) => f.name));
|
|
6639
6861
|
let parsed;
|
|
6640
6862
|
try {
|
|
6641
|
-
parsed = parseFlags(rest);
|
|
6863
|
+
parsed = parseFlags(rest, booleanFlags);
|
|
6642
6864
|
} catch (err) {
|
|
6643
6865
|
if (err instanceof ApiError)
|
|
6644
6866
|
fail(err.message, err.code, 2);
|
|
6645
6867
|
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
6646
6868
|
}
|
|
6647
|
-
|
|
6869
|
+
if (parsed.help) {
|
|
6870
|
+
process.stdout.write(`${verbHelp(verb)}
|
|
6871
|
+
`);
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6648
6874
|
try {
|
|
6649
|
-
|
|
6875
|
+
validateAgainstSpec(verb, parsed.flags);
|
|
6650
6876
|
} catch (err) {
|
|
6651
|
-
|
|
6877
|
+
if (err instanceof ApiError)
|
|
6878
|
+
fail(err.message, err.code, 2);
|
|
6879
|
+
fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
|
|
6652
6880
|
}
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
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;
|
|
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);
|
|
6674
6887
|
}
|
|
6888
|
+
}
|
|
6889
|
+
try {
|
|
6890
|
+
const result = await verb.handler(client, parsed);
|
|
6675
6891
|
emit(result, parsed.pretty);
|
|
6676
6892
|
} catch (err) {
|
|
6677
6893
|
if (err instanceof ApiError)
|
|
6678
6894
|
fail(err.message, err.code, 1);
|
|
6679
6895
|
fail(err instanceof Error ? err.message : String(err), "RPC_ERROR", 1);
|
|
6680
6896
|
} finally {
|
|
6681
|
-
client
|
|
6897
|
+
client?.close();
|
|
6682
6898
|
}
|
|
6683
6899
|
}
|
|
6684
|
-
var
|
|
6900
|
+
var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS;
|
|
6685
6901
|
var init_api_cmd = __esm(() => {
|
|
6686
6902
|
init_daemon_process();
|
|
6687
6903
|
init_interactive_command();
|
|
6688
6904
|
init_client2();
|
|
6689
6905
|
init_prompt_delivery();
|
|
6690
6906
|
init_vendor();
|
|
6691
|
-
|
|
6907
|
+
init_version();
|
|
6908
|
+
TASK_STATUSES = ["backlog", "in_progress", "in_review", "done", "canceled", "error"];
|
|
6692
6909
|
ApiError = class ApiError extends Error {
|
|
6693
6910
|
code;
|
|
6694
6911
|
constructor(message, code) {
|
|
@@ -6696,6 +6913,241 @@ var init_api_cmd = __esm(() => {
|
|
|
6696
6913
|
this.code = code;
|
|
6697
6914
|
}
|
|
6698
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
|
+
];
|
|
6699
7151
|
});
|
|
6700
7152
|
|
|
6701
7153
|
// src/cli/update.ts
|
|
@@ -7206,31 +7658,66 @@ var init_daemon_cmd = __esm(() => {
|
|
|
7206
7658
|
});
|
|
7207
7659
|
|
|
7208
7660
|
// src/lib/skill-install.ts
|
|
7209
|
-
import { existsSync as existsSync4 } from "fs";
|
|
7661
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
7210
7662
|
import { homedir as homedir9 } from "os";
|
|
7211
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
|
+
}
|
|
7212
7670
|
function kobeSkillPaths(opts = {}) {
|
|
7213
7671
|
const home = opts.home ?? homedir9();
|
|
7214
7672
|
const cwd = opts.cwd ?? process.cwd();
|
|
7215
7673
|
return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
|
|
7216
7674
|
}
|
|
7217
|
-
function
|
|
7218
|
-
|
|
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 };
|
|
7219
7692
|
}
|
|
7220
7693
|
function maybeHintSkillInstall() {
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
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
|
+
`);
|
|
7224
7705
|
return;
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
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:
|
|
7228
7715
|
${SKILL_INSTALL_COMMAND}
|
|
7229
|
-
(check anytime with \`kobe doctor\`)
|
|
7230
7716
|
|
|
7231
7717
|
`);
|
|
7718
|
+
}
|
|
7232
7719
|
}
|
|
7233
|
-
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";
|
|
7234
7721
|
var init_skill_install = __esm(() => {
|
|
7235
7722
|
init_repos();
|
|
7236
7723
|
});
|
|
@@ -7242,7 +7729,7 @@ __export(exports_maintenance, {
|
|
|
7242
7729
|
runReloadSubcommand: () => runReloadSubcommand,
|
|
7243
7730
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
7244
7731
|
});
|
|
7245
|
-
import { existsSync as existsSync5, readFileSync as
|
|
7732
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, statSync } from "fs";
|
|
7246
7733
|
import { unlink as unlink6 } from "fs/promises";
|
|
7247
7734
|
import { join as join8 } from "path";
|
|
7248
7735
|
import { createInterface } from "readline";
|
|
@@ -7291,7 +7778,7 @@ function describeFile(path6) {
|
|
|
7291
7778
|
}
|
|
7292
7779
|
function taskCount(tasksPath) {
|
|
7293
7780
|
try {
|
|
7294
|
-
const parsed = JSON.parse(
|
|
7781
|
+
const parsed = JSON.parse(readFileSync7(tasksPath, "utf8"));
|
|
7295
7782
|
return Array.isArray(parsed.tasks) ? parsed.tasks.length : null;
|
|
7296
7783
|
} catch {
|
|
7297
7784
|
return null;
|
|
@@ -7299,7 +7786,7 @@ function taskCount(tasksPath) {
|
|
|
7299
7786
|
}
|
|
7300
7787
|
function tailFile(path6, n) {
|
|
7301
7788
|
try {
|
|
7302
|
-
const lines =
|
|
7789
|
+
const lines = readFileSync7(path6, "utf8").split(`
|
|
7303
7790
|
`).filter((l) => l.trim().length > 0);
|
|
7304
7791
|
return lines.slice(-n).join(`
|
|
7305
7792
|
`);
|
|
@@ -7379,11 +7866,16 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
7379
7866
|
out.push("tmux: \u2717 not found on PATH (task sessions need tmux)");
|
|
7380
7867
|
}
|
|
7381
7868
|
out.push("");
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
out.push("skill: \u2717 kobe agent skill not installed (optional \u2014 lets Claude Code drive `kobe api`)");
|
|
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`)");
|
|
7386
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})`);
|
|
7876
|
+
out.push(` \u2192 ${SKILL_INSTALL_COMMAND}`);
|
|
7877
|
+
} else {
|
|
7878
|
+
out.push(`skill: \u2713 kobe agent skill installed (v${skill.installedVersion})`);
|
|
7387
7879
|
}
|
|
7388
7880
|
out.push("");
|
|
7389
7881
|
const count = taskCount(tasksPath);
|
|
@@ -7551,6 +8043,106 @@ var init_maintenance = __esm(() => {
|
|
|
7551
8043
|
init_client2();
|
|
7552
8044
|
});
|
|
7553
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
|
+
|
|
7554
8146
|
// ../../node_modules/.bun/entities@7.0.1/node_modules/entities/dist/esm/decode-codepoint.js
|
|
7555
8147
|
function replaceCodePoint(codePoint) {
|
|
7556
8148
|
var _a2;
|
|
@@ -13021,7 +13613,7 @@ var init_binary3 = __esm(() => {
|
|
|
13021
13613
|
});
|
|
13022
13614
|
|
|
13023
13615
|
// src/engine/account-detect.ts
|
|
13024
|
-
import { readFileSync as
|
|
13616
|
+
import { readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
13025
13617
|
import { homedir as homedir14 } from "os";
|
|
13026
13618
|
import path10 from "path";
|
|
13027
13619
|
function claudeGlobalConfigPath(env, home) {
|
|
@@ -13234,7 +13826,7 @@ var init_account_detect = __esm(() => {
|
|
|
13234
13826
|
return null;
|
|
13235
13827
|
throw err;
|
|
13236
13828
|
}
|
|
13237
|
-
return
|
|
13829
|
+
return readFileSync8(p, "utf8");
|
|
13238
13830
|
},
|
|
13239
13831
|
env(name) {
|
|
13240
13832
|
return process.env[name];
|
|
@@ -14785,12 +15377,12 @@ var init_focus = __esm(() => {
|
|
|
14785
15377
|
});
|
|
14786
15378
|
|
|
14787
15379
|
// src/tui/context/kv.tsx
|
|
14788
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
15380
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
14789
15381
|
import { dirname as dirname6 } from "path";
|
|
14790
15382
|
function loadInitial() {
|
|
14791
15383
|
const statePath2 = kvStatePath();
|
|
14792
15384
|
try {
|
|
14793
|
-
const text =
|
|
15385
|
+
const text = readFileSync9(statePath2, "utf8");
|
|
14794
15386
|
const parsed = JSON.parse(text);
|
|
14795
15387
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14796
15388
|
return parsed;
|
|
@@ -14886,10 +15478,10 @@ var init_kv = __esm(() => {
|
|
|
14886
15478
|
});
|
|
14887
15479
|
|
|
14888
15480
|
// src/tui/lib/persisted-ui-prefs.ts
|
|
14889
|
-
import { readFileSync as
|
|
15481
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
14890
15482
|
function readPersistedUiPrefs(fallbackTheme) {
|
|
14891
15483
|
try {
|
|
14892
|
-
const parsed = JSON.parse(
|
|
15484
|
+
const parsed = JSON.parse(readFileSync10(kvStatePath(), "utf8"));
|
|
14893
15485
|
const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
|
|
14894
15486
|
const transparent = parsed.transparentBackground === true;
|
|
14895
15487
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
@@ -22260,6 +22852,7 @@ function topLevelUsage() {
|
|
|
22260
22852
|
" api <verb> Scriptable RPC surface for agents (see `kobe api --help`)",
|
|
22261
22853
|
" daemon <verb> Manage the daemon (start|stop|status|restart)",
|
|
22262
22854
|
" theme <verb> Manage user themes (list|add|remove)",
|
|
22855
|
+
" skill <verb> Install the kobe agent skill (install|status|command)",
|
|
22263
22856
|
" update [target] Self-update kobe",
|
|
22264
22857
|
" doctor Diagnose daemon / tmux / state (read-only)",
|
|
22265
22858
|
" reset [--hard] Recover a wedged install",
|
|
@@ -22521,6 +23114,11 @@ async function main() {
|
|
|
22521
23114
|
await runReloadSubcommand2(rest);
|
|
22522
23115
|
return;
|
|
22523
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
|
+
}
|
|
22524
23122
|
if (subcommand === "new-chattab") {
|
|
22525
23123
|
const flags = parseOpsFlags(rest);
|
|
22526
23124
|
const session = flags.session;
|
package/package.json
CHANGED