@sma1lboy/kobe 0.5.12 → 0.5.15
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 +2 -5
- package/dist/bin/kobed.js +118 -33
- package/dist/cli/index.js +6630 -711
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -28,11 +28,10 @@ that's the gap kobe fills.
|
|
|
28
28
|
|
|
29
29
|
[](https://www.npmjs.com/package/@sma1lboy/kobe)
|
|
30
30
|
|
|
31
|
-
You need
|
|
31
|
+
You need two things on `PATH`:
|
|
32
32
|
|
|
33
33
|
- [**Bun**](https://bun.sh) ≥ 1.0 — kobe's renderer is opentui, which uses Bun-FFI.
|
|
34
34
|
- [**`claude`** CLI](https://docs.anthropic.com/en/docs/claude-code) — the engine kobe drives. Run `claude --version` to confirm it's installed and signed in.
|
|
35
|
-
- **`tmux`** — the embedded terminal pane uses one tmux session per task. On macOS: `brew install tmux`.
|
|
36
35
|
|
|
37
36
|
Then:
|
|
38
37
|
|
|
@@ -148,9 +147,7 @@ and confirm `claude --version` works in the same shell you launched kobe from.
|
|
|
148
147
|
**`bun: command not found`** — install [Bun](https://bun.sh) (`curl -fsSL https://bun.sh/install | bash`).
|
|
149
148
|
kobe's renderer requires Bun ≥ 1.0; it does not run under Node.
|
|
150
149
|
|
|
151
|
-
**The terminal pane is blank
|
|
152
|
-
The embedded terminal is one tmux session per task; without `tmux` on `PATH`,
|
|
153
|
-
the pane stays empty but the rest of kobe still works.
|
|
150
|
+
**The terminal pane is blank** — kobe starts your `$SHELL` through Bun's native PTY. Confirm `$SHELL` points at an installed shell, your Bun version supports `Bun.spawn({ terminal })`, and the active task's worktree path still exists. `KOBE_TERMINAL_BACKEND=pipe` is available only as a fallback.
|
|
154
151
|
|
|
155
152
|
**`posix_spawnp failed` when running `bun run test:behavior`** — on macOS arm64,
|
|
156
153
|
Bun's installer occasionally ships `node-pty`'s prebuilt `spawn-helper` without
|
package/dist/bin/kobed.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
34
|
var __returnValue = (v) => v;
|
|
5
35
|
function __exportSetter(name, newValue) {
|
|
6
36
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -99,30 +129,36 @@ class KobeDaemonClient {
|
|
|
99
129
|
nextId = 1;
|
|
100
130
|
pending = new Map;
|
|
101
131
|
handlers = new Map;
|
|
132
|
+
lifecycleHandlers = new Map;
|
|
133
|
+
connecting = null;
|
|
134
|
+
disposed = false;
|
|
102
135
|
constructor(socketPath) {
|
|
103
136
|
this.socketPath = socketPath;
|
|
104
137
|
}
|
|
105
138
|
connect() {
|
|
106
139
|
if (this.socket)
|
|
107
140
|
return Promise.resolve();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
141
|
+
if (this.disposed)
|
|
142
|
+
return Promise.reject(new Error("daemon client disposed"));
|
|
143
|
+
if (this.connecting)
|
|
144
|
+
return this.connecting;
|
|
145
|
+
const p = this.openSocket();
|
|
146
|
+
this.connecting = p;
|
|
147
|
+
const cleanup = () => {
|
|
148
|
+
if (this.connecting === p)
|
|
149
|
+
this.connecting = null;
|
|
150
|
+
};
|
|
151
|
+
p.then(cleanup, cleanup);
|
|
152
|
+
return p;
|
|
121
153
|
}
|
|
122
154
|
close() {
|
|
155
|
+
this.disposed = true;
|
|
123
156
|
this.socket?.end();
|
|
124
157
|
this.socket = null;
|
|
125
158
|
}
|
|
159
|
+
forceDisconnect() {
|
|
160
|
+
this.socket?.destroy();
|
|
161
|
+
}
|
|
126
162
|
on(name, handler) {
|
|
127
163
|
let set = this.handlers.get(name);
|
|
128
164
|
if (!set) {
|
|
@@ -136,6 +172,19 @@ class KobeDaemonClient {
|
|
|
136
172
|
this.handlers.delete(name);
|
|
137
173
|
};
|
|
138
174
|
}
|
|
175
|
+
onLifecycle(name, handler) {
|
|
176
|
+
let set = this.lifecycleHandlers.get(name);
|
|
177
|
+
if (!set) {
|
|
178
|
+
set = new Set;
|
|
179
|
+
this.lifecycleHandlers.set(name, set);
|
|
180
|
+
}
|
|
181
|
+
set.add(handler);
|
|
182
|
+
return () => {
|
|
183
|
+
set?.delete(handler);
|
|
184
|
+
if (set?.size === 0)
|
|
185
|
+
this.lifecycleHandlers.delete(name);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
139
188
|
async request(name, payload) {
|
|
140
189
|
await this.connect();
|
|
141
190
|
const socket = this.socket;
|
|
@@ -148,6 +197,44 @@ class KobeDaemonClient {
|
|
|
148
197
|
socket.write(frameToLine({ type: "request", id, name, payload }));
|
|
149
198
|
return promise;
|
|
150
199
|
}
|
|
200
|
+
openSocket() {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const socket = connect(this.socketPath);
|
|
203
|
+
this.socket = socket;
|
|
204
|
+
const onConnect = () => {
|
|
205
|
+
socket.off("error", onError);
|
|
206
|
+
resolve();
|
|
207
|
+
};
|
|
208
|
+
const onError = (err) => {
|
|
209
|
+
socket.off("connect", onConnect);
|
|
210
|
+
if (this.socket === socket)
|
|
211
|
+
this.socket = null;
|
|
212
|
+
reject(err);
|
|
213
|
+
};
|
|
214
|
+
socket.once("connect", onConnect);
|
|
215
|
+
socket.once("error", onError);
|
|
216
|
+
socket.on("data", (chunk) => this.onData(chunk.toString("utf8")));
|
|
217
|
+
socket.on("close", () => this.onSocketClose(socket));
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
onSocketClose(which) {
|
|
221
|
+
if (this.socket !== which && this.socket !== null)
|
|
222
|
+
return;
|
|
223
|
+
this.socket = null;
|
|
224
|
+
for (const pending of this.pending.values())
|
|
225
|
+
pending.reject(new Error("daemon connection closed"));
|
|
226
|
+
this.pending.clear();
|
|
227
|
+
this.emitLifecycle("close");
|
|
228
|
+
}
|
|
229
|
+
emitLifecycle(name) {
|
|
230
|
+
for (const handler of this.lifecycleHandlers.get(name) ?? []) {
|
|
231
|
+
try {
|
|
232
|
+
handler();
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(`[kobe] lifecycle handler for "${name}" threw:`, err);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
151
238
|
onData(chunk) {
|
|
152
239
|
this.buffer += chunk;
|
|
153
240
|
let nl = this.buffer.indexOf(`
|
|
@@ -192,11 +279,10 @@ import { spawn } from "child_process";
|
|
|
192
279
|
import { existsSync } from "fs";
|
|
193
280
|
import { dirname, join as join2, resolve } from "path";
|
|
194
281
|
import { fileURLToPath } from "url";
|
|
195
|
-
async function
|
|
282
|
+
async function ensureDaemonReachable() {
|
|
196
283
|
const socketPath = defaultDaemonSocketPath();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return client;
|
|
284
|
+
if (await testCanConnect(socketPath))
|
|
285
|
+
return socketPath;
|
|
200
286
|
const { entry, runWithBun } = resolveKobedEntry();
|
|
201
287
|
const child = runWithBun ? spawn(process.execPath, [entry, "start"], {
|
|
202
288
|
detached: true,
|
|
@@ -211,24 +297,26 @@ async function connectOrStartDaemon() {
|
|
|
211
297
|
const deadline = Date.now() + 5000;
|
|
212
298
|
let lastErr;
|
|
213
299
|
while (Date.now() < deadline) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return next;
|
|
218
|
-
} catch (err) {
|
|
219
|
-
lastErr = err;
|
|
220
|
-
next.close();
|
|
221
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
222
|
-
}
|
|
300
|
+
if (await testCanConnect(socketPath))
|
|
301
|
+
return socketPath;
|
|
302
|
+
await new Promise((resolveTimer) => setTimeout(resolveTimer, 100));
|
|
223
303
|
}
|
|
224
|
-
throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message :
|
|
304
|
+
throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message : "timeout"}`);
|
|
225
305
|
}
|
|
226
|
-
async function
|
|
306
|
+
async function connectOrStartDaemon() {
|
|
307
|
+
const socketPath = await ensureDaemonReachable();
|
|
308
|
+
const client = new KobeDaemonClient(socketPath);
|
|
309
|
+
await client.connect();
|
|
310
|
+
return client;
|
|
311
|
+
}
|
|
312
|
+
async function testCanConnect(socketPath) {
|
|
313
|
+
const probe = new KobeDaemonClient(socketPath);
|
|
227
314
|
try {
|
|
228
|
-
await
|
|
315
|
+
await probe.connect();
|
|
316
|
+
probe.close();
|
|
229
317
|
return true;
|
|
230
318
|
} catch {
|
|
231
|
-
|
|
319
|
+
probe.close();
|
|
232
320
|
return false;
|
|
233
321
|
}
|
|
234
322
|
}
|
|
@@ -2613,9 +2701,6 @@ function kobeStateDir() {
|
|
|
2613
2701
|
function kvStatePath() {
|
|
2614
2702
|
return join5(homeDir(), ".config", "kobe", "state.json");
|
|
2615
2703
|
}
|
|
2616
|
-
function tmuxBin() {
|
|
2617
|
-
return process.env.KOBE_TMUX_BIN ?? "tmux";
|
|
2618
|
-
}
|
|
2619
2704
|
var init_env = () => {};
|
|
2620
2705
|
|
|
2621
2706
|
// src/state/repos.ts
|