@sma1lboy/kobe 0.5.27 → 0.5.28
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/README.md +12 -5
- package/dist/cli/index.js +184 -202
- package/package.json +1 -1
- package/dist/share/skills/kobe/SKILL.md +0 -109
package/README.md
CHANGED
|
@@ -253,16 +253,19 @@ If the daemon isn't running, `kobe api ...` exits 2 with
|
|
|
253
253
|
|
|
254
254
|
### Install the agent skill
|
|
255
255
|
|
|
256
|
-
`kobe api` gives the *capability*. The
|
|
256
|
+
`kobe api` gives the *capability*. The SKILL.md gives the model the
|
|
257
257
|
*intent* — when to fan out, how to scope subtask prompts, how to read results
|
|
258
|
-
back. Install it
|
|
258
|
+
back. Install it via the [Vercel Labs agent-skills CLI](https://github.com/vercel-labs/skills):
|
|
259
259
|
|
|
260
260
|
```bash
|
|
261
|
-
kobe skill
|
|
262
|
-
kobe skill install --yes # overwrite an existing copy
|
|
263
|
-
kobe skill uninstall # remove it
|
|
261
|
+
npx skills add Sma1lboy/kobe --skill kobe --agent claude-code
|
|
264
262
|
```
|
|
265
263
|
|
|
264
|
+
The skill source lives at `.agents/skills/kobe/SKILL.md` in this repo,
|
|
265
|
+
which is one of the directories `npx skills` scans by default. The
|
|
266
|
+
agent-skills CLI fetches it directly from GitHub and writes it to
|
|
267
|
+
`~/.claude/skills/kobe/SKILL.md`.
|
|
268
|
+
|
|
266
269
|
After install, Claude Code automatically picks up the skill on its next launch.
|
|
267
270
|
For project-level overrides, copy the file to `<repo>/.claude/skills/kobe/SKILL.md`
|
|
268
271
|
and customise — Claude Code's discovery order is project > user > none.
|
|
@@ -270,6 +273,10 @@ and customise — Claude Code's discovery order is project > user > none.
|
|
|
270
273
|
`kobe diagnose` reports whether the skill is installed, which is the
|
|
271
274
|
fastest way to confirm.
|
|
272
275
|
|
|
276
|
+
> `kobe skill install` is deprecated as of v0.6 — use the `npx skills`
|
|
277
|
+
> command above. `kobe skill uninstall` still works for cleaning up an
|
|
278
|
+
> older copy.
|
|
279
|
+
|
|
273
280
|
## Coming later
|
|
274
281
|
|
|
275
282
|
- Homebrew tap (mirroring [`sma1lboy/homebrew-codefox`](https://github.com/sma1lboy/homebrew-codefox)) so you can `brew install kobe` without touching Bun directly.
|
package/dist/cli/index.js
CHANGED
|
@@ -17481,7 +17481,7 @@ var init_package = __esm(() => {
|
|
|
17481
17481
|
package_default = {
|
|
17482
17482
|
$schema: "https://json.schemastore.org/package.json",
|
|
17483
17483
|
name: "@sma1lboy/kobe",
|
|
17484
|
-
version: "0.5.
|
|
17484
|
+
version: "0.5.28",
|
|
17485
17485
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
17486
17486
|
type: "module",
|
|
17487
17487
|
packageManager: "bun@1.3.13",
|
|
@@ -17685,59 +17685,39 @@ var init_version = __esm(() => {
|
|
|
17685
17685
|
var exports_skill_cmd = {};
|
|
17686
17686
|
__export(exports_skill_cmd, {
|
|
17687
17687
|
runSkillSubcommand: () => runSkillSubcommand,
|
|
17688
|
-
probeInstalledSkill: () => probeInstalledSkill
|
|
17688
|
+
probeInstalledSkill: () => probeInstalledSkill,
|
|
17689
|
+
ensureSkillInstalled: () => ensureSkillInstalled
|
|
17689
17690
|
});
|
|
17691
|
+
import { spawn as spawn7 } from "child_process";
|
|
17690
17692
|
import { existsSync as existsSync3 } from "fs";
|
|
17691
|
-
import {
|
|
17693
|
+
import { stat as stat2, unlink as unlink2 } from "fs/promises";
|
|
17692
17694
|
import { homedir as homedir7 } from "os";
|
|
17693
|
-
import {
|
|
17694
|
-
import { fileURLToPath } from "url";
|
|
17695
|
-
function resolveBundledSkill() {
|
|
17696
|
-
const here = fileURLToPath(import.meta.url);
|
|
17697
|
-
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
17698
|
-
throw new Error("kobe skill install is not yet supported from the standalone binary. " + "Install kobe from npm (`npm install -g @sma1lboy/kobe`) and re-run, " + "or copy share/skills/kobe/SKILL.md from the kobe source tree manually.");
|
|
17699
|
-
}
|
|
17700
|
-
const dir = dirname3(here);
|
|
17701
|
-
const sourcePath = resolve(dir, "../../share/skills/kobe/SKILL.md");
|
|
17702
|
-
if (existsSync3(sourcePath))
|
|
17703
|
-
return sourcePath;
|
|
17704
|
-
const distPath = resolve(dir, "../share/skills/kobe/SKILL.md");
|
|
17705
|
-
if (existsSync3(distPath))
|
|
17706
|
-
return distPath;
|
|
17707
|
-
throw new Error(`kobe: bundled SKILL.md not found (looked at ${sourcePath} and ${distPath}). If you are running from source, the share/ directory should sit next to src/.`);
|
|
17708
|
-
}
|
|
17695
|
+
import { join as join7 } from "path";
|
|
17709
17696
|
function resolveInstallTarget() {
|
|
17710
17697
|
const home = process.env.HOME ?? homedir7();
|
|
17711
17698
|
return join7(home, SKILL_REL_PATH);
|
|
17712
17699
|
}
|
|
17713
|
-
async function
|
|
17714
|
-
|
|
17715
|
-
return
|
|
17716
|
-
} catch (err) {
|
|
17717
|
-
if (err.code === "ENOENT")
|
|
17718
|
-
return null;
|
|
17719
|
-
throw err;
|
|
17720
|
-
}
|
|
17721
|
-
}
|
|
17722
|
-
async function runInstall(argv) {
|
|
17723
|
-
const force = argv.includes("--yes") || argv.includes("--force");
|
|
17724
|
-
const source = resolveBundledSkill();
|
|
17700
|
+
async function ensureSkillInstalled() {
|
|
17701
|
+
if (process.env.KOBE_NO_SKILL_AUTOINSTALL === "1")
|
|
17702
|
+
return;
|
|
17725
17703
|
const target = resolveInstallTarget();
|
|
17726
|
-
|
|
17727
|
-
const bundled = await readFile3(source, "utf8");
|
|
17728
|
-
if (existing === bundled) {
|
|
17729
|
-
console.log(`kobe skill: already installed at ${target}`);
|
|
17704
|
+
if (existsSync3(target))
|
|
17730
17705
|
return;
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
const
|
|
17734
|
-
|
|
17735
|
-
|
|
17736
|
-
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17706
|
+
console.error("kobe: first-run setup \u2014 installing skill via `npx skills add`. " + "Press Ctrl+C to skip (set KOBE_NO_SKILL_AUTOINSTALL=1 to opt out long-term).");
|
|
17707
|
+
await new Promise((resolve) => {
|
|
17708
|
+
const proc = spawn7("npx", ["--yes", "skills", "add", "Sma1lboy/kobe", "--skill", "kobe", "--agent", "claude-code"], { stdio: "inherit" });
|
|
17709
|
+
proc.on("error", () => resolve());
|
|
17710
|
+
proc.on("exit", () => resolve());
|
|
17711
|
+
});
|
|
17712
|
+
}
|
|
17713
|
+
async function runInstall() {
|
|
17714
|
+
console.error(`kobe skill install is deprecated as of v0.6.
|
|
17715
|
+
The skill is now distributed via the Vercel Labs agent-skills CLI:
|
|
17716
|
+
|
|
17717
|
+
${NPX_HINT}
|
|
17718
|
+
|
|
17719
|
+
See https://github.com/vercel-labs/skills for the install tool.`);
|
|
17720
|
+
process.exit(2);
|
|
17741
17721
|
}
|
|
17742
17722
|
async function runUninstall() {
|
|
17743
17723
|
const target = resolveInstallTarget();
|
|
@@ -17751,23 +17731,23 @@ async function runUninstall() {
|
|
|
17751
17731
|
console.log(`kobe skill: removed ${target}`);
|
|
17752
17732
|
}
|
|
17753
17733
|
async function runSkillSubcommand(argv) {
|
|
17754
|
-
const [command
|
|
17734
|
+
const [command] = argv;
|
|
17755
17735
|
if (command === "install") {
|
|
17756
|
-
await runInstall(
|
|
17736
|
+
await runInstall();
|
|
17757
17737
|
return;
|
|
17758
17738
|
}
|
|
17759
17739
|
if (command === "uninstall" || command === "remove") {
|
|
17760
17740
|
await runUninstall();
|
|
17761
17741
|
return;
|
|
17762
17742
|
}
|
|
17763
|
-
console.error("usage: kobe skill <install|uninstall>
|
|
17743
|
+
console.error("usage: kobe skill <install|uninstall>");
|
|
17764
17744
|
process.exit(2);
|
|
17765
17745
|
}
|
|
17766
17746
|
function probeInstalledSkill() {
|
|
17767
17747
|
const target = resolveInstallTarget();
|
|
17768
17748
|
return { path: target, installed: existsSync3(target) };
|
|
17769
17749
|
}
|
|
17770
|
-
var SKILL_REL_PATH = ".claude/skills/kobe/SKILL.md";
|
|
17750
|
+
var SKILL_REL_PATH = ".claude/skills/kobe/SKILL.md", NPX_HINT = "npx skills add Sma1lboy/kobe --skill kobe --agent claude-code";
|
|
17771
17751
|
var init_skill_cmd = () => {};
|
|
17772
17752
|
|
|
17773
17753
|
// src/cli/diagnose.ts
|
|
@@ -17994,7 +17974,7 @@ async function buildDiagnoseReport() {
|
|
|
17994
17974
|
try {
|
|
17995
17975
|
const probe = probeInstalledSkill();
|
|
17996
17976
|
lines.push(formatKv("path:", probe.path));
|
|
17997
|
-
lines.push(formatKv("installed:", probe.installed ? "yes" : "no (run `kobe skill
|
|
17977
|
+
lines.push(formatKv("installed:", probe.installed ? "yes" : "no (run `npx skills add Sma1lboy/kobe --skill kobe --agent claude-code`)"));
|
|
17998
17978
|
} catch (err) {
|
|
17999
17979
|
lines.push(formatKv("status:", `(unavailable: ${err.message})`));
|
|
18000
17980
|
}
|
|
@@ -18254,7 +18234,7 @@ __export(exports_theme, {
|
|
|
18254
18234
|
runThemeSubcommand: () => runThemeSubcommand
|
|
18255
18235
|
});
|
|
18256
18236
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
18257
|
-
import { basename, join as join10, resolve
|
|
18237
|
+
import { basename, join as join10, resolve } from "path";
|
|
18258
18238
|
function fail2(message) {
|
|
18259
18239
|
process.stderr.write(`kobe theme: ${message}
|
|
18260
18240
|
`);
|
|
@@ -18304,7 +18284,7 @@ async function readSource(source) {
|
|
|
18304
18284
|
const defaultName2 = file2.endsWith(".json") ? file2.slice(0, -".json".length) : file2;
|
|
18305
18285
|
return { text: text2, defaultName: defaultName2 };
|
|
18306
18286
|
}
|
|
18307
|
-
const abs =
|
|
18287
|
+
const abs = resolve(process.cwd(), source);
|
|
18308
18288
|
let text;
|
|
18309
18289
|
try {
|
|
18310
18290
|
text = readFileSync6(abs, "utf8");
|
|
@@ -18465,8 +18445,8 @@ class SocketClient {
|
|
|
18465
18445
|
connected;
|
|
18466
18446
|
constructor(path7, onClose) {
|
|
18467
18447
|
this.socket = connect(path7);
|
|
18468
|
-
this.connected = new Promise((
|
|
18469
|
-
this.socket.once("connect",
|
|
18448
|
+
this.connected = new Promise((resolve2, reject) => {
|
|
18449
|
+
this.socket.once("connect", resolve2);
|
|
18470
18450
|
this.socket.once("error", reject);
|
|
18471
18451
|
});
|
|
18472
18452
|
this.socket.on("data", (chunk) => this.onData(chunk.toString("utf8")));
|
|
@@ -18514,8 +18494,8 @@ class SocketClient {
|
|
|
18514
18494
|
async call(method, params) {
|
|
18515
18495
|
await this.connected;
|
|
18516
18496
|
const id = this.nextId++;
|
|
18517
|
-
const promise = new Promise((
|
|
18518
|
-
this.pending.set(id, { resolve:
|
|
18497
|
+
const promise = new Promise((resolve2, reject) => {
|
|
18498
|
+
this.pending.set(id, { resolve: resolve2, reject });
|
|
18519
18499
|
});
|
|
18520
18500
|
this.socket.write(`${JSON.stringify({ id, method, params })}
|
|
18521
18501
|
`);
|
|
@@ -18881,19 +18861,19 @@ class KobeDaemonClient {
|
|
|
18881
18861
|
if (!socket)
|
|
18882
18862
|
throw new Error("daemon connection is not open");
|
|
18883
18863
|
const id = String(this.nextId++);
|
|
18884
|
-
const promise = new Promise((
|
|
18885
|
-
this.pending.set(id, { resolve: (value) =>
|
|
18864
|
+
const promise = new Promise((resolve2, reject) => {
|
|
18865
|
+
this.pending.set(id, { resolve: (value) => resolve2(value), reject });
|
|
18886
18866
|
});
|
|
18887
18867
|
socket.write(frameToLine({ type: "request", id, name, payload }));
|
|
18888
18868
|
return promise;
|
|
18889
18869
|
}
|
|
18890
18870
|
openSocket() {
|
|
18891
|
-
return new Promise((
|
|
18871
|
+
return new Promise((resolve2, reject) => {
|
|
18892
18872
|
const socket = connect2(this.socketPath);
|
|
18893
18873
|
this.socket = socket;
|
|
18894
18874
|
const onConnect = () => {
|
|
18895
18875
|
socket.off("error", onError);
|
|
18896
|
-
|
|
18876
|
+
resolve2();
|
|
18897
18877
|
};
|
|
18898
18878
|
const onError = (err) => {
|
|
18899
18879
|
socket.off("connect", onConnect);
|
|
@@ -18965,23 +18945,23 @@ class KobeDaemonClient {
|
|
|
18965
18945
|
var init_client = () => {};
|
|
18966
18946
|
|
|
18967
18947
|
// src/client/daemon-process.ts
|
|
18968
|
-
import { spawn as
|
|
18948
|
+
import { spawn as spawn8 } from "child_process";
|
|
18969
18949
|
import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync3, openSync } from "fs";
|
|
18970
18950
|
import { unlink as unlink3 } from "fs/promises";
|
|
18971
18951
|
import { homedir as homedir10 } from "os";
|
|
18972
|
-
import { dirname as
|
|
18973
|
-
import { fileURLToPath
|
|
18952
|
+
import { dirname as dirname3, join as join12, resolve as resolve2 } from "path";
|
|
18953
|
+
import { fileURLToPath } from "url";
|
|
18974
18954
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
18975
18955
|
let stdio = "ignore";
|
|
18976
18956
|
let logFd;
|
|
18977
18957
|
try {
|
|
18978
|
-
mkdirSync3(
|
|
18958
|
+
mkdirSync3(dirname3(logPath), { recursive: true });
|
|
18979
18959
|
logFd = openSync(logPath, "a");
|
|
18980
18960
|
stdio = ["ignore", logFd, logFd];
|
|
18981
18961
|
} catch {
|
|
18982
18962
|
stdio = "ignore";
|
|
18983
18963
|
}
|
|
18984
|
-
const child =
|
|
18964
|
+
const child = spawn8(command, [...args], { detached: true, stdio, env });
|
|
18985
18965
|
child.unref();
|
|
18986
18966
|
if (logFd !== undefined) {
|
|
18987
18967
|
try {
|
|
@@ -19061,15 +19041,15 @@ async function testCanConnect(socketPath) {
|
|
|
19061
19041
|
}
|
|
19062
19042
|
}
|
|
19063
19043
|
function resolveKobeSpawn(subcommand) {
|
|
19064
|
-
const here =
|
|
19044
|
+
const here = fileURLToPath(import.meta.url);
|
|
19065
19045
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
19066
19046
|
return [process.execPath, ...subcommand];
|
|
19067
19047
|
}
|
|
19068
|
-
const dir =
|
|
19069
|
-
const sourceEntry =
|
|
19048
|
+
const dir = dirname3(here);
|
|
19049
|
+
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
19070
19050
|
if (existsSync6(sourceEntry))
|
|
19071
19051
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
19072
|
-
const distEntry =
|
|
19052
|
+
const distEntry = resolve2(dir, "../cli/index.js");
|
|
19073
19053
|
if (existsSync6(distEntry))
|
|
19074
19054
|
return [process.execPath, distEntry, ...subcommand];
|
|
19075
19055
|
throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
|
|
@@ -19164,9 +19144,9 @@ function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
|
|
|
19164
19144
|
}
|
|
19165
19145
|
|
|
19166
19146
|
// src/engine/command-discovery.ts
|
|
19167
|
-
import { readFile as
|
|
19147
|
+
import { readFile as readFile3, readdir as readdir2, realpath, stat as stat4 } from "fs/promises";
|
|
19168
19148
|
import { homedir as homedir11 } from "os";
|
|
19169
|
-
import { dirname as
|
|
19149
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
19170
19150
|
function resolveHome() {
|
|
19171
19151
|
return process.env.HOME ?? homedir11();
|
|
19172
19152
|
}
|
|
@@ -19260,7 +19240,7 @@ async function readSkillDoc(skillMd, fallbackName) {
|
|
|
19260
19240
|
if (!await isFile(skillMd))
|
|
19261
19241
|
return null;
|
|
19262
19242
|
try {
|
|
19263
|
-
const content = await
|
|
19243
|
+
const content = await readFile3(skillMd, "utf-8");
|
|
19264
19244
|
return {
|
|
19265
19245
|
name: extractFrontmatterField(content, "name") || fallbackName,
|
|
19266
19246
|
description: extractFrontmatterField(content, "description") ?? "",
|
|
@@ -19300,7 +19280,7 @@ async function findUpwardSkillRoots(cwd, relativeRoot) {
|
|
|
19300
19280
|
const candidate = join13(cur, relativeRoot);
|
|
19301
19281
|
if (await isDir(candidate))
|
|
19302
19282
|
out.push(candidate);
|
|
19303
|
-
const parent =
|
|
19283
|
+
const parent = dirname4(cur);
|
|
19304
19284
|
if (parent === cur)
|
|
19305
19285
|
break;
|
|
19306
19286
|
cur = parent;
|
|
@@ -19336,7 +19316,7 @@ var init_builtin_slashes = __esm(() => {
|
|
|
19336
19316
|
});
|
|
19337
19317
|
|
|
19338
19318
|
// src/engine/claude-code-local/commands.ts
|
|
19339
|
-
import { readFile as
|
|
19319
|
+
import { readFile as readFile4, stat as stat5 } from "fs/promises";
|
|
19340
19320
|
import { join as join14 } from "path";
|
|
19341
19321
|
async function isFile2(path7) {
|
|
19342
19322
|
try {
|
|
@@ -19356,7 +19336,7 @@ async function scanCommandsDir(dir) {
|
|
|
19356
19336
|
continue;
|
|
19357
19337
|
let description = "";
|
|
19358
19338
|
try {
|
|
19359
|
-
description = extractFrontmatterField(await
|
|
19339
|
+
description = extractFrontmatterField(await readFile4(full, "utf-8"), "description") ?? "";
|
|
19360
19340
|
} catch {
|
|
19361
19341
|
description = "";
|
|
19362
19342
|
}
|
|
@@ -19462,7 +19442,7 @@ function normalizeClaudeContent(content) {
|
|
|
19462
19442
|
|
|
19463
19443
|
// src/engine/claude-code-local/history.ts
|
|
19464
19444
|
import { randomUUID } from "crypto";
|
|
19465
|
-
import { appendFile, mkdir as
|
|
19445
|
+
import { appendFile, mkdir as mkdir2, readFile as readFile5, readdir as readdir3, unlink as unlink4, writeFile as writeFile2 } from "fs/promises";
|
|
19466
19446
|
import { homedir as homedir12 } from "os";
|
|
19467
19447
|
import path7 from "path";
|
|
19468
19448
|
function encodeCwd(cwd) {
|
|
@@ -19566,13 +19546,13 @@ async function appendInterruptedUserPrompt(sessionId, cwd, prompt, deps = defaul
|
|
|
19566
19546
|
const filePath = path7.join(projectDir, `${sessionId}.jsonl`);
|
|
19567
19547
|
let lines = [];
|
|
19568
19548
|
try {
|
|
19569
|
-
const raw = await
|
|
19549
|
+
const raw = await readFile5(filePath, "utf8");
|
|
19570
19550
|
lines = raw.split(`
|
|
19571
19551
|
`).filter((l2) => l2.length > 0);
|
|
19572
19552
|
} catch (err) {
|
|
19573
19553
|
if (err.code !== "ENOENT")
|
|
19574
19554
|
throw err;
|
|
19575
|
-
await
|
|
19555
|
+
await mkdir2(projectDir, { recursive: true });
|
|
19576
19556
|
}
|
|
19577
19557
|
let lastConvIdx = -1;
|
|
19578
19558
|
let lastConvRecord = null;
|
|
@@ -19643,7 +19623,7 @@ var init_history = __esm(() => {
|
|
|
19643
19623
|
}
|
|
19644
19624
|
},
|
|
19645
19625
|
async readFile(p2) {
|
|
19646
|
-
return await
|
|
19626
|
+
return await readFile5(p2, "utf8");
|
|
19647
19627
|
},
|
|
19648
19628
|
async pathExists(p2) {
|
|
19649
19629
|
try {
|
|
@@ -19713,21 +19693,21 @@ function signalTree(proc, signal) {
|
|
|
19713
19693
|
} catch {}
|
|
19714
19694
|
}
|
|
19715
19695
|
function waitForExit(proc) {
|
|
19716
|
-
return new Promise((
|
|
19696
|
+
return new Promise((resolve3) => {
|
|
19717
19697
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
19718
|
-
|
|
19698
|
+
resolve3();
|
|
19719
19699
|
return;
|
|
19720
19700
|
}
|
|
19721
|
-
proc.once("close", () =>
|
|
19722
|
-
proc.once("exit", () =>
|
|
19701
|
+
proc.once("close", () => resolve3());
|
|
19702
|
+
proc.once("exit", () => resolve3());
|
|
19723
19703
|
});
|
|
19724
19704
|
}
|
|
19725
19705
|
function delay(ms) {
|
|
19726
|
-
return new Promise((
|
|
19706
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
19727
19707
|
}
|
|
19728
19708
|
|
|
19729
19709
|
// src/engine/claude-code-local/sessions.ts
|
|
19730
|
-
import { readFile as
|
|
19710
|
+
import { readFile as readFile6, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
19731
19711
|
import { homedir as homedir13 } from "os";
|
|
19732
19712
|
import path8 from "path";
|
|
19733
19713
|
async function listSessionsForCwd(cwd, deps = defaultDeps3) {
|
|
@@ -19806,7 +19786,7 @@ var init_sessions = __esm(() => {
|
|
|
19806
19786
|
}
|
|
19807
19787
|
},
|
|
19808
19788
|
async readFile(p2) {
|
|
19809
|
-
return await
|
|
19789
|
+
return await readFile6(p2, "utf8");
|
|
19810
19790
|
},
|
|
19811
19791
|
async stat(p2) {
|
|
19812
19792
|
const s2 = await stat6(p2);
|
|
@@ -19816,10 +19796,10 @@ var init_sessions = __esm(() => {
|
|
|
19816
19796
|
});
|
|
19817
19797
|
|
|
19818
19798
|
// src/engine/claude-code-local/spawn.ts
|
|
19819
|
-
import { spawn as
|
|
19799
|
+
import { spawn as spawn9 } from "child_process";
|
|
19820
19800
|
function spawnClaudeProcess(opts) {
|
|
19821
19801
|
const args = buildArgs(opts);
|
|
19822
|
-
const proc =
|
|
19802
|
+
const proc = spawn9(opts.binaryPath, args, {
|
|
19823
19803
|
cwd: opts.cwd,
|
|
19824
19804
|
env: { ...process.env, ...opts.env ?? {} },
|
|
19825
19805
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -20052,7 +20032,7 @@ class ClaudeCodeLocal {
|
|
|
20052
20032
|
}
|
|
20053
20033
|
if (session.closed)
|
|
20054
20034
|
return;
|
|
20055
|
-
await new Promise((
|
|
20035
|
+
await new Promise((resolve3) => session.waiters.push(resolve3));
|
|
20056
20036
|
}
|
|
20057
20037
|
}
|
|
20058
20038
|
};
|
|
@@ -20219,7 +20199,7 @@ var init_claude_code_local = __esm(() => {
|
|
|
20219
20199
|
});
|
|
20220
20200
|
|
|
20221
20201
|
// src/engine/codex-local/app-server.ts
|
|
20222
|
-
import { spawn as
|
|
20202
|
+
import { spawn as spawn10 } from "child_process";
|
|
20223
20203
|
import { readFileSync as readFileSync7 } from "fs";
|
|
20224
20204
|
function resolveCodexBackend(env = process.env, readPreference = readCodexBackendPreference) {
|
|
20225
20205
|
if (env.KOBE_CODEX_BACKEND === "exec")
|
|
@@ -20241,7 +20221,7 @@ function readCodexBackendPreference() {
|
|
|
20241
20221
|
}
|
|
20242
20222
|
function spawnCodexAppServerTurn(opts) {
|
|
20243
20223
|
const args = buildAppServerArgs(opts);
|
|
20244
|
-
const proc =
|
|
20224
|
+
const proc = spawn10(opts.binaryPath, args, {
|
|
20245
20225
|
cwd: opts.cwd,
|
|
20246
20226
|
env: { ...process.env, ...opts.env ?? {} },
|
|
20247
20227
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -20317,16 +20297,16 @@ class AppServerRpc {
|
|
|
20317
20297
|
nextId = 1;
|
|
20318
20298
|
readyResolve = () => {};
|
|
20319
20299
|
readyReject = () => {};
|
|
20320
|
-
ready = new Promise((
|
|
20321
|
-
this.readyResolve =
|
|
20300
|
+
ready = new Promise((resolve3, reject) => {
|
|
20301
|
+
this.readyResolve = resolve3;
|
|
20322
20302
|
this.readyReject = reject;
|
|
20323
20303
|
});
|
|
20324
20304
|
closed;
|
|
20325
20305
|
constructor(proc, opts) {
|
|
20326
20306
|
this.proc = proc;
|
|
20327
20307
|
this.opts = opts;
|
|
20328
|
-
this.closed = new Promise((
|
|
20329
|
-
proc.once("exit", (code, signal) =>
|
|
20308
|
+
this.closed = new Promise((resolve3) => {
|
|
20309
|
+
proc.once("exit", (code, signal) => resolve3({ code, signal }));
|
|
20330
20310
|
});
|
|
20331
20311
|
proc.stdout.setEncoding("utf8");
|
|
20332
20312
|
proc.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
@@ -20403,8 +20383,8 @@ class AppServerRpc {
|
|
|
20403
20383
|
call(method, params) {
|
|
20404
20384
|
const id = this.nextId++;
|
|
20405
20385
|
this.send({ jsonrpc: "2.0", id, method, params });
|
|
20406
|
-
return new Promise((
|
|
20407
|
-
this.pending.set(id, { resolve:
|
|
20386
|
+
return new Promise((resolve3, reject) => {
|
|
20387
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
20408
20388
|
});
|
|
20409
20389
|
}
|
|
20410
20390
|
notify(method, params) {
|
|
@@ -20826,7 +20806,7 @@ function validPositive(v2) {
|
|
|
20826
20806
|
}
|
|
20827
20807
|
|
|
20828
20808
|
// src/engine/codex-local/history.ts
|
|
20829
|
-
import { readFile as
|
|
20809
|
+
import { readFile as readFile7, readdir as readdir5, unlink as unlink5 } from "fs/promises";
|
|
20830
20810
|
import { homedir as homedir15 } from "os";
|
|
20831
20811
|
import path10 from "path";
|
|
20832
20812
|
async function listRolloutFiles(deps = defaultDeps5) {
|
|
@@ -21113,7 +21093,7 @@ var init_history2 = __esm(() => {
|
|
|
21113
21093
|
}
|
|
21114
21094
|
},
|
|
21115
21095
|
async readFile(p2) {
|
|
21116
|
-
return await
|
|
21096
|
+
return await readFile7(p2, "utf8");
|
|
21117
21097
|
}
|
|
21118
21098
|
};
|
|
21119
21099
|
});
|
|
@@ -21276,10 +21256,10 @@ var init_sessions2 = __esm(() => {
|
|
|
21276
21256
|
});
|
|
21277
21257
|
|
|
21278
21258
|
// src/engine/codex-local/spawn.ts
|
|
21279
|
-
import { spawn as
|
|
21259
|
+
import { spawn as spawn11 } from "child_process";
|
|
21280
21260
|
function spawnCodexProcess(opts) {
|
|
21281
21261
|
const args = buildArgs2(opts);
|
|
21282
|
-
const proc =
|
|
21262
|
+
const proc = spawn11(opts.binaryPath, args, {
|
|
21283
21263
|
cwd: opts.cwd,
|
|
21284
21264
|
env: { ...process.env, ...opts.env ?? {} },
|
|
21285
21265
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -21530,7 +21510,7 @@ class CodexLocal {
|
|
|
21530
21510
|
}
|
|
21531
21511
|
if (session.closed)
|
|
21532
21512
|
return;
|
|
21533
|
-
await new Promise((
|
|
21513
|
+
await new Promise((resolve3) => session.waiters.push(resolve3));
|
|
21534
21514
|
}
|
|
21535
21515
|
}
|
|
21536
21516
|
};
|
|
@@ -21723,7 +21703,7 @@ class CodexLocal {
|
|
|
21723
21703
|
signal: null,
|
|
21724
21704
|
seen: false
|
|
21725
21705
|
};
|
|
21726
|
-
const exitObserved = new Promise((
|
|
21706
|
+
const exitObserved = new Promise((resolve3) => {
|
|
21727
21707
|
spawned.proc.once("exit", (code, signal) => {
|
|
21728
21708
|
exitInfo.code = code;
|
|
21729
21709
|
exitInfo.signal = signal;
|
|
@@ -21731,7 +21711,7 @@ class CodexLocal {
|
|
|
21731
21711
|
if (!bound) {
|
|
21732
21712
|
rejectHandle(new Error(formatExitMsg("codex exited before session id was captured", code, signal, stderrTail)));
|
|
21733
21713
|
}
|
|
21734
|
-
|
|
21714
|
+
resolve3();
|
|
21735
21715
|
});
|
|
21736
21716
|
});
|
|
21737
21717
|
(async () => {
|
|
@@ -21904,7 +21884,7 @@ var init_binary3 = __esm(() => {
|
|
|
21904
21884
|
});
|
|
21905
21885
|
|
|
21906
21886
|
// src/engine/gemini-local/history.ts
|
|
21907
|
-
import { readFile as
|
|
21887
|
+
import { readFile as readFile8, readdir as readdir6, unlink as unlink6 } from "fs/promises";
|
|
21908
21888
|
import { homedir as homedir17 } from "os";
|
|
21909
21889
|
import path12 from "path";
|
|
21910
21890
|
async function listChatFiles(deps = defaultDeps7) {
|
|
@@ -22153,14 +22133,14 @@ var init_history3 = __esm(() => {
|
|
|
22153
22133
|
}
|
|
22154
22134
|
},
|
|
22155
22135
|
async readFile(p2) {
|
|
22156
|
-
return await
|
|
22136
|
+
return await readFile8(p2, "utf8");
|
|
22157
22137
|
},
|
|
22158
22138
|
unlink: unlink6
|
|
22159
22139
|
};
|
|
22160
22140
|
});
|
|
22161
22141
|
|
|
22162
22142
|
// src/engine/gemini-local/sessions.ts
|
|
22163
|
-
import { readFile as
|
|
22143
|
+
import { readFile as readFile9, readdir as readdir7, stat as stat8 } from "fs/promises";
|
|
22164
22144
|
import { homedir as homedir18 } from "os";
|
|
22165
22145
|
import path13 from "path";
|
|
22166
22146
|
async function listSessionsForCwd3(cwd, deps) {
|
|
@@ -22241,17 +22221,17 @@ var init_sessions3 = __esm(() => {
|
|
|
22241
22221
|
}
|
|
22242
22222
|
},
|
|
22243
22223
|
async readFile(p2) {
|
|
22244
|
-
return await
|
|
22224
|
+
return await readFile9(p2, "utf8");
|
|
22245
22225
|
},
|
|
22246
22226
|
async unlink() {}
|
|
22247
22227
|
};
|
|
22248
22228
|
});
|
|
22249
22229
|
|
|
22250
22230
|
// src/engine/gemini-local/spawn.ts
|
|
22251
|
-
import { spawn as
|
|
22231
|
+
import { spawn as spawn12 } from "child_process";
|
|
22252
22232
|
function spawnGeminiProcess(opts) {
|
|
22253
22233
|
const args = buildArgs3(opts);
|
|
22254
|
-
const proc =
|
|
22234
|
+
const proc = spawn12(opts.binaryPath, args, {
|
|
22255
22235
|
cwd: opts.cwd,
|
|
22256
22236
|
env: { ...process.env, ...opts.env ?? {} },
|
|
22257
22237
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -22449,7 +22429,7 @@ class GeminiLocal {
|
|
|
22449
22429
|
}
|
|
22450
22430
|
if (session.closed)
|
|
22451
22431
|
return;
|
|
22452
|
-
await new Promise((
|
|
22432
|
+
await new Promise((resolve3) => session.waiters.push(resolve3));
|
|
22453
22433
|
}
|
|
22454
22434
|
}
|
|
22455
22435
|
};
|
|
@@ -22528,14 +22508,14 @@ class GeminiLocal {
|
|
|
22528
22508
|
signal: null,
|
|
22529
22509
|
seen: false
|
|
22530
22510
|
};
|
|
22531
|
-
const exitObserved = new Promise((
|
|
22511
|
+
const exitObserved = new Promise((resolve3) => {
|
|
22532
22512
|
spawned.proc.once("exit", (code, signal) => {
|
|
22533
22513
|
exitInfo.code = code;
|
|
22534
22514
|
exitInfo.signal = signal;
|
|
22535
22515
|
exitInfo.seen = true;
|
|
22536
22516
|
if (!bound)
|
|
22537
22517
|
rejectHandle(new Error(formatExitMsg2("gemini exited before session id was captured", code, signal, stderrTail)));
|
|
22538
|
-
|
|
22518
|
+
resolve3();
|
|
22539
22519
|
});
|
|
22540
22520
|
});
|
|
22541
22521
|
(async () => {
|
|
@@ -22634,11 +22614,11 @@ var init_default_engines = __esm(() => {
|
|
|
22634
22614
|
});
|
|
22635
22615
|
|
|
22636
22616
|
// src/orchestrator/bridge/server.ts
|
|
22637
|
-
import { mkdir as
|
|
22617
|
+
import { mkdir as mkdir3, unlink as unlink7 } from "fs/promises";
|
|
22638
22618
|
import { createServer } from "net";
|
|
22639
|
-
import { dirname as
|
|
22619
|
+
import { dirname as dirname5 } from "path";
|
|
22640
22620
|
async function startBridgeServer(orch, socketPath) {
|
|
22641
|
-
await
|
|
22621
|
+
await mkdir3(dirname5(socketPath), { recursive: true });
|
|
22642
22622
|
await unlink7(socketPath).catch(() => {});
|
|
22643
22623
|
const conns = new Set;
|
|
22644
22624
|
const server = createServer((conn) => {
|
|
@@ -22666,11 +22646,11 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
22666
22646
|
});
|
|
22667
22647
|
conn.on("error", () => {});
|
|
22668
22648
|
});
|
|
22669
|
-
await new Promise((
|
|
22649
|
+
await new Promise((resolve3, reject) => {
|
|
22670
22650
|
server.once("error", reject);
|
|
22671
22651
|
server.listen(socketPath, () => {
|
|
22672
22652
|
server.removeListener("error", reject);
|
|
22673
|
-
|
|
22653
|
+
resolve3();
|
|
22674
22654
|
});
|
|
22675
22655
|
});
|
|
22676
22656
|
return {
|
|
@@ -22679,7 +22659,7 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
22679
22659
|
for (const conn of conns)
|
|
22680
22660
|
conn.destroy();
|
|
22681
22661
|
conns.clear();
|
|
22682
|
-
await new Promise((
|
|
22662
|
+
await new Promise((resolve3) => server.close(() => resolve3()));
|
|
22683
22663
|
await unlink7(socketPath).catch(() => {});
|
|
22684
22664
|
}
|
|
22685
22665
|
};
|
|
@@ -22772,10 +22752,10 @@ __export(exports_bridge, {
|
|
|
22772
22752
|
startBridge: () => startBridge,
|
|
22773
22753
|
bridgeSocketPathForHome: () => bridgeSocketPathForHome
|
|
22774
22754
|
});
|
|
22775
|
-
import { mkdir as
|
|
22755
|
+
import { mkdir as mkdir4, unlink as unlink8, writeFile as writeFile3 } from "fs/promises";
|
|
22776
22756
|
import { homedir as homedir19 } from "os";
|
|
22777
22757
|
import { join as join16 } from "path";
|
|
22778
|
-
import { fileURLToPath as
|
|
22758
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22779
22759
|
function bridgeSocketPathForHome(home, pid = process.pid) {
|
|
22780
22760
|
const runDir = join16(home, ".kobe", "run");
|
|
22781
22761
|
return fitSocketPath(join16(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
|
|
@@ -22785,11 +22765,11 @@ async function startBridge(orch, opts = {}) {
|
|
|
22785
22765
|
const runDir = join16(home, ".kobe", "run");
|
|
22786
22766
|
const socketPath = bridgeSocketPathForHome(home);
|
|
22787
22767
|
const mcpConfigPath = join16(runDir, `mcp-${process.pid}.json`);
|
|
22788
|
-
await
|
|
22768
|
+
await mkdir4(runDir, { recursive: true });
|
|
22789
22769
|
const server = await startBridgeServer(orch, socketPath);
|
|
22790
|
-
await
|
|
22770
|
+
await mkdir4(runDir, { recursive: true });
|
|
22791
22771
|
const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
22792
|
-
const entry =
|
|
22772
|
+
const entry = fileURLToPath2(new URL(`../../cli/index${moduleExt}`, import.meta.url));
|
|
22793
22773
|
const mcpConfig = {
|
|
22794
22774
|
mcpServers: {
|
|
22795
22775
|
kobe: {
|
|
@@ -23267,12 +23247,12 @@ class MetadataSuggester {
|
|
|
23267
23247
|
...context.permissionMode ? { permissionMode: context.permissionMode } : {}
|
|
23268
23248
|
};
|
|
23269
23249
|
let timer = null;
|
|
23270
|
-
const timeout = new Promise((
|
|
23250
|
+
const timeout = new Promise((resolve3) => {
|
|
23271
23251
|
timer = setTimeout(() => {
|
|
23272
23252
|
timedOut = true;
|
|
23273
23253
|
if (handle)
|
|
23274
23254
|
context.engine.stop(handle).catch(() => {});
|
|
23275
|
-
|
|
23255
|
+
resolve3(null);
|
|
23276
23256
|
}, SUGGESTION_TIMEOUT_MS);
|
|
23277
23257
|
});
|
|
23278
23258
|
const run = (async () => {
|
|
@@ -23865,8 +23845,8 @@ class TaskRunner {
|
|
|
23865
23845
|
});
|
|
23866
23846
|
} else {
|
|
23867
23847
|
let releaseLatch = () => {};
|
|
23868
|
-
const latch = new Promise((
|
|
23869
|
-
releaseLatch =
|
|
23848
|
+
const latch = new Promise((resolve3) => {
|
|
23849
|
+
releaseLatch = resolve3;
|
|
23870
23850
|
});
|
|
23871
23851
|
this.firstSpawnLatches.set(key, latch);
|
|
23872
23852
|
try {
|
|
@@ -24471,8 +24451,8 @@ class SlugAllocator {
|
|
|
24471
24451
|
async allocate(repo) {
|
|
24472
24452
|
const previous = this.chain;
|
|
24473
24453
|
let release;
|
|
24474
|
-
this.chain = new Promise((
|
|
24475
|
-
release =
|
|
24454
|
+
this.chain = new Promise((resolve3) => {
|
|
24455
|
+
release = resolve3;
|
|
24476
24456
|
});
|
|
24477
24457
|
await previous;
|
|
24478
24458
|
try {
|
|
@@ -25696,7 +25676,7 @@ var onRejection, onException;
|
|
|
25696
25676
|
// src/engine/claude-code-local/plan-usage.ts
|
|
25697
25677
|
import { execFile } from "child_process";
|
|
25698
25678
|
import { createHash as createHash2 } from "crypto";
|
|
25699
|
-
import { readFile as
|
|
25679
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
25700
25680
|
import { homedir as homedir21, userInfo } from "os";
|
|
25701
25681
|
import { join as join17 } from "path";
|
|
25702
25682
|
import { promisify } from "util";
|
|
@@ -25731,7 +25711,7 @@ async function readPlainTextToken() {
|
|
|
25731
25711
|
const configDir = process.env.CLAUDE_CONFIG_DIR ?? join17(homedir21(), ".claude");
|
|
25732
25712
|
const path16 = join17(configDir, ".credentials.json");
|
|
25733
25713
|
try {
|
|
25734
|
-
const raw = await
|
|
25714
|
+
const raw = await readFile10(path16, "utf8");
|
|
25735
25715
|
return parseStoredOAuth(raw);
|
|
25736
25716
|
} catch {
|
|
25737
25717
|
return null;
|
|
@@ -25853,12 +25833,12 @@ var init_plan_usage_poller = __esm(() => {
|
|
|
25853
25833
|
});
|
|
25854
25834
|
|
|
25855
25835
|
// src/daemon/rc-bridge.ts
|
|
25856
|
-
import { spawn as
|
|
25836
|
+
import { spawn as spawn13 } from "child_process";
|
|
25857
25837
|
function createRcBridge(options = {}) {
|
|
25858
25838
|
const stopGraceMs = options.stopGraceMs ?? 5000;
|
|
25859
25839
|
const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
|
|
25860
25840
|
const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
|
|
25861
|
-
const spawner = options.spawner ?? ((cmd, args, cwd) =>
|
|
25841
|
+
const spawner = options.spawner ?? ((cmd, args, cwd) => spawn13(cmd, [...args], {
|
|
25862
25842
|
cwd,
|
|
25863
25843
|
stdio: ["ignore", "pipe", "pipe"],
|
|
25864
25844
|
env: { ...process.env }
|
|
@@ -25888,11 +25868,11 @@ function createRcBridge(options = {}) {
|
|
|
25888
25868
|
}
|
|
25889
25869
|
function settleReady(status) {
|
|
25890
25870
|
clearReadyTimer();
|
|
25891
|
-
const
|
|
25871
|
+
const resolve3 = readyResolve;
|
|
25892
25872
|
readyResolve = null;
|
|
25893
25873
|
readyReject = null;
|
|
25894
|
-
if (
|
|
25895
|
-
|
|
25874
|
+
if (resolve3)
|
|
25875
|
+
resolve3(status);
|
|
25896
25876
|
}
|
|
25897
25877
|
function failReady(err) {
|
|
25898
25878
|
clearReadyTimer();
|
|
@@ -25978,8 +25958,8 @@ function createRcBridge(options = {}) {
|
|
|
25978
25958
|
update({ state: "error", errorMessage: err.message });
|
|
25979
25959
|
failReady(err);
|
|
25980
25960
|
});
|
|
25981
|
-
return new Promise((
|
|
25982
|
-
readyResolve =
|
|
25961
|
+
return new Promise((resolve3, reject) => {
|
|
25962
|
+
readyResolve = resolve3;
|
|
25983
25963
|
readyReject = reject;
|
|
25984
25964
|
readyTimer = setTimeout(() => {
|
|
25985
25965
|
if (proc) {
|
|
@@ -26001,8 +25981,8 @@ function createRcBridge(options = {}) {
|
|
|
26001
25981
|
return snapshot;
|
|
26002
25982
|
}
|
|
26003
25983
|
update({ ...snapshot, state: "stopping" });
|
|
26004
|
-
const onExitPromise = new Promise((
|
|
26005
|
-
const onceExit = () =>
|
|
25984
|
+
const onExitPromise = new Promise((resolve3) => {
|
|
25985
|
+
const onceExit = () => resolve3();
|
|
26006
25986
|
child.once("exit", onceExit);
|
|
26007
25987
|
});
|
|
26008
25988
|
try {
|
|
@@ -26031,17 +26011,17 @@ var init_rc_bridge = __esm(() => {
|
|
|
26031
26011
|
});
|
|
26032
26012
|
|
|
26033
26013
|
// src/daemon/server.ts
|
|
26034
|
-
import { mkdir as
|
|
26014
|
+
import { mkdir as mkdir5, readFile as readFile11, unlink as unlink9, writeFile as writeFile4 } from "fs/promises";
|
|
26035
26015
|
import { createServer as createServer2 } from "net";
|
|
26036
|
-
import { dirname as
|
|
26016
|
+
import { dirname as dirname6 } from "path";
|
|
26037
26017
|
async function startDaemonServer(orch, options = {}) {
|
|
26038
26018
|
const socketPath = options.socketPath ?? defaultDaemonSocketPath(options.homeDir);
|
|
26039
26019
|
const pidPath = options.pidPath ?? defaultDaemonPidPath(options.homeDir);
|
|
26040
26020
|
const startedAt = options.startedAt ?? new Date;
|
|
26041
26021
|
const clients = new Set;
|
|
26042
26022
|
let nextClientId = 1;
|
|
26043
|
-
await
|
|
26044
|
-
await
|
|
26023
|
+
await mkdir5(dirname6(socketPath), { recursive: true });
|
|
26024
|
+
await mkdir5(dirname6(pidPath), { recursive: true });
|
|
26045
26025
|
await unlink9(socketPath).catch(() => {});
|
|
26046
26026
|
const server = createServer2((socket) => {
|
|
26047
26027
|
const client = {
|
|
@@ -26086,17 +26066,17 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
26086
26066
|
client.subscriptions.clear();
|
|
26087
26067
|
client.socket.destroy();
|
|
26088
26068
|
}
|
|
26089
|
-
await new Promise((
|
|
26069
|
+
await new Promise((resolve3) => server.close(() => resolve3()));
|
|
26090
26070
|
await unlink9(socketPath).catch(() => {});
|
|
26091
26071
|
await unlink9(pidPath).catch(() => {});
|
|
26092
26072
|
}
|
|
26093
26073
|
};
|
|
26094
26074
|
planUsagePoller.start();
|
|
26095
|
-
await new Promise((
|
|
26075
|
+
await new Promise((resolve3, reject) => {
|
|
26096
26076
|
server.once("error", reject);
|
|
26097
26077
|
server.listen(socketPath, () => {
|
|
26098
26078
|
server.removeListener("error", reject);
|
|
26099
|
-
|
|
26079
|
+
resolve3();
|
|
26100
26080
|
});
|
|
26101
26081
|
});
|
|
26102
26082
|
await writeFile4(pidPath, `${process.pid}
|
|
@@ -26464,7 +26444,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
26464
26444
|
}
|
|
26465
26445
|
async function readPidFile(pidPath) {
|
|
26466
26446
|
try {
|
|
26467
|
-
const raw = await
|
|
26447
|
+
const raw = await readFile11(pidPath, "utf8");
|
|
26468
26448
|
const pid = Number(raw.trim());
|
|
26469
26449
|
return Number.isFinite(pid) ? pid : null;
|
|
26470
26450
|
} catch {
|
|
@@ -26647,7 +26627,7 @@ async function runDaemonSubcommand(argv) {
|
|
|
26647
26627
|
const stopRequest = client.request("daemon.stop").catch(() => {
|
|
26648
26628
|
return;
|
|
26649
26629
|
});
|
|
26650
|
-
const stopTimeout = new Promise((
|
|
26630
|
+
const stopTimeout = new Promise((resolve3) => setTimeout(resolve3, 2000));
|
|
26651
26631
|
await Promise.race([stopRequest, stopTimeout]);
|
|
26652
26632
|
client.close();
|
|
26653
26633
|
if (oldPid && oldPid !== process.pid) {
|
|
@@ -26665,12 +26645,12 @@ async function runDaemonSubcommand(argv) {
|
|
|
26665
26645
|
} catch {}
|
|
26666
26646
|
escalated = true;
|
|
26667
26647
|
}
|
|
26668
|
-
await new Promise((
|
|
26648
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
26669
26649
|
}
|
|
26670
26650
|
try {
|
|
26671
26651
|
process.kill(oldPid, 0);
|
|
26672
26652
|
process.kill(oldPid, "SIGKILL");
|
|
26673
|
-
await new Promise((
|
|
26653
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
26674
26654
|
} catch {}
|
|
26675
26655
|
}
|
|
26676
26656
|
await unlink10(socketPath).catch(() => {});
|
|
@@ -28663,12 +28643,12 @@ var init_settings_dialog = __esm(() => {
|
|
|
28663
28643
|
init_model();
|
|
28664
28644
|
init_sections();
|
|
28665
28645
|
SettingsDialog.show = (dialog, kv, orchestrator) => {
|
|
28666
|
-
return new Promise((
|
|
28646
|
+
return new Promise((resolve3) => {
|
|
28667
28647
|
dialog.replace(() => createComponent2(SettingsDialog, {
|
|
28668
28648
|
kv,
|
|
28669
28649
|
orchestrator,
|
|
28670
|
-
onClose: () =>
|
|
28671
|
-
}), () =>
|
|
28650
|
+
onClose: () => resolve3()
|
|
28651
|
+
}), () => resolve3());
|
|
28672
28652
|
});
|
|
28673
28653
|
};
|
|
28674
28654
|
});
|
|
@@ -28767,7 +28747,7 @@ var init_pulse = () => {};
|
|
|
28767
28747
|
// src/tui/lib/sound.ts
|
|
28768
28748
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
28769
28749
|
import { tmpdir as tmpdir4 } from "os";
|
|
28770
|
-
import { basename as basename2, isAbsolute, join as join19, resolve as
|
|
28750
|
+
import { basename as basename2, isAbsolute, join as join19, resolve as resolve3 } from "path";
|
|
28771
28751
|
function args(player, file, volume) {
|
|
28772
28752
|
if (player === "ffplay")
|
|
28773
28753
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -28825,7 +28805,7 @@ function pulse(volume = 0.4) {
|
|
|
28825
28805
|
var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
28826
28806
|
var init_sound = __esm(() => {
|
|
28827
28807
|
init_pulse();
|
|
28828
|
-
pulseAsset = isAbsolute(pulse_default) ? pulse_default :
|
|
28808
|
+
pulseAsset = isAbsolute(pulse_default) ? pulse_default : resolve3(import.meta.dir, pulse_default);
|
|
28829
28809
|
DIR = join19(tmpdir4(), "kobe-sfx");
|
|
28830
28810
|
PLAYERS = [
|
|
28831
28811
|
"ffplay",
|
|
@@ -28845,7 +28825,7 @@ var init_sound = __esm(() => {
|
|
|
28845
28825
|
|
|
28846
28826
|
// src/tui/context/kv.tsx
|
|
28847
28827
|
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
28848
|
-
import { dirname as
|
|
28828
|
+
import { dirname as dirname7 } from "path";
|
|
28849
28829
|
function loadInitial() {
|
|
28850
28830
|
const statePath2 = kvStatePath();
|
|
28851
28831
|
try {
|
|
@@ -28877,7 +28857,7 @@ var init_kv = __esm(() => {
|
|
|
28877
28857
|
writeTimer = null;
|
|
28878
28858
|
const statePath2 = kvStatePath();
|
|
28879
28859
|
try {
|
|
28880
|
-
mkdirSync5(
|
|
28860
|
+
mkdirSync5(dirname7(statePath2), {
|
|
28881
28861
|
recursive: true
|
|
28882
28862
|
});
|
|
28883
28863
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -28916,7 +28896,7 @@ var init_kv = __esm(() => {
|
|
|
28916
28896
|
}
|
|
28917
28897
|
const statePath2 = kvStatePath();
|
|
28918
28898
|
try {
|
|
28919
|
-
mkdirSync5(
|
|
28899
|
+
mkdirSync5(dirname7(statePath2), {
|
|
28920
28900
|
recursive: true
|
|
28921
28901
|
});
|
|
28922
28902
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -30479,7 +30459,7 @@ var init_create_pr_button = __esm(() => {
|
|
|
30479
30459
|
});
|
|
30480
30460
|
|
|
30481
30461
|
// src/tui/lib/worktree-opener.ts
|
|
30482
|
-
import { spawn as
|
|
30462
|
+
import { spawn as spawn14 } from "child_process";
|
|
30483
30463
|
import { existsSync as existsSync10 } from "fs";
|
|
30484
30464
|
import { basename as basename4, delimiter, isAbsolute as isAbsolute2, join as join20 } from "path";
|
|
30485
30465
|
function executableOnPath(command, env, exists) {
|
|
@@ -30538,7 +30518,7 @@ function buildOpenWorktreeCommand(worktreePath, opener) {
|
|
|
30538
30518
|
function openWorktree(worktreePath, opener, deps = {}) {
|
|
30539
30519
|
if (!worktreePath)
|
|
30540
30520
|
return false;
|
|
30541
|
-
const spawnFn = deps.spawn ??
|
|
30521
|
+
const spawnFn = deps.spawn ?? spawn14;
|
|
30542
30522
|
const [command, args2] = buildOpenWorktreeCommand(worktreePath, opener);
|
|
30543
30523
|
try {
|
|
30544
30524
|
const child = spawnFn(command, args2, { detached: true, stdio: "ignore" });
|
|
@@ -32121,7 +32101,7 @@ class FakeAIEngine {
|
|
|
32121
32101
|
}
|
|
32122
32102
|
if (q2.closed)
|
|
32123
32103
|
return;
|
|
32124
|
-
await new Promise((
|
|
32104
|
+
await new Promise((resolve4) => q2.waiters.push(resolve4));
|
|
32125
32105
|
}
|
|
32126
32106
|
}
|
|
32127
32107
|
};
|
|
@@ -32455,7 +32435,7 @@ async function mountFakeEngineServer(fake) {
|
|
|
32455
32435
|
}
|
|
32456
32436
|
});
|
|
32457
32437
|
});
|
|
32458
|
-
await new Promise((
|
|
32438
|
+
await new Promise((resolve4) => server.listen(port, "127.0.0.1", () => resolve4()));
|
|
32459
32439
|
server.unref();
|
|
32460
32440
|
}
|
|
32461
32441
|
var init_engine_bootstrap = __esm(() => {
|
|
@@ -32621,7 +32601,7 @@ var init_use_pane_sizes = __esm(() => {
|
|
|
32621
32601
|
});
|
|
32622
32602
|
|
|
32623
32603
|
// src/tui/component/new-task-dialog/state.ts
|
|
32624
|
-
import { spawn as
|
|
32604
|
+
import { spawn as spawn15 } from "child_process";
|
|
32625
32605
|
import * as fs5 from "fs";
|
|
32626
32606
|
import * as os from "os";
|
|
32627
32607
|
import * as path17 from "path";
|
|
@@ -32907,10 +32887,10 @@ function findAvailableFolderName(parentDir, base) {
|
|
|
32907
32887
|
return trimmed;
|
|
32908
32888
|
}
|
|
32909
32889
|
function cloneRepo(url, target, onProgress) {
|
|
32910
|
-
return new Promise((
|
|
32890
|
+
return new Promise((resolve4) => {
|
|
32911
32891
|
let stderrBuf = "";
|
|
32912
32892
|
try {
|
|
32913
|
-
const child =
|
|
32893
|
+
const child = spawn15("git", ["clone", "--progress", url, target], {
|
|
32914
32894
|
stdio: ["ignore", "ignore", "pipe"]
|
|
32915
32895
|
});
|
|
32916
32896
|
child.stderr?.setEncoding("utf-8");
|
|
@@ -32924,18 +32904,18 @@ function cloneRepo(url, target, onProgress) {
|
|
|
32924
32904
|
}
|
|
32925
32905
|
});
|
|
32926
32906
|
child.on("error", (err) => {
|
|
32927
|
-
|
|
32907
|
+
resolve4({ ok: false, error: err.message });
|
|
32928
32908
|
});
|
|
32929
32909
|
child.on("close", (code) => {
|
|
32930
32910
|
if (code === 0) {
|
|
32931
|
-
|
|
32911
|
+
resolve4({ ok: true, path: target });
|
|
32932
32912
|
return;
|
|
32933
32913
|
}
|
|
32934
32914
|
const tail = stderrBuf.split(/[\r\n]+/).filter((s2) => s2.trim().length > 0).pop() ?? `git clone exited with ${code}`;
|
|
32935
|
-
|
|
32915
|
+
resolve4({ ok: false, error: tail });
|
|
32936
32916
|
});
|
|
32937
32917
|
} catch (err) {
|
|
32938
|
-
|
|
32918
|
+
resolve4({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
32939
32919
|
}
|
|
32940
32920
|
});
|
|
32941
32921
|
}
|
|
@@ -33728,16 +33708,16 @@ var init_dialog2 = __esm(() => {
|
|
|
33728
33708
|
|
|
33729
33709
|
// src/tui/component/new-task-dialog/index.tsx
|
|
33730
33710
|
function show(dialog, defaultRepo, savedRepos, options) {
|
|
33731
|
-
return new Promise((
|
|
33711
|
+
return new Promise((resolve4) => {
|
|
33732
33712
|
dialog.replace(() => createComponent2(NewTaskDialogView, {
|
|
33733
33713
|
defaultRepo,
|
|
33734
33714
|
savedRepos,
|
|
33735
33715
|
get defaultCloneParent() {
|
|
33736
33716
|
return options?.defaultCloneParent;
|
|
33737
33717
|
},
|
|
33738
|
-
onSubmit: (v2) =>
|
|
33739
|
-
onCancel: () =>
|
|
33740
|
-
}), () =>
|
|
33718
|
+
onSubmit: (v2) => resolve4(v2),
|
|
33719
|
+
onCancel: () => resolve4(undefined)
|
|
33720
|
+
}), () => resolve4(undefined));
|
|
33741
33721
|
dialog.setSize("medium");
|
|
33742
33722
|
});
|
|
33743
33723
|
}
|
|
@@ -34462,7 +34442,7 @@ var init_dialog3 = __esm(() => {
|
|
|
34462
34442
|
|
|
34463
34443
|
// src/tui/component/quick-fork-dialog/index.tsx
|
|
34464
34444
|
function show2(dialog, input) {
|
|
34465
|
-
return new Promise((
|
|
34445
|
+
return new Promise((resolve4) => {
|
|
34466
34446
|
dialog.replace(() => createComponent2(QuickForkDialogView, {
|
|
34467
34447
|
get repo() {
|
|
34468
34448
|
return input.repo;
|
|
@@ -34476,9 +34456,9 @@ function show2(dialog, input) {
|
|
|
34476
34456
|
get effort() {
|
|
34477
34457
|
return input.effort;
|
|
34478
34458
|
},
|
|
34479
|
-
onSubmit: (result) =>
|
|
34480
|
-
onCancel: () =>
|
|
34481
|
-
}), () =>
|
|
34459
|
+
onSubmit: (result) => resolve4(result),
|
|
34460
|
+
onCancel: () => resolve4(undefined)
|
|
34461
|
+
}), () => resolve4(undefined));
|
|
34482
34462
|
dialog.setSize("large");
|
|
34483
34463
|
});
|
|
34484
34464
|
}
|
|
@@ -34569,15 +34549,15 @@ var init_dialog4 = __esm(() => {
|
|
|
34569
34549
|
|
|
34570
34550
|
// src/tui/component/rename-task-dialog/index.tsx
|
|
34571
34551
|
function show3(dialog, currentTitle, opts = {}) {
|
|
34572
|
-
return new Promise((
|
|
34552
|
+
return new Promise((resolve4) => {
|
|
34573
34553
|
dialog.replace(() => createComponent2(RenameTaskDialogView, {
|
|
34574
34554
|
currentTitle,
|
|
34575
34555
|
get dialogTitle() {
|
|
34576
34556
|
return opts.dialogTitle;
|
|
34577
34557
|
},
|
|
34578
|
-
onSubmit: (v2) =>
|
|
34579
|
-
onCancel: () =>
|
|
34580
|
-
}), () =>
|
|
34558
|
+
onSubmit: (v2) => resolve4(v2),
|
|
34559
|
+
onCancel: () => resolve4(undefined)
|
|
34560
|
+
}), () => resolve4(undefined));
|
|
34581
34561
|
});
|
|
34582
34562
|
}
|
|
34583
34563
|
var RenameTaskDialog;
|
|
@@ -36001,8 +35981,8 @@ function makeDropdownWindow(items, cursor, maxVisible) {
|
|
|
36001
35981
|
|
|
36002
35982
|
// src/tui/panes/chat/composer/history-store.ts
|
|
36003
35983
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
36004
|
-
import { appendFile as appendFile2, mkdir as
|
|
36005
|
-
import { dirname as
|
|
35984
|
+
import { appendFile as appendFile2, mkdir as mkdir6, readFile as readFile12, rename as rename2, writeFile as writeFile5 } from "fs/promises";
|
|
35985
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
36006
35986
|
function defaultHistoryPath() {
|
|
36007
35987
|
return join22(kobeStateDir(), "composer-history.jsonl");
|
|
36008
35988
|
}
|
|
@@ -36048,8 +36028,8 @@ function loadFromDisk(path18 = defaultHistoryPath()) {
|
|
|
36048
36028
|
function appendToDisk(entry, path18 = defaultHistoryPath()) {
|
|
36049
36029
|
const next = writeQueue.then(async () => {
|
|
36050
36030
|
try {
|
|
36051
|
-
const dir =
|
|
36052
|
-
await
|
|
36031
|
+
const dir = dirname8(path18);
|
|
36032
|
+
await mkdir6(dir, { recursive: true });
|
|
36053
36033
|
const line = `${JSON.stringify(entry)}
|
|
36054
36034
|
`;
|
|
36055
36035
|
await appendFile2(path18, line, { encoding: "utf8", mode: 384 });
|
|
@@ -36064,7 +36044,7 @@ async function pruneToCap(path18 = defaultHistoryPath(), cap = DISK_HISTORY_CAP)
|
|
|
36064
36044
|
try {
|
|
36065
36045
|
if (!existsSync12(path18))
|
|
36066
36046
|
return;
|
|
36067
|
-
const text = await
|
|
36047
|
+
const text = await readFile12(path18, "utf8");
|
|
36068
36048
|
const lines = text.split(`
|
|
36069
36049
|
`).filter((l2) => l2.length > 0);
|
|
36070
36050
|
if (lines.length <= cap)
|
|
@@ -36466,7 +36446,7 @@ var init_HistoryPalette = __esm(() => {
|
|
|
36466
36446
|
|
|
36467
36447
|
// src/tui/panes/chat/composer/history-palette.tsx
|
|
36468
36448
|
function show4(dialog, opts) {
|
|
36469
|
-
return new Promise((
|
|
36449
|
+
return new Promise((resolve4) => {
|
|
36470
36450
|
dialog.replace(() => createComponent2(HistoryPaletteView, {
|
|
36471
36451
|
get taskLabelFor() {
|
|
36472
36452
|
return opts.taskLabelFor;
|
|
@@ -36474,9 +36454,9 @@ function show4(dialog, opts) {
|
|
|
36474
36454
|
get currentProject() {
|
|
36475
36455
|
return opts.currentProject;
|
|
36476
36456
|
},
|
|
36477
|
-
onSubmit: (v2) =>
|
|
36478
|
-
onCancel: () =>
|
|
36479
|
-
}), () =>
|
|
36457
|
+
onSubmit: (v2) => resolve4(v2),
|
|
36458
|
+
onCancel: () => resolve4(undefined)
|
|
36459
|
+
}), () => resolve4(undefined));
|
|
36480
36460
|
});
|
|
36481
36461
|
}
|
|
36482
36462
|
var HistoryPalette;
|
|
@@ -41428,15 +41408,15 @@ var init_ModelPicker = __esm(() => {
|
|
|
41428
41408
|
init_keymap();
|
|
41429
41409
|
init_dialog();
|
|
41430
41410
|
ModelPicker.show = (dialog, current, currentEffort, currentVendor, lockedVendor) => {
|
|
41431
|
-
return new Promise((
|
|
41411
|
+
return new Promise((resolve4) => {
|
|
41432
41412
|
dialog.replace(() => createComponent2(ModelPicker, {
|
|
41433
41413
|
current,
|
|
41434
41414
|
currentEffort,
|
|
41435
41415
|
currentVendor,
|
|
41436
41416
|
lockedVendor,
|
|
41437
|
-
onPick: (choice) =>
|
|
41438
|
-
onCancel: () =>
|
|
41439
|
-
}), () =>
|
|
41417
|
+
onPick: (choice) => resolve4(choice),
|
|
41418
|
+
onCancel: () => resolve4(undefined)
|
|
41419
|
+
}), () => resolve4(undefined));
|
|
41440
41420
|
});
|
|
41441
41421
|
};
|
|
41442
41422
|
});
|
|
@@ -42035,13 +42015,13 @@ var init_store2 = __esm(() => {
|
|
|
42035
42015
|
});
|
|
42036
42016
|
|
|
42037
42017
|
// src/tui/panes/chat/bash-mode.ts
|
|
42038
|
-
import { spawn as
|
|
42018
|
+
import { spawn as spawn16 } from "child_process";
|
|
42039
42019
|
async function runBashCommand(opts) {
|
|
42040
42020
|
const shell = resolveShell();
|
|
42041
|
-
return await new Promise((
|
|
42021
|
+
return await new Promise((resolve4, reject) => {
|
|
42042
42022
|
let child;
|
|
42043
42023
|
try {
|
|
42044
|
-
child =
|
|
42024
|
+
child = spawn16(shell, ["-c", opts.command], {
|
|
42045
42025
|
cwd: opts.cwd,
|
|
42046
42026
|
stdio: ["ignore", "pipe", "pipe"],
|
|
42047
42027
|
env: process.env,
|
|
@@ -42060,7 +42040,7 @@ async function runBashCommand(opts) {
|
|
|
42060
42040
|
if (timeoutHandle)
|
|
42061
42041
|
clearTimeout(timeoutHandle);
|
|
42062
42042
|
opts.signal.removeEventListener("abort", onAbort);
|
|
42063
|
-
|
|
42043
|
+
resolve4(result);
|
|
42064
42044
|
};
|
|
42065
42045
|
child.stdout?.setEncoding("utf8");
|
|
42066
42046
|
child.stderr?.setEncoding("utf8");
|
|
@@ -45182,7 +45162,7 @@ var init_tui = __esm(() => {
|
|
|
45182
45162
|
});
|
|
45183
45163
|
|
|
45184
45164
|
// src/cli/index.ts
|
|
45185
|
-
import { resolve as
|
|
45165
|
+
import { resolve as resolve4 } from "path";
|
|
45186
45166
|
|
|
45187
45167
|
// src/cli/daemon-mode.ts
|
|
45188
45168
|
function parseCliArgs(argv) {
|
|
@@ -45208,7 +45188,7 @@ function parseCliArgs(argv) {
|
|
|
45208
45188
|
|
|
45209
45189
|
// src/cli/index.ts
|
|
45210
45190
|
async function runAddSubcommand(arg) {
|
|
45211
|
-
const target =
|
|
45191
|
+
const target = resolve4(process.cwd(), arg && arg.length > 0 ? arg : ".");
|
|
45212
45192
|
const { addSavedRepo: addSavedRepo2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
45213
45193
|
const result = addSavedRepo2(target);
|
|
45214
45194
|
if (result.added) {
|
|
@@ -45281,6 +45261,8 @@ async function main() {
|
|
|
45281
45261
|
await runSkillSubcommand2(rest);
|
|
45282
45262
|
return;
|
|
45283
45263
|
}
|
|
45264
|
+
const { ensureSkillInstalled: ensureSkillInstalled2 } = await Promise.resolve().then(() => (init_skill_cmd(), exports_skill_cmd));
|
|
45265
|
+
await ensureSkillInstalled2();
|
|
45284
45266
|
const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
|
|
45285
45267
|
await startTui2({ daemonMode: parsed.daemonMode });
|
|
45286
45268
|
}
|
package/package.json
CHANGED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: kobe
|
|
3
|
-
description: Spawn and coordinate parallel coding tasks via the kobe TUI from your shell. Use when the user asks to "try N approaches in parallel", "fan out", "compare implementations side-by-side", or "split this into subtasks". One task per attempt, each gets its own git worktree and its own agent session.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# kobe — parallel coding tasks from your shell
|
|
7
|
-
|
|
8
|
-
kobe is a local terminal UI that runs many AI coding sessions at once.
|
|
9
|
-
Each task lives in its own git worktree with its own agent session.
|
|
10
|
-
When the user asks for parallel exploration, fan out by spawning kobe
|
|
11
|
-
tasks instead of doing N attempts sequentially in one chat.
|
|
12
|
-
|
|
13
|
-
The daemon must already be running. The TUI auto-starts it; if not, run
|
|
14
|
-
`kobe daemon start` once.
|
|
15
|
-
|
|
16
|
-
## Triggers — fire fan-out when the user says
|
|
17
|
-
|
|
18
|
-
- "try N approaches in parallel", "并行试 N 个"
|
|
19
|
-
- "fan out", "split this into subtasks"
|
|
20
|
-
- "compare X and Y side-by-side"
|
|
21
|
-
- "explore a few different ways to..."
|
|
22
|
-
- explicit count: "spin up 3 tasks for..."
|
|
23
|
-
|
|
24
|
-
Single ambiguous tasks → no fan-out. Just do them in your own chat.
|
|
25
|
-
|
|
26
|
-
## How — the five verbs
|
|
27
|
-
|
|
28
|
-
Each verb is a one-shot shell command. Reads stdin: none. Writes a JSON
|
|
29
|
-
object to stdout, exits 0. On error, writes
|
|
30
|
-
`{"error":{"message":"...","code":"..."}}` to stderr and exits non-zero.
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# Spawn a new task. Returns task JSON; read `.taskId`.
|
|
34
|
-
kobe api spawn-task --repo $PWD --prompt "<scoped prompt>" [--title T] [--base-branch B]
|
|
35
|
-
|
|
36
|
-
# Create an extra chat tab on an existing task.
|
|
37
|
-
kobe api create-tab --task-id <id> [--title T]
|
|
38
|
-
|
|
39
|
-
# Send a follow-up prompt to a task (resumes the agent session).
|
|
40
|
-
kobe api send --task-id <id> --prompt "<text>" [--tab-id TID]
|
|
41
|
-
|
|
42
|
-
# Read a task's current state (status, branch, worktree, tabs).
|
|
43
|
-
kobe api get-task --task-id <id>
|
|
44
|
-
|
|
45
|
-
# Read a single tab off a task.
|
|
46
|
-
kobe api get-tab --task-id <id> --tab-id <tab-id>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Add `--pretty` to any verb to pretty-print stdout for inspection.
|
|
50
|
-
Output to stdout is always one JSON object terminated with `\n`.
|
|
51
|
-
|
|
52
|
-
## Workflow — fan-out + report back
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# 1. Spawn 3 parallel attempts. Cap at 3-4.
|
|
56
|
-
T1=$(kobe api spawn-task --repo "$PWD" --prompt "Approach A: use a state machine" | jq -r .taskId)
|
|
57
|
-
T2=$(kobe api spawn-task --repo "$PWD" --prompt "Approach B: use event sourcing" | jq -r .taskId)
|
|
58
|
-
T3=$(kobe api spawn-task --repo "$PWD" --prompt "Approach C: use a reducer pattern" | jq -r .taskId)
|
|
59
|
-
|
|
60
|
-
# 2. Tell the user what you spawned so the sidebar reads back correctly.
|
|
61
|
-
echo "Spawned three tasks: $T1 (state machine), $T2 (event sourcing), $T3 (reducer)"
|
|
62
|
-
|
|
63
|
-
# 3. Poll until each is idle. Don't tight-loop; sleep 5-15s between checks.
|
|
64
|
-
for ID in $T1 $T2 $T3; do
|
|
65
|
-
while true; do
|
|
66
|
-
STATUS=$(kobe api get-task --task-id "$ID" | jq -r .task.status)
|
|
67
|
-
case "$STATUS" in
|
|
68
|
-
idle|awaiting-approval|done) break ;;
|
|
69
|
-
esac
|
|
70
|
-
sleep 10
|
|
71
|
-
done
|
|
72
|
-
done
|
|
73
|
-
|
|
74
|
-
# 4. Read each result and aggregate. The active tab's id is on the task.
|
|
75
|
-
for ID in $T1 $T2 $T3; do
|
|
76
|
-
TAB=$(kobe api get-task --task-id "$ID" | jq -r .task.activeTabId)
|
|
77
|
-
kobe api get-tab --task-id "$ID" --tab-id "$TAB"
|
|
78
|
-
done
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Do
|
|
82
|
-
|
|
83
|
-
- Cap fan-out at 3-4 in parallel. The orchestrator caps total concurrency at 4.
|
|
84
|
-
- Give each subtask its own scoped prompt — don't dump the whole parent conversation.
|
|
85
|
-
- Tell the user what was spawned, with IDs, so they can follow along in the sidebar.
|
|
86
|
-
- Poll status with `sleep` between calls. 5-15 seconds is fine.
|
|
87
|
-
- Aggregate results back into your own chat before reporting to the user.
|
|
88
|
-
|
|
89
|
-
## Don't
|
|
90
|
-
|
|
91
|
-
- Don't fan out for single simple tasks. One thing → do it yourself in chat.
|
|
92
|
-
- Don't recursively spawn from inside a spawned task. There is no recursion
|
|
93
|
-
guard yet; you will starve the concurrency cap and the inner spawn will
|
|
94
|
-
hang.
|
|
95
|
-
- Don't use `kobe api send` as a chat channel. Every send is a full agent
|
|
96
|
-
turn — it costs tokens and time. Send a complete instruction, not a
|
|
97
|
-
conversation.
|
|
98
|
-
- Don't delete tasks. There is no `kobe api delete` verb on purpose. Cleanup
|
|
99
|
-
happens in the TUI by the user.
|
|
100
|
-
|
|
101
|
-
## When the daemon is not running
|
|
102
|
-
|
|
103
|
-
`kobe api ...` exits 2 with `{"error":{"code":"BAD_DAEMON",...}}` on stderr.
|
|
104
|
-
If you see this:
|
|
105
|
-
|
|
106
|
-
> The kobe daemon is not running. Ask the user to run `kobe daemon start`
|
|
107
|
-
> (or launch the kobe TUI, which auto-starts it), then re-try.
|
|
108
|
-
|
|
109
|
-
Don't try to start the daemon yourself; that's the user's call.
|