@sma1lboy/kobe 0.7.28 → 0.7.29

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 CHANGED
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
90
90
  package_default = {
91
91
  $schema: "https://json.schemastore.org/package.json",
92
92
  name: "@sma1lboy/kobe",
93
- version: "0.7.28",
93
+ version: "0.7.29",
94
94
  description: "TUI orchestrator for Claude Code (codename)",
95
95
  type: "module",
96
96
  packageManager: "bun@1.3.13",
@@ -175,6 +175,9 @@ function kobeSettingsDir() {
175
175
  function keybindingsConfigPath() {
176
176
  return join(kobeSettingsDir(), "keybindings.yaml");
177
177
  }
178
+ function issueAssetsDir() {
179
+ return join(kobeStateDir(), "issue-assets");
180
+ }
178
181
  function remoteControlSocketPath(host, user, port) {
179
182
  const hash = createHash("sha1").update(`${user}@${host}:${port ?? 22}`).digest("hex").slice(0, 16);
180
183
  return join(kobeStateDir(), "ssh", `${hash}.sock`);
@@ -193,7 +196,9 @@ __export(exports_version, {
193
196
  recommendedGlobalInstallCommand: () => recommendedGlobalInstallCommand,
194
197
  isNewerSemver: () => isNewerSemver,
195
198
  fetchReleaseSummaries: () => fetchReleaseSummaries,
199
+ fetchReleaseNotesRange: () => fetchReleaseNotesRange,
196
200
  fetchReleaseNotes: () => fetchReleaseNotes,
201
+ compareSemver: () => compareSemver,
197
202
  checkLatestVersion: () => checkLatestVersion,
198
203
  UPDATE_SCRIPT_URL: () => UPDATE_SCRIPT_URL,
199
204
  UPDATE_COMMAND: () => UPDATE_COMMAND,
@@ -234,20 +239,23 @@ async function fetchLatestFromRegistry(packageName) {
234
239
  }
235
240
  }
236
241
  function isNewerSemver(latest, current) {
242
+ return compareSemver(latest, current) > 0;
243
+ }
244
+ function compareSemver(aVersion, bVersion) {
237
245
  const norm = (v) => v.split("-")[0] ?? v;
238
- const a = norm(latest).split(".").map((s) => Number.parseInt(s, 10));
239
- const b = norm(current).split(".").map((s) => Number.parseInt(s, 10));
246
+ const a = norm(aVersion).split(".").map((s) => Number.parseInt(s, 10));
247
+ const b = norm(bVersion).split(".").map((s) => Number.parseInt(s, 10));
240
248
  for (let i = 0;i < 3; i++) {
241
249
  const av = a[i] ?? 0;
242
250
  const bv = b[i] ?? 0;
243
251
  if (Number.isNaN(av) || Number.isNaN(bv))
244
- return false;
252
+ return 0;
245
253
  if (av > bv)
246
- return true;
254
+ return 1;
247
255
  if (av < bv)
248
- return false;
256
+ return -1;
249
257
  }
250
- return false;
258
+ return 0;
251
259
  }
252
260
  async function checkLatestVersion(opts = {}) {
253
261
  const fake = process.env.KOBE_FAKE_UPDATE;
@@ -298,6 +306,42 @@ async function fetchReleaseNotes(version) {
298
306
  clearTimeout(timer);
299
307
  }
300
308
  }
309
+ async function fetchReleaseNotesRange(args) {
310
+ const slug = repoSlug();
311
+ if (!slug)
312
+ return [];
313
+ const limit = args.limit ?? 100;
314
+ const ctrl = new AbortController;
315
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
316
+ try {
317
+ const res = await fetch(`https://api.github.com/repos/${slug}/releases?per_page=${limit}`, {
318
+ signal: ctrl.signal,
319
+ headers: {
320
+ accept: "application/vnd.github+json",
321
+ "x-github-api-version": "2022-11-28"
322
+ }
323
+ });
324
+ if (!res.ok)
325
+ return [];
326
+ const body = await res.json();
327
+ if (!Array.isArray(body))
328
+ return [];
329
+ return body.map((release) => {
330
+ const version = versionFromTagName(release.tag_name);
331
+ if (!version || typeof release.html_url !== "string" || typeof release.body !== "string")
332
+ return null;
333
+ if (compareSemver(version, args.current) <= 0)
334
+ return null;
335
+ if (compareSemver(version, args.latest) > 0)
336
+ return null;
337
+ return { version, url: release.html_url, body: release.body };
338
+ }).filter((release) => release !== null);
339
+ } catch {
340
+ return [];
341
+ } finally {
342
+ clearTimeout(timer);
343
+ }
344
+ }
301
345
  async function fetchReleaseSummaries(limit = 12) {
302
346
  const slug = repoSlug();
303
347
  if (!slug)
@@ -622,6 +666,7 @@ __export(exports_repos, {
622
666
  setRepoInitOverride: () => setRepoInitOverride,
623
667
  setPersistedString: () => setPersistedString,
624
668
  resolveRepoRoot: () => resolveRepoRoot,
669
+ resolveMainRepoRoot: () => resolveMainRepoRoot,
625
670
  removeSavedRepo: () => removeSavedRepo,
626
671
  remoteRepoKey: () => remoteRepoKey,
627
672
  normalizeSavedRepos: () => normalizeSavedRepos,
@@ -657,6 +702,19 @@ function resolveRepoRoot(absPath) {
657
702
  } catch {}
658
703
  return top;
659
704
  }
705
+ function resolveMainRepoRoot(absPath) {
706
+ if (isRemoteRepoKey(absPath))
707
+ return absPath;
708
+ const r = spawnSync3("git", ["worktree", "list", "--porcelain"], {
709
+ cwd: absPath,
710
+ encoding: "utf8",
711
+ shell: false
712
+ });
713
+ if (r.status !== 0)
714
+ return resolveRepoRoot(absPath);
715
+ const first = (r.stdout ?? "").split(/\r?\n/).find((line) => line.startsWith("worktree "))?.slice("worktree ".length).trim();
716
+ return first || resolveRepoRoot(absPath);
717
+ }
660
718
  function statePath() {
661
719
  return kvStatePath();
662
720
  }
@@ -1323,6 +1381,7 @@ function coerceTask(value) {
1323
1381
  vendor: isVendorId(v.vendor) ? v.vendor : DEFAULT_TASK_VENDOR,
1324
1382
  prStatus: coercePRStatus(v.prStatus),
1325
1383
  ...typeof v.position === "number" && Number.isFinite(v.position) ? { position: v.position } : {},
1384
+ ...typeof v.modelEffort === "string" && v.modelEffort.length > 0 ? { modelEffort: v.modelEffort } : {},
1326
1385
  createdAt: v.createdAt,
1327
1386
  updatedAt: v.updatedAt
1328
1387
  };
@@ -3657,7 +3716,8 @@ class Orchestrator {
3657
3716
  worktreePath: "",
3658
3717
  status: "backlog",
3659
3718
  kind: "task",
3660
- vendor: input.vendor ?? DEFAULT_TASK_VENDOR
3719
+ vendor: input.vendor ?? DEFAULT_TASK_VENDOR,
3720
+ ...input.modelEffort ? { modelEffort: input.modelEffort } : {}
3661
3721
  });
3662
3722
  if (input.baseRef)
3663
3723
  this.pendingBaseRefs.set(task.id, input.baseRef);
@@ -3961,6 +4021,7 @@ function serializeTask(task) {
3961
4021
  vendor: task.vendor,
3962
4022
  prStatus: task.prStatus,
3963
4023
  position: task.position,
4024
+ modelEffort: task.modelEffort,
3964
4025
  createdAt: task.createdAt,
3965
4026
  updatedAt: task.updatedAt
3966
4027
  };
@@ -3973,6 +4034,7 @@ var DAEMON_PROTOCOL_VERSION = 3, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NA
3973
4034
  var init_protocol = __esm(() => {
3974
4035
  CHANNEL_NAMES = [
3975
4036
  "task.snapshot",
4037
+ "issue.snapshot",
3976
4038
  "active-task",
3977
4039
  "update",
3978
4040
  "engine-state",
@@ -3980,7 +4042,6 @@ var init_protocol = __esm(() => {
3980
4042
  "keybindings",
3981
4043
  "task.jobs",
3982
4044
  "worktree.changes",
3983
- "task.conflicts",
3984
4045
  "session.deliver"
3985
4046
  ];
3986
4047
  CHANNEL_NAME_SET = new Set(CHANNEL_NAMES);
@@ -6313,6 +6374,7 @@ var init_registry = __esm(() => {
6313
6374
  builtin: true,
6314
6375
  displayName: "Codex",
6315
6376
  defaultCommand: ["codex"],
6377
+ effortLevels: ["none", "low", "medium", "high", "xhigh"],
6316
6378
  history: codexHistoryReader,
6317
6379
  detectAccount: (deps) => detectCodexAccount(deps),
6318
6380
  createHookAdapter: () => new NoopHookAdapter("codex"),
@@ -6893,347 +6955,6 @@ var init_auto_title_poller = __esm(() => {
6893
6955
  init_chat_tab_naming();
6894
6956
  });
6895
6957
 
6896
- // src/lib/poll-scheduling.ts
6897
- import { spawn as spawn2 } from "child_process";
6898
- function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
6899
- if (timedOut)
6900
- return startedAt + cfg.slowRetryMs;
6901
- return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
6902
- }
6903
- function shouldPoll(state, now) {
6904
- return !state.inFlight && now >= state.nextAllowedAt;
6905
- }
6906
- function maybeStartScheduledRun(state, cfg, run, onValue) {
6907
- const startedAt = Date.now();
6908
- if (!shouldPoll(state, startedAt))
6909
- return false;
6910
- state.inFlight = true;
6911
- const controller = new AbortController;
6912
- const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
6913
- (async () => {
6914
- let value;
6915
- let ok = false;
6916
- try {
6917
- value = await run(controller.signal);
6918
- ok = true;
6919
- } catch {}
6920
- clearTimeout(timer);
6921
- const timedOut = controller.signal.aborted;
6922
- state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
6923
- state.inFlight = false;
6924
- if (ok && !timedOut)
6925
- onValue(value);
6926
- })();
6927
- return true;
6928
- }
6929
- function spawnCapture(cmd, args, opts) {
6930
- return new Promise((resolve2) => {
6931
- let out = "";
6932
- let settled = false;
6933
- const finish = (status) => {
6934
- if (settled)
6935
- return;
6936
- settled = true;
6937
- resolve2({ status, stdout: out });
6938
- };
6939
- const child = spawn2(cmd, args.slice(), {
6940
- cwd: opts.cwd,
6941
- stdio: ["ignore", "pipe", "ignore"],
6942
- env: opts.env,
6943
- signal: opts.signal,
6944
- killSignal: "SIGKILL"
6945
- });
6946
- child.stdout?.on("data", (chunk) => {
6947
- out += String(chunk);
6948
- });
6949
- child.on("error", () => finish(null));
6950
- child.on("close", (code) => finish(code));
6951
- });
6952
- }
6953
- var init_poll_scheduling = () => {};
6954
-
6955
- // ../kobe-daemon/src/daemon/conflict-collector.ts
6956
- class GitGate {
6957
- limit;
6958
- active = 0;
6959
- waiters = [];
6960
- constructor(limit = MAX_CONCURRENT_GIT) {
6961
- this.limit = limit;
6962
- }
6963
- async run(fn) {
6964
- if (this.active >= this.limit) {
6965
- await new Promise((resolve2) => this.waiters.push(resolve2));
6966
- }
6967
- this.active += 1;
6968
- try {
6969
- return await fn();
6970
- } finally {
6971
- this.active -= 1;
6972
- this.waiters.shift()?.();
6973
- }
6974
- }
6975
- }
6976
- async function git(cwd, args, signal, gate) {
6977
- return gate.run(() => spawnCapture("git", args, { cwd, env: { ...process.env, ...LOCK_FREE_ENV }, signal }));
6978
- }
6979
- function parsePorcelainPaths(stdout) {
6980
- const paths = [];
6981
- for (const line of stdout.split(`
6982
- `)) {
6983
- if (line.length < 4)
6984
- continue;
6985
- const rest = line.slice(3);
6986
- const arrow = rest.indexOf(" -> ");
6987
- if (arrow >= 0) {
6988
- paths.push(rest.slice(0, arrow), rest.slice(arrow + 4));
6989
- } else {
6990
- paths.push(rest);
6991
- }
6992
- }
6993
- return paths;
6994
- }
6995
- async function resolveBaseRef(worktree, signal, gate) {
6996
- for (const ref of BASE_REF_CANDIDATES) {
6997
- const res = await git(worktree, ["rev-parse", "--verify", "--quiet", ref], signal, gate);
6998
- if (res.status === 0)
6999
- return ref;
7000
- }
7001
- return null;
7002
- }
7003
- async function collectFootprint(worktree, repo, baseRef, signal, gate) {
7004
- const head = await git(worktree, ["rev-parse", "HEAD"], signal, gate);
7005
- if (head.status !== 0)
7006
- throw new Error("rev-parse HEAD failed");
7007
- const files = new Set;
7008
- const status = await git(worktree, ["status", "--porcelain=v1"], signal, gate);
7009
- if (status.status !== 0)
7010
- throw new Error("git status failed");
7011
- for (const p of parsePorcelainPaths(status.stdout))
7012
- files.add(p);
7013
- if (baseRef) {
7014
- const diff = await git(worktree, ["diff", "--name-only", `${baseRef}...HEAD`], signal, gate);
7015
- if (diff.status === 0) {
7016
- for (const p of diff.stdout.split(`
7017
- `))
7018
- if (p)
7019
- files.add(p);
7020
- }
7021
- }
7022
- return { repo, head: head.stdout.trim(), files };
7023
- }
7024
- function sameFootprint(a, b) {
7025
- if (a.head !== b.head || a.files.size !== b.files.size)
7026
- return false;
7027
- for (const f of a.files)
7028
- if (!b.files.has(f))
7029
- return false;
7030
- return true;
7031
- }
7032
- function trackedConflictTasks(tasks) {
7033
- return tasks.filter((t) => !t.archived && (t.kind ?? "task") !== "main" && !!t.worktreePath && !isRemoteRepoKey(t.repo) && !isRemoteRepoKey(t.worktreePath));
7034
- }
7035
- function overlapPairs(cards) {
7036
- const ids = [...cards.keys()].sort();
7037
- const pairs = [];
7038
- for (let i = 0;i < ids.length; i++) {
7039
- for (let j = i + 1;j < ids.length; j++) {
7040
- const a = cards.get(ids[i]);
7041
- const b = cards.get(ids[j]);
7042
- if (a.repo !== b.repo)
7043
- continue;
7044
- const files = [...a.files].filter((f) => b.files.has(f)).sort();
7045
- if (files.length === 0)
7046
- continue;
7047
- pairs.push({ a: ids[i], b: ids[j], files, level: "overlap" });
7048
- }
7049
- }
7050
- return pairs;
7051
- }
7052
- function parseMergeTreeNames(stdout) {
7053
- const lines = stdout.split(`
7054
- `);
7055
- const names = [];
7056
- for (const line of lines.slice(1)) {
7057
- if (!line)
7058
- break;
7059
- names.push(line);
7060
- }
7061
- return names;
7062
- }
7063
- function samePairs(a, b) {
7064
- return JSON.stringify(a) === JSON.stringify(b);
7065
- }
7066
-
7067
- class ConflictCollector {
7068
- orch;
7069
- bus;
7070
- options;
7071
- entries = new Map;
7072
- baseRefs = new Map;
7073
- mergeProbes = new Map;
7074
- gate = new GitGate;
7075
- mergeTreeUnsupported = false;
7076
- lastPublished = [];
7077
- stopped = false;
7078
- constructor(orch, bus, options = {}) {
7079
- this.orch = orch;
7080
- this.bus = bus;
7081
- this.options = options;
7082
- }
7083
- tick() {
7084
- if (this.stopped)
7085
- return;
7086
- if (this.options.hasSubscribers && !this.options.hasSubscribers())
7087
- return;
7088
- try {
7089
- const tracked = trackedConflictTasks(this.orch.listTasks());
7090
- const trackedIds = new Set(tracked.map((t) => t.id));
7091
- let pruned = false;
7092
- for (const id of this.entries.keys()) {
7093
- if (trackedIds.has(id))
7094
- continue;
7095
- if (this.entries.get(id)?.value)
7096
- pruned = true;
7097
- this.entries.delete(id);
7098
- }
7099
- if (pruned)
7100
- this.recompute();
7101
- for (const task of tracked)
7102
- this.maybeCollect(task);
7103
- } catch (err) {
7104
- logDaemonError("conflict-radar", err);
7105
- }
7106
- }
7107
- stop() {
7108
- this.stopped = true;
7109
- }
7110
- maybeCollect(task) {
7111
- const id = task.id;
7112
- let entry = this.entries.get(id);
7113
- if (!entry) {
7114
- entry = { inFlight: false, nextAllowedAt: 0 };
7115
- this.entries.set(id, entry);
7116
- }
7117
- const cadence = this.options.cadence ?? {
7118
- timeoutMs: CONFLICTS_TIMEOUT_MS,
7119
- slowRetryMs: CONFLICTS_SLOW_RETRY_MS,
7120
- minIntervalMs: CONFLICTS_MIN_INTERVAL_MS
7121
- };
7122
- const run = this.options.footprint ?? (async (t, signal) => {
7123
- const baseRef = await this.baseRefFor(t.worktreePath, signal);
7124
- return collectFootprint(t.worktreePath, t.repo, baseRef, signal, this.gate);
7125
- });
7126
- maybeStartScheduledRun(entry, cadence, (signal) => run(task, signal), (value) => {
7127
- if (this.stopped)
7128
- return;
7129
- if (this.entries.get(id) !== entry)
7130
- return;
7131
- if (entry.value && sameFootprint(entry.value, value))
7132
- return;
7133
- entry.value = value;
7134
- this.recompute();
7135
- });
7136
- }
7137
- baseRefFor(worktree, signal) {
7138
- const cached = this.baseRefs.get(worktree);
7139
- if (cached)
7140
- return cached;
7141
- const promise = resolveBaseRef(worktree, signal, this.gate).catch(() => null);
7142
- this.baseRefs.set(worktree, promise);
7143
- return promise;
7144
- }
7145
- recompute() {
7146
- const cards = new Map;
7147
- for (const [id, entry] of this.entries) {
7148
- if (entry.value)
7149
- cards.set(id, entry.value);
7150
- }
7151
- const pairs = overlapPairs(cards);
7152
- const resolved = [];
7153
- for (const pair of pairs) {
7154
- const a = cards.get(pair.a);
7155
- const b = cards.get(pair.b);
7156
- const key = [a.repo, ...[a.head, b.head].sort()].join("\x00");
7157
- const probe2 = this.mergeProbes.get(key);
7158
- if (probe2?.state === "conflict") {
7159
- resolved.push({
7160
- ...pair,
7161
- level: "conflict",
7162
- files: probe2.files.length > 0 ? probe2.files : pair.files
7163
- });
7164
- continue;
7165
- }
7166
- resolved.push(pair);
7167
- if (!probe2 && !this.mergeTreeUnsupported && a.head !== b.head) {
7168
- this.scheduleMergeProbe(key, cards, pair);
7169
- }
7170
- }
7171
- if (samePairs(this.lastPublished, resolved))
7172
- return;
7173
- this.lastPublished = resolved;
7174
- const payload = { pairs: resolved };
7175
- this.bus.publish("task.conflicts", payload);
7176
- }
7177
- scheduleMergeProbe(key, cards, pair) {
7178
- const a = cards.get(pair.a);
7179
- const b = cards.get(pair.b);
7180
- this.mergeProbes.set(key, { state: "pending" });
7181
- const worktree = this.worktreeOf(pair.a);
7182
- const probe2 = this.options.probeMerge ?? (async (wt, headA, headB) => {
7183
- const res = await git(wt, ["merge-tree", "--write-tree", "--name-only", headA, headB], AbortSignal.timeout(MERGE_TREE_TIMEOUT_MS), this.gate);
7184
- if (res.status === 0)
7185
- return { conflict: false, files: [] };
7186
- if (res.status === 1)
7187
- return { conflict: true, files: parseMergeTreeNames(res.stdout) };
7188
- return null;
7189
- });
7190
- if (!worktree) {
7191
- this.mergeProbes.delete(key);
7192
- return;
7193
- }
7194
- probe2(worktree, a.head, b.head).then((result) => {
7195
- if (this.stopped)
7196
- return;
7197
- if (result === null) {
7198
- if (!this.mergeTreeUnsupported) {
7199
- this.mergeTreeUnsupported = true;
7200
- console.log("[conflict-radar] merge-tree dry-run unavailable (git < 2.38 or no merge base) \u2014 radar degrades to file-overlap only");
7201
- }
7202
- this.mergeProbes.delete(key);
7203
- return;
7204
- }
7205
- this.mergeProbes.set(key, result.conflict ? { state: "conflict", files: result.files } : { state: "clean" });
7206
- this.recompute();
7207
- }).catch((err) => {
7208
- this.mergeProbes.delete(key);
7209
- logDaemonError("conflict-radar", err);
7210
- });
7211
- }
7212
- worktreeOf(taskId) {
7213
- const task = this.orch.listTasks().find((t) => t.id === taskId);
7214
- return task?.worktreePath || undefined;
7215
- }
7216
- }
7217
- function startConflictCollector(orch, bus, tickMs = DEFAULT_CONFLICTS_TICK_MS, hasSubscribers) {
7218
- if (tickMs <= 0)
7219
- return () => {};
7220
- const collector = new ConflictCollector(orch, bus, { hasSubscribers });
7221
- collector.tick();
7222
- const timer = setInterval(() => collector.tick(), tickMs);
7223
- timer.unref?.();
7224
- return () => {
7225
- clearInterval(timer);
7226
- collector.stop();
7227
- };
7228
- }
7229
- var DEFAULT_CONFLICTS_TICK_MS = 5000, CONFLICTS_TIMEOUT_MS = 5000, CONFLICTS_SLOW_RETRY_MS = 60000, CONFLICTS_MIN_INTERVAL_MS = 1e4, MAX_CONCURRENT_GIT = 3, MERGE_TREE_TIMEOUT_MS = 8000, LOCK_FREE_ENV, BASE_REF_CANDIDATES;
7230
- var init_conflict_collector = __esm(() => {
7231
- init_poll_scheduling();
7232
- init_repos();
7233
- LOCK_FREE_ENV = { GIT_OPTIONAL_LOCKS: "0" };
7234
- BASE_REF_CANDIDATES = ["origin/HEAD", "origin/main", "origin/master", "main", "master"];
7235
- });
7236
-
7237
6958
  // ../kobe-daemon/src/daemon/event-bus.ts
7238
6959
  class DaemonEventBus {
7239
6960
  last = new Map;
@@ -7466,7 +7187,8 @@ function createDaemonHandlerRegistry() {
7466
7187
  title: optionalString(payload, "title"),
7467
7188
  branch: optionalString(payload, "branch"),
7468
7189
  baseRef: optionalString(payload, "baseRef"),
7469
- vendor: optionalVendor(payload, "vendor")
7190
+ vendor: optionalVendor(payload, "vendor"),
7191
+ modelEffort: optionalString(payload, "effort")
7470
7192
  });
7471
7193
  return { taskId: task.id, task: serializeTask(task) };
7472
7194
  }
@@ -7542,7 +7264,25 @@ function createDaemonHandlerRegistry() {
7542
7264
  if (status !== "backlog" && status !== "in_progress" && status !== "in_review" && status !== "done" && status !== "canceled" && status !== "error") {
7543
7265
  throw new Error("status must be a TaskStatus");
7544
7266
  }
7267
+ const linked = status === "done" ? ctx.orch.getTask(taskId) : undefined;
7268
+ const prevStatus = linked?.status;
7545
7269
  await ctx.orch.setStatus(taskId, status);
7270
+ if (status === "done" && prevStatus !== "done" && linked) {
7271
+ try {
7272
+ const state = await ctx.issues.list(linked.repo);
7273
+ const found = state.issues.find((i) => i.taskId === taskId);
7274
+ if (found && found.status !== "done") {
7275
+ const next = await ctx.issues.mutate(linked.repo, {
7276
+ type: "setStatus",
7277
+ id: found.id,
7278
+ status: "done"
7279
+ });
7280
+ ctx.bus.publish("issue.snapshot", next);
7281
+ }
7282
+ } catch (err) {
7283
+ logDaemonError("issue-done-mirror", err);
7284
+ }
7285
+ }
7546
7286
  return {};
7547
7287
  }
7548
7288
  },
@@ -7645,6 +7385,20 @@ function createDaemonHandlerRegistry() {
7645
7385
  return {};
7646
7386
  }
7647
7387
  },
7388
+ {
7389
+ name: "issue.list",
7390
+ async handle(payload, ctx) {
7391
+ return ctx.issues.list(requireString(payload, "repoRoot"));
7392
+ }
7393
+ },
7394
+ {
7395
+ name: "issue.mutate",
7396
+ async handle(payload, ctx) {
7397
+ const state = await ctx.issues.mutate(requireString(payload, "repoRoot"), payload.op);
7398
+ ctx.bus.publish("issue.snapshot", state);
7399
+ return state;
7400
+ }
7401
+ },
7648
7402
  {
7649
7403
  name: "session.deliver",
7650
7404
  async handle(payload, ctx) {
@@ -7775,19 +7529,254 @@ var init_handlers = __esm(() => {
7775
7529
  init_protocol();
7776
7530
  });
7777
7531
 
7532
+ // ../kobe-daemon/src/daemon/issues-store.ts
7533
+ import { execFile } from "child_process";
7534
+ import { mkdir as mkdir6, readFile as readFile7, realpath, rename as rename2, stat as stat4, writeFile as writeFile4 } from "fs/promises";
7535
+ import { homedir as homedir13 } from "os";
7536
+ import { dirname as dirname5, isAbsolute, join as join4, resolve as resolve2 } from "path";
7537
+ import { promisify } from "util";
7538
+ function isGitNotRepositoryError(err) {
7539
+ const message = err instanceof Error ? err.message : String(err);
7540
+ return message.includes("not a git repository");
7541
+ }
7542
+ function defaultIssuesStorePath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir13()) {
7543
+ return join4(homeDir2, ".kobe", "issues.json");
7544
+ }
7545
+ function isValidStatus(value) {
7546
+ return typeof value === "string" && ISSUE_STATUSES.includes(value);
7547
+ }
7548
+ function normalizeIssue(entry) {
7549
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry))
7550
+ return null;
7551
+ const raw = entry;
7552
+ if (typeof raw.id !== "number")
7553
+ return null;
7554
+ return {
7555
+ id: raw.id,
7556
+ title: typeof raw.title === "string" ? raw.title : "(untitled)",
7557
+ status: isValidStatus(raw.status) ? raw.status : "open",
7558
+ created: typeof raw.created === "string" ? raw.created : "",
7559
+ body: typeof raw.body === "string" ? raw.body : "",
7560
+ taskId: typeof raw.taskId === "string" ? raw.taskId : undefined
7561
+ };
7562
+ }
7563
+ function emptyStore() {
7564
+ return { version: 1, repos: {} };
7565
+ }
7566
+ function todayStamp() {
7567
+ const d = new Date;
7568
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
7569
+ const dd = String(d.getDate()).padStart(2, "0");
7570
+ return `${d.getFullYear()}-${mm}-${dd}`;
7571
+ }
7572
+ async function gitCommonDir(path11) {
7573
+ const { stdout } = await execFileAsync("git", ["-C", path11, "rev-parse", "--git-common-dir"]);
7574
+ const dir = stdout.trim();
7575
+ return realpath(isAbsolute(dir) ? dir : resolve2(path11, dir));
7576
+ }
7577
+ async function gitTopLevel(path11) {
7578
+ const { stdout } = await execFileAsync("git", ["-C", path11, "rev-parse", "--show-toplevel"]);
7579
+ return stdout.trim();
7580
+ }
7581
+ async function gitMainWorktree(path11) {
7582
+ const { stdout } = await execFileAsync("git", ["-C", path11, "worktree", "list", "--porcelain"]);
7583
+ const first = stdout.split(/\r?\n/).find((line) => line.startsWith("worktree "))?.slice("worktree ".length).trim();
7584
+ return first ? realpath(first) : gitTopLevel(path11);
7585
+ }
7586
+ async function resolveRepo(raw) {
7587
+ if (typeof raw !== "string" || raw.length === 0)
7588
+ throw new Error("repoRoot is required");
7589
+ const absolute = resolve2(raw);
7590
+ const s = await stat4(absolute).catch(() => null);
7591
+ if (!s?.isDirectory())
7592
+ throw new Error("repoRoot does not exist");
7593
+ try {
7594
+ const [repoRoot, repoKey] = await Promise.all([gitMainWorktree(absolute), gitCommonDir(absolute)]);
7595
+ return { repoRoot, repoKey };
7596
+ } catch (err) {
7597
+ if (isGitNotRepositoryError(err))
7598
+ throw new Error("repoRoot is not a git repository");
7599
+ throw err;
7600
+ }
7601
+ }
7602
+ async function readStore(path11) {
7603
+ try {
7604
+ const raw = JSON.parse(await readFile7(path11, "utf8"));
7605
+ const repos = {};
7606
+ if (raw.repos && typeof raw.repos === "object") {
7607
+ for (const [key, value] of Object.entries(raw.repos)) {
7608
+ if (!value || typeof value !== "object")
7609
+ continue;
7610
+ const record = value;
7611
+ repos[key] = {
7612
+ repoRoot: typeof record.repoRoot === "string" ? record.repoRoot : "",
7613
+ nextId: typeof record.nextId === "number" ? record.nextId : 1,
7614
+ issues: Array.isArray(record.issues) ? record.issues.map(normalizeIssue).filter((issue) => issue !== null) : []
7615
+ };
7616
+ }
7617
+ }
7618
+ return { version: 1, repos };
7619
+ } catch (err) {
7620
+ if (err.code === "ENOENT")
7621
+ return emptyStore();
7622
+ throw err;
7623
+ }
7624
+ }
7625
+ async function writeStore(path11, store) {
7626
+ await mkdir6(dirname5(path11), { recursive: true });
7627
+ const tmp = `${path11}.tmp`;
7628
+ await writeFile4(tmp, `${JSON.stringify(store, null, 2)}
7629
+ `, "utf8");
7630
+ await rename2(tmp, path11);
7631
+ }
7632
+ function response(repoRoot, record) {
7633
+ return {
7634
+ repoRoot,
7635
+ exists: record !== null,
7636
+ nextId: record?.nextId ?? 1,
7637
+ issues: record?.issues ?? []
7638
+ };
7639
+ }
7640
+ async function withLock(key, fn) {
7641
+ const tail = locks.get(key) ?? Promise.resolve();
7642
+ const run = tail.then(fn);
7643
+ const settled = run.then(() => {
7644
+ return;
7645
+ }, () => {
7646
+ return;
7647
+ });
7648
+ locks.set(key, settled);
7649
+ settled.then(() => {
7650
+ if (locks.get(key) === settled)
7651
+ locks.delete(key);
7652
+ });
7653
+ return run;
7654
+ }
7655
+
7656
+ class IssuesStore {
7657
+ path;
7658
+ constructor(path11 = defaultIssuesStorePath()) {
7659
+ this.path = path11;
7660
+ }
7661
+ async list(repo) {
7662
+ const { repoRoot, repoKey } = await resolveRepo(repo);
7663
+ return withLock(repoKey, async () => {
7664
+ const store = await readStore(this.path);
7665
+ const record = store.repos[repoKey] ?? null;
7666
+ if (record && record.repoRoot !== repoRoot) {
7667
+ record.repoRoot = repoRoot;
7668
+ await writeStore(this.path, store);
7669
+ }
7670
+ return response(repoRoot, record);
7671
+ });
7672
+ }
7673
+ async mutate(repo, op) {
7674
+ const { repoRoot, repoKey } = await resolveRepo(repo);
7675
+ if (!op || typeof op !== "object" || Array.isArray(op) || typeof op.type !== "string") {
7676
+ throw new Error("missing op");
7677
+ }
7678
+ return withLock(repoKey, async () => {
7679
+ const store = await readStore(this.path);
7680
+ let record = store.repos[repoKey];
7681
+ if (!record) {
7682
+ record = { repoRoot, nextId: 1, issues: [] };
7683
+ store.repos[repoKey] = record;
7684
+ }
7685
+ record.repoRoot = repoRoot;
7686
+ const typed = op;
7687
+ if (typed.type === "create") {
7688
+ if (typeof typed.title !== "string" || typed.title.trim().length === 0) {
7689
+ throw new Error("create requires a non-empty title");
7690
+ }
7691
+ if (typed.body !== undefined && typeof typed.body !== "string")
7692
+ throw new Error("body must be a string");
7693
+ record.issues = [
7694
+ {
7695
+ id: record.nextId,
7696
+ title: typed.title,
7697
+ status: "open",
7698
+ created: todayStamp(),
7699
+ body: typeof typed.body === "string" ? typed.body : ""
7700
+ },
7701
+ ...record.issues
7702
+ ];
7703
+ record.nextId += 1;
7704
+ } else if (typed.type === "setStatus") {
7705
+ if (typeof typed.id !== "number")
7706
+ throw new Error("setStatus requires a numeric id");
7707
+ if (!isValidStatus(typed.status))
7708
+ throw new Error(`invalid status: must be one of ${ISSUE_STATUSES.join(", ")}`);
7709
+ const issue = record.issues.find((i) => i.id === typed.id);
7710
+ if (!issue)
7711
+ throw new Error(`no issue #${typed.id}`);
7712
+ issue.status = typed.status;
7713
+ } else if (typed.type === "update") {
7714
+ if (typeof typed.id !== "number")
7715
+ throw new Error("update requires a numeric id");
7716
+ if (typed.title !== undefined && (typeof typed.title !== "string" || typed.title.trim().length === 0)) {
7717
+ throw new Error("title must be a non-empty string");
7718
+ }
7719
+ if (typed.body !== undefined && typeof typed.body !== "string")
7720
+ throw new Error("body must be a string");
7721
+ const issue = record.issues.find((i) => i.id === typed.id);
7722
+ if (!issue)
7723
+ throw new Error(`no issue #${typed.id}`);
7724
+ if (typeof typed.title === "string")
7725
+ issue.title = typed.title;
7726
+ if (typeof typed.body === "string")
7727
+ issue.body = typed.body;
7728
+ } else if (typed.type === "link") {
7729
+ if (typeof typed.id !== "number")
7730
+ throw new Error("link requires a numeric id");
7731
+ if (typeof typed.taskId !== "string" || typed.taskId.length === 0) {
7732
+ throw new Error("link requires a non-empty taskId");
7733
+ }
7734
+ const issue = record.issues.find((i) => i.id === typed.id);
7735
+ if (!issue)
7736
+ throw new Error(`no issue #${typed.id}`);
7737
+ issue.taskId = typed.taskId;
7738
+ } else if (typed.type === "unlink") {
7739
+ if (typeof typed.id !== "number")
7740
+ throw new Error("unlink requires a numeric id");
7741
+ const issue = record.issues.find((i) => i.id === typed.id);
7742
+ if (!issue)
7743
+ throw new Error(`no issue #${typed.id}`);
7744
+ issue.taskId = undefined;
7745
+ } else if (typed.type === "delete") {
7746
+ if (typeof typed.id !== "number")
7747
+ throw new Error("delete requires a numeric id");
7748
+ const nextIssues = record.issues.filter((i) => i.id !== typed.id);
7749
+ if (nextIssues.length === record.issues.length)
7750
+ throw new Error(`no issue #${typed.id}`);
7751
+ record.issues = nextIssues;
7752
+ } else {
7753
+ throw new Error(`unknown op type: ${typed.type}`);
7754
+ }
7755
+ await writeStore(this.path, store);
7756
+ return response(repoRoot, record);
7757
+ });
7758
+ }
7759
+ }
7760
+ var execFileAsync, ISSUE_STATUSES, locks;
7761
+ var init_issues_store = __esm(() => {
7762
+ execFileAsync = promisify(execFile);
7763
+ ISSUE_STATUSES = ["open", "doing", "hold", "done"];
7764
+ locks = new Map;
7765
+ });
7766
+
7778
7767
  // ../kobe-daemon/src/daemon/keybindings-watcher.ts
7779
7768
  import { mkdirSync as mkdirSync2, watch } from "fs";
7780
- import { homedir as homedir13 } from "os";
7781
- import { basename as basename3, dirname as dirname5, join as join4 } from "path";
7782
- function defaultKeybindingsPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir13()) {
7783
- return join4(homeDir2, ".kobe", "settings", "keybindings.yaml");
7769
+ import { homedir as homedir14 } from "os";
7770
+ import { basename as basename3, dirname as dirname6, join as join5 } from "path";
7771
+ function defaultKeybindingsPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir14()) {
7772
+ return join5(homeDir2, ".kobe", "settings", "keybindings.yaml");
7784
7773
  }
7785
7774
  function startKeybindingsWatcher(bus, options = {}) {
7786
7775
  const debounceMs = options.debounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS;
7787
7776
  if (debounceMs <= 0)
7788
7777
  return () => {};
7789
7778
  const filePath = options.path ?? defaultKeybindingsPath();
7790
- const dir = dirname5(filePath);
7779
+ const dir = dirname6(filePath);
7791
7780
  const baseYaml = basename3(filePath);
7792
7781
  const baseYml = baseYaml.replace(/\.yaml$/, ".yml");
7793
7782
  let rev = 0;
@@ -7831,10 +7820,10 @@ var init_keybindings_watcher = () => {};
7831
7820
 
7832
7821
  // ../kobe-daemon/src/daemon/ui-prefs-watcher.ts
7833
7822
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, watch as watch2 } from "fs";
7834
- import { homedir as homedir14 } from "os";
7835
- import { basename as basename4, dirname as dirname6, join as join5 } from "path";
7836
- function defaultUiPrefsStatePath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir14()) {
7837
- return join5(homeDir2, ".config", "kobe", "state.json");
7823
+ import { homedir as homedir15 } from "os";
7824
+ import { basename as basename4, dirname as dirname7, join as join6 } from "path";
7825
+ function defaultUiPrefsStatePath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir15()) {
7826
+ return join6(homeDir2, ".config", "kobe", "state.json");
7838
7827
  }
7839
7828
  function readUiPrefsFromStateFile(statePath2) {
7840
7829
  let parsed = {};
@@ -7858,7 +7847,7 @@ function startUiPrefsWatcher(bus, options = {}) {
7858
7847
  if (debounceMs <= 0)
7859
7848
  return () => {};
7860
7849
  const statePath2 = options.statePath ?? defaultUiPrefsStatePath();
7861
- const stateDir = dirname6(statePath2);
7850
+ const stateDir = dirname7(statePath2);
7862
7851
  const stateFile = basename4(statePath2);
7863
7852
  let last = readUiPrefsFromStateFile(statePath2);
7864
7853
  bus.publish("ui-prefs", last);
@@ -7904,6 +7893,65 @@ var init_ui_prefs_watcher = __esm(() => {
7904
7893
  FOCUS_ACCENT_SLOT_NAMES = ["primary", "success", "info"];
7905
7894
  });
7906
7895
 
7896
+ // src/lib/poll-scheduling.ts
7897
+ import { spawn as spawn2 } from "child_process";
7898
+ function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
7899
+ if (timedOut)
7900
+ return startedAt + cfg.slowRetryMs;
7901
+ return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
7902
+ }
7903
+ function shouldPoll(state, now) {
7904
+ return !state.inFlight && now >= state.nextAllowedAt;
7905
+ }
7906
+ function maybeStartScheduledRun(state, cfg, run, onValue) {
7907
+ const startedAt = Date.now();
7908
+ if (!shouldPoll(state, startedAt))
7909
+ return false;
7910
+ state.inFlight = true;
7911
+ const controller = new AbortController;
7912
+ const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
7913
+ (async () => {
7914
+ let value;
7915
+ let ok = false;
7916
+ try {
7917
+ value = await run(controller.signal);
7918
+ ok = true;
7919
+ } catch {}
7920
+ clearTimeout(timer);
7921
+ const timedOut = controller.signal.aborted;
7922
+ state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
7923
+ state.inFlight = false;
7924
+ if (ok && !timedOut)
7925
+ onValue(value);
7926
+ })();
7927
+ return true;
7928
+ }
7929
+ function spawnCapture(cmd, args, opts) {
7930
+ return new Promise((resolve3) => {
7931
+ let out = "";
7932
+ let settled = false;
7933
+ const finish = (status) => {
7934
+ if (settled)
7935
+ return;
7936
+ settled = true;
7937
+ resolve3({ status, stdout: out });
7938
+ };
7939
+ const child = spawn2(cmd, args.slice(), {
7940
+ cwd: opts.cwd,
7941
+ stdio: ["ignore", "pipe", "ignore"],
7942
+ env: opts.env,
7943
+ signal: opts.signal,
7944
+ killSignal: "SIGKILL"
7945
+ });
7946
+ child.stdout?.on("data", (chunk) => {
7947
+ out += String(chunk);
7948
+ });
7949
+ child.on("error", () => finish(null));
7950
+ child.on("close", (code) => finish(code));
7951
+ });
7952
+ }
7953
+ var init_poll_scheduling = () => {};
7954
+
7907
7955
  // src/tui/panes/sidebar/worktree-changes.ts
7908
7956
  var exports_worktree_changes = {};
7909
7957
  __export(exports_worktree_changes, {
@@ -8074,9 +8122,9 @@ var init_worktree_changes_collector = __esm(() => {
8074
8122
  });
8075
8123
 
8076
8124
  // ../kobe-daemon/src/daemon/server.ts
8077
- import { mkdir as mkdir6, readFile as readFile7, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
8125
+ import { mkdir as mkdir7, readFile as readFile8, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
8078
8126
  import { createServer } from "net";
8079
- import { dirname as dirname7 } from "path";
8127
+ import { dirname as dirname8 } from "path";
8080
8128
  function resolveIdleGraceMs() {
8081
8129
  const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
8082
8130
  if (raw === undefined)
@@ -8131,8 +8179,9 @@ async function startDaemonServer(orch, options = {}) {
8131
8179
  broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
8132
8180
  });
8133
8181
  const activity = new DaemonActivityRegistry(bus);
8134
- await mkdir6(dirname7(socketPath), { recursive: true });
8135
- await mkdir6(dirname7(pidPath), { recursive: true });
8182
+ const issues = new IssuesStore(defaultIssuesStorePath(options.homeDir));
8183
+ await mkdir7(dirname8(socketPath), { recursive: true });
8184
+ await mkdir7(dirname8(pidPath), { recursive: true });
8136
8185
  await unlink4(socketPath).catch(() => {});
8137
8186
  const server = createServer((socket) => {
8138
8187
  const client = {
@@ -8184,7 +8233,6 @@ async function startDaemonServer(orch, options = {}) {
8184
8233
  debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
8185
8234
  });
8186
8235
  const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS, hasSubscribers);
8187
- const stopConflictCollector = startConflictCollector(orch, bus, options.conflictsTickMs ?? DEFAULT_CONFLICTS_TICK_MS, hasSubscribers);
8188
8236
  const serverApi = {
8189
8237
  socketPath,
8190
8238
  pidPath,
@@ -8200,26 +8248,25 @@ async function startDaemonServer(orch, options = {}) {
8200
8248
  stopUiPrefsWatcher();
8201
8249
  stopKeybindingsWatcher();
8202
8250
  stopWorktreeChangesCollector();
8203
- stopConflictCollector();
8204
8251
  activity.close();
8205
8252
  broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
8206
8253
  for (const client of Array.from(clients)) {
8207
8254
  client.socket.destroy();
8208
8255
  }
8209
- await new Promise((resolve2) => server.close(() => resolve2()));
8256
+ await new Promise((resolve3) => server.close(() => resolve3()));
8210
8257
  await unlink4(socketPath).catch(() => {});
8211
8258
  await unlink4(pidPath).catch(() => {});
8212
8259
  }
8213
8260
  };
8214
- await new Promise((resolve2, reject) => {
8261
+ await new Promise((resolve3, reject) => {
8215
8262
  const evented = server;
8216
8263
  evented.once("error", reject);
8217
8264
  server.listen(socketPath, () => {
8218
8265
  evented.removeListener("error", reject);
8219
- resolve2();
8266
+ resolve3();
8220
8267
  });
8221
8268
  });
8222
- await writeFile4(pidPath, `${process.pid}
8269
+ await writeFile5(pidPath, `${process.pid}
8223
8270
  `, "utf8");
8224
8271
  async function stopSoon() {
8225
8272
  if (stopping)
@@ -8264,6 +8311,7 @@ async function startDaemonServer(orch, options = {}) {
8264
8311
  orch,
8265
8312
  bus,
8266
8313
  activity,
8314
+ issues,
8267
8315
  daemon: { startedAt, socketPath, pid: process.pid, guiCount, stopSoon },
8268
8316
  clientId: client.id
8269
8317
  });
@@ -8304,7 +8352,7 @@ async function startDaemonServer(orch, options = {}) {
8304
8352
  }
8305
8353
  async function readPidFile(pidPath) {
8306
8354
  try {
8307
- const raw = await readFile7(pidPath, "utf8");
8355
+ const raw = await readFile8(pidPath, "utf8");
8308
8356
  const pid = Number(raw.trim());
8309
8357
  return Number.isFinite(pid) ? pid : null;
8310
8358
  } catch {
@@ -8331,14 +8379,15 @@ var init_server = __esm(() => {
8331
8379
  init_version();
8332
8380
  init_activity_registry();
8333
8381
  init_auto_title_poller();
8334
- init_conflict_collector();
8335
8382
  init_handlers();
8383
+ init_issues_store();
8336
8384
  init_keybindings_watcher();
8337
8385
  init_paths2();
8338
8386
  init_protocol();
8339
8387
  init_ui_prefs_watcher();
8340
8388
  init_worktree_changes_collector();
8341
8389
  init_handlers();
8390
+ init_issues_store();
8342
8391
  DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
8343
8392
  });
8344
8393
 
@@ -8361,7 +8410,7 @@ async function stopDaemonProcess(socketPath, pidPath) {
8361
8410
  const stopRequest = client.request("daemon.stop").catch(() => {
8362
8411
  return;
8363
8412
  });
8364
- const stopTimeout = new Promise((resolve2) => setTimeout(resolve2, 2000));
8413
+ const stopTimeout = new Promise((resolve3) => setTimeout(resolve3, 2000));
8365
8414
  await Promise.race([stopRequest, stopTimeout]);
8366
8415
  client.close();
8367
8416
  if (wasAlive && targetPid !== null) {
@@ -8380,13 +8429,13 @@ async function stopDaemonProcess(socketPath, pidPath) {
8380
8429
  method = "sigterm";
8381
8430
  escalated = true;
8382
8431
  }
8383
- await new Promise((resolve2) => setTimeout(resolve2, 50));
8432
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
8384
8433
  }
8385
8434
  try {
8386
8435
  process.kill(targetPid, 0);
8387
8436
  process.kill(targetPid, "SIGKILL");
8388
8437
  method = "sigkill";
8389
- await new Promise((resolve2) => setTimeout(resolve2, 100));
8438
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
8390
8439
  } catch {}
8391
8440
  }
8392
8441
  await unlink5(socketPath).catch(() => {});
@@ -8408,13 +8457,13 @@ __export(exports_daemon_process, {
8408
8457
  });
8409
8458
  import { spawn as spawn3 } from "child_process";
8410
8459
  import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync } from "fs";
8411
- import { dirname as dirname8, resolve as resolve2 } from "path";
8460
+ import { dirname as dirname9, resolve as resolve3 } from "path";
8412
8461
  import { fileURLToPath as fileURLToPath2 } from "url";
8413
8462
  function spawnDetachedDaemon(command, args, env, logPath) {
8414
8463
  let stdio = "ignore";
8415
8464
  let logFd;
8416
8465
  try {
8417
- mkdirSync4(dirname8(logPath), { recursive: true });
8466
+ mkdirSync4(dirname9(logPath), { recursive: true });
8418
8467
  logFd = openSync(logPath, "a");
8419
8468
  stdio = ["ignore", logFd, logFd];
8420
8469
  } catch {
@@ -8467,8 +8516,8 @@ async function testDaemonResponds(socketPath, timeoutMs = DAEMON_HELLO_TIMEOUT_M
8467
8516
  }
8468
8517
  const replied = probe2.request("hello", { protocolVersion: DAEMON_PROTOCOL_VERSION }).then(() => true).catch(() => true);
8469
8518
  let timer;
8470
- const timedOut = new Promise((resolve3) => {
8471
- timer = setTimeout(() => resolve3(false), timeoutMs);
8519
+ const timedOut = new Promise((resolve4) => {
8520
+ timer = setTimeout(() => resolve4(false), timeoutMs);
8472
8521
  });
8473
8522
  const alive = await Promise.race([replied, timedOut]);
8474
8523
  if (timer)
@@ -8481,11 +8530,11 @@ function resolveKobeSpawn(subcommand) {
8481
8530
  if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
8482
8531
  return [process.execPath, ...subcommand];
8483
8532
  }
8484
- const dir = dirname8(here);
8533
+ const dir = dirname9(here);
8485
8534
  const candidates = [
8486
- resolve2(dir, "../cli/index.ts"),
8487
- resolve2(dir, "../../../kobe/src/cli/index.ts"),
8488
- resolve2(dir, "../cli/index.js")
8535
+ resolve3(dir, "../cli/index.ts"),
8536
+ resolve3(dir, "../../../kobe/src/cli/index.ts"),
8537
+ resolve3(dir, "../cli/index.js")
8489
8538
  ];
8490
8539
  const entry = candidates.find((candidate) => existsSync5(candidate));
8491
8540
  if (entry)
@@ -8507,7 +8556,7 @@ __export(exports_repo_cmd, {
8507
8556
  runRepoSubcommand: () => runRepoSubcommand
8508
8557
  });
8509
8558
  import { readFileSync as readFileSync4 } from "fs";
8510
- import { resolve as resolve3 } from "path";
8559
+ import { resolve as resolve4 } from "path";
8511
8560
  function usageError(message) {
8512
8561
  process.stderr.write(`kobe repo: ${message}
8513
8562
 
@@ -8517,7 +8566,7 @@ ${REPO_USAGE}
8517
8566
  }
8518
8567
  function readArgFile(path11) {
8519
8568
  try {
8520
- return readFileSync4(resolve3(process.cwd(), path11), "utf8");
8569
+ return readFileSync4(resolve4(process.cwd(), path11), "utf8");
8521
8570
  } catch (err) {
8522
8571
  usageError(`cannot read ${path11}: ${err instanceof Error ? err.message : String(err)}`);
8523
8572
  }
@@ -8580,13 +8629,13 @@ async function runRepoSubcommand(args) {
8580
8629
  }
8581
8630
  const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
8582
8631
  const { existsSync: existsSync6 } = await import("fs");
8583
- const { join: join6 } = await import("path");
8632
+ const { join: join7 } = await import("path");
8584
8633
  if (verb === "show") {
8585
8634
  const [pathArg] = rest.filter((a) => !a.startsWith("-"));
8586
- const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
8635
+ const repo = resolveRepoRoot2(resolve4(process.cwd(), pathArg ?? "."));
8587
8636
  const override = getRepoInitOverride2(repo);
8588
- const hasFileScript = existsSync6(join6(repo, ".kobe", "init.sh"));
8589
- const hasFilePrompt = existsSync6(join6(repo, ".kobe", "init-prompt.md"));
8637
+ const hasFileScript = existsSync6(join7(repo, ".kobe", "init.sh"));
8638
+ const hasFilePrompt = existsSync6(join7(repo, ".kobe", "init-prompt.md"));
8590
8639
  console.log(`repo: ${repo}`);
8591
8640
  console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
8592
8641
  console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
@@ -8599,7 +8648,7 @@ async function runRepoSubcommand(args) {
8599
8648
  if (flags.initScript === undefined && flags.initPrompt === undefined) {
8600
8649
  usageError("set needs at least one of --init-script(-file) / --init-prompt(-file)");
8601
8650
  }
8602
- const repo = resolveRepoRoot2(resolve3(process.cwd(), flags.path ?? "."));
8651
+ const repo = resolveRepoRoot2(resolve4(process.cwd(), flags.path ?? "."));
8603
8652
  const next = setRepoInitOverride2(repo, {
8604
8653
  ...flags.initScript !== undefined ? { initScript: flags.initScript } : {},
8605
8654
  ...flags.initPrompt !== undefined ? { initPrompt: flags.initPrompt } : {}
@@ -8611,7 +8660,7 @@ async function runRepoSubcommand(args) {
8611
8660
  }
8612
8661
  if (verb === "unset") {
8613
8662
  const { path: path11, clearScript, clearPrompt } = parseUnsetArgs(rest);
8614
- const repo = resolveRepoRoot2(resolve3(process.cwd(), path11 ?? "."));
8663
+ const repo = resolveRepoRoot2(resolve4(process.cwd(), path11 ?? "."));
8615
8664
  const next = setRepoInitOverride2(repo, {
8616
8665
  ...clearScript ? { initScript: "" } : {},
8617
8666
  ...clearPrompt ? { initPrompt: "" } : {}
@@ -8684,15 +8733,30 @@ function parseEngineCommand(command) {
8684
8733
  }
8685
8734
  return out;
8686
8735
  }
8687
- function interactiveEngineCommand(vendor) {
8736
+ function interactiveEngineCommand(vendor, effort) {
8688
8737
  const v = vendor ?? "claude";
8689
8738
  const override = getPersistedString(engineCommandKey(v))?.trim();
8690
- if (override) {
8691
- const argv = parseEngineCommand(override);
8692
- if (argv.length > 0)
8693
- return argv;
8694
- }
8695
- return defaultEngineCommand(v);
8739
+ const base = (() => {
8740
+ if (override) {
8741
+ const argv = parseEngineCommand(override);
8742
+ if (argv.length > 0)
8743
+ return argv;
8744
+ }
8745
+ return defaultEngineCommand(v);
8746
+ })();
8747
+ return withEngineEffort(base, v, effort);
8748
+ }
8749
+ function withEngineEffort(argv, vendor, effort) {
8750
+ const trimmed = effort?.trim();
8751
+ if (!trimmed)
8752
+ return argv;
8753
+ const v = vendor ?? "claude";
8754
+ const levels = engineEntry(v).effortLevels;
8755
+ if (!levels?.includes(trimmed))
8756
+ return argv;
8757
+ if (v === "codex")
8758
+ return [...argv, "-c", `model_reasoning_effort=${trimmed}`];
8759
+ return argv;
8696
8760
  }
8697
8761
  function withClaudeSessionId(argv, vendor) {
8698
8762
  if ((vendor ?? "claude") !== "claude")
@@ -8929,7 +8993,7 @@ async function deliverFirstPrompt(session, prompt) {
8929
8993
  return;
8930
8994
  await pasteAndSubmit(pane, prompt);
8931
8995
  }
8932
- var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
8996
+ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
8933
8997
  var init_prompt_delivery = __esm(() => {
8934
8998
  init_client2();
8935
8999
  });
@@ -10837,7 +10901,7 @@ function normalizeHex(value) {
10837
10901
  }
10838
10902
  function resolveThemeSlotHex(theme, slot, mode = "dark") {
10839
10903
  const defs = theme.defs ?? {};
10840
- function resolve4(c, chain) {
10904
+ function resolve5(c, chain) {
10841
10905
  if (typeof c === "string") {
10842
10906
  if (c === "transparent" || c === "none")
10843
10907
  return null;
@@ -10848,17 +10912,17 @@ function resolveThemeSlotHex(theme, slot, mode = "dark") {
10848
10912
  const next = defs[c] ?? theme.theme[c];
10849
10913
  if (next === undefined)
10850
10914
  return null;
10851
- return resolve4(next, [...chain, c]);
10915
+ return resolve5(next, [...chain, c]);
10852
10916
  }
10853
10917
  if (!c || typeof c !== "object")
10854
10918
  return null;
10855
10919
  const variant = c[mode];
10856
- return typeof variant === "string" ? resolve4(variant, chain) : null;
10920
+ return typeof variant === "string" ? resolve5(variant, chain) : null;
10857
10921
  }
10858
10922
  const value = theme.theme[slot];
10859
10923
  if (value === undefined)
10860
10924
  return null;
10861
- return resolve4(value, [slot]);
10925
+ return resolve5(value, [slot]);
10862
10926
  }
10863
10927
 
10864
10928
  // src/tui/context/theme/schema.ts
@@ -10913,9 +10977,9 @@ var init_schema = () => {};
10913
10977
 
10914
10978
  // src/tui/context/theme/loader.ts
10915
10979
  import { readFileSync as readFileSync6, readdirSync } from "fs";
10916
- import { join as join6 } from "path";
10980
+ import { join as join7 } from "path";
10917
10981
  function userThemesDir() {
10918
- return join6(kobeStateDir(), "themes");
10982
+ return join7(kobeStateDir(), "themes");
10919
10983
  }
10920
10984
  function loadUserThemes() {
10921
10985
  const dir = userThemesDir();
@@ -10929,7 +10993,7 @@ function loadUserThemes() {
10929
10993
  for (const file of entries) {
10930
10994
  if (!file.endsWith(".json"))
10931
10995
  continue;
10932
- const path11 = join6(dir, file);
10996
+ const path11 = join7(dir, file);
10933
10997
  let parsed;
10934
10998
  try {
10935
10999
  const text = readFileSync6(path11, "utf8");
@@ -11745,12 +11809,12 @@ __export(exports_repo_init, {
11745
11809
  resolveRepoInit: () => resolveRepoInit
11746
11810
  });
11747
11811
  import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
11748
- import { join as join7 } from "path";
11812
+ import { join as join8 } from "path";
11749
11813
  function repoFileScript(worktreePath) {
11750
- return existsSync7(join7(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
11814
+ return existsSync7(join8(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
11751
11815
  }
11752
11816
  function repoFilePrompt(worktreePath) {
11753
- const p = join7(worktreePath, INIT_PROMPT_REL);
11817
+ const p = join8(worktreePath, INIT_PROMPT_REL);
11754
11818
  if (!existsSync7(p))
11755
11819
  return;
11756
11820
  try {
@@ -11772,8 +11836,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
11772
11836
  var INIT_SCRIPT_REL, INIT_PROMPT_REL;
11773
11837
  var init_repo_init = __esm(() => {
11774
11838
  init_repos();
11775
- INIT_SCRIPT_REL = join7(".kobe", "init.sh");
11776
- INIT_PROMPT_REL = join7(".kobe", "init-prompt.md");
11839
+ INIT_SCRIPT_REL = join8(".kobe", "init.sh");
11840
+ INIT_PROMPT_REL = join8(".kobe", "init-prompt.md");
11777
11841
  });
11778
11842
 
11779
11843
  // src/cli/api-cmd.ts
@@ -11800,7 +11864,7 @@ __export(exports_api_cmd, {
11800
11864
  API_VERBS: () => API_VERBS,
11801
11865
  API_SCHEMA_VERSION: () => API_SCHEMA_VERSION
11802
11866
  });
11803
- import { resolve as resolve4 } from "path";
11867
+ import { resolve as resolve5 } from "path";
11804
11868
  function groupOf(verbName) {
11805
11869
  for (const [group, names] of Object.entries(VERB_GROUPS)) {
11806
11870
  if (names.includes(verbName))
@@ -11965,10 +12029,10 @@ class VerbArgs {
11965
12029
  }
11966
12030
  path(name) {
11967
12031
  const v = this.str(name);
11968
- return v === undefined ? undefined : resolve4(process.cwd(), v);
12032
+ return v === undefined ? undefined : resolve5(process.cwd(), v);
11969
12033
  }
11970
12034
  requirePath(name) {
11971
- return resolve4(process.cwd(), this.require(name));
12035
+ return resolve5(process.cwd(), this.require(name));
11972
12036
  }
11973
12037
  }
11974
12038
  function parseAgentsSpec(spec) {
@@ -12154,10 +12218,22 @@ function daemonOf(ctx) {
12154
12218
  async function simpleRpc(ctx, name, payload) {
12155
12219
  return daemonOf(ctx).request(name, payload);
12156
12220
  }
12221
+ async function issueUpdate(ctx) {
12222
+ const title = ctx.args.str("title");
12223
+ const body = ctx.args.str("body");
12224
+ if (title === undefined && body === undefined) {
12225
+ throw new ApiError("issue-update requires --title and/or --body", "MISSING_FLAG");
12226
+ }
12227
+ return simpleRpc(ctx, "issue.mutate", {
12228
+ repoRoot: ctx.args.requirePath("repo"),
12229
+ op: { type: "update", id: ctx.args.int("id"), title, body }
12230
+ });
12231
+ }
12157
12232
  async function add(ctx) {
12158
12233
  const daemon = daemonOf(ctx);
12159
- const { args } = ctx;
12160
- const payload = { repo: args.requirePath("repo") };
12234
+ const { args, runtime } = ctx;
12235
+ const repo = await runtime.resolveRepoRoot(args.requirePath("repo"));
12236
+ const payload = { repo };
12161
12237
  const title = args.str("title");
12162
12238
  if (title)
12163
12239
  payload.title = title;
@@ -12167,11 +12243,12 @@ async function add(ctx) {
12167
12243
  const baseRef = args.str("base-branch");
12168
12244
  if (baseRef)
12169
12245
  payload.baseRef = baseRef;
12170
- const vendor = args.vendor();
12246
+ const vendor = args.vendor() ?? await runtime.defaultVendor();
12171
12247
  if (vendor)
12172
12248
  payload.vendor = vendor;
12173
12249
  const res = await daemon.request("task.create", payload);
12174
12250
  const taskId = res.taskId;
12251
+ await daemon.request("task.setActive", { taskId });
12175
12252
  const status = args.enumOf("status");
12176
12253
  if (status)
12177
12254
  await daemon.request("task.status", { taskId, status });
@@ -12186,6 +12263,7 @@ async function add(ctx) {
12186
12263
  if (!prompt)
12187
12264
  return { taskId, task, started: false };
12188
12265
  const delivered = await ctx.runtime.deliverPrompt(daemon, { id: taskId, worktreePath: task.worktreePath, vendor: task.vendor, repo: task.repo }, prompt);
12266
+ task = (await daemon.request("task.get", { taskId })).task;
12189
12267
  return { taskId, task, started: delivered.started, engineReady: delivered.engineReady, session: delivered.session };
12190
12268
  }
12191
12269
  async function send(ctx) {
@@ -12281,13 +12359,14 @@ async function adopt(ctx) {
12281
12359
  }
12282
12360
  async function fanOut(ctx) {
12283
12361
  const daemon = daemonOf(ctx);
12284
- const { args } = ctx;
12285
- const repo = args.requirePath("repo");
12362
+ const { args, runtime } = ctx;
12363
+ const repo = await runtime.resolveRepoRoot(args.requirePath("repo"));
12286
12364
  const prompt = args.require("prompt");
12287
12365
  const title = args.str("title");
12288
12366
  const baseRef = args.str("base-branch");
12289
12367
  const agentsSpec = args.str("agents");
12290
- const plan = agentsSpec ? parseAgentsSpec(agentsSpec) : new Array(args.int("count") ?? 1).fill(args.vendor() ?? "claude");
12368
+ const defaultVendor = await runtime.defaultVendor();
12369
+ const plan = agentsSpec ? parseAgentsSpec(agentsSpec) : new Array(args.int("count") ?? 1).fill(args.vendor() ?? defaultVendor ?? "claude");
12291
12370
  if (plan.length > FANOUT_CAP) {
12292
12371
  throw new ApiError(`fan-out of ${plan.length} exceeds the cap of ${FANOUT_CAP} \u2014 spawn in batches`, "BAD_FLAG");
12293
12372
  }
@@ -12422,7 +12501,7 @@ ${apiUsage()}`, "BAD_VERB", 2);
12422
12501
  session?.close();
12423
12502
  }
12424
12503
  }
12425
- var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS, realPromptDeliveryOps, defaultApiRuntime;
12504
+ var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES, ISSUE_STATUSES2, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS, realPromptDeliveryOps, defaultApiRuntime;
12426
12505
  var init_api_cmd = __esm(() => {
12427
12506
  init_interactive_command();
12428
12507
  init_feedback();
@@ -12432,6 +12511,7 @@ var init_api_cmd = __esm(() => {
12432
12511
  init_version();
12433
12512
  init_daemon_session();
12434
12513
  TASK_STATUSES = ["backlog", "in_progress", "in_review", "done", "canceled", "error"];
12514
+ ISSUE_STATUSES2 = ["open", "doing", "hold", "done"];
12435
12515
  ApiError = class ApiError extends Error {
12436
12516
  code;
12437
12517
  constructor(message, code) {
@@ -12477,6 +12557,7 @@ var init_api_cmd = __esm(() => {
12477
12557
  create: ["add", "fan-out"],
12478
12558
  drive: ["send", "dispatch", "note", "set-active"],
12479
12559
  edit: ["rename", "set-branch", "set-vendor", "set-status"],
12560
+ issues: ["issue-list", "issue-create", "issue-set-status", "issue-update"],
12480
12561
  lifecycle: ["archive", "pin", "delete"],
12481
12562
  worktree: ["ensure-worktree", "adopt", "discover-adoptable"],
12482
12563
  feedback: ["feedback"]
@@ -12593,6 +12674,49 @@ var init_api_cmd = __esm(() => {
12593
12674
  offline: true,
12594
12675
  handler: feedback
12595
12676
  },
12677
+ {
12678
+ name: "issue-list",
12679
+ summary: "List daemon-owned issues for a repo.",
12680
+ flags: [F.repo()],
12681
+ handler: (ctx) => simpleRpc(ctx, "issue.list", { repoRoot: ctx.args.requirePath("repo") })
12682
+ },
12683
+ {
12684
+ name: "issue-create",
12685
+ summary: "Create a daemon-owned issue for a repo.",
12686
+ flags: [
12687
+ F.repo(),
12688
+ { name: "title", type: "string", required: true, placeholder: "T", description: "Issue title." },
12689
+ { name: "body", type: "string", placeholder: "TEXT", description: "Issue body." }
12690
+ ],
12691
+ handler: (ctx) => simpleRpc(ctx, "issue.mutate", {
12692
+ repoRoot: ctx.args.requirePath("repo"),
12693
+ op: { type: "create", title: ctx.args.require("title"), body: ctx.args.str("body") }
12694
+ })
12695
+ },
12696
+ {
12697
+ name: "issue-set-status",
12698
+ summary: "Set a daemon-owned issue's status.",
12699
+ flags: [
12700
+ F.repo(),
12701
+ { name: "id", type: "int", required: true, placeholder: "N", description: "Issue id." },
12702
+ { name: "status", type: "enum", required: true, values: ISSUE_STATUSES2, description: "New issue status." }
12703
+ ],
12704
+ handler: (ctx) => simpleRpc(ctx, "issue.mutate", {
12705
+ repoRoot: ctx.args.requirePath("repo"),
12706
+ op: { type: "setStatus", id: ctx.args.int("id"), status: ctx.args.requireEnum("status") }
12707
+ })
12708
+ },
12709
+ {
12710
+ name: "issue-update",
12711
+ summary: "Update a daemon-owned issue's title and/or body.",
12712
+ flags: [
12713
+ F.repo(),
12714
+ { name: "id", type: "int", required: true, placeholder: "N", description: "Issue id." },
12715
+ { name: "title", type: "string", placeholder: "T", description: "New title." },
12716
+ { name: "body", type: "string", placeholder: "TEXT", description: "New body." }
12717
+ ],
12718
+ handler: issueUpdate
12719
+ },
12596
12720
  {
12597
12721
  name: "collect",
12598
12722
  summary: "Read-only comparison snapshot of several tasks (identity, branch, .running, uncommitted .changes).",
@@ -12718,7 +12842,12 @@ var init_api_cmd = __esm(() => {
12718
12842
  defaultApiRuntime = {
12719
12843
  isTaskRunning: (taskId) => sessionExists(tmuxSessionName(taskId)),
12720
12844
  deliverPrompt: (client, target, prompt) => deliverPrompt(client, target, prompt),
12721
- resolveRepoRoot: async (absPath) => (await Promise.resolve().then(() => (init_repos(), exports_repos))).resolveRepoRoot(absPath),
12845
+ resolveRepoRoot: async (absPath) => (await Promise.resolve().then(() => (init_repos(), exports_repos))).resolveMainRepoRoot(absPath),
12846
+ defaultVendor: async () => {
12847
+ const { getPersistedString: getPersistedString2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
12848
+ const value = getPersistedString2("lastSelectedVendor")?.trim();
12849
+ return value ? value : undefined;
12850
+ },
12722
12851
  readWorktreeChanges: async (worktreePath) => (await Promise.resolve().then(() => (init_worktree_changes(), exports_worktree_changes))).readWorktreeChanges(worktreePath),
12723
12852
  tearDownSession: async (taskId) => {
12724
12853
  const session = tmuxSessionName(taskId);
@@ -12820,7 +12949,7 @@ __export(exports_theme, {
12820
12949
  runThemeSubcommand: () => runThemeSubcommand
12821
12950
  });
12822
12951
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
12823
- import { basename as basename5, join as join8, resolve as resolve5 } from "path";
12952
+ import { basename as basename5, join as join9, resolve as resolve6 } from "path";
12824
12953
  function fail3(message) {
12825
12954
  process.stderr.write(`kobe theme: ${message}
12826
12955
  `);
@@ -12851,7 +12980,7 @@ function listThemes() {
12851
12980
  } else {
12852
12981
  for (const f of userFiles) {
12853
12982
  const name = f.slice(0, -".json".length);
12854
- const path11 = join8(dir, f);
12983
+ const path11 = join9(dir, f);
12855
12984
  const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
12856
12985
  lines.push(` ${name}${overridesBundled} ${path11}`);
12857
12986
  }
@@ -12877,7 +13006,7 @@ async function readSource(source) {
12877
13006
  const defaultName2 = file2.endsWith(".json") ? file2.slice(0, -".json".length) : file2;
12878
13007
  return { text: text2, defaultName: defaultName2 };
12879
13008
  }
12880
- const abs = resolve5(process.cwd(), source);
13009
+ const abs = resolve6(process.cwd(), source);
12881
13010
  let text;
12882
13011
  try {
12883
13012
  text = readFileSync9(abs, "utf8");
@@ -12941,7 +13070,7 @@ async function addTheme(args) {
12941
13070
  }
12942
13071
  const dir = userThemesDir();
12943
13072
  mkdirSync5(dir, { recursive: true });
12944
- const dest = join8(dir, `${name}.json`);
13073
+ const dest = join9(dir, `${name}.json`);
12945
13074
  if (existsSync8(dest) && !opts.force) {
12946
13075
  fail3(`${dest} already exists (pass --force to overwrite)`);
12947
13076
  }
@@ -12959,7 +13088,7 @@ function removeTheme(args) {
12959
13088
  if (BUNDLED_NAMES.includes(name)) {
12960
13089
  fail3(`"${name}" is a built-in theme and cannot be removed`);
12961
13090
  }
12962
- const dest = join8(userThemesDir(), `${name}.json`);
13091
+ const dest = join9(userThemesDir(), `${name}.json`);
12963
13092
  if (!existsSync8(dest)) {
12964
13093
  fail3(`no user theme named "${name}" (looked for ${dest})`);
12965
13094
  }
@@ -13120,9 +13249,9 @@ var init_feedback_cmd = __esm(() => {
13120
13249
  });
13121
13250
 
13122
13251
  // src/core/index.ts
13123
- import { homedir as homedir15 } from "os";
13252
+ import { homedir as homedir16 } from "os";
13124
13253
  async function createKobeCore(options = {}) {
13125
- const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir15();
13254
+ const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir16();
13126
13255
  const store = new TaskIndexStore({ homeDir: homeDir2 });
13127
13256
  await store.load();
13128
13257
  const worktrees = new GitWorktreeManager;
@@ -13240,8 +13369,8 @@ var init_daemon_cmd = __esm(() => {
13240
13369
 
13241
13370
  // src/lib/skill-install.ts
13242
13371
  import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
13243
- import { homedir as homedir16 } from "os";
13244
- import { join as join9 } from "path";
13372
+ import { homedir as homedir17 } from "os";
13373
+ import { join as join10 } from "path";
13245
13374
  function npxSkillsArgv(opts = {}) {
13246
13375
  return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
13247
13376
  }
@@ -13249,9 +13378,9 @@ function npxSkillsCommand(opts = {}) {
13249
13378
  return `npx ${npxSkillsArgv(opts).join(" ")}`;
13250
13379
  }
13251
13380
  function kobeSkillPaths(opts = {}) {
13252
- const home = opts.home ?? homedir16();
13381
+ const home = opts.home ?? homedir17();
13253
13382
  const cwd = opts.cwd ?? process.cwd();
13254
- return [join9(home, SKILL_REL_PATH), join9(cwd, SKILL_REL_PATH)];
13383
+ return [join10(home, SKILL_REL_PATH), join10(cwd, SKILL_REL_PATH)];
13255
13384
  }
13256
13385
  function parseSkillVersion(content) {
13257
13386
  const m = content.match(/kobe-skill-version:\s*(\d+)/);
@@ -13298,7 +13427,7 @@ kobe: your kobe agent skill is out of date (${was}; this kobe wants v${state.cur
13298
13427
  `);
13299
13428
  }
13300
13429
  }
13301
- 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";
13430
+ var KOBE_SKILL_VERSION = 2, 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";
13302
13431
  var init_skill_install = __esm(() => {
13303
13432
  init_repos();
13304
13433
  });
@@ -13312,7 +13441,7 @@ __export(exports_maintenance, {
13312
13441
  });
13313
13442
  import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
13314
13443
  import { unlink as unlink6 } from "fs/promises";
13315
- import { join as join10 } from "path";
13444
+ import { join as join11 } from "path";
13316
13445
  import { createInterface as createInterface2 } from "readline";
13317
13446
  function isProcessAlive2(pid) {
13318
13447
  try {
@@ -13409,7 +13538,7 @@ async function runDoctorSubcommand(argv = []) {
13409
13538
  const socketPath = defaultDaemonSocketPath();
13410
13539
  const pidPath = defaultDaemonPidPath();
13411
13540
  const logPath = defaultDaemonLogPath();
13412
- const tasksPath = join10(kobeStateDir(), "tasks.json");
13541
+ const tasksPath = join11(kobeStateDir(), "tasks.json");
13413
13542
  const statePath2 = kvStatePath();
13414
13543
  const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
13415
13544
  const status = await probeDaemonStatus(socketPath);
@@ -13476,7 +13605,7 @@ async function runDoctorSubcommand(argv = []) {
13476
13605
  async function confirmTty(prompt) {
13477
13606
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
13478
13607
  try {
13479
- const answer = await new Promise((resolve6) => rl.question(prompt, resolve6));
13608
+ const answer = await new Promise((resolve7) => rl.question(prompt, resolve7));
13480
13609
  return /^y(es)?$/i.test(answer.trim());
13481
13610
  } finally {
13482
13611
  rl.close();
@@ -13528,7 +13657,7 @@ async function runResetSubcommand(argv) {
13528
13657
  const yes = argv.includes("--yes") || argv.includes("-y");
13529
13658
  const socketPath = defaultDaemonSocketPath();
13530
13659
  const pidPath = defaultDaemonPidPath();
13531
- const tasksPath = join10(kobeStateDir(), "tasks.json");
13660
+ const tasksPath = join11(kobeStateDir(), "tasks.json");
13532
13661
  const statePath2 = kvStatePath();
13533
13662
  console.log("kobe reset will:");
13534
13663
  console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
@@ -13632,6 +13761,22 @@ var init_maintenance = __esm(() => {
13632
13761
  init_version();
13633
13762
  });
13634
13763
 
13764
+ // src/tui/lib/editor-prefs.ts
13765
+ function normalizeEditorKind(value) {
13766
+ return EDITOR_KINDS.includes(value) ? value : DEFAULT_EDITOR_KIND;
13767
+ }
13768
+ var EDITOR_KINDS, AUTO_EDITOR_CANDIDATES, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "auto";
13769
+ var init_editor_prefs = __esm(() => {
13770
+ EDITOR_KINDS = ["auto", "vim", "nvim", "nano", "emacs", "custom"];
13771
+ AUTO_EDITOR_CANDIDATES = ["nvim", "vim", "emacs", "nano"];
13772
+ });
13773
+
13774
+ // src/tui/lib/settings-surface.ts
13775
+ function normalizeSettingsSurface(value) {
13776
+ return value === "taskpanel" ? "taskpanel" : "chattab";
13777
+ }
13778
+ var SETTINGS_SURFACE_KEY = "settings.surface", DEFAULT_SETTINGS_SURFACE = "chattab";
13779
+
13635
13780
  // src/web/diff.ts
13636
13781
  async function runGit(cwd, args) {
13637
13782
  try {
@@ -13748,8 +13893,8 @@ async function handleDiffRequest(req, url) {
13748
13893
  return Response.json({ error: "worktreePath must be an absolute path" }, { status: 400 });
13749
13894
  }
13750
13895
  try {
13751
- const stat4 = await Bun.file(worktreePath).stat();
13752
- if (!stat4.isDirectory()) {
13896
+ const stat5 = await Bun.file(worktreePath).stat();
13897
+ if (!stat5.isDirectory()) {
13753
13898
  return Response.json({ error: "worktreePath is not a directory" }, { status: 400 });
13754
13899
  }
13755
13900
  } catch {
@@ -13817,7 +13962,7 @@ async function handleDiffRequest(req, url) {
13817
13962
  var GIT_TIMEOUT_MS = 15000, UNTRACKED_DIFF_CONCURRENCY = 8;
13818
13963
 
13819
13964
  // src/web/history.ts
13820
- import { isAbsolute } from "path";
13965
+ import { isAbsolute as isAbsolute2 } from "path";
13821
13966
  function isSafeVendor(value) {
13822
13967
  return typeof value === "string" && value.length > 0 && /^[A-Za-z0-9_-]+$/.test(value);
13823
13968
  }
@@ -13827,7 +13972,7 @@ function isSafeSessionId(value) {
13827
13972
  async function handleSessions(url) {
13828
13973
  const worktreePath = url.searchParams.get("worktreePath");
13829
13974
  const vendor = url.searchParams.get("vendor") ?? "claude";
13830
- if (!worktreePath || !isAbsolute(worktreePath)) {
13975
+ if (!worktreePath || !isAbsolute2(worktreePath)) {
13831
13976
  return Response.json({ error: "worktreePath must be an absolute path" }, { status: 400 });
13832
13977
  }
13833
13978
  if (!isSafeVendor(vendor)) {
@@ -13873,16 +14018,16 @@ var init_history4 = __esm(() => {
13873
14018
  });
13874
14019
 
13875
14020
  // src/web/notes.ts
13876
- import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
13877
- import { join as join11 } from "path";
14021
+ import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
14022
+ import { join as join12 } from "path";
13878
14023
  function notesDir() {
13879
- return join11(kobeStateDir(), "notes");
14024
+ return join12(kobeStateDir(), "notes");
13880
14025
  }
13881
14026
  function isSafeTaskId(taskId) {
13882
14027
  return typeof taskId === "string" && taskId.length > 0 && /^[A-Za-z0-9_-]+$/.test(taskId);
13883
14028
  }
13884
14029
  function noteFilePath(taskId) {
13885
- return join11(notesDir(), `${taskId}.md`);
14030
+ return join12(notesDir(), `${taskId}.md`);
13886
14031
  }
13887
14032
  async function handleGet(url) {
13888
14033
  const taskId = url.searchParams.get("taskId");
@@ -13892,7 +14037,7 @@ async function handleGet(url) {
13892
14037
  try {
13893
14038
  let markdown = "";
13894
14039
  try {
13895
- markdown = await readFile8(noteFilePath(taskId), "utf8");
14040
+ markdown = await readFile9(noteFilePath(taskId), "utf8");
13896
14041
  } catch (err) {
13897
14042
  if (err.code !== "ENOENT")
13898
14043
  throw err;
@@ -13916,8 +14061,8 @@ async function handlePut(req) {
13916
14061
  return Response.json({ error: "markdown must be a string" }, { status: 400 });
13917
14062
  }
13918
14063
  try {
13919
- await mkdir7(notesDir(), { recursive: true });
13920
- await writeFile5(noteFilePath(body.taskId), body.markdown, "utf8");
14064
+ await mkdir8(notesDir(), { recursive: true });
14065
+ await writeFile6(noteFilePath(body.taskId), body.markdown, "utf8");
13921
14066
  return Response.json({ ok: true });
13922
14067
  } catch (err) {
13923
14068
  return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
@@ -14025,12 +14170,12 @@ var SPA_CHANNELS, SPA_CHANNEL_SET;
14025
14170
  var init_spa_channels = __esm(() => {
14026
14171
  SPA_CHANNELS = [
14027
14172
  "task.snapshot",
14173
+ "issue.snapshot",
14028
14174
  "active-task",
14029
14175
  "engine-state",
14030
14176
  "update",
14031
14177
  "task.jobs",
14032
14178
  "worktree.changes",
14033
- "task.conflicts",
14034
14179
  "session.deliver",
14035
14180
  "ui-prefs"
14036
14181
  ];
@@ -14039,7 +14184,25 @@ var init_spa_channels = __esm(() => {
14039
14184
 
14040
14185
  // ../kobe-web/server/daemon-link.ts
14041
14186
  function sleep2(ms) {
14042
- return new Promise((resolve6) => setTimeout(resolve6, ms));
14187
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
14188
+ }
14189
+ function normalizedPath(path11) {
14190
+ return path11.length > 1 ? path11.replace(/\/+$/, "") : path11;
14191
+ }
14192
+ function issueSnapshotAliases(tasks, repoRoot) {
14193
+ const root = normalizedPath(repoRoot);
14194
+ const aliases = new Set([repoRoot]);
14195
+ for (const task of tasks) {
14196
+ const taskRepo = normalizedPath(task.repo);
14197
+ const taskWorktree = normalizedPath(task.worktreePath);
14198
+ if (taskRepo === root || taskWorktree === root) {
14199
+ if (task.repo)
14200
+ aliases.add(task.repo);
14201
+ if (task.worktreePath)
14202
+ aliases.add(task.worktreePath);
14203
+ }
14204
+ }
14205
+ return [...aliases];
14043
14206
  }
14044
14207
 
14045
14208
  class DaemonLink {
@@ -14055,7 +14218,7 @@ class DaemonLink {
14055
14218
  update = null;
14056
14219
  jobs = {};
14057
14220
  worktreeChanges = {};
14058
- conflicts = [];
14221
+ issueSnapshots = {};
14059
14222
  deliver = null;
14060
14223
  uiPrefs = null;
14061
14224
  async start() {
@@ -14069,7 +14232,7 @@ class DaemonLink {
14069
14232
  update: this.update,
14070
14233
  jobs: this.jobs,
14071
14234
  worktreeChanges: this.worktreeChanges,
14072
- conflicts: this.conflicts,
14235
+ issueSnapshots: this.issueSnapshots,
14073
14236
  deliver: this.deliver,
14074
14237
  uiPrefs: this.uiPrefs,
14075
14238
  connected: this.connected
@@ -14181,9 +14344,15 @@ class DaemonLink {
14181
14344
  case "worktree.changes":
14182
14345
  this.worktreeChanges = payload.changes;
14183
14346
  break;
14184
- case "task.conflicts":
14185
- this.conflicts = payload.pairs;
14347
+ case "issue.snapshot": {
14348
+ const state = payload;
14349
+ const next = { ...this.issueSnapshots };
14350
+ for (const alias of issueSnapshotAliases(this.tasks, state.repoRoot)) {
14351
+ next[alias] = { ...state, repoRoot: alias };
14352
+ }
14353
+ this.issueSnapshots = next;
14186
14354
  break;
14355
+ }
14187
14356
  case "session.deliver":
14188
14357
  this.deliver = payload;
14189
14358
  break;
@@ -14218,6 +14387,169 @@ var init_daemon_link = __esm(() => {
14218
14387
  init_spa_channels();
14219
14388
  });
14220
14389
 
14390
+ // ../kobe-web/server/issue-assets-route.ts
14391
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
14392
+ import { mkdir as mkdir9 } from "fs/promises";
14393
+ import { join as join13, resolve as resolve7 } from "path";
14394
+ function repoHashOf(repoRoot) {
14395
+ return createHash5("sha1").update(repoRoot).digest("hex").slice(0, 16);
14396
+ }
14397
+ async function handlePost(req) {
14398
+ const declared = Number.parseInt(req.headers.get("content-length") ?? "", 10);
14399
+ if (Number.isFinite(declared) && declared > MAX_ASSET_BYTES) {
14400
+ return Response.json({ error: "asset too large" }, { status: 413 });
14401
+ }
14402
+ let form;
14403
+ try {
14404
+ form = await req.formData();
14405
+ } catch {
14406
+ return Response.json({ error: "invalid form data" }, { status: 400 });
14407
+ }
14408
+ const repoRoot = form.get("repoRoot");
14409
+ if (typeof repoRoot !== "string" || repoRoot.length === 0) {
14410
+ return Response.json({ error: "missing repoRoot" }, { status: 400 });
14411
+ }
14412
+ const file = form.get("file");
14413
+ if (!(file instanceof File)) {
14414
+ return Response.json({ error: "file must be a File" }, { status: 400 });
14415
+ }
14416
+ if (file.size > MAX_ASSET_BYTES) {
14417
+ return Response.json({ error: "asset too large" }, { status: 413 });
14418
+ }
14419
+ const ext = CONTENT_TYPE_EXT[file.type];
14420
+ if (!ext) {
14421
+ return Response.json({ error: `unsupported content-type: ${file.type || "unknown"}` }, { status: 415 });
14422
+ }
14423
+ try {
14424
+ const repoHash = repoHashOf(repoRoot);
14425
+ const dir = join13(issueAssetsDir(), repoHash);
14426
+ await mkdir9(dir, { recursive: true });
14427
+ const assetId = randomUUID2();
14428
+ const name = `${assetId}.${ext}`;
14429
+ await Bun.write(join13(dir, name), file);
14430
+ return Response.json({ url: `${ASSETS_ROUTE}/${repoHash}/${name}` });
14431
+ } catch (err) {
14432
+ return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
14433
+ }
14434
+ }
14435
+ async function handleGet2(pathname) {
14436
+ const rest = pathname.slice(ASSETS_ROUTE.length + 1);
14437
+ const slash = rest.indexOf("/");
14438
+ if (slash < 0)
14439
+ return Response.json({ error: "not found" }, { status: 404 });
14440
+ const repoHash = rest.slice(0, slash);
14441
+ const fileSeg = rest.slice(slash + 1);
14442
+ if (!REPO_HASH_RE.test(repoHash) || !ASSET_FILE_RE.test(fileSeg)) {
14443
+ return Response.json({ error: "invalid asset path" }, { status: 400 });
14444
+ }
14445
+ const root = issueAssetsDir();
14446
+ const resolved = resolve7(root, repoHash, fileSeg);
14447
+ if (resolved !== join13(root, repoHash, fileSeg) || !resolved.startsWith(`${root}/`)) {
14448
+ return Response.json({ error: "invalid asset path" }, { status: 400 });
14449
+ }
14450
+ const ext = fileSeg.slice(fileSeg.lastIndexOf(".") + 1).toLowerCase();
14451
+ const contentType = EXT_CONTENT_TYPE[ext];
14452
+ if (!contentType)
14453
+ return Response.json({ error: "invalid asset path" }, { status: 400 });
14454
+ const file = Bun.file(resolved);
14455
+ if (!await file.exists())
14456
+ return Response.json({ error: "not found" }, { status: 404 });
14457
+ return new Response(file, {
14458
+ headers: {
14459
+ "content-type": contentType,
14460
+ "cache-control": "public, max-age=31536000, immutable",
14461
+ "x-content-type-options": "nosniff"
14462
+ }
14463
+ });
14464
+ }
14465
+ async function handleIssueAssetsRequest(req, url) {
14466
+ if (url.pathname === ASSETS_ROUTE) {
14467
+ if (req.method === "POST")
14468
+ return handlePost(req);
14469
+ return Response.json({ error: "method not allowed" }, { status: 405 });
14470
+ }
14471
+ if (url.pathname.startsWith(`${ASSETS_ROUTE}/`)) {
14472
+ if (req.method === "GET")
14473
+ return handleGet2(url.pathname);
14474
+ return Response.json({ error: "method not allowed" }, { status: 405 });
14475
+ }
14476
+ return null;
14477
+ }
14478
+ var ASSETS_ROUTE = "/api/issue-assets", MAX_ASSET_BYTES, CONTENT_TYPE_EXT, EXT_CONTENT_TYPE, REPO_HASH_RE, ASSET_FILE_RE;
14479
+ var init_issue_assets_route = __esm(() => {
14480
+ init_env();
14481
+ MAX_ASSET_BYTES = 10 * 1024 * 1024;
14482
+ CONTENT_TYPE_EXT = {
14483
+ "image/png": "png",
14484
+ "image/jpeg": "jpg",
14485
+ "image/gif": "gif",
14486
+ "image/webp": "webp"
14487
+ };
14488
+ EXT_CONTENT_TYPE = {
14489
+ png: "image/png",
14490
+ jpg: "image/jpeg",
14491
+ jpeg: "image/jpeg",
14492
+ gif: "image/gif",
14493
+ webp: "image/webp"
14494
+ };
14495
+ REPO_HASH_RE = /^[a-f0-9]{16}$/;
14496
+ ASSET_FILE_RE = /^[A-Za-z0-9_-]+\.[a-z0-9]+$/;
14497
+ });
14498
+
14499
+ // ../kobe-web/server/issues-route.ts
14500
+ function errorResponse(err, status = 500) {
14501
+ return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status });
14502
+ }
14503
+ function statusForIssueError(err) {
14504
+ const message = err instanceof Error ? err.message : String(err);
14505
+ if (/^no issue #\d+$/.test(message))
14506
+ return 404;
14507
+ if (message === "repoRoot is required" || message === "repoRoot does not exist" || message === "repoRoot is not a git repository" || message === "missing op" || message === "create requires a non-empty title" || message === "body must be a string" || message === "setStatus requires a numeric id" || message.startsWith("invalid status:") || message === "update requires a numeric id" || message === "title must be a non-empty string" || message === "link requires a numeric id" || message === "link requires a non-empty taskId" || message === "unlink requires a numeric id" || message === "delete requires a numeric id" || message.startsWith("unknown op type:")) {
14508
+ return 400;
14509
+ }
14510
+ return 500;
14511
+ }
14512
+ async function handleGet3(link, url) {
14513
+ const repoRoot = url.searchParams.get("repoRoot");
14514
+ if (!repoRoot)
14515
+ return Response.json({ error: "missing repoRoot" }, { status: 400 });
14516
+ try {
14517
+ return Response.json(await link.request("issue.list", { repoRoot }));
14518
+ } catch (err) {
14519
+ return errorResponse(err, statusForIssueError(err));
14520
+ }
14521
+ }
14522
+ async function handlePost2(link, req) {
14523
+ let parsed;
14524
+ try {
14525
+ parsed = await req.json();
14526
+ } catch {
14527
+ return Response.json({ error: "invalid JSON body" }, { status: 400 });
14528
+ }
14529
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
14530
+ return Response.json({ error: "body must be a JSON object" }, { status: 400 });
14531
+ }
14532
+ const body = parsed;
14533
+ if (typeof body.repoRoot !== "string" || body.repoRoot.length === 0) {
14534
+ return Response.json({ error: "missing repoRoot" }, { status: 400 });
14535
+ }
14536
+ try {
14537
+ return Response.json(await link.request("issue.mutate", { repoRoot: body.repoRoot, op: body.op }));
14538
+ } catch (err) {
14539
+ return errorResponse(err, statusForIssueError(err));
14540
+ }
14541
+ }
14542
+ async function handleIssuesRequest(req, url, link) {
14543
+ if (url.pathname !== ISSUES_ROUTE)
14544
+ return null;
14545
+ if (req.method === "GET")
14546
+ return handleGet3(link, url);
14547
+ if (req.method === "POST")
14548
+ return handlePost2(link, req);
14549
+ return Response.json({ error: "method not allowed" }, { status: 405 });
14550
+ }
14551
+ var ISSUES_ROUTE = "/api/issues";
14552
+
14221
14553
  // ../kobe-web/server/rpc-allowlist.ts
14222
14554
  var WEB_RPC_ALLOWLIST, WEB_RPC_ALLOWSET;
14223
14555
  var init_rpc_allowlist = __esm(() => {
@@ -14266,7 +14598,7 @@ async function ensureTaskSession(link, taskId) {
14266
14598
  const ok = await ensureSession({
14267
14599
  name: session,
14268
14600
  cwd: worktreePath,
14269
- command: interactiveEngineCommand(task.vendor),
14601
+ command: interactiveEngineCommand(task.vendor, task.modelEffort),
14270
14602
  taskId,
14271
14603
  vendor: task.vendor,
14272
14604
  initScript: init.initScript
@@ -14284,7 +14616,7 @@ async function engineSpec(link, taskId) {
14284
14616
  const protocolTaskId = task.kind === "main" ? undefined : taskId;
14285
14617
  const dispatcherTaskId = task.kind === "main" ? taskId : undefined;
14286
14618
  const argv = [
14287
- ...withDispatcherProtocol(withWorktreeProtocol(interactiveEngineCommand(task.vendor), task.vendor, protocolTaskId), task.vendor, dispatcherTaskId)
14619
+ ...withDispatcherProtocol(withWorktreeProtocol(interactiveEngineCommand(task.vendor, task.modelEffort), task.vendor, protocolTaskId), task.vendor, dispatcherTaskId)
14288
14620
  ];
14289
14621
  const init = resolveRepoInit(task.repo ?? "", worktreePath);
14290
14622
  const quoted = shellQuote2(argv);
@@ -14312,7 +14644,7 @@ var init_session = __esm(() => {
14312
14644
 
14313
14645
  // ../kobe-web/server/bridge.ts
14314
14646
  import { existsSync as existsSync11 } from "fs";
14315
- import { join as join12, normalize as normalize2 } from "path";
14647
+ import { join as join14, normalize as normalize2 } from "path";
14316
14648
  function sseResponse(register) {
14317
14649
  let unregister = null;
14318
14650
  let heartbeat = null;
@@ -14374,12 +14706,140 @@ async function rpcResponse(req, link, tearDown) {
14374
14706
  async function enginesResponse() {
14375
14707
  try {
14376
14708
  const ids = await availableEngineIds();
14377
- const engines = ids.map((id) => ({ id, label: engineDisplayName(id) }));
14709
+ const engines = ids.map((id) => ({
14710
+ id,
14711
+ label: engineDisplayName(id),
14712
+ effortLevels: engineEntry(id).effortLevels
14713
+ }));
14378
14714
  return Response.json({ engines: engines.length > 0 ? engines : [{ id: "claude", label: "Claude" }] });
14379
14715
  } catch (err) {
14380
14716
  return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
14381
14717
  }
14382
14718
  }
14719
+ function cliInvocationResponse() {
14720
+ return Response.json({ api: kobeApiInvocation() });
14721
+ }
14722
+ function stringValue(value, fallback = "") {
14723
+ return typeof value === "string" ? value : fallback;
14724
+ }
14725
+ function boolValue(value, fallback) {
14726
+ return typeof value === "boolean" ? value : fallback;
14727
+ }
14728
+ function customEngineIdsFrom(state) {
14729
+ const raw = state.customEngineIds;
14730
+ return Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
14731
+ }
14732
+ function humanizeSlug(id) {
14733
+ return id.split(/[-_]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
14734
+ }
14735
+ function engineCommandText(state, id) {
14736
+ const override = stringValue(state[engineCommandKey(id)]).trim();
14737
+ return override || defaultEngineCommand(id).join(" ");
14738
+ }
14739
+ function engineLabelText(state, id) {
14740
+ const override = stringValue(state[engineNameKey(id)]).trim();
14741
+ return override || engineDisplayName(id);
14742
+ }
14743
+ function settingsSnapshot() {
14744
+ const state = loadStateFile();
14745
+ const custom = customEngineIdsFrom(state);
14746
+ const engineIds = [...BUILTIN_VENDORS, ...custom];
14747
+ const defaultEngine = stringValue(state.lastSelectedVendor, "claude");
14748
+ const focusAccent = stringValue(state.focusAccent, "primary");
14749
+ return Response.json({
14750
+ activeTheme: stringValue(state.activeTheme, "claude"),
14751
+ transparentBackground: boolValue(state.transparentBackground, false),
14752
+ focusAccent: FOCUS_ACCENTS.includes(focusAccent) ? focusAccent : "primary",
14753
+ notificationsToast: state["notifications.toast.enabled"] !== false,
14754
+ notificationsSound: state["notifications.sound.enabled"] !== false,
14755
+ settingsSurface: normalizeSettingsSurface(state[SETTINGS_SURFACE_KEY] ?? DEFAULT_SETTINGS_SURFACE),
14756
+ editorKind: normalizeEditorKind(state[EDITOR_KIND_KEY] ?? DEFAULT_EDITOR_KIND),
14757
+ editorCustomCommand: stringValue(state[EDITOR_CUSTOM_KEY]),
14758
+ remoteProjects: state["experimental.remoteProjects"] === true,
14759
+ autoStatus: state[AUTO_STATUS_KEY] === true,
14760
+ dispatcher: state[DISPATCHER_KEY] === true,
14761
+ defaultEngine,
14762
+ engines: engineIds.map((id) => ({
14763
+ id,
14764
+ label: engineLabelText(state, id),
14765
+ command: engineCommandText(state, id),
14766
+ isBuiltin: isBuiltinVendor(id),
14767
+ isCustom: !isBuiltinVendor(id),
14768
+ isDefault: id === defaultEngine
14769
+ }))
14770
+ });
14771
+ }
14772
+ function putIfString(patch, key, value) {
14773
+ if (typeof value === "string")
14774
+ patch[key] = value.trim();
14775
+ }
14776
+ function putIfBool(patch, key, value) {
14777
+ if (typeof value === "boolean")
14778
+ patch[key] = value;
14779
+ }
14780
+ async function settingsPatch(req) {
14781
+ try {
14782
+ const body = await req.json();
14783
+ const patch = {};
14784
+ putIfString(patch, "activeTheme", body.activeTheme);
14785
+ putIfBool(patch, "transparentBackground", body.transparentBackground);
14786
+ if (FOCUS_ACCENTS.includes(body.focusAccent)) {
14787
+ patch.focusAccent = body.focusAccent;
14788
+ }
14789
+ putIfBool(patch, "notifications.toast.enabled", body.notificationsToast);
14790
+ putIfBool(patch, "notifications.sound.enabled", body.notificationsSound);
14791
+ if (body.settingsSurface === "chattab" || body.settingsSurface === "taskpanel") {
14792
+ patch[SETTINGS_SURFACE_KEY] = body.settingsSurface;
14793
+ }
14794
+ if (EDITOR_KINDS.includes(body.editorKind))
14795
+ patch[EDITOR_KIND_KEY] = body.editorKind;
14796
+ putIfString(patch, EDITOR_CUSTOM_KEY, body.editorCustomCommand);
14797
+ putIfBool(patch, "experimental.remoteProjects", body.remoteProjects);
14798
+ putIfBool(patch, AUTO_STATUS_KEY, body.autoStatus);
14799
+ putIfBool(patch, DISPATCHER_KEY, body.dispatcher);
14800
+ putIfString(patch, "lastSelectedVendor", body.defaultEngine);
14801
+ const state = loadStateFile();
14802
+ const custom = customEngineIdsFrom(state);
14803
+ const known = new Set([...BUILTIN_VENDORS, ...custom]);
14804
+ const updates = Array.isArray(body.engineUpdates) ? body.engineUpdates : [];
14805
+ for (const raw of updates) {
14806
+ if (!raw || typeof raw !== "object")
14807
+ continue;
14808
+ const update = raw;
14809
+ if (typeof update.id !== "string" || !known.has(update.id))
14810
+ continue;
14811
+ putIfString(patch, engineCommandKey(update.id), update.command);
14812
+ putIfString(patch, engineNameKey(update.id), update.label);
14813
+ }
14814
+ if (body.addEngine && typeof body.addEngine === "object") {
14815
+ const add2 = body.addEngine;
14816
+ const id = typeof add2.id === "string" ? add2.id.trim().toLowerCase() : "";
14817
+ if (!ENGINE_ID_RE.test(id) || isBuiltinVendor(id) || known.has(id)) {
14818
+ return Response.json({ error: "invalid or duplicate engine id" }, { status: 400 });
14819
+ }
14820
+ const nextCustom = [...custom, id];
14821
+ patch.customEngineIds = nextCustom;
14822
+ patch[engineCommandKey(id)] = stringValue(add2.command).trim();
14823
+ const label = stringValue(add2.label).trim();
14824
+ patch[engineNameKey(id)] = label && label !== id ? label : humanizeSlug(id);
14825
+ }
14826
+ if (typeof body.removeEngine === "string") {
14827
+ const id = body.removeEngine;
14828
+ if (!isBuiltinVendor(id)) {
14829
+ patch.customEngineIds = custom.filter((engine) => engine !== id);
14830
+ patch[engineCommandKey(id)] = undefined;
14831
+ patch[engineNameKey(id)] = undefined;
14832
+ if (state.lastSelectedVendor === id)
14833
+ patch.lastSelectedVendor = "claude";
14834
+ }
14835
+ }
14836
+ if (Object.keys(patch).length > 0)
14837
+ patchStateFile(patch);
14838
+ return settingsSnapshot();
14839
+ } catch (err) {
14840
+ return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
14841
+ }
14842
+ }
14383
14843
  async function sessionResponse(req, link) {
14384
14844
  try {
14385
14845
  const { taskId } = await req.json();
@@ -14426,6 +14886,12 @@ function createRequestHandler(deps) {
14426
14886
  return specResponse(url, link, terminalSpec);
14427
14887
  if (url.pathname === "/api/engines" && req.method === "GET")
14428
14888
  return enginesResponse();
14889
+ if (url.pathname === "/api/cli-invocation" && req.method === "GET")
14890
+ return cliInvocationResponse();
14891
+ if (url.pathname === "/api/settings" && req.method === "GET")
14892
+ return settingsSnapshot();
14893
+ if (url.pathname === "/api/settings" && req.method === "PATCH")
14894
+ return settingsPatch(req);
14429
14895
  if (url.pathname === "/api/quick-prompts" && req.method === "GET")
14430
14896
  return quickPromptsGet();
14431
14897
  if (url.pathname === "/api/quick-prompts" && req.method === "PUT")
@@ -14439,6 +14905,12 @@ function createRequestHandler(deps) {
14439
14905
  const history = await handleHistoryRequest(req, url);
14440
14906
  if (history)
14441
14907
  return history;
14908
+ const issues = await handleIssuesRequest(req, url, link);
14909
+ if (issues)
14910
+ return issues;
14911
+ const issueAssets = await handleIssueAssetsRequest(req, url);
14912
+ if (issueAssets)
14913
+ return issueAssets;
14442
14914
  const themes = handleThemesRequest(req, url);
14443
14915
  if (themes)
14444
14916
  return themes;
@@ -14467,10 +14939,10 @@ async function quickPromptsPut(req) {
14467
14939
  }
14468
14940
  async function staticResponse(pathname, staticDir) {
14469
14941
  const rel = pathname === "/" ? "/index.html" : pathname;
14470
- const resolved = normalize2(join12(staticDir, rel));
14942
+ const resolved = normalize2(join14(staticDir, rel));
14471
14943
  if (!resolved.startsWith(staticDir))
14472
14944
  return new Response("forbidden", { status: 403 });
14473
- const file = Bun.file(existsSync11(resolved) ? resolved : join12(staticDir, "index.html"));
14945
+ const file = Bun.file(existsSync11(resolved) ? resolved : join14(staticDir, "index.html"));
14474
14946
  if (!await file.exists()) {
14475
14947
  return new Response("kobe web assets not built \u2014 run `bun --filter kobe-web build`", { status: 503 });
14476
14948
  }
@@ -14542,17 +15014,26 @@ async function createBridgeServer(opts = {}) {
14542
15014
  }
14543
15015
  };
14544
15016
  }
14545
- var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", QUICK_PROMPT_KEYS;
15017
+ var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", FOCUS_ACCENTS, ENGINE_ID_RE, QUICK_PROMPT_KEYS;
14546
15018
  var init_bridge = __esm(() => {
14547
15019
  init_account_detect();
14548
15020
  init_interactive_command();
15021
+ init_registry();
15022
+ init_auto_status();
15023
+ init_dispatcher();
14549
15024
  init_repos();
15025
+ init_store();
15026
+ init_editor_prefs();
15027
+ init_vendor();
14550
15028
  init_history4();
14551
15029
  init_notes();
14552
15030
  init_themes();
14553
15031
  init_daemon_link();
15032
+ init_issue_assets_route();
14554
15033
  init_rpc_allowlist();
14555
15034
  init_session();
15035
+ FOCUS_ACCENTS = ["primary", "success", "info"];
15036
+ ENGINE_ID_RE = /^[a-z][a-z0-9_-]{0,47}$/;
14556
15037
  QUICK_PROMPT_KEYS = {
14557
15038
  review: "boardPrompt.review",
14558
15039
  pr: "boardPrompt.pr"
@@ -14570,18 +15051,18 @@ __export(exports_web_cmd, {
14570
15051
  runWebSubcommand: () => runWebSubcommand
14571
15052
  });
14572
15053
  import { existsSync as existsSync12 } from "fs";
14573
- import { homedir as homedir17 } from "os";
14574
- import { resolve as resolve6 } from "path";
15054
+ import { homedir as homedir18 } from "os";
15055
+ import { resolve as resolve8 } from "path";
14575
15056
  import { fileURLToPath as fileURLToPath3 } from "url";
14576
15057
  function homeLabel() {
14577
15058
  const explicit = process.env.KOBE_HOME_DIR?.trim();
14578
- return explicit ? `sandbox: ${explicit}` : `${homedir17()}/.kobe (production)`;
15059
+ return explicit ? `sandbox: ${explicit}` : `${homedir18()}/.kobe (production)`;
14579
15060
  }
14580
15061
  function resolveStaticDir() {
14581
15062
  const here = fileURLToPath3(import.meta.url);
14582
15063
  const candidates = [
14583
- resolve6(here, "../../../../kobe-web/dist"),
14584
- resolve6(here, "../../web-ui")
15064
+ resolve8(here, "../../../../kobe-web/dist"),
15065
+ resolve8(here, "../../web-ui")
14585
15066
  ];
14586
15067
  for (const dir of candidates) {
14587
15068
  if (existsSync12(`${dir}/index.html`))
@@ -14592,8 +15073,8 @@ function resolveStaticDir() {
14592
15073
  function resolvePtyServer() {
14593
15074
  const here = fileURLToPath3(import.meta.url);
14594
15075
  const candidates = [
14595
- resolve6(here, "../../../../kobe-web/pty-server.mjs"),
14596
- resolve6(here, "../../web-ui/pty-server.mjs")
15076
+ resolve8(here, "../../../../kobe-web/pty-server.mjs"),
15077
+ resolve8(here, "../../web-ui/pty-server.mjs")
14597
15078
  ];
14598
15079
  for (const file of candidates) {
14599
15080
  if (existsSync12(file))
@@ -14855,15 +15336,15 @@ __export(exports_hook_cmd, {
14855
15336
  parseWorktreeAddPath: () => parseWorktreeAddPath,
14856
15337
  ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
14857
15338
  });
14858
- import { homedir as homedir18 } from "os";
14859
- import { join as join13, resolve as resolve7 } from "path";
15339
+ import { homedir as homedir19 } from "os";
15340
+ import { join as join15, resolve as resolve9 } from "path";
14860
15341
  async function readTextWithTimeout(read, timeoutMs = STDIN_READ_TIMEOUT_MS) {
14861
15342
  let raceTimer;
14862
15343
  try {
14863
15344
  return await Promise.race([
14864
15345
  read(),
14865
- new Promise((resolve8) => {
14866
- raceTimer = setTimeout(() => resolve8(""), timeoutMs);
15346
+ new Promise((resolve10) => {
15347
+ raceTimer = setTimeout(() => resolve10(""), timeoutMs);
14867
15348
  })
14868
15349
  ]);
14869
15350
  } finally {
@@ -14932,7 +15413,7 @@ async function runWorktreeCreatedHook() {
14932
15413
  if (!rawPath)
14933
15414
  return;
14934
15415
  const cwd = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
14935
- const worktreePath = resolve7(cwd, rawPath);
15416
+ const worktreePath = resolve9(cwd, rawPath);
14936
15417
  const client = await connectIfRunning();
14937
15418
  if (!client)
14938
15419
  return;
@@ -14987,7 +15468,7 @@ function activityHookAdapters() {
14987
15468
  return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsHooks());
14988
15469
  }
14989
15470
  function globalSettingsPath() {
14990
- return join13(homedir18(), ".claude", "settings.json");
15471
+ return join15(homedir19(), ".claude", "settings.json");
14991
15472
  }
14992
15473
  function persistedSyncPath(stored) {
14993
15474
  if (!stored || stored === "off")
@@ -14995,7 +15476,7 @@ function persistedSyncPath(stored) {
14995
15476
  if (stored === "global")
14996
15477
  return globalSettingsPath();
14997
15478
  if (stored.startsWith("repo:"))
14998
- return join13(resolve7(stored.slice(5)), ".claude", "settings.json");
15479
+ return join15(resolve9(stored.slice(5)), ".claude", "settings.json");
14999
15480
  return stored;
15000
15481
  }
15001
15482
  async function ensureGlobalKobeHooks() {
@@ -16544,7 +17025,11 @@ class RemoteOrchestrator {
16544
17025
  });
16545
17026
  }
16546
17027
  async createTask(input) {
16547
- const res = await this.client.request("task.create", input);
17028
+ const { modelEffort, ...rest } = input;
17029
+ const res = await this.client.request("task.create", {
17030
+ ...rest,
17031
+ effort: modelEffort
17032
+ });
16548
17033
  return deserializeTask(res.task);
16549
17034
  }
16550
17035
  async ensureMainTask(repo) {
@@ -16717,6 +17202,7 @@ function deserializeTask(s) {
16717
17202
  pinned: s.pinned,
16718
17203
  vendor: s.vendor,
16719
17204
  prStatus: s.prStatus,
17205
+ modelEffort: s.modelEffort,
16720
17206
  createdAt: s.createdAt,
16721
17207
  updatedAt: s.updatedAt
16722
17208
  };
@@ -17043,7 +17529,7 @@ function addTheme2(name, theme) {
17043
17529
  }
17044
17530
  function resolveTheme(theme, mode = "dark") {
17045
17531
  const defs = theme.defs ?? {};
17046
- function resolve8(c, chain = []) {
17532
+ function resolve10(c, chain = []) {
17047
17533
  if (typeof c === "string") {
17048
17534
  if (c === "transparent" || c === "none")
17049
17535
  return RGBA.fromInts(0, 0, 0, 0);
@@ -17055,13 +17541,13 @@ function resolveTheme(theme, mode = "dark") {
17055
17541
  const next = defs[c] ?? theme.theme[c];
17056
17542
  if (next === undefined)
17057
17543
  return RGBA.fromInts(0, 0, 0);
17058
- return resolve8(next, [...chain, c]);
17544
+ return resolve10(next, [...chain, c]);
17059
17545
  }
17060
- return resolve8(c[mode], chain);
17546
+ return resolve10(c[mode], chain);
17061
17547
  }
17062
17548
  const out = {};
17063
17549
  for (const [k, v] of Object.entries(theme.theme)) {
17064
- out[k] = resolve8(v);
17550
+ out[k] = resolve10(v);
17065
17551
  }
17066
17552
  const text = out.text ?? RGBA.fromHex("#ffffff");
17067
17553
  const background = out.background ?? RGBA.fromHex("#000000");
@@ -17538,13 +18024,13 @@ function validateRepoPath(repo) {
17538
18024
  const trimmed = repo.trim();
17539
18025
  if (!trimmed)
17540
18026
  return "repo path is required";
17541
- let stat4;
18027
+ let stat5;
17542
18028
  try {
17543
- stat4 = fs3.statSync(trimmed);
18029
+ stat5 = fs3.statSync(trimmed);
17544
18030
  } catch {
17545
18031
  return `path does not exist: ${trimmed}`;
17546
18032
  }
17547
- if (!stat4.isDirectory())
18033
+ if (!stat5.isDirectory())
17548
18034
  return `not a directory: ${trimmed}`;
17549
18035
  try {
17550
18036
  const out = spawnSync10("git", ["rev-parse", "--git-dir"], {
@@ -17726,8 +18212,8 @@ function findAvailableFolderName(parentDir, base) {
17726
18212
  if (!parentExpanded)
17727
18213
  return base;
17728
18214
  try {
17729
- const stat4 = fs5.statSync(parentExpanded);
17730
- if (!stat4.isDirectory())
18215
+ const stat5 = fs5.statSync(parentExpanded);
18216
+ if (!stat5.isDirectory())
17731
18217
  return base;
17732
18218
  } catch {
17733
18219
  return base;
@@ -17742,7 +18228,7 @@ function findAvailableFolderName(parentDir, base) {
17742
18228
  return trimmed;
17743
18229
  }
17744
18230
  function cloneRepo(url, target, onProgress) {
17745
- return new Promise((resolve8) => {
18231
+ return new Promise((resolve10) => {
17746
18232
  let stderrBuf = "";
17747
18233
  try {
17748
18234
  const child = spawn4("git", ["clone", "--progress", url, target], {
@@ -17759,18 +18245,18 @@ function cloneRepo(url, target, onProgress) {
17759
18245
  }
17760
18246
  });
17761
18247
  child.on("error", (err) => {
17762
- resolve8({ ok: false, error: err.message });
18248
+ resolve10({ ok: false, error: err.message });
17763
18249
  });
17764
18250
  child.on("close", (code) => {
17765
18251
  if (code === 0) {
17766
- resolve8({ ok: true, path: target });
18252
+ resolve10({ ok: true, path: target });
17767
18253
  return;
17768
18254
  }
17769
18255
  const tail = stderrBuf.split(/[\r\n]+/).filter((s) => s.trim().length > 0).pop() ?? `git clone exited with ${code}`;
17770
- resolve8({ ok: false, error: tail });
18256
+ resolve10({ ok: false, error: tail });
17771
18257
  });
17772
18258
  } catch (err) {
17773
- resolve8({ ok: false, error: err instanceof Error ? err.message : String(err) });
18259
+ resolve10({ ok: false, error: err instanceof Error ? err.message : String(err) });
17774
18260
  }
17775
18261
  });
17776
18262
  }
@@ -17872,7 +18358,7 @@ function clampCursor(cursor, listLength) {
17872
18358
  return 0;
17873
18359
  return Math.max(0, Math.min(listLength - 1, cursor));
17874
18360
  }
17875
- function resolveBaseRef2(typed, filteredBranches, cursor) {
18361
+ function resolveBaseRef(typed, filteredBranches, cursor) {
17876
18362
  const picked = filteredBranches[cursor];
17877
18363
  if (picked)
17878
18364
  return picked;
@@ -18446,7 +18932,7 @@ function NewTaskDialogView(props) {
18446
18932
  setBaseRef(stripNewlines(v));
18447
18933
  });
18448
18934
  setProp(_el$28, "onSubmit", () => {
18449
- setBaseRef(resolveBaseRef2(baseRef(), branchFiltered(), branchCursor()));
18935
+ setBaseRef(resolveBaseRef(baseRef(), branchFiltered(), branchCursor()));
18450
18936
  setBaseRefTouched(true);
18451
18937
  setField("confirm");
18452
18938
  });
@@ -18960,7 +19446,7 @@ var init_dialog2 = __esm(() => {
18960
19446
 
18961
19447
  // src/tui/component/new-task-dialog/index.tsx
18962
19448
  function show(dialog, defaultRepo, savedRepos, options) {
18963
- return new Promise((resolve8) => {
19449
+ return new Promise((resolve10) => {
18964
19450
  dialog.replace(() => createComponent2(NewTaskDialogView, {
18965
19451
  defaultRepo,
18966
19452
  savedRepos,
@@ -18976,9 +19462,9 @@ function show(dialog, defaultRepo, savedRepos, options) {
18976
19462
  get discoverAdoptable() {
18977
19463
  return options?.discoverAdoptable;
18978
19464
  },
18979
- onSubmit: (v) => resolve8(v),
18980
- onCancel: () => resolve8(undefined)
18981
- }), () => resolve8(undefined));
19465
+ onSubmit: (v) => resolve10(v),
19466
+ onCancel: () => resolve10(undefined)
19467
+ }), () => resolve10(undefined));
18982
19468
  dialog.setSize("medium");
18983
19469
  });
18984
19470
  }
@@ -19147,11 +19633,11 @@ function QuickTaskComposerView(props) {
19147
19633
  })();
19148
19634
  }
19149
19635
  function show2(dialog, opts) {
19150
- return new Promise((resolve8) => {
19636
+ return new Promise((resolve10) => {
19151
19637
  dialog.replace(() => createComponent2(QuickTaskComposerView, mergeProps3(opts, {
19152
- onSubmit: (r) => resolve8(r),
19153
- onCancel: () => resolve8(undefined)
19154
- })), () => resolve8(undefined));
19638
+ onSubmit: (r) => resolve10(r),
19639
+ onCancel: () => resolve10(undefined)
19640
+ })), () => resolve10(undefined));
19155
19641
  dialog.setSize("medium");
19156
19642
  });
19157
19643
  }
@@ -19922,7 +20408,7 @@ var init_pulse = () => {};
19922
20408
  // src/tui/lib/sound.ts
19923
20409
  import { existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
19924
20410
  import { tmpdir as tmpdir2 } from "os";
19925
- import { basename as basename6, isAbsolute as isAbsolute2, join as join15, resolve as resolve8 } from "path";
20411
+ import { basename as basename6, isAbsolute as isAbsolute3, join as join17, resolve as resolve10 } from "path";
19926
20412
  function args(player, file, volume) {
19927
20413
  if (player === "ffplay")
19928
20414
  return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
@@ -19945,13 +20431,13 @@ function pickPlayer() {
19945
20431
  return cachedPlayer;
19946
20432
  const path12 = process.env.PATH ?? "";
19947
20433
  const segments = path12.split(":").filter(Boolean);
19948
- cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join15(dir, p)))) ?? null;
20434
+ cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join17(dir, p)))) ?? null;
19949
20435
  return cachedPlayer;
19950
20436
  }
19951
20437
  async function ensureAsset() {
19952
20438
  cachedPath ??= (async () => {
19953
20439
  mkdirSync6(DIR, { recursive: true });
19954
- const dest = join15(DIR, basename6(pulseAsset));
20440
+ const dest = join17(DIR, basename6(pulseAsset));
19955
20441
  const out = Bun.file(dest);
19956
20442
  if (await out.exists())
19957
20443
  return dest;
@@ -19980,8 +20466,8 @@ function pulse(volume = 0.4) {
19980
20466
  var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
19981
20467
  var init_sound = __esm(() => {
19982
20468
  init_pulse();
19983
- pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
19984
- DIR = join15(tmpdir2(), "kobe-sfx");
20469
+ pulseAsset = isAbsolute3(pulse_default) ? pulse_default : resolve10(import.meta.dir, pulse_default);
20470
+ DIR = join17(tmpdir2(), "kobe-sfx");
19985
20471
  PLAYERS = [
19986
20472
  "ffplay",
19987
20473
  "mpv",
@@ -20999,7 +21485,7 @@ var init_dialog3 = __esm(() => {
20999
21485
 
21000
21486
  // src/tui/component/rename-task-dialog/index.tsx
21001
21487
  function show3(dialog, currentTitle, opts = {}) {
21002
- return new Promise((resolve9) => {
21488
+ return new Promise((resolve11) => {
21003
21489
  dialog.replace(() => createComponent2(RenameTaskDialogView, {
21004
21490
  currentTitle,
21005
21491
  get dialogTitle() {
@@ -21017,9 +21503,9 @@ function show3(dialog, currentTitle, opts = {}) {
21017
21503
  get allowEmpty() {
21018
21504
  return opts.allowEmpty;
21019
21505
  },
21020
- onSubmit: (v) => resolve9(v),
21021
- onCancel: () => resolve9(undefined)
21022
- }), () => resolve9(undefined));
21506
+ onSubmit: (v) => resolve11(v),
21507
+ onCancel: () => resolve11(undefined)
21508
+ }), () => resolve11(undefined));
21023
21509
  });
21024
21510
  }
21025
21511
  var RenameTaskDialog;
@@ -21031,22 +21517,6 @@ var init_rename_task_dialog = __esm(() => {
21031
21517
  };
21032
21518
  });
21033
21519
 
21034
- // src/tui/lib/editor-prefs.ts
21035
- function normalizeEditorKind(value) {
21036
- return EDITOR_KINDS.includes(value) ? value : DEFAULT_EDITOR_KIND;
21037
- }
21038
- var EDITOR_KINDS, AUTO_EDITOR_CANDIDATES, EDITOR_KIND_KEY = "editor.kind", EDITOR_CUSTOM_KEY = "editor.customCommand", DEFAULT_EDITOR_KIND = "auto";
21039
- var init_editor_prefs = __esm(() => {
21040
- EDITOR_KINDS = ["auto", "vim", "nvim", "nano", "emacs", "custom"];
21041
- AUTO_EDITOR_CANDIDATES = ["nvim", "vim", "emacs", "nano"];
21042
- });
21043
-
21044
- // src/tui/lib/settings-surface.ts
21045
- function normalizeSettingsSurface(value) {
21046
- return value === "taskpanel" ? "taskpanel" : "chattab";
21047
- }
21048
- var SETTINGS_SURFACE_KEY = "settings.surface", DEFAULT_SETTINGS_SURFACE = "chattab";
21049
-
21050
21520
  // src/tui/ui/dialog-confirm.tsx
21051
21521
  import { TextAttributes as TextAttributes6 } from "@opentui/core";
21052
21522
  function titlecase(s) {
@@ -21157,18 +21627,18 @@ var init_dialog_confirm = __esm(() => {
21157
21627
  init_keymap();
21158
21628
  init_dialog();
21159
21629
  DialogConfirm.show = (dialog, title, message, label, confirmLabel, options) => {
21160
- return new Promise((resolve9) => {
21630
+ return new Promise((resolve11) => {
21161
21631
  dialog.replace(() => createComponent2(DialogConfirm, {
21162
21632
  title,
21163
21633
  message,
21164
- onConfirm: () => resolve9(true),
21165
- onCancel: () => resolve9(false),
21634
+ onConfirm: () => resolve11(true),
21635
+ onCancel: () => resolve11(false),
21166
21636
  label,
21167
21637
  confirmLabel,
21168
21638
  get initialActive() {
21169
21639
  return options?.initialActive;
21170
21640
  }
21171
- }), () => resolve9(undefined));
21641
+ }), () => resolve11(undefined));
21172
21642
  dialog.setSize("small");
21173
21643
  });
21174
21644
  };
@@ -21176,7 +21646,7 @@ var init_dialog_confirm = __esm(() => {
21176
21646
 
21177
21647
  // src/tui/component/settings-dialog/actions.ts
21178
21648
  import { unlinkSync as unlinkSync2 } from "fs";
21179
- import { join as join16 } from "path";
21649
+ import { join as join18 } from "path";
21180
21650
  function hasRestartableDaemon(orchestrator) {
21181
21651
  return orchestrator instanceof RemoteOrchestrator;
21182
21652
  }
@@ -21193,7 +21663,7 @@ async function confirmResetState(dialog, kv, renderer) {
21193
21663
  return;
21194
21664
  kv.clear();
21195
21665
  try {
21196
- unlinkSync2(join16(homeDir(), ".kobe", "tasks.json"));
21666
+ unlinkSync2(join18(homeDir(), ".kobe", "tasks.json"));
21197
21667
  } catch (err) {
21198
21668
  if (err.code !== "ENOENT") {
21199
21669
  console.error("kobe: failed to delete tasks.json during reset:", err);
@@ -22507,7 +22977,7 @@ var init_sections = __esm(() => {
22507
22977
 
22508
22978
  // src/tui/component/settings-dialog.tsx
22509
22979
  import { TextAttributes as TextAttributes8 } from "@opentui/core";
22510
- function humanizeSlug(id) {
22980
+ function humanizeSlug2(id) {
22511
22981
  return id.split(/[-_]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
22512
22982
  }
22513
22983
  function SettingsDialog(props) {
@@ -22622,7 +23092,7 @@ function SettingsDialog(props) {
22622
23092
  const v = props.kv.get(engineCommandKey(vendor), "");
22623
23093
  return typeof v === "string" ? v.trim() : "";
22624
23094
  }
22625
- function engineCommandText(vendor) {
23095
+ function engineCommandText2(vendor) {
22626
23096
  return engineOverride(vendor) || defaultEngineCommand(vendor).join(" ");
22627
23097
  }
22628
23098
  function engineIsDefault(vendor) {
@@ -22648,7 +23118,7 @@ function SettingsDialog(props) {
22648
23118
  setDefaultEngineSig(vendor);
22649
23119
  }
22650
23120
  async function editEngine(vendor) {
22651
- const next = await RenameTaskDialog.show(dialog, engineCommandText(vendor), {
23121
+ const next = await RenameTaskDialog.show(dialog, engineCommandText2(vendor), {
22652
23122
  dialogTitle: `${engineName(vendor)} launch command`,
22653
23123
  fieldLabel: "command",
22654
23124
  submitLabel: "save",
@@ -22707,7 +23177,7 @@ function SettingsDialog(props) {
22707
23177
  if (command.trim())
22708
23178
  props.kv.set(engineCommandKey(id), command.trim());
22709
23179
  const typedName = name?.trim() ?? "";
22710
- props.kv.set(engineNameKey(id), typedName && typedName !== id ? typedName : humanizeSlug(id));
23180
+ props.kv.set(engineNameKey(id), typedName && typedName !== id ? typedName : humanizeSlug2(id));
22711
23181
  }
22712
23182
  function currentEngineRow() {
22713
23183
  if (section() !== "engines" || level() !== "body")
@@ -22973,7 +23443,7 @@ function SettingsDialog(props) {
22973
23443
  },
22974
23444
  isCustom: (v) => !isBuiltinVendor(v),
22975
23445
  displayName: engineName,
22976
- commandText: engineCommandText,
23446
+ commandText: engineCommandText2,
22977
23447
  isDefault: engineIsDefault,
22978
23448
  isDefaultEngine,
22979
23449
  editEngine: (v) => void editEngine(v),
@@ -23090,17 +23560,17 @@ var init_settings_dialog = __esm(() => {
23090
23560
  init_sections();
23091
23561
  SettingsDialog.show = (dialog, kv, orchestrator) => {
23092
23562
  let visualPrefsChanged = false;
23093
- return new Promise((resolve9) => {
23563
+ return new Promise((resolve11) => {
23094
23564
  dialog.replace(() => createComponent2(SettingsDialog, {
23095
23565
  kv,
23096
23566
  orchestrator,
23097
23567
  onVisualPrefsChange: () => {
23098
23568
  visualPrefsChanged = true;
23099
23569
  },
23100
- onClose: () => resolve9({
23570
+ onClose: () => resolve11({
23101
23571
  visualPrefsChanged
23102
23572
  })
23103
- }), () => resolve9({
23573
+ }), () => resolve11({
23104
23574
  visualPrefsChanged
23105
23575
  }));
23106
23576
  });
@@ -23502,15 +23972,15 @@ var init_task_actions = __esm(() => {
23502
23972
  // src/tui/lib/worktree-opener.ts
23503
23973
  import { spawn as spawn5 } from "child_process";
23504
23974
  import { existsSync as existsSync15 } from "fs";
23505
- import { basename as basename7, delimiter, isAbsolute as isAbsolute3, join as join17 } from "path";
23975
+ import { basename as basename7, delimiter, isAbsolute as isAbsolute4, join as join19 } from "path";
23506
23976
  function executableOnPath(command, env, exists) {
23507
- if (isAbsolute3(command))
23977
+ if (isAbsolute4(command))
23508
23978
  return exists(command);
23509
23979
  const pathEnv = env.PATH ?? "";
23510
23980
  for (const dir of pathEnv.split(delimiter)) {
23511
23981
  if (!dir)
23512
23982
  continue;
23513
- if (exists(join17(dir, command)))
23983
+ if (exists(join19(dir, command)))
23514
23984
  return true;
23515
23985
  }
23516
23986
  return false;
@@ -23643,11 +24113,11 @@ var init_background_poll = __esm(() => {
23643
24113
  });
23644
24114
 
23645
24115
  // src/tui/panes/sidebar/git-head.ts
23646
- import { stat as stat4 } from "fs/promises";
23647
- import { join as join18 } from "path";
24116
+ import { stat as stat5 } from "fs/promises";
24117
+ import { join as join20 } from "path";
23648
24118
  async function headFingerprint(repo) {
23649
24119
  try {
23650
- const st = await stat4(join18(repo, ".git", "HEAD"));
24120
+ const st = await stat5(join20(repo, ".git", "HEAD"));
23651
24121
  return `${st.mtimeMs}:${st.size}`;
23652
24122
  } catch {
23653
24123
  return null;
@@ -24130,6 +24600,28 @@ function Sidebar(props) {
24130
24600
  const dims = useTerminalDimensions();
24131
24601
  const [hover, setHover] = createSignal(null);
24132
24602
  const [cursorIndex, setCursorIndex] = createSignal(-1);
24603
+ let scrollRef;
24604
+ let outerBoxRef;
24605
+ const rowEls = new Map;
24606
+ createEffect(() => {
24607
+ const w = props.width ? props.width() : SIDEBAR_WIDTH;
24608
+ const el = outerBoxRef;
24609
+ if (!el)
24610
+ return;
24611
+ el.width = w;
24612
+ el.flexShrink = 1;
24613
+ el.minHeight = 0;
24614
+ });
24615
+ createEffect(on([cursorIndex, rows], ([i]) => {
24616
+ if (!scrollRef)
24617
+ return;
24618
+ if (scrollRef.viewport.height <= 0)
24619
+ return;
24620
+ const el = rowEls.get(i);
24621
+ if (!el)
24622
+ return;
24623
+ scrollRef.scrollChildIntoView(el.id);
24624
+ }));
24133
24625
  createEffect(on(() => [props.selectedId(), flatIds()], ([id, ids]) => {
24134
24626
  const cur = untrack(cursorIndex);
24135
24627
  if (id === null) {
@@ -24242,7 +24734,11 @@ function Sidebar(props) {
24242
24734
  insertNode(_el$5, _el$6);
24243
24735
  insertNode(_el$5, _el$22);
24244
24736
  insertNode(_el$5, _el$28);
24245
- setProp(_el$5, "flexShrink", 0);
24737
+ use((r) => {
24738
+ outerBoxRef = r;
24739
+ }, _el$5);
24740
+ setProp(_el$5, "flexGrow", 1);
24741
+ setProp(_el$5, "minHeight", 0);
24246
24742
  setProp(_el$5, "flexDirection", "column");
24247
24743
  setProp(_el$5, "paddingTop", 1);
24248
24744
  setProp(_el$5, "paddingBottom", 1);
@@ -24270,9 +24766,9 @@ function Sidebar(props) {
24270
24766
  setProp(_el$35, "onMouseUp", () => props.onHeaderStatusClick?.());
24271
24767
  insert(_el$35, () => status().label);
24272
24768
  effect((_p$) => {
24273
- var _v$20 = status().emphasize ? theme.warning : theme.textMuted, _v$21 = status().emphasize ? TextAttributes11.BOLD : TextAttributes11.DIM;
24274
- _v$20 !== _p$.e && (_p$.e = setProp(_el$35, "fg", _v$20, _p$.e));
24275
- _v$21 !== _p$.t && (_p$.t = setProp(_el$35, "attributes", _v$21, _p$.t));
24769
+ var _v$19 = status().emphasize ? theme.warning : theme.textMuted, _v$20 = status().emphasize ? TextAttributes11.BOLD : TextAttributes11.DIM;
24770
+ _v$19 !== _p$.e && (_p$.e = setProp(_el$35, "fg", _v$19, _p$.e));
24771
+ _v$20 !== _p$.t && (_p$.t = setProp(_el$35, "attributes", _v$20, _p$.t));
24276
24772
  return _p$;
24277
24773
  }, {
24278
24774
  e: undefined,
@@ -24384,9 +24880,9 @@ function Sidebar(props) {
24384
24880
  setProp(_el$36, "onMouseUp", () => setView(tab.view));
24385
24881
  insert(_el$36, () => tab.label);
24386
24882
  effect((_p$) => {
24387
- var _v$22 = active() ? theme.primary : theme.textMuted, _v$23 = active() ? TextAttributes11.BOLD : undefined;
24388
- _v$22 !== _p$.e && (_p$.e = setProp(_el$36, "fg", _v$22, _p$.e));
24389
- _v$23 !== _p$.t && (_p$.t = setProp(_el$36, "attributes", _v$23, _p$.t));
24883
+ var _v$21 = active() ? theme.primary : theme.textMuted, _v$22 = active() ? TextAttributes11.BOLD : undefined;
24884
+ _v$21 !== _p$.e && (_p$.e = setProp(_el$36, "fg", _v$21, _p$.e));
24885
+ _v$22 !== _p$.t && (_p$.t = setProp(_el$36, "attributes", _v$22, _p$.t));
24390
24886
  return _p$;
24391
24887
  }, {
24392
24888
  e: undefined,
@@ -24420,7 +24916,11 @@ function Sidebar(props) {
24420
24916
  }
24421
24917
  }), null);
24422
24918
  insertNode(_el$28, _el$29);
24919
+ use((r) => {
24920
+ scrollRef = r;
24921
+ }, _el$28);
24423
24922
  setProp(_el$28, "flexGrow", 1);
24923
+ setProp(_el$28, "minHeight", 0);
24424
24924
  setProp(_el$28, "verticalScrollbarOptions", {
24425
24925
  trackOptions: {
24426
24926
  foregroundColor: "transparent"
@@ -24529,6 +25029,13 @@ function Sidebar(props) {
24529
25029
  });
24530
25030
  }
24531
25031
  }), _el$38);
25032
+ use((r) => {
25033
+ rowEls.set(flatIndex, r);
25034
+ onCleanup(() => {
25035
+ if (rowEls.get(flatIndex) === r)
25036
+ rowEls.delete(flatIndex);
25037
+ });
25038
+ }, _el$38);
24532
25039
  setProp(_el$38, "flexDirection", "column");
24533
25040
  setProp(_el$38, "gap", 0);
24534
25041
  setProp(_el$38, "onMouseUp", () => {
@@ -24565,12 +25072,12 @@ function Sidebar(props) {
24565
25072
  setProp(_el$43, "flexGrow", 1);
24566
25073
  insert(_el$43, () => spacedTitle(rowView().titleText, titleBudget()));
24567
25074
  effect((_p$) => {
24568
- var _v$24 = barColor(), _v$25 = stateColor(), _v$26 = TextAttributes11.BOLD, _v$27 = theme.text, _v$28 = TextAttributes11.BOLD;
24569
- _v$24 !== _p$.e && (_p$.e = setProp(_el$40, "fg", _v$24, _p$.e));
24570
- _v$25 !== _p$.t && (_p$.t = setProp(_el$42, "fg", _v$25, _p$.t));
24571
- _v$26 !== _p$.a && (_p$.a = setProp(_el$42, "attributes", _v$26, _p$.a));
24572
- _v$27 !== _p$.o && (_p$.o = setProp(_el$43, "fg", _v$27, _p$.o));
24573
- _v$28 !== _p$.i && (_p$.i = setProp(_el$43, "attributes", _v$28, _p$.i));
25075
+ var _v$23 = barColor(), _v$24 = stateColor(), _v$25 = TextAttributes11.BOLD, _v$26 = theme.text, _v$27 = TextAttributes11.BOLD;
25076
+ _v$23 !== _p$.e && (_p$.e = setProp(_el$40, "fg", _v$23, _p$.e));
25077
+ _v$24 !== _p$.t && (_p$.t = setProp(_el$42, "fg", _v$24, _p$.t));
25078
+ _v$25 !== _p$.a && (_p$.a = setProp(_el$42, "attributes", _v$25, _p$.a));
25079
+ _v$26 !== _p$.o && (_p$.o = setProp(_el$43, "fg", _v$26, _p$.o));
25080
+ _v$27 !== _p$.i && (_p$.i = setProp(_el$43, "attributes", _v$27, _p$.i));
24574
25081
  return _p$;
24575
25082
  }, {
24576
25083
  e: undefined,
@@ -24624,10 +25131,10 @@ function Sidebar(props) {
24624
25131
  }
24625
25132
  }), null);
24626
25133
  effect((_p$) => {
24627
- var _v$29 = barColor(), _v$30 = theme.textMuted, _v$31 = TextAttributes11.DIM;
24628
- _v$29 !== _p$.e && (_p$.e = setProp(_el$45, "fg", _v$29, _p$.e));
24629
- _v$30 !== _p$.t && (_p$.t = setProp(_el$47, "fg", _v$30, _p$.t));
24630
- _v$31 !== _p$.a && (_p$.a = setProp(_el$47, "attributes", _v$31, _p$.a));
25134
+ var _v$28 = barColor(), _v$29 = theme.textMuted, _v$30 = TextAttributes11.DIM;
25135
+ _v$28 !== _p$.e && (_p$.e = setProp(_el$45, "fg", _v$28, _p$.e));
25136
+ _v$29 !== _p$.t && (_p$.t = setProp(_el$47, "fg", _v$29, _p$.t));
25137
+ _v$30 !== _p$.a && (_p$.a = setProp(_el$47, "attributes", _v$30, _p$.a));
24631
25138
  return _p$;
24632
25139
  }, {
24633
25140
  e: undefined,
@@ -24673,12 +25180,12 @@ function Sidebar(props) {
24673
25180
  }
24674
25181
  }), null);
24675
25182
  effect((_p$) => {
24676
- var _v$32 = barColor(), _v$33 = stateColor(), _v$34 = TextAttributes11.BOLD, _v$35 = theme.text, _v$36 = isSelected() || isCursor() ? TextAttributes11.BOLD : undefined;
24677
- _v$32 !== _p$.e && (_p$.e = setProp(_el$53, "fg", _v$32, _p$.e));
24678
- _v$33 !== _p$.t && (_p$.t = setProp(_el$55, "fg", _v$33, _p$.t));
24679
- _v$34 !== _p$.a && (_p$.a = setProp(_el$55, "attributes", _v$34, _p$.a));
24680
- _v$35 !== _p$.o && (_p$.o = setProp(_el$56, "fg", _v$35, _p$.o));
24681
- _v$36 !== _p$.i && (_p$.i = setProp(_el$56, "attributes", _v$36, _p$.i));
25183
+ var _v$31 = barColor(), _v$32 = stateColor(), _v$33 = TextAttributes11.BOLD, _v$34 = theme.text, _v$35 = isSelected() || isCursor() ? TextAttributes11.BOLD : undefined;
25184
+ _v$31 !== _p$.e && (_p$.e = setProp(_el$53, "fg", _v$31, _p$.e));
25185
+ _v$32 !== _p$.t && (_p$.t = setProp(_el$55, "fg", _v$32, _p$.t));
25186
+ _v$33 !== _p$.a && (_p$.a = setProp(_el$55, "attributes", _v$33, _p$.a));
25187
+ _v$34 !== _p$.o && (_p$.o = setProp(_el$56, "fg", _v$34, _p$.o));
25188
+ _v$35 !== _p$.i && (_p$.i = setProp(_el$56, "attributes", _v$35, _p$.i));
24682
25189
  return _p$;
24683
25190
  }, {
24684
25191
  e: undefined,
@@ -24744,10 +25251,10 @@ function Sidebar(props) {
24744
25251
  }
24745
25252
  }), null);
24746
25253
  effect((_p$) => {
24747
- var _v$37 = barColor(), _v$38 = theme.textMuted, _v$39 = TextAttributes11.DIM;
24748
- _v$37 !== _p$.e && (_p$.e = setProp(_el$60, "fg", _v$37, _p$.e));
24749
- _v$38 !== _p$.t && (_p$.t = setProp(_el$62, "fg", _v$38, _p$.t));
24750
- _v$39 !== _p$.a && (_p$.a = setProp(_el$62, "attributes", _v$39, _p$.a));
25254
+ var _v$36 = barColor(), _v$37 = theme.textMuted, _v$38 = TextAttributes11.DIM;
25255
+ _v$36 !== _p$.e && (_p$.e = setProp(_el$60, "fg", _v$36, _p$.e));
25256
+ _v$37 !== _p$.t && (_p$.t = setProp(_el$62, "fg", _v$37, _p$.t));
25257
+ _v$38 !== _p$.a && (_p$.a = setProp(_el$62, "attributes", _v$38, _p$.a));
24751
25258
  return _p$;
24752
25259
  }, {
24753
25260
  e: undefined,
@@ -24852,9 +25359,9 @@ function Sidebar(props) {
24852
25359
  return () => _c$2() ? truncatePathTail(l.text, innerW()) : truncateTitle(l.text, innerW());
24853
25360
  })());
24854
25361
  effect((_p$) => {
24855
- var _v$45 = l.dim ? theme.textMuted : theme.text, _v$46 = l.bold ? TextAttributes11.BOLD : l.dim ? TextAttributes11.DIM : undefined;
24856
- _v$45 !== _p$.e && (_p$.e = setProp(_el$70, "fg", _v$45, _p$.e));
24857
- _v$46 !== _p$.t && (_p$.t = setProp(_el$70, "attributes", _v$46, _p$.t));
25362
+ var _v$44 = l.dim ? theme.textMuted : theme.text, _v$45 = l.bold ? TextAttributes11.BOLD : l.dim ? TextAttributes11.DIM : undefined;
25363
+ _v$44 !== _p$.e && (_p$.e = setProp(_el$70, "fg", _v$44, _p$.e));
25364
+ _v$45 !== _p$.t && (_p$.t = setProp(_el$70, "attributes", _v$45, _p$.t));
24858
25365
  return _p$;
24859
25366
  }, {
24860
25367
  e: undefined,
@@ -24864,12 +25371,12 @@ function Sidebar(props) {
24864
25371
  })()
24865
25372
  }));
24866
25373
  effect((_p$) => {
24867
- var _v$40 = left(), _v$41 = top(), _v$42 = boxW(), _v$43 = theme.focusAccent, _v$44 = theme.backgroundElement;
24868
- _v$40 !== _p$.e && (_p$.e = setProp(_el$69, "left", _v$40, _p$.e));
24869
- _v$41 !== _p$.t && (_p$.t = setProp(_el$69, "top", _v$41, _p$.t));
24870
- _v$42 !== _p$.a && (_p$.a = setProp(_el$69, "width", _v$42, _p$.a));
24871
- _v$43 !== _p$.o && (_p$.o = setProp(_el$69, "borderColor", _v$43, _p$.o));
24872
- _v$44 !== _p$.i && (_p$.i = setProp(_el$69, "backgroundColor", _v$44, _p$.i));
25374
+ var _v$39 = left(), _v$40 = top(), _v$41 = boxW(), _v$42 = theme.focusAccent, _v$43 = theme.backgroundElement;
25375
+ _v$39 !== _p$.e && (_p$.e = setProp(_el$69, "left", _v$39, _p$.e));
25376
+ _v$40 !== _p$.t && (_p$.t = setProp(_el$69, "top", _v$40, _p$.t));
25377
+ _v$41 !== _p$.a && (_p$.a = setProp(_el$69, "width", _v$41, _p$.a));
25378
+ _v$42 !== _p$.o && (_p$.o = setProp(_el$69, "borderColor", _v$42, _p$.o));
25379
+ _v$43 !== _p$.i && (_p$.i = setProp(_el$69, "backgroundColor", _v$43, _p$.i));
24873
25380
  return _p$;
24874
25381
  }, {
24875
25382
  e: undefined,
@@ -24883,19 +25390,17 @@ function Sidebar(props) {
24883
25390
  }
24884
25391
  }), null);
24885
25392
  effect((_p$) => {
24886
- var _v$15 = props.width ? props.width() : SIDEBAR_WIDTH, _v$16 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$17 = TextAttributes11.BOLD, _v$18 = theme.textMuted, _v$19 = TextAttributes11.DIM;
24887
- _v$15 !== _p$.e && (_p$.e = setProp(_el$5, "width", _v$15, _p$.e));
24888
- _v$16 !== _p$.t && (_p$.t = setProp(_el$8, "fg", _v$16, _p$.t));
24889
- _v$17 !== _p$.a && (_p$.a = setProp(_el$8, "attributes", _v$17, _p$.a));
24890
- _v$18 !== _p$.o && (_p$.o = setProp(_el$24, "fg", _v$18, _p$.o));
24891
- _v$19 !== _p$.i && (_p$.i = setProp(_el$24, "attributes", _v$19, _p$.i));
25393
+ var _v$15 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$16 = TextAttributes11.BOLD, _v$17 = theme.textMuted, _v$18 = TextAttributes11.DIM;
25394
+ _v$15 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$15, _p$.e));
25395
+ _v$16 !== _p$.t && (_p$.t = setProp(_el$8, "attributes", _v$16, _p$.t));
25396
+ _v$17 !== _p$.a && (_p$.a = setProp(_el$24, "fg", _v$17, _p$.a));
25397
+ _v$18 !== _p$.o && (_p$.o = setProp(_el$24, "attributes", _v$18, _p$.o));
24892
25398
  return _p$;
24893
25399
  }, {
24894
25400
  e: undefined,
24895
25401
  t: undefined,
24896
25402
  a: undefined,
24897
- o: undefined,
24898
- i: undefined
25403
+ o: undefined
24899
25404
  });
24900
25405
  return _el$5;
24901
25406
  })();
@@ -24911,6 +25416,7 @@ var init_Sidebar = __esm(() => {
24911
25416
  init_solid();
24912
25417
  init_solid();
24913
25418
  init_solid();
25419
+ init_solid();
24914
25420
  init_dev();
24915
25421
  init_theme2();
24916
25422
  init_theme2();
@@ -24937,7 +25443,7 @@ __export(exports_host3, {
24937
25443
  legendCap: () => legendCap
24938
25444
  });
24939
25445
  import { existsSync as existsSync16 } from "fs";
24940
- import { stat as stat5 } from "fs/promises";
25446
+ import { stat as stat6 } from "fs/promises";
24941
25447
  import { TextAttributes as TextAttributes12 } from "@opentui/core";
24942
25448
  function worktreeCwdUsable(cwd) {
24943
25449
  return !!cwd && worktreeUsable(cwd);
@@ -25373,52 +25879,6 @@ function ShortcutHints(props) {
25373
25879
  label: "move panes"
25374
25880
  });
25375
25881
  }
25376
- const prev = b["tmux.tab.prev"];
25377
- const next = b["tmux.tab.next"];
25378
- if (prev?.chord === "ctrl+[" && next?.chord === "ctrl+]") {
25379
- out.push({
25380
- k: "ctrl+[/]",
25381
- label: "switch tabs"
25382
- });
25383
- } else {
25384
- if (prev)
25385
- out.push({
25386
- k: prev.chord,
25387
- label: "prev tab"
25388
- });
25389
- if (next)
25390
- out.push({
25391
- k: next.chord,
25392
- label: "next tab"
25393
- });
25394
- }
25395
- if (b["tmux.tab.new"])
25396
- out.push({
25397
- k: b["tmux.tab.new"].chord,
25398
- label: "new tab"
25399
- });
25400
- if (b["tmux.tab.chooseEngine"])
25401
- out.push({
25402
- k: b["tmux.tab.chooseEngine"].chord,
25403
- label: "engine tab"
25404
- });
25405
- out.push({
25406
- k: "prefix t",
25407
- label: "engine tab"
25408
- }, {
25409
- k: "prefix f",
25410
- label: "new task"
25411
- });
25412
- if (b["tmux.tab.rename"])
25413
- out.push({
25414
- k: b["tmux.tab.rename"].chord,
25415
- label: "rename tab"
25416
- });
25417
- if (b["tmux.tab.close"])
25418
- out.push({
25419
- k: b["tmux.tab.close"].chord,
25420
- label: "close tab"
25421
- });
25422
25882
  if (b["tmux.detach"])
25423
25883
  out.push({
25424
25884
  k: b["tmux.detach"].chord,
@@ -25430,12 +25890,8 @@ function ShortcutHints(props) {
25430
25890
  keymapVersion();
25431
25891
  const rows = [
25432
25892
  {
25433
- ids: ["sidebar.select"],
25434
- label: "open"
25435
- },
25436
- {
25437
- ids: ["tasks.focusEngine"],
25438
- label: "focus engine"
25893
+ ids: ["help.open"],
25894
+ label: "full help"
25439
25895
  },
25440
25896
  {
25441
25897
  ids: ["task.new"],
@@ -25446,34 +25902,24 @@ function ShortcutHints(props) {
25446
25902
  label: "settings"
25447
25903
  },
25448
25904
  {
25449
- ids: ["tasks.openWorktree"],
25450
- label: "open wt"
25451
- },
25452
- {
25453
- ids: ["sidebar.view"],
25454
- label: "views"
25455
- },
25456
- {
25457
- ids: ["sidebar.sort"],
25458
- label: "sort"
25905
+ ids: ["sidebar.select"],
25906
+ label: "open"
25459
25907
  },
25460
25908
  {
25461
- ids: ["sidebar.localMerge"],
25462
- label: "move task",
25463
- dimWhenMain: true
25909
+ ids: ["tasks.focusEngine"],
25910
+ label: "focus engine"
25464
25911
  },
25465
25912
  {
25466
- ids: ["sidebar.archive", "sidebar.delete"],
25467
- label: "un/archive\xB7delete"
25913
+ ids: ["tasks.openWorktree"],
25914
+ label: "open wt"
25468
25915
  },
25469
25916
  {
25470
- ids: ["sidebar.rename", "tasks.renameBranch", "tasks.cycleEngine"],
25471
- label: "name/branch/engine",
25472
- dimWhenMain: true
25917
+ ids: ["sidebar.delete"],
25918
+ label: "delete"
25473
25919
  },
25474
25920
  {
25475
- ids: ["help.open"],
25476
- label: "help"
25921
+ ids: ["sidebar.view"],
25922
+ label: "views"
25477
25923
  }
25478
25924
  ];
25479
25925
  const out = [];
@@ -25617,7 +26063,7 @@ async function setupTasksPane(opts) {
25617
26063
  (async () => {
25618
26064
  let fingerprint = "missing";
25619
26065
  try {
25620
- const st = await stat5(store2.filePath);
26066
+ const st = await stat6(store2.filePath);
25621
26067
  fingerprint = `${st.mtimeMs}:${st.size}`;
25622
26068
  } catch {}
25623
26069
  if (fingerprint === lastTasksFileFingerprint)
@@ -25889,18 +26335,18 @@ function openExternalUrl(url) {
25889
26335
  function releaseBodyLines(body) {
25890
26336
  return body.replace(/\r\n/g, `
25891
26337
  `).split(`
25892
- `).map((line) => line.trim()).filter(Boolean).slice(0, 40);
26338
+ `).map((line) => line.trim()).filter(Boolean);
25893
26339
  }
25894
26340
  function waitForKeypress() {
25895
26341
  if (!process.stdin.isTTY)
25896
26342
  return Promise.resolve();
25897
- return new Promise((resolve9) => {
26343
+ return new Promise((resolve11) => {
25898
26344
  const stdin = process.stdin;
25899
26345
  const done = () => {
25900
26346
  stdin.off("data", done);
25901
26347
  stdin.setRawMode?.(false);
25902
26348
  stdin.pause();
25903
- resolve9();
26349
+ resolve11();
25904
26350
  };
25905
26351
  stdin.setRawMode?.(true);
25906
26352
  stdin.resume();
@@ -25913,13 +26359,12 @@ function UpdatePage() {
25913
26359
  } = useTheme();
25914
26360
  const renderer = useRenderer();
25915
26361
  const [info, setInfo] = createSignal(null);
25916
- const [notes, setNotes] = createSignal(null);
26362
+ const [releaseNotes, setReleaseNotes] = createSignal([]);
25917
26363
  const [loadingNotes, setLoadingNotes] = createSignal(true);
25918
26364
  const [selected, setSelected] = createSignal("update");
25919
26365
  const [status, setStatus] = createSignal(null);
25920
26366
  const latest = createMemo(() => info()?.latest ?? CURRENT_VERSION);
25921
- const releaseUrl = createMemo(() => notes()?.url ?? releasePageUrl(latest()));
25922
- const lines = createMemo(() => releaseBodyLines(notes()?.body ?? ""));
26367
+ const releaseUrl = createMemo(() => releaseNotes()[0]?.url ?? releasePageUrl(latest()));
25923
26368
  const actions = createMemo(() => [{
25924
26369
  id: "update",
25925
26370
  key: "U",
@@ -25944,9 +26389,12 @@ function UpdatePage() {
25944
26389
  force: true
25945
26390
  });
25946
26391
  setInfo(next);
25947
- const version = next?.latest ?? CURRENT_VERSION;
25948
- const fetched = await fetchReleaseNotes(version);
25949
- setNotes(fetched);
26392
+ const latestVersion = next?.latest ?? CURRENT_VERSION;
26393
+ const fetched = await fetchReleaseNotesRange({
26394
+ current: CURRENT_VERSION,
26395
+ latest: latestVersion
26396
+ });
26397
+ setReleaseNotes(fetched);
25950
26398
  setLoadingNotes(false);
25951
26399
  }
25952
26400
  function move(delta) {
@@ -25966,7 +26414,7 @@ function UpdatePage() {
25966
26414
  }
25967
26415
  async function runUpdater() {
25968
26416
  setStatus("Leaving the TUI page and running the updater in this tmux window...");
25969
- await new Promise((resolve9) => setTimeout(resolve9, 30));
26417
+ await new Promise((resolve11) => setTimeout(resolve11, 30));
25970
26418
  renderer?.destroy();
25971
26419
  process.stdout.write(`
25972
26420
  kobe ${CURRENT_VERSION} -> latest
@@ -26025,12 +26473,12 @@ kobe update failed with exit code ${code}.
26025
26473
  }]
26026
26474
  }));
26027
26475
  return (() => {
26028
- var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$5 = createElement("text"), _el$7 = createElement("box"), _el$8 = createElement("text"), _el$0 = createElement("text"), _el$1 = createTextNode(`v`), _el$10 = createElement("text"), _el$12 = createElement("text"), _el$13 = createTextNode(`v`), _el$14 = createElement("box"), _el$16 = createElement("box"), _el$17 = createElement("text"), _el$19 = createElement("scrollbox"), _el$20 = createElement("box");
26476
+ var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$5 = createElement("text"), _el$7 = createElement("box"), _el$8 = createElement("text"), _el$0 = createElement("text"), _el$1 = createTextNode(`v`), _el$10 = createElement("text"), _el$12 = createElement("text"), _el$13 = createTextNode(`v`), _el$14 = createElement("box"), _el$16 = createElement("box"), _el$17 = createElement("text"), _el$18 = createTextNode(`\u2500\u2500 changes from v`), _el$19 = createTextNode(` to v`), _el$20 = createTextNode(` \u2500\u2500`), _el$21 = createElement("scrollbox"), _el$22 = createElement("box");
26029
26477
  insertNode(_el$, _el$2);
26030
26478
  insertNode(_el$, _el$7);
26031
26479
  insertNode(_el$, _el$14);
26032
26480
  insertNode(_el$, _el$16);
26033
- insertNode(_el$, _el$19);
26481
+ insertNode(_el$, _el$21);
26034
26482
  setProp(_el$, "flexDirection", "column");
26035
26483
  setProp(_el$, "flexGrow", 1);
26036
26484
  setProp(_el$, "paddingTop", 1);
@@ -26073,36 +26521,36 @@ kobe update failed with exit code ${code}.
26073
26521
  return actions();
26074
26522
  },
26075
26523
  children: (action) => (() => {
26076
- var _el$25 = createElement("box"), _el$26 = createElement("box"), _el$27 = createElement("text"), _el$28 = createTextNode(`[`), _el$29 = createTextNode(`]`), _el$30 = createElement("box"), _el$31 = createElement("text"), _el$32 = createElement("text");
26077
- insertNode(_el$25, _el$26);
26078
- insertNode(_el$25, _el$30);
26079
- insertNode(_el$25, _el$32);
26080
- setProp(_el$25, "flexDirection", "row");
26081
- setProp(_el$25, "gap", 1);
26082
- setProp(_el$25, "paddingLeft", 1);
26083
- setProp(_el$25, "paddingRight", 1);
26084
- setProp(_el$25, "onMouseUp", () => activate(action.id));
26085
- insertNode(_el$26, _el$27);
26086
- setProp(_el$26, "width", 4);
26087
- setProp(_el$26, "flexShrink", 0);
26524
+ var _el$27 = createElement("box"), _el$28 = createElement("box"), _el$29 = createElement("text"), _el$30 = createTextNode(`[`), _el$31 = createTextNode(`]`), _el$32 = createElement("box"), _el$33 = createElement("text"), _el$34 = createElement("text");
26088
26525
  insertNode(_el$27, _el$28);
26089
- insertNode(_el$27, _el$29);
26090
- setProp(_el$27, "wrapMode", "none");
26091
- insert(_el$27, () => action.key, _el$29);
26092
- insertNode(_el$30, _el$31);
26093
- setProp(_el$30, "width", 14);
26094
- setProp(_el$30, "flexShrink", 0);
26095
- setProp(_el$31, "wrapMode", "none");
26096
- insert(_el$31, () => action.label);
26097
- setProp(_el$32, "wrapMode", "word");
26098
- insert(_el$32, () => action.detail);
26526
+ insertNode(_el$27, _el$32);
26527
+ insertNode(_el$27, _el$34);
26528
+ setProp(_el$27, "flexDirection", "row");
26529
+ setProp(_el$27, "gap", 1);
26530
+ setProp(_el$27, "paddingLeft", 1);
26531
+ setProp(_el$27, "paddingRight", 1);
26532
+ setProp(_el$27, "onMouseUp", () => activate(action.id));
26533
+ insertNode(_el$28, _el$29);
26534
+ setProp(_el$28, "width", 4);
26535
+ setProp(_el$28, "flexShrink", 0);
26536
+ insertNode(_el$29, _el$30);
26537
+ insertNode(_el$29, _el$31);
26538
+ setProp(_el$29, "wrapMode", "none");
26539
+ insert(_el$29, () => action.key, _el$31);
26540
+ insertNode(_el$32, _el$33);
26541
+ setProp(_el$32, "width", 14);
26542
+ setProp(_el$32, "flexShrink", 0);
26543
+ setProp(_el$33, "wrapMode", "none");
26544
+ insert(_el$33, () => action.label);
26545
+ setProp(_el$34, "wrapMode", "word");
26546
+ insert(_el$34, () => action.detail);
26099
26547
  effect((_p$) => {
26100
26548
  var _v$12 = selected() === action.id ? theme.primary : undefined, _v$13 = selected() === action.id ? theme.selectedListItemText : theme.accent, _v$14 = TextAttributes13.BOLD, _v$15 = selected() === action.id ? theme.selectedListItemText : theme.text, _v$16 = selected() === action.id ? theme.selectedListItemText : theme.textMuted;
26101
- _v$12 !== _p$.e && (_p$.e = setProp(_el$25, "backgroundColor", _v$12, _p$.e));
26102
- _v$13 !== _p$.t && (_p$.t = setProp(_el$27, "fg", _v$13, _p$.t));
26103
- _v$14 !== _p$.a && (_p$.a = setProp(_el$27, "attributes", _v$14, _p$.a));
26104
- _v$15 !== _p$.o && (_p$.o = setProp(_el$31, "fg", _v$15, _p$.o));
26105
- _v$16 !== _p$.i && (_p$.i = setProp(_el$32, "fg", _v$16, _p$.i));
26549
+ _v$12 !== _p$.e && (_p$.e = setProp(_el$27, "backgroundColor", _v$12, _p$.e));
26550
+ _v$13 !== _p$.t && (_p$.t = setProp(_el$29, "fg", _v$13, _p$.t));
26551
+ _v$14 !== _p$.a && (_p$.a = setProp(_el$29, "attributes", _v$14, _p$.a));
26552
+ _v$15 !== _p$.o && (_p$.o = setProp(_el$33, "fg", _v$15, _p$.o));
26553
+ _v$16 !== _p$.i && (_p$.i = setProp(_el$34, "fg", _v$16, _p$.i));
26106
26554
  return _p$;
26107
26555
  }, {
26108
26556
  e: undefined,
@@ -26111,7 +26559,7 @@ kobe update failed with exit code ${code}.
26111
26559
  o: undefined,
26112
26560
  i: undefined
26113
26561
  });
26114
- return _el$25;
26562
+ return _el$27;
26115
26563
  })()
26116
26564
  }));
26117
26565
  insert(_el$, createComponent2(Show, {
@@ -26129,49 +26577,78 @@ kobe update failed with exit code ${code}.
26129
26577
  insertNode(_el$16, _el$17);
26130
26578
  setProp(_el$16, "flexShrink", 0);
26131
26579
  setProp(_el$16, "paddingTop", 1);
26132
- insertNode(_el$17, createTextNode(`\u2500\u2500 release notes \u2500\u2500`));
26580
+ insertNode(_el$17, _el$18);
26581
+ insertNode(_el$17, _el$19);
26582
+ insertNode(_el$17, _el$20);
26133
26583
  setProp(_el$17, "wrapMode", "none");
26134
- insertNode(_el$19, _el$20);
26135
- setProp(_el$19, "flexGrow", 1);
26136
- setProp(_el$19, "flexShrink", 1);
26137
- setProp(_el$19, "stickyScroll", false);
26138
- setProp(_el$20, "flexDirection", "column");
26139
- setProp(_el$20, "paddingRight", 1);
26140
- setProp(_el$20, "paddingBottom", 1);
26141
- setProp(_el$20, "gap", 0);
26142
- insert(_el$20, createComponent2(Show, {
26584
+ insert(_el$17, CURRENT_VERSION, _el$19);
26585
+ insert(_el$17, latest, _el$20);
26586
+ insertNode(_el$21, _el$22);
26587
+ setProp(_el$21, "flexGrow", 1);
26588
+ setProp(_el$21, "flexShrink", 1);
26589
+ setProp(_el$21, "stickyScroll", false);
26590
+ setProp(_el$22, "flexDirection", "column");
26591
+ setProp(_el$22, "paddingRight", 1);
26592
+ setProp(_el$22, "paddingBottom", 1);
26593
+ setProp(_el$22, "gap", 0);
26594
+ insert(_el$22, createComponent2(Show, {
26143
26595
  get when() {
26144
26596
  return loadingNotes();
26145
26597
  },
26146
26598
  get children() {
26147
- var _el$21 = createElement("text");
26148
- insertNode(_el$21, createTextNode(`Loading release notes...`));
26149
- effect((_$p) => setProp(_el$21, "fg", theme.textMuted, _$p));
26150
- return _el$21;
26599
+ var _el$23 = createElement("text");
26600
+ insertNode(_el$23, createTextNode(`Loading release notes...`));
26601
+ effect((_$p) => setProp(_el$23, "fg", theme.textMuted, _$p));
26602
+ return _el$23;
26151
26603
  }
26152
26604
  }), null);
26153
- insert(_el$20, createComponent2(Show, {
26605
+ insert(_el$22, createComponent2(Show, {
26154
26606
  get when() {
26155
- return memo2(() => !!!loadingNotes())() && lines().length === 0;
26607
+ return memo2(() => !!!loadingNotes())() && releaseNotes().length === 0;
26156
26608
  },
26157
26609
  get children() {
26158
- var _el$23 = createElement("text");
26159
- insertNode(_el$23, createTextNode(`Release notes are unavailable. Use Open release to view the GitHub release page.`));
26160
- setProp(_el$23, "wrapMode", "word");
26161
- effect((_$p) => setProp(_el$23, "fg", theme.textMuted, _$p));
26162
- return _el$23;
26610
+ var _el$25 = createElement("text");
26611
+ insertNode(_el$25, createTextNode(`Release notes are unavailable. Use Open release to view the GitHub release page.`));
26612
+ setProp(_el$25, "wrapMode", "word");
26613
+ effect((_$p) => setProp(_el$25, "fg", theme.textMuted, _$p));
26614
+ return _el$25;
26163
26615
  }
26164
26616
  }), null);
26165
- insert(_el$20, createComponent2(For, {
26617
+ insert(_el$22, createComponent2(For, {
26166
26618
  get each() {
26167
- return lines();
26168
- },
26169
- children: (line) => (() => {
26170
- var _el$33 = createElement("text");
26171
- setProp(_el$33, "wrapMode", "word");
26172
- insert(_el$33, line);
26173
- effect((_$p) => setProp(_el$33, "fg", theme.textMuted, _$p));
26174
- return _el$33;
26619
+ return releaseNotes();
26620
+ },
26621
+ children: (release) => (() => {
26622
+ var _el$35 = createElement("box"), _el$36 = createElement("text"), _el$37 = createTextNode(`v`);
26623
+ insertNode(_el$35, _el$36);
26624
+ setProp(_el$35, "flexDirection", "column");
26625
+ setProp(_el$35, "paddingBottom", 1);
26626
+ setProp(_el$35, "gap", 0);
26627
+ insertNode(_el$36, _el$37);
26628
+ setProp(_el$36, "wrapMode", "none");
26629
+ insert(_el$36, () => release.version, null);
26630
+ insert(_el$35, createComponent2(For, {
26631
+ get each() {
26632
+ return releaseBodyLines(release.body);
26633
+ },
26634
+ children: (line) => (() => {
26635
+ var _el$38 = createElement("text");
26636
+ setProp(_el$38, "wrapMode", "word");
26637
+ insert(_el$38, line);
26638
+ effect((_$p) => setProp(_el$38, "fg", theme.textMuted, _$p));
26639
+ return _el$38;
26640
+ })()
26641
+ }), null);
26642
+ effect((_p$) => {
26643
+ var _v$17 = theme.text, _v$18 = TextAttributes13.BOLD;
26644
+ _v$17 !== _p$.e && (_p$.e = setProp(_el$36, "fg", _v$17, _p$.e));
26645
+ _v$18 !== _p$.t && (_p$.t = setProp(_el$36, "attributes", _v$18, _p$.t));
26646
+ return _p$;
26647
+ }, {
26648
+ e: undefined,
26649
+ t: undefined
26650
+ });
26651
+ return _el$35;
26175
26652
  })()
26176
26653
  }), null);
26177
26654
  effect((_p$) => {
@@ -26193,7 +26670,7 @@ kobe update failed with exit code ${code}.
26193
26670
  _v$0 !== _p$.d && (_p$.d = setProp(_el$12, "attributes", _v$0, _p$.d));
26194
26671
  _v$1 !== _p$.l && (_p$.l = setProp(_el$17, "fg", _v$1, _p$.l));
26195
26672
  _v$10 !== _p$.u && (_p$.u = setProp(_el$17, "attributes", _v$10, _p$.u));
26196
- _v$11 !== _p$.c && (_p$.c = setProp(_el$19, "verticalScrollbarOptions", _v$11, _p$.c));
26673
+ _v$11 !== _p$.c && (_p$.c = setProp(_el$21, "verticalScrollbarOptions", _v$11, _p$.c));
26197
26674
  return _p$;
26198
26675
  }, {
26199
26676
  e: undefined,
@@ -26513,7 +26990,7 @@ var gitWrapper;
26513
26990
  var init_git2 = __esm(() => {
26514
26991
  gitWrapper = {
26515
26992
  spawn(args2, cwd) {
26516
- return new Promise((resolve9, reject) => {
26993
+ return new Promise((resolve11, reject) => {
26517
26994
  const child = nodeSpawn("git", [...args2], {
26518
26995
  cwd,
26519
26996
  shell: false,
@@ -26532,7 +27009,7 @@ var init_git2 = __esm(() => {
26532
27009
  });
26533
27010
  child.on("error", reject);
26534
27011
  child.on("close", (status, signal) => {
26535
- resolve9({ stdout, stderr, status, signal });
27012
+ resolve11({ stdout, stderr, status, signal });
26536
27013
  });
26537
27014
  });
26538
27015
  }
@@ -27471,7 +27948,7 @@ var init_filetree = __esm(() => {
27471
27948
  // src/tui/ops/pr-prompt.ts
27472
27949
  import { promises as fs6 } from "fs";
27473
27950
  import path12 from "path";
27474
- async function git2(cwd, args2) {
27951
+ async function git(cwd, args2) {
27475
27952
  const controller = new AbortController;
27476
27953
  const timer = setTimeout(() => controller.abort(), GIT_TIMEOUT_MS2);
27477
27954
  try {
@@ -27490,20 +27967,20 @@ async function git2(cwd, args2) {
27490
27967
  }
27491
27968
  }
27492
27969
  async function currentBranch2(cwd) {
27493
- return await git2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
27970
+ return await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
27494
27971
  }
27495
27972
  async function targetBranch(cwd) {
27496
- const out = await git2(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
27973
+ const out = await git(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
27497
27974
  if (!out)
27498
27975
  return "main";
27499
27976
  return out.startsWith("origin/") ? out.slice("origin/".length) : out;
27500
27977
  }
27501
27978
  async function hasUpstream(cwd) {
27502
- const out = await git2(cwd, ["rev-parse", "--abbrev-ref", "@{u}"]);
27979
+ const out = await git(cwd, ["rev-parse", "--abbrev-ref", "@{u}"]);
27503
27980
  return out !== null && out.length > 0;
27504
27981
  }
27505
27982
  async function dirtyCount(cwd) {
27506
- const out = await git2(cwd, ["status", "--porcelain"]);
27983
+ const out = await git(cwd, ["status", "--porcelain"]);
27507
27984
  if (!out)
27508
27985
  return 0;
27509
27986
  return out.split(`
@@ -27584,7 +28061,7 @@ __export(exports_host7, {
27584
28061
  startOpsHost: () => startOpsHost,
27585
28062
  nextActivityPollDelay: () => nextActivityPollDelay
27586
28063
  });
27587
- import { createHash as createHash5 } from "crypto";
28064
+ import { createHash as createHash6 } from "crypto";
27588
28065
  import { SyntaxStyle } from "@opentui/core";
27589
28066
  function nextActivityPollDelay(currentMs, idleStreak) {
27590
28067
  if (idleStreak < ACTIVITY_IDLE_RAMP_POLLS)
@@ -27757,7 +28234,7 @@ function basename8(p) {
27757
28234
  return i >= 0 ? p.slice(i + 1) : p;
27758
28235
  }
27759
28236
  function fingerprint(text) {
27760
- return createHash5("sha1").update(text).digest("hex");
28237
+ return createHash6("sha1").update(text).digest("hex");
27761
28238
  }
27762
28239
  async function startOpsHost(args2) {
27763
28240
  await bootPaneHost({
@@ -28025,7 +28502,7 @@ __export(exports_direct, {
28025
28502
  startDirectTmux: () => startDirectTmux,
28026
28503
  chooseInitialTask: () => chooseInitialTask
28027
28504
  });
28028
- import { resolve as resolve9 } from "path";
28505
+ import { resolve as resolve11 } from "path";
28029
28506
  function chooseInitialTask(tasks, choice = {}) {
28030
28507
  const byId = (id) => id ? tasks.find((t) => t.id === id) : undefined;
28031
28508
  const active = byId(choice.activeTaskId);
@@ -28061,7 +28538,7 @@ async function ensureRepos(orchestrator) {
28061
28538
  normalizeSavedRepos();
28062
28539
  let repos = [...getSavedRepos()];
28063
28540
  if (repos.length === 0) {
28064
- const added = addSavedRepo(resolve9(process.cwd()));
28541
+ const added = addSavedRepo(resolve11(process.cwd()));
28065
28542
  repos = [added.path];
28066
28543
  }
28067
28544
  for (const repo of repos) {
@@ -28071,7 +28548,7 @@ async function ensureRepos(orchestrator) {
28071
28548
  console.error(`[kobe] ensureMainTask failed for ${repo}:`, err);
28072
28549
  }
28073
28550
  }
28074
- return repos[0] ?? resolve9(process.cwd());
28551
+ return repos[0] ?? resolve11(process.cwd());
28075
28552
  }
28076
28553
  async function startDirectTmux() {
28077
28554
  setClientLogContext("gui");
@@ -28182,7 +28659,7 @@ var init_tui = __esm(() => {
28182
28659
  // src/cli/index.ts
28183
28660
  init_path_glob();
28184
28661
  init_vendor();
28185
- import { resolve as resolve10 } from "path";
28662
+ import { resolve as resolve12 } from "path";
28186
28663
 
28187
28664
  // src/cli/usage.ts
28188
28665
  init_version();
@@ -28241,7 +28718,7 @@ async function runAddSubcommand(rest) {
28241
28718
  ${ADD_USAGE}`);
28242
28719
  process.exit(2);
28243
28720
  }
28244
- const target = resolve10(process.cwd(), arg && arg.length > 0 ? arg : ".");
28721
+ const target = resolve12(process.cwd(), arg && arg.length > 0 ? arg : ".");
28245
28722
  const { addSavedRepo: addSavedRepo2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
28246
28723
  const result = addSavedRepo2(target);
28247
28724
  if (result.added) {
@@ -28340,7 +28817,7 @@ async function runAdoptSubcommand(args2) {
28340
28817
  }
28341
28818
  }
28342
28819
  const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
28343
- const repo = resolveRepoRoot2(resolve10(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
28820
+ const repo = resolveRepoRoot2(resolve12(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
28344
28821
  const vendor = coerceVendorId(vendorArg);
28345
28822
  const orch = await openLocalOrchestrator();
28346
28823
  const worktrees = await orch.discoverAdoptableWorktrees(repo);