@sma1lboy/kobe 0.7.8 → 0.7.10
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 +303 -338
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -70,7 +70,7 @@ var init_package = __esm(() => {
|
|
|
70
70
|
package_default = {
|
|
71
71
|
$schema: "https://json.schemastore.org/package.json",
|
|
72
72
|
name: "@sma1lboy/kobe",
|
|
73
|
-
version: "0.7.
|
|
73
|
+
version: "0.7.10",
|
|
74
74
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
75
75
|
type: "module",
|
|
76
76
|
packageManager: "bun@1.3.13",
|
|
@@ -4997,6 +4997,58 @@ var init_auto_title_poller = __esm(() => {
|
|
|
4997
4997
|
init_chat_tab_naming();
|
|
4998
4998
|
});
|
|
4999
4999
|
|
|
5000
|
+
// src/daemon/cwd-task.ts
|
|
5001
|
+
function normalize(p) {
|
|
5002
|
+
return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
5003
|
+
}
|
|
5004
|
+
function isAncestorOrSelf(wt, cwd) {
|
|
5005
|
+
return cwd === wt || cwd.startsWith(`${wt}/`);
|
|
5006
|
+
}
|
|
5007
|
+
function matchTaskByCwd(tasks, cwd) {
|
|
5008
|
+
const target = normalize(cwd);
|
|
5009
|
+
let bestId;
|
|
5010
|
+
let bestLen = -1;
|
|
5011
|
+
for (const t of tasks) {
|
|
5012
|
+
if (!t.worktreePath)
|
|
5013
|
+
continue;
|
|
5014
|
+
const wt = normalize(t.worktreePath);
|
|
5015
|
+
if (isAncestorOrSelf(wt, target) && wt.length > bestLen) {
|
|
5016
|
+
bestLen = wt.length;
|
|
5017
|
+
bestId = t.id;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
return bestId;
|
|
5021
|
+
}
|
|
5022
|
+
function findAdoptableWorktree(tasks, cwd) {
|
|
5023
|
+
const target = normalize(cwd);
|
|
5024
|
+
const repos = new Set;
|
|
5025
|
+
const known = new Set;
|
|
5026
|
+
for (const t of tasks) {
|
|
5027
|
+
if (t.repo)
|
|
5028
|
+
repos.add(normalize(t.repo));
|
|
5029
|
+
if (t.worktreePath)
|
|
5030
|
+
known.add(normalize(t.worktreePath));
|
|
5031
|
+
}
|
|
5032
|
+
const seg = `/${KOBE_WORKTREE_ROOT_SUBPATH}/`;
|
|
5033
|
+
for (const repo of repos) {
|
|
5034
|
+
const prefix = `${repo}${seg}`;
|
|
5035
|
+
if (!target.startsWith(prefix))
|
|
5036
|
+
continue;
|
|
5037
|
+
const rest = target.slice(prefix.length);
|
|
5038
|
+
const name = rest.split("/")[0];
|
|
5039
|
+
if (!name)
|
|
5040
|
+
continue;
|
|
5041
|
+
const worktreePath = `${prefix}${name}`;
|
|
5042
|
+
if (known.has(worktreePath))
|
|
5043
|
+
return;
|
|
5044
|
+
return { repo, worktreePath };
|
|
5045
|
+
}
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
var init_cwd_task = __esm(() => {
|
|
5049
|
+
init_paths();
|
|
5050
|
+
});
|
|
5051
|
+
|
|
5000
5052
|
// src/daemon/event-bus.ts
|
|
5001
5053
|
class DaemonEventBus {
|
|
5002
5054
|
last = new Map;
|
|
@@ -5317,10 +5369,24 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5317
5369
|
return {};
|
|
5318
5370
|
}
|
|
5319
5371
|
case "engine.reportEvent": {
|
|
5320
|
-
const taskId = requireString(payload, "taskId");
|
|
5321
5372
|
const kind = requireString(payload, "kind");
|
|
5322
5373
|
if (!isEngineActivityKind(kind))
|
|
5323
5374
|
throw new Error(`unknown engine event kind: ${kind}`);
|
|
5375
|
+
const explicitId = optionalString(payload, "taskId");
|
|
5376
|
+
const cwd = optionalString(payload, "cwd");
|
|
5377
|
+
if (!explicitId && cwd && kind === "session-start") {
|
|
5378
|
+
const cand = findAdoptableWorktree(orch.listTasks(), cwd);
|
|
5379
|
+
if (cand) {
|
|
5380
|
+
try {
|
|
5381
|
+
await orch.adoptWorktree({ repo: cand.repo, worktreePath: cand.worktreePath, ifExists: "return" });
|
|
5382
|
+
} catch (err) {
|
|
5383
|
+
logDaemonError("worktree-autosync", err);
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
const taskId = explicitId ?? (cwd ? matchTaskByCwd(orch.listTasks(), cwd) : undefined);
|
|
5388
|
+
if (!taskId)
|
|
5389
|
+
return {};
|
|
5324
5390
|
const detail = optionalActivityDetail(payload);
|
|
5325
5391
|
reportActivity(taskId, kind, detail);
|
|
5326
5392
|
return {};
|
|
@@ -5464,6 +5530,7 @@ var init_server = __esm(() => {
|
|
|
5464
5530
|
init_hook_events();
|
|
5465
5531
|
init_version();
|
|
5466
5532
|
init_auto_title_poller();
|
|
5533
|
+
init_cwd_task();
|
|
5467
5534
|
init_paths2();
|
|
5468
5535
|
init_protocol();
|
|
5469
5536
|
DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
|
|
@@ -5863,172 +5930,6 @@ var init_prompt_delivery = __esm(() => {
|
|
|
5863
5930
|
init_client2();
|
|
5864
5931
|
});
|
|
5865
5932
|
|
|
5866
|
-
// src/engine/claude-code-local/hook-adapter.ts
|
|
5867
|
-
import { existsSync as existsSync2 } from "fs";
|
|
5868
|
-
import { appendFile as appendFile3, mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
5869
|
-
import { dirname as dirname6, join as join4 } from "path";
|
|
5870
|
-
function isObject6(v) {
|
|
5871
|
-
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
5872
|
-
}
|
|
5873
|
-
async function readJsonObject(path6) {
|
|
5874
|
-
try {
|
|
5875
|
-
const parsed = JSON.parse(await readFile6(path6, "utf8"));
|
|
5876
|
-
return isObject6(parsed) ? parsed : {};
|
|
5877
|
-
} catch {
|
|
5878
|
-
return {};
|
|
5879
|
-
}
|
|
5880
|
-
}
|
|
5881
|
-
function isKobeWorktreeSyncGroup(group) {
|
|
5882
|
-
if (!isObject6(group) || !Array.isArray(group.hooks))
|
|
5883
|
-
return false;
|
|
5884
|
-
return group.hooks.some((h) => isObject6(h) && typeof h.command === "string" && h.command.includes(WORKTREE_SYNC_MARKER));
|
|
5885
|
-
}
|
|
5886
|
-
function mergeWorktreeSyncHook(current, command) {
|
|
5887
|
-
const { hooks: rawHooks, ...restSettings } = current;
|
|
5888
|
-
const { WorktreeCreate, ...otherHooks } = isObject6(rawHooks) ? rawHooks : {};
|
|
5889
|
-
const prior = Array.isArray(WorktreeCreate) ? WorktreeCreate : [];
|
|
5890
|
-
const kept = prior.filter((g) => !isKobeWorktreeSyncGroup(g));
|
|
5891
|
-
if (command !== null)
|
|
5892
|
-
kept.push({ hooks: [{ type: "command", command }] });
|
|
5893
|
-
const nextHooks = { ...otherHooks };
|
|
5894
|
-
if (kept.length > 0)
|
|
5895
|
-
nextHooks.WorktreeCreate = kept;
|
|
5896
|
-
return Object.keys(nextHooks).length > 0 ? { ...restSettings, hooks: nextHooks } : { ...restSettings };
|
|
5897
|
-
}
|
|
5898
|
-
function buildClaudeHooks(taskId, inv = kobeCliInvocation()) {
|
|
5899
|
-
const out = {};
|
|
5900
|
-
for (const { event, matcher, verb } of EVENT_MAP) {
|
|
5901
|
-
const command = shellQuoteArgv([...inv, "hook", verb, "--task-id", taskId]);
|
|
5902
|
-
const group = { hooks: [{ type: "command", command }] };
|
|
5903
|
-
if (matcher)
|
|
5904
|
-
group.matcher = matcher;
|
|
5905
|
-
out[event] = [group];
|
|
5906
|
-
}
|
|
5907
|
-
return out;
|
|
5908
|
-
}
|
|
5909
|
-
function mergeClaudeHooks(existing, kobeHooks) {
|
|
5910
|
-
const merged = { ...existing };
|
|
5911
|
-
for (const event of KOBE_HOOK_EVENTS)
|
|
5912
|
-
merged[event] = kobeHooks[event];
|
|
5913
|
-
return merged;
|
|
5914
|
-
}
|
|
5915
|
-
|
|
5916
|
-
class ClaudeHookAdapter {
|
|
5917
|
-
vendor = "claude";
|
|
5918
|
-
supportsHooks() {
|
|
5919
|
-
return true;
|
|
5920
|
-
}
|
|
5921
|
-
supportsWorktreeSync() {
|
|
5922
|
-
return true;
|
|
5923
|
-
}
|
|
5924
|
-
async installWorktreeSyncHook(settingsFilePath) {
|
|
5925
|
-
await this.editWorktreeSyncHook(settingsFilePath, true);
|
|
5926
|
-
}
|
|
5927
|
-
async removeWorktreeSyncHook(settingsFilePath) {
|
|
5928
|
-
await this.editWorktreeSyncHook(settingsFilePath, false);
|
|
5929
|
-
}
|
|
5930
|
-
async editWorktreeSyncHook(settingsFilePath, install) {
|
|
5931
|
-
const current = await readJsonObject(settingsFilePath);
|
|
5932
|
-
const command = install ? shellQuoteArgv([...kobeCliInvocation(), "hook", "worktree-created"]) : null;
|
|
5933
|
-
const next = mergeWorktreeSyncHook(current, command);
|
|
5934
|
-
if (JSON.stringify(next) === JSON.stringify(current))
|
|
5935
|
-
return;
|
|
5936
|
-
await mkdir5(dirname6(settingsFilePath), { recursive: true });
|
|
5937
|
-
await writeFile4(settingsFilePath, `${JSON.stringify(next, null, 2)}
|
|
5938
|
-
`);
|
|
5939
|
-
}
|
|
5940
|
-
async installTaskHooks(ctx) {
|
|
5941
|
-
try {
|
|
5942
|
-
const claudeDir = join4(ctx.worktreeDir, ".claude");
|
|
5943
|
-
const settingsPath = join4(claudeDir, "settings.local.json");
|
|
5944
|
-
await mkdir5(claudeDir, { recursive: true });
|
|
5945
|
-
let current = {};
|
|
5946
|
-
try {
|
|
5947
|
-
const raw = await readFile6(settingsPath, "utf8");
|
|
5948
|
-
const parsed = JSON.parse(raw);
|
|
5949
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
5950
|
-
current = parsed;
|
|
5951
|
-
} catch {}
|
|
5952
|
-
const existingHooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks) ? current.hooks : {};
|
|
5953
|
-
current.hooks = mergeClaudeHooks(existingHooks, buildClaudeHooks(ctx.taskId));
|
|
5954
|
-
await writeFile4(settingsPath, `${JSON.stringify(current, null, 2)}
|
|
5955
|
-
`);
|
|
5956
|
-
await hideFromGit(ctx.worktreeDir, ".claude/settings.local.json");
|
|
5957
|
-
} catch {}
|
|
5958
|
-
}
|
|
5959
|
-
}
|
|
5960
|
-
async function hideFromGit(worktreeDir, relPath) {
|
|
5961
|
-
if (hiddenWorktrees.has(worktreeDir))
|
|
5962
|
-
return;
|
|
5963
|
-
try {
|
|
5964
|
-
const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
|
|
5965
|
-
stdout: "pipe",
|
|
5966
|
-
stderr: "ignore"
|
|
5967
|
-
});
|
|
5968
|
-
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
5969
|
-
if (code !== 0)
|
|
5970
|
-
return;
|
|
5971
|
-
let commonDir = out.trim();
|
|
5972
|
-
if (!commonDir)
|
|
5973
|
-
return;
|
|
5974
|
-
if (!commonDir.startsWith("/"))
|
|
5975
|
-
commonDir = join4(worktreeDir, commonDir);
|
|
5976
|
-
const excludePath = join4(commonDir, "info", "exclude");
|
|
5977
|
-
const existing = existsSync2(excludePath) ? await readFile6(excludePath, "utf8") : "";
|
|
5978
|
-
if (existing.split(`
|
|
5979
|
-
`).some((l) => l.trim() === relPath)) {
|
|
5980
|
-
hiddenWorktrees.add(worktreeDir);
|
|
5981
|
-
return;
|
|
5982
|
-
}
|
|
5983
|
-
await mkdir5(join4(commonDir, "info"), { recursive: true });
|
|
5984
|
-
await appendFile3(excludePath, `${existing.endsWith(`
|
|
5985
|
-
`) || existing === "" ? "" : `
|
|
5986
|
-
`}${relPath}
|
|
5987
|
-
`);
|
|
5988
|
-
hiddenWorktrees.add(worktreeDir);
|
|
5989
|
-
} catch {}
|
|
5990
|
-
}
|
|
5991
|
-
var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created", hiddenWorktrees;
|
|
5992
|
-
var init_hook_adapter = __esm(() => {
|
|
5993
|
-
init_invocation();
|
|
5994
|
-
EVENT_MAP = [
|
|
5995
|
-
{ event: "SessionStart", verb: "session-start" },
|
|
5996
|
-
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
5997
|
-
{ event: "Stop", verb: "turn-complete" },
|
|
5998
|
-
{ event: "StopFailure", verb: "turn-failed" },
|
|
5999
|
-
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
6000
|
-
{ event: "SessionEnd", verb: "session-end" }
|
|
6001
|
-
];
|
|
6002
|
-
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
6003
|
-
hiddenWorktrees = new Set;
|
|
6004
|
-
});
|
|
6005
|
-
|
|
6006
|
-
// src/engine/hook-adapter.ts
|
|
6007
|
-
function createEngineHookAdapter(vendor) {
|
|
6008
|
-
if (vendor === "claude")
|
|
6009
|
-
return new ClaudeHookAdapter;
|
|
6010
|
-
return new NoopHookAdapter(vendor);
|
|
6011
|
-
}
|
|
6012
|
-
|
|
6013
|
-
class NoopHookAdapter {
|
|
6014
|
-
vendor;
|
|
6015
|
-
constructor(vendor) {
|
|
6016
|
-
this.vendor = vendor;
|
|
6017
|
-
}
|
|
6018
|
-
supportsHooks() {
|
|
6019
|
-
return false;
|
|
6020
|
-
}
|
|
6021
|
-
async installTaskHooks() {}
|
|
6022
|
-
supportsWorktreeSync() {
|
|
6023
|
-
return false;
|
|
6024
|
-
}
|
|
6025
|
-
async installWorktreeSyncHook() {}
|
|
6026
|
-
async removeWorktreeSyncHook() {}
|
|
6027
|
-
}
|
|
6028
|
-
var init_hook_adapter2 = __esm(() => {
|
|
6029
|
-
init_hook_adapter();
|
|
6030
|
-
});
|
|
6031
|
-
|
|
6032
5933
|
// src/tui/panes/terminal/tmux.ts
|
|
6033
5934
|
var exports_tmux = {};
|
|
6034
5935
|
__export(exports_tmux, {
|
|
@@ -6079,12 +5980,6 @@ async function ensureSession(opts) {
|
|
|
6079
5980
|
}
|
|
6080
5981
|
}
|
|
6081
5982
|
async function ensureSessionImpl(opts) {
|
|
6082
|
-
if (opts.taskId && opts.cwd && opts.cwd.includes(`/${KOBE_WORKTREE_ROOT_SUBPATH}/`)) {
|
|
6083
|
-
await createEngineHookAdapter(coerceVendorId(opts.vendor)).installTaskHooks({
|
|
6084
|
-
worktreeDir: opts.cwd,
|
|
6085
|
-
taskId: opts.taskId
|
|
6086
|
-
});
|
|
6087
|
-
}
|
|
6088
5983
|
if (await sessionExists(opts.name)) {
|
|
6089
5984
|
const sessionOptions = await getSessionOptions(opts.name, ["@kobe_worktree", "@kobe_vendor"]);
|
|
6090
5985
|
const taggedWorktree = sessionOptions["@kobe_worktree"] ?? "";
|
|
@@ -6499,10 +6394,8 @@ async function quickCreate(session) {
|
|
|
6499
6394
|
var CHAT_TAB_SWITCH_BINDINGS, CHAT_TAB_CLOSE_BINDING, CHAT_TAB_RENAME_BINDING, CHAT_TAB_ENGINE_PROMPT, CHAT_TAB_CHOOSE_ENGINE_BINDINGS, CHAT_TAB_STATE_OPTION = "@kobe_tab_state", PANE_VERSION_OPTION = "@kobe_pane_version", CHAT_TAB_STATUS_FORMAT = "#{?#{==:#{@kobe_tab_state},running},\u25CF,#{?#{==:#{@kobe_tab_state},done},\u2713,#{?#{==:#{@kobe_tab_state},error},!,#{?#{==:#{@kobe_tab_state},unknown},?,\u25CB}}}} #I:#W", CHAT_TAB_STATUS_CURRENT_FORMAT, ensureSessionLocks;
|
|
6500
6395
|
var init_tmux = __esm(() => {
|
|
6501
6396
|
init_invocation();
|
|
6502
|
-
init_hook_adapter2();
|
|
6503
6397
|
init_interactive_command();
|
|
6504
6398
|
init_env();
|
|
6505
|
-
init_paths();
|
|
6506
6399
|
init_client2();
|
|
6507
6400
|
init_prompt_delivery();
|
|
6508
6401
|
init_vendor();
|
|
@@ -6545,14 +6438,14 @@ var exports_repo_init = {};
|
|
|
6545
6438
|
__export(exports_repo_init, {
|
|
6546
6439
|
resolveRepoInit: () => resolveRepoInit
|
|
6547
6440
|
});
|
|
6548
|
-
import { existsSync as
|
|
6549
|
-
import { join as
|
|
6441
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
6442
|
+
import { join as join4 } from "path";
|
|
6550
6443
|
function repoFileScript(worktreePath) {
|
|
6551
|
-
return
|
|
6444
|
+
return existsSync2(join4(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
|
|
6552
6445
|
}
|
|
6553
6446
|
function repoFilePrompt(worktreePath) {
|
|
6554
|
-
const p =
|
|
6555
|
-
if (!
|
|
6447
|
+
const p = join4(worktreePath, INIT_PROMPT_REL);
|
|
6448
|
+
if (!existsSync2(p))
|
|
6556
6449
|
return;
|
|
6557
6450
|
try {
|
|
6558
6451
|
const text = readFileSync3(p, "utf8");
|
|
@@ -6573,8 +6466,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
|
|
|
6573
6466
|
var INIT_SCRIPT_REL, INIT_PROMPT_REL;
|
|
6574
6467
|
var init_repo_init = __esm(() => {
|
|
6575
6468
|
init_repos();
|
|
6576
|
-
INIT_SCRIPT_REL =
|
|
6577
|
-
INIT_PROMPT_REL =
|
|
6469
|
+
INIT_SCRIPT_REL = join4(".kobe", "init.sh");
|
|
6470
|
+
INIT_PROMPT_REL = join4(".kobe", "init-prompt.md");
|
|
6578
6471
|
});
|
|
6579
6472
|
|
|
6580
6473
|
// src/tui/panes/sidebar/worktree-changes.ts
|
|
@@ -7586,9 +7479,9 @@ var init_schema = () => {};
|
|
|
7586
7479
|
|
|
7587
7480
|
// src/tui/context/theme/loader.ts
|
|
7588
7481
|
import { readFileSync as readFileSync4, readdirSync } from "fs";
|
|
7589
|
-
import { join as
|
|
7482
|
+
import { join as join5 } from "path";
|
|
7590
7483
|
function userThemesDir() {
|
|
7591
|
-
return
|
|
7484
|
+
return join5(kobeStateDir(), "themes");
|
|
7592
7485
|
}
|
|
7593
7486
|
function loadUserThemes() {
|
|
7594
7487
|
const dir = userThemesDir();
|
|
@@ -7602,7 +7495,7 @@ function loadUserThemes() {
|
|
|
7602
7495
|
for (const file of entries) {
|
|
7603
7496
|
if (!file.endsWith(".json"))
|
|
7604
7497
|
continue;
|
|
7605
|
-
const path6 =
|
|
7498
|
+
const path6 = join5(dir, file);
|
|
7606
7499
|
let parsed;
|
|
7607
7500
|
try {
|
|
7608
7501
|
const text = readFileSync4(path6, "utf8");
|
|
@@ -7632,8 +7525,8 @@ var exports_theme = {};
|
|
|
7632
7525
|
__export(exports_theme, {
|
|
7633
7526
|
runThemeSubcommand: () => runThemeSubcommand
|
|
7634
7527
|
});
|
|
7635
|
-
import { existsSync as
|
|
7636
|
-
import { basename as basename3, join as
|
|
7528
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
7529
|
+
import { basename as basename3, join as join6, resolve as resolve5 } from "path";
|
|
7637
7530
|
function fail2(message) {
|
|
7638
7531
|
process.stderr.write(`kobe theme: ${message}
|
|
7639
7532
|
`);
|
|
@@ -7664,7 +7557,7 @@ function listThemes() {
|
|
|
7664
7557
|
} else {
|
|
7665
7558
|
for (const f of userFiles) {
|
|
7666
7559
|
const name = f.slice(0, -".json".length);
|
|
7667
|
-
const path6 =
|
|
7560
|
+
const path6 = join6(dir, f);
|
|
7668
7561
|
const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
|
|
7669
7562
|
lines.push(` ${name}${overridesBundled} ${path6}`);
|
|
7670
7563
|
}
|
|
@@ -7754,8 +7647,8 @@ async function addTheme(args) {
|
|
|
7754
7647
|
}
|
|
7755
7648
|
const dir = userThemesDir();
|
|
7756
7649
|
mkdirSync3(dir, { recursive: true });
|
|
7757
|
-
const dest =
|
|
7758
|
-
if (
|
|
7650
|
+
const dest = join6(dir, `${name}.json`);
|
|
7651
|
+
if (existsSync3(dest) && !opts.force) {
|
|
7759
7652
|
fail2(`${dest} already exists (pass --force to overwrite)`);
|
|
7760
7653
|
}
|
|
7761
7654
|
writeFileSync2(dest, `${JSON.stringify(result.theme, null, 2)}
|
|
@@ -7772,8 +7665,8 @@ function removeTheme(args) {
|
|
|
7772
7665
|
if (BUNDLED_NAMES.includes(name)) {
|
|
7773
7666
|
fail2(`"${name}" is a built-in theme and cannot be removed`);
|
|
7774
7667
|
}
|
|
7775
|
-
const dest =
|
|
7776
|
-
if (!
|
|
7668
|
+
const dest = join6(userThemesDir(), `${name}.json`);
|
|
7669
|
+
if (!existsSync3(dest)) {
|
|
7777
7670
|
fail2(`no user theme named "${name}" (looked for ${dest})`);
|
|
7778
7671
|
}
|
|
7779
7672
|
unlinkSync(dest);
|
|
@@ -7956,9 +7849,9 @@ var init_daemon_cmd = __esm(() => {
|
|
|
7956
7849
|
});
|
|
7957
7850
|
|
|
7958
7851
|
// src/lib/skill-install.ts
|
|
7959
|
-
import { existsSync as
|
|
7852
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
7960
7853
|
import { homedir as homedir9 } from "os";
|
|
7961
|
-
import { join as
|
|
7854
|
+
import { join as join7 } from "path";
|
|
7962
7855
|
function npxSkillsArgv(opts = {}) {
|
|
7963
7856
|
return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
|
|
7964
7857
|
}
|
|
@@ -7968,14 +7861,14 @@ function npxSkillsCommand(opts = {}) {
|
|
|
7968
7861
|
function kobeSkillPaths(opts = {}) {
|
|
7969
7862
|
const home = opts.home ?? homedir9();
|
|
7970
7863
|
const cwd = opts.cwd ?? process.cwd();
|
|
7971
|
-
return [
|
|
7864
|
+
return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
|
|
7972
7865
|
}
|
|
7973
7866
|
function parseSkillVersion(content) {
|
|
7974
7867
|
const m = content.match(/kobe-skill-version:\s*(\d+)/);
|
|
7975
7868
|
return m ? Number.parseInt(m[1], 10) : null;
|
|
7976
7869
|
}
|
|
7977
7870
|
function kobeSkillState(opts) {
|
|
7978
|
-
const path6 = kobeSkillPaths(opts).find((p) =>
|
|
7871
|
+
const path6 = kobeSkillPaths(opts).find((p) => existsSync4(p));
|
|
7979
7872
|
if (!path6) {
|
|
7980
7873
|
return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
|
|
7981
7874
|
}
|
|
@@ -8027,9 +7920,9 @@ __export(exports_maintenance, {
|
|
|
8027
7920
|
runReloadSubcommand: () => runReloadSubcommand,
|
|
8028
7921
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
8029
7922
|
});
|
|
8030
|
-
import { existsSync as
|
|
7923
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, statSync } from "fs";
|
|
8031
7924
|
import { unlink as unlink6 } from "fs/promises";
|
|
8032
|
-
import { join as
|
|
7925
|
+
import { join as join8 } from "path";
|
|
8033
7926
|
import { createInterface } from "readline";
|
|
8034
7927
|
function isProcessAlive2(pid) {
|
|
8035
7928
|
try {
|
|
@@ -8126,7 +8019,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
8126
8019
|
const socketPath = defaultDaemonSocketPath();
|
|
8127
8020
|
const pidPath = defaultDaemonPidPath();
|
|
8128
8021
|
const logPath = defaultDaemonLogPath();
|
|
8129
|
-
const tasksPath =
|
|
8022
|
+
const tasksPath = join8(kobeStateDir(), "tasks.json");
|
|
8130
8023
|
const statePath2 = kvStatePath();
|
|
8131
8024
|
const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
|
|
8132
8025
|
const status = await probeDaemonStatus(socketPath);
|
|
@@ -8154,7 +8047,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
8154
8047
|
} else {
|
|
8155
8048
|
out.push("daemon: \u2717 not running (no pidfile)");
|
|
8156
8049
|
}
|
|
8157
|
-
if (
|
|
8050
|
+
if (existsSync5(socketPath))
|
|
8158
8051
|
out.push(` orphan socket file present: ${socketPath}`);
|
|
8159
8052
|
const tail = tailFile(logPath, 8);
|
|
8160
8053
|
if (tail) {
|
|
@@ -8245,7 +8138,7 @@ async function runResetSubcommand(argv) {
|
|
|
8245
8138
|
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
8246
8139
|
const socketPath = defaultDaemonSocketPath();
|
|
8247
8140
|
const pidPath = defaultDaemonPidPath();
|
|
8248
|
-
const tasksPath =
|
|
8141
|
+
const tasksPath = join8(kobeStateDir(), "tasks.json");
|
|
8249
8142
|
const statePath2 = kvStatePath();
|
|
8250
8143
|
console.log("kobe reset will:");
|
|
8251
8144
|
console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
|
|
@@ -8449,14 +8342,148 @@ var init_skill_cmd = __esm(() => {
|
|
|
8449
8342
|
SKILL_VERBS = ["install", "status", "command"];
|
|
8450
8343
|
});
|
|
8451
8344
|
|
|
8345
|
+
// src/engine/claude-code-local/hook-adapter.ts
|
|
8346
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
8347
|
+
import { dirname as dirname6 } from "path";
|
|
8348
|
+
function isObject6(v) {
|
|
8349
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8350
|
+
}
|
|
8351
|
+
async function readJsonObject(path6) {
|
|
8352
|
+
try {
|
|
8353
|
+
const parsed = JSON.parse(await readFile6(path6, "utf8"));
|
|
8354
|
+
return isObject6(parsed) ? parsed : {};
|
|
8355
|
+
} catch {
|
|
8356
|
+
return {};
|
|
8357
|
+
}
|
|
8358
|
+
}
|
|
8359
|
+
function isKobeWorktreeSyncGroup(group) {
|
|
8360
|
+
if (!isObject6(group) || !Array.isArray(group.hooks))
|
|
8361
|
+
return false;
|
|
8362
|
+
return group.hooks.some((h) => isObject6(h) && typeof h.command === "string" && h.command.includes(WORKTREE_SYNC_MARKER));
|
|
8363
|
+
}
|
|
8364
|
+
function isKobeActivityGroup(group) {
|
|
8365
|
+
if (!isObject6(group) || !Array.isArray(group.hooks))
|
|
8366
|
+
return false;
|
|
8367
|
+
return group.hooks.some((h) => isObject6(h) && typeof h.command === "string" && ACTIVITY_MARKERS.some((m) => h.command.includes(m)));
|
|
8368
|
+
}
|
|
8369
|
+
function mergeWorktreeSyncHook(current, command) {
|
|
8370
|
+
const { hooks: rawHooks, ...restSettings } = current;
|
|
8371
|
+
const { WorktreeCreate, ...otherHooks } = isObject6(rawHooks) ? rawHooks : {};
|
|
8372
|
+
const prior = Array.isArray(WorktreeCreate) ? WorktreeCreate : [];
|
|
8373
|
+
const kept = prior.filter((g) => !isKobeWorktreeSyncGroup(g));
|
|
8374
|
+
if (command !== null)
|
|
8375
|
+
kept.push({ hooks: [{ type: "command", command }] });
|
|
8376
|
+
const nextHooks = { ...otherHooks };
|
|
8377
|
+
if (kept.length > 0)
|
|
8378
|
+
nextHooks.WorktreeCreate = kept;
|
|
8379
|
+
return Object.keys(nextHooks).length > 0 ? { ...restSettings, hooks: nextHooks } : { ...restSettings };
|
|
8380
|
+
}
|
|
8381
|
+
function buildClaudeHooks(inv = kobeCliInvocation()) {
|
|
8382
|
+
const out = {};
|
|
8383
|
+
for (const { event, matcher, verb } of EVENT_MAP) {
|
|
8384
|
+
const command = shellQuoteArgv([...inv, "hook", verb]);
|
|
8385
|
+
const group = { hooks: [{ type: "command", command }] };
|
|
8386
|
+
if (matcher)
|
|
8387
|
+
group.matcher = matcher;
|
|
8388
|
+
out[event] = [group];
|
|
8389
|
+
}
|
|
8390
|
+
return out;
|
|
8391
|
+
}
|
|
8392
|
+
function mergeActivityHooks(current, install, inv = kobeCliInvocation()) {
|
|
8393
|
+
const { hooks: rawHooks, ...restSettings } = current;
|
|
8394
|
+
const hooks = isObject6(rawHooks) ? { ...rawHooks } : {};
|
|
8395
|
+
const built = install ? buildClaudeHooks(inv) : {};
|
|
8396
|
+
for (const { event } of EVENT_MAP) {
|
|
8397
|
+
const prior = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
8398
|
+
const kept = prior.filter((g) => !isKobeActivityGroup(g));
|
|
8399
|
+
if (install && Array.isArray(built[event]))
|
|
8400
|
+
kept.push(...built[event]);
|
|
8401
|
+
if (kept.length > 0)
|
|
8402
|
+
hooks[event] = kept;
|
|
8403
|
+
else
|
|
8404
|
+
delete hooks[event];
|
|
8405
|
+
}
|
|
8406
|
+
return Object.keys(hooks).length > 0 ? { ...restSettings, hooks } : { ...restSettings };
|
|
8407
|
+
}
|
|
8408
|
+
|
|
8409
|
+
class ClaudeHookAdapter {
|
|
8410
|
+
vendor = "claude";
|
|
8411
|
+
supportsHooks() {
|
|
8412
|
+
return true;
|
|
8413
|
+
}
|
|
8414
|
+
supportsWorktreeSync() {
|
|
8415
|
+
return true;
|
|
8416
|
+
}
|
|
8417
|
+
async installActivityHooks(settingsFilePath) {
|
|
8418
|
+
await this.editSettings(settingsFilePath, (cur) => mergeActivityHooks(cur, true));
|
|
8419
|
+
}
|
|
8420
|
+
async removeActivityHooks(settingsFilePath) {
|
|
8421
|
+
await this.editSettings(settingsFilePath, (cur) => mergeActivityHooks(cur, false));
|
|
8422
|
+
}
|
|
8423
|
+
async removeWorktreeSyncHook(settingsFilePath) {
|
|
8424
|
+
await this.editSettings(settingsFilePath, (cur) => mergeWorktreeSyncHook(cur, null));
|
|
8425
|
+
}
|
|
8426
|
+
async editSettings(settingsFilePath, transform) {
|
|
8427
|
+
try {
|
|
8428
|
+
const current = await readJsonObject(settingsFilePath);
|
|
8429
|
+
const next = transform(current);
|
|
8430
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
8431
|
+
return;
|
|
8432
|
+
await mkdir5(dirname6(settingsFilePath), { recursive: true });
|
|
8433
|
+
await writeFile4(settingsFilePath, `${JSON.stringify(next, null, 2)}
|
|
8434
|
+
`);
|
|
8435
|
+
} catch {}
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
var EVENT_MAP, KOBE_HOOK_EVENTS, ACTIVITY_MARKERS, WORKTREE_SYNC_MARKER = "worktree-created";
|
|
8439
|
+
var init_hook_adapter = __esm(() => {
|
|
8440
|
+
init_invocation();
|
|
8441
|
+
EVENT_MAP = [
|
|
8442
|
+
{ event: "SessionStart", verb: "session-start" },
|
|
8443
|
+
{ event: "UserPromptSubmit", verb: "turn-start" },
|
|
8444
|
+
{ event: "Stop", verb: "turn-complete" },
|
|
8445
|
+
{ event: "StopFailure", verb: "turn-failed" },
|
|
8446
|
+
{ event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
|
|
8447
|
+
{ event: "SessionEnd", verb: "session-end" }
|
|
8448
|
+
];
|
|
8449
|
+
KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
|
|
8450
|
+
ACTIVITY_MARKERS = EVENT_MAP.map((e) => shellQuoteArgv(["hook", e.verb]));
|
|
8451
|
+
});
|
|
8452
|
+
|
|
8453
|
+
// src/engine/hook-adapter.ts
|
|
8454
|
+
function createEngineHookAdapter(vendor) {
|
|
8455
|
+
if (vendor === "claude")
|
|
8456
|
+
return new ClaudeHookAdapter;
|
|
8457
|
+
return new NoopHookAdapter(vendor);
|
|
8458
|
+
}
|
|
8459
|
+
|
|
8460
|
+
class NoopHookAdapter {
|
|
8461
|
+
vendor;
|
|
8462
|
+
constructor(vendor) {
|
|
8463
|
+
this.vendor = vendor;
|
|
8464
|
+
}
|
|
8465
|
+
supportsHooks() {
|
|
8466
|
+
return false;
|
|
8467
|
+
}
|
|
8468
|
+
async installActivityHooks() {}
|
|
8469
|
+
async removeActivityHooks() {}
|
|
8470
|
+
supportsWorktreeSync() {
|
|
8471
|
+
return false;
|
|
8472
|
+
}
|
|
8473
|
+
async removeWorktreeSyncHook() {}
|
|
8474
|
+
}
|
|
8475
|
+
var init_hook_adapter2 = __esm(() => {
|
|
8476
|
+
init_hook_adapter();
|
|
8477
|
+
});
|
|
8478
|
+
|
|
8452
8479
|
// src/cli/hook-cmd.ts
|
|
8453
8480
|
var exports_hook_cmd = {};
|
|
8454
8481
|
__export(exports_hook_cmd, {
|
|
8455
8482
|
runHookSubcommand: () => runHookSubcommand,
|
|
8456
|
-
|
|
8483
|
+
ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
|
|
8457
8484
|
});
|
|
8458
8485
|
import { homedir as homedir10 } from "os";
|
|
8459
|
-
import {
|
|
8486
|
+
import { join as join9, resolve as resolve6 } from "path";
|
|
8460
8487
|
async function readStdinPayload() {
|
|
8461
8488
|
try {
|
|
8462
8489
|
const text = await Promise.race([
|
|
@@ -8496,16 +8523,11 @@ async function runHookSubcommand(argv) {
|
|
|
8496
8523
|
return;
|
|
8497
8524
|
}
|
|
8498
8525
|
try {
|
|
8499
|
-
if (verb === "worktree-created") {
|
|
8500
|
-
await reportWorktreeCreated();
|
|
8501
|
-
return;
|
|
8502
|
-
}
|
|
8503
8526
|
if (!verb || !isEngineActivityKind(verb))
|
|
8504
8527
|
return;
|
|
8505
|
-
const taskId = flagValue(rest, "--task-id");
|
|
8506
|
-
if (!taskId)
|
|
8507
|
-
return;
|
|
8508
8528
|
const payload = await readStdinPayload();
|
|
8529
|
+
const taskId = flagValue(rest, "--task-id");
|
|
8530
|
+
const cwd = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
8509
8531
|
let detail;
|
|
8510
8532
|
if (verb === "turn-failed") {
|
|
8511
8533
|
detail = { failure: failureFromErrorType(payload.error_type) };
|
|
@@ -8516,126 +8538,69 @@ async function runHookSubcommand(argv) {
|
|
|
8516
8538
|
if (!client)
|
|
8517
8539
|
return;
|
|
8518
8540
|
try {
|
|
8519
|
-
await client.request("engine.reportEvent", {
|
|
8541
|
+
await client.request("engine.reportEvent", {
|
|
8542
|
+
...taskId ? { taskId } : { cwd },
|
|
8543
|
+
kind: verb,
|
|
8544
|
+
...detail ? { detail } : {}
|
|
8545
|
+
});
|
|
8520
8546
|
} finally {
|
|
8521
8547
|
client.close();
|
|
8522
8548
|
}
|
|
8523
8549
|
} catch {}
|
|
8524
8550
|
}
|
|
8525
|
-
async function reportWorktreeCreated() {
|
|
8526
|
-
const payload = await readStdinPayload();
|
|
8527
|
-
const worktreePath = typeof payload.worktree_path === "string" ? payload.worktree_path : undefined;
|
|
8528
|
-
if (!worktreePath)
|
|
8529
|
-
return;
|
|
8530
|
-
const repo = await deriveRepoRoot(worktreePath);
|
|
8531
|
-
if (!repo)
|
|
8532
|
-
return;
|
|
8533
|
-
const client = await connectIfRunning();
|
|
8534
|
-
if (!client)
|
|
8535
|
-
return;
|
|
8536
|
-
try {
|
|
8537
|
-
await client.request("worktree.adopt", { repo, worktreePath, ifExists: "return" });
|
|
8538
|
-
} finally {
|
|
8539
|
-
client.close();
|
|
8540
|
-
}
|
|
8541
|
-
}
|
|
8542
|
-
async function deriveRepoRoot(worktreePath) {
|
|
8543
|
-
try {
|
|
8544
|
-
const proc = Bun.spawn(["git", "-C", worktreePath, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
8545
|
-
stdout: "pipe",
|
|
8546
|
-
stderr: "ignore"
|
|
8547
|
-
});
|
|
8548
|
-
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
8549
|
-
if (code !== 0)
|
|
8550
|
-
return;
|
|
8551
|
-
const commonDir = out.trim();
|
|
8552
|
-
return commonDir ? dirname7(commonDir) : undefined;
|
|
8553
|
-
} catch {
|
|
8554
|
-
return;
|
|
8555
|
-
}
|
|
8556
|
-
}
|
|
8557
|
-
function syncSettingsPath(scope) {
|
|
8558
|
-
if (scope.kind === "repo")
|
|
8559
|
-
return join10(resolve6(scope.path), ".claude", "settings.json");
|
|
8560
|
-
return join10(homedir10(), ".claude", "settings.json");
|
|
8561
|
-
}
|
|
8562
8551
|
function worktreeSyncAdapters() {
|
|
8563
8552
|
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsWorktreeSync());
|
|
8564
8553
|
}
|
|
8554
|
+
function activityHookAdapters() {
|
|
8555
|
+
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsHooks());
|
|
8556
|
+
}
|
|
8557
|
+
function globalSettingsPath() {
|
|
8558
|
+
return join9(homedir10(), ".claude", "settings.json");
|
|
8559
|
+
}
|
|
8565
8560
|
function persistedSyncPath(stored) {
|
|
8566
8561
|
if (!stored || stored === "off")
|
|
8567
8562
|
return;
|
|
8568
8563
|
if (stored === "global")
|
|
8569
|
-
return
|
|
8564
|
+
return globalSettingsPath();
|
|
8570
8565
|
if (stored.startsWith("repo:"))
|
|
8571
|
-
return
|
|
8566
|
+
return join9(resolve6(stored.slice(5)), ".claude", "settings.json");
|
|
8572
8567
|
return stored;
|
|
8573
8568
|
}
|
|
8574
|
-
async function
|
|
8569
|
+
async function ensureGlobalKobeHooks() {
|
|
8575
8570
|
try {
|
|
8576
|
-
const
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
if (adapters.length === 0)
|
|
8581
|
-
return;
|
|
8582
|
-
const path6 = persistedSyncPath(stored) ?? syncSettingsPath({ kind: "global" });
|
|
8583
|
-
for (const a of adapters)
|
|
8584
|
-
await a.installWorktreeSyncHook(path6);
|
|
8585
|
-
if (!stored)
|
|
8586
|
-
setPersistedString(SYNC_SETTING_KEY, path6);
|
|
8571
|
+
const globalPath = globalSettingsPath();
|
|
8572
|
+
for (const a of activityHookAdapters())
|
|
8573
|
+
await a.installActivityHooks(globalPath);
|
|
8574
|
+
await cleanupWorktreeSyncHook();
|
|
8587
8575
|
} catch {}
|
|
8588
8576
|
}
|
|
8589
|
-
async function
|
|
8590
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
8591
|
-
process.stdout.write([
|
|
8592
|
-
"Usage: kobe hook setup [--global | --repo <path> | --off]",
|
|
8593
|
-
"",
|
|
8594
|
-
"Syncs an external `claude --worktree` into kobe as a task. This is ON",
|
|
8595
|
-
"by default (installed globally into ~/.claude on launch). Use this to",
|
|
8596
|
-
"move it to one repo (--repo <path>) or to turn it OFF (--off).",
|
|
8597
|
-
""
|
|
8598
|
-
].join(`
|
|
8599
|
-
`));
|
|
8600
|
-
return;
|
|
8601
|
-
}
|
|
8602
|
-
const off = argv.includes("--off");
|
|
8603
|
-
const repoIdx = argv.indexOf("--repo");
|
|
8604
|
-
const repoPath = repoIdx !== -1 ? argv[repoIdx + 1] : undefined;
|
|
8605
|
-
if (repoIdx !== -1 && !repoPath) {
|
|
8606
|
-
process.stderr.write(`kobe hook setup: --repo requires a path
|
|
8607
|
-
`);
|
|
8608
|
-
process.exit(2);
|
|
8609
|
-
}
|
|
8577
|
+
async function cleanupWorktreeSyncHook() {
|
|
8610
8578
|
const adapters = worktreeSyncAdapters();
|
|
8611
|
-
if (adapters.length === 0)
|
|
8612
|
-
process.stdout.write(`kobe hook setup: no engine supports external worktree sync \u2014 nothing to do
|
|
8613
|
-
`);
|
|
8579
|
+
if (adapters.length === 0)
|
|
8614
8580
|
return;
|
|
8615
|
-
|
|
8616
|
-
const
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
await a.removeWorktreeSyncHook(prevPath);
|
|
8621
|
-
}
|
|
8622
|
-
setPersistedString(SYNC_SETTING_KEY, "off");
|
|
8623
|
-
process.stdout.write(`kobe hook setup: external worktree sync disabled${prevPath ? ` (removed from ${prevPath})` : ""}
|
|
8624
|
-
`);
|
|
8625
|
-
return;
|
|
8626
|
-
}
|
|
8627
|
-
const scope = repoPath ? { kind: "repo", path: repoPath } : { kind: "global" };
|
|
8628
|
-
const path6 = syncSettingsPath(scope);
|
|
8629
|
-
if (prevPath && prevPath !== path6) {
|
|
8630
|
-
for (const a of adapters)
|
|
8631
|
-
await a.removeWorktreeSyncHook(prevPath);
|
|
8632
|
-
}
|
|
8581
|
+
const stored = getPersistedString(SYNC_SETTING_KEY);
|
|
8582
|
+
const paths = new Set([globalSettingsPath()]);
|
|
8583
|
+
const prev = persistedSyncPath(stored);
|
|
8584
|
+
if (prev)
|
|
8585
|
+
paths.add(prev);
|
|
8633
8586
|
for (const a of adapters)
|
|
8634
|
-
|
|
8635
|
-
|
|
8587
|
+
for (const p of paths)
|
|
8588
|
+
await a.removeWorktreeSyncHook(p);
|
|
8589
|
+
if (stored !== "off")
|
|
8590
|
+
setPersistedString(SYNC_SETTING_KEY, "off");
|
|
8591
|
+
}
|
|
8592
|
+
async function runHookSetup(_argv) {
|
|
8593
|
+
await cleanupWorktreeSyncHook();
|
|
8636
8594
|
process.stdout.write([
|
|
8637
|
-
|
|
8638
|
-
"
|
|
8595
|
+
"kobe hook setup is deprecated and now a no-op (cleanup only).",
|
|
8596
|
+
"",
|
|
8597
|
+
"The old external-worktree sync used a global WorktreeCreate hook, which is",
|
|
8598
|
+
"a VCS provider hook \u2014 its presence broke `claude --worktree` / EnterWorktree",
|
|
8599
|
+
"in every repo. Any hook kobe previously installed has been removed.",
|
|
8600
|
+
"",
|
|
8601
|
+
"Sync is now automatic: a `claude --worktree` (or any session) started in a",
|
|
8602
|
+
"worktree under a repo kobe already tracks is adopted as a task on launch.",
|
|
8603
|
+
"To adopt existing worktrees on demand, use the New Task dialog or `kobe adopt`.",
|
|
8639
8604
|
""
|
|
8640
8605
|
].join(`
|
|
8641
8606
|
`));
|
|
@@ -13854,7 +13819,7 @@ var init_rename_task_dialog = __esm(() => {
|
|
|
13854
13819
|
|
|
13855
13820
|
// src/engine/claude-code-local/binary.ts
|
|
13856
13821
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
13857
|
-
import { existsSync as
|
|
13822
|
+
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
13858
13823
|
import { homedir as homedir12 } from "os";
|
|
13859
13824
|
import path7 from "path";
|
|
13860
13825
|
async function findClaudeBinary(deps = defaultDeps4) {
|
|
@@ -13941,7 +13906,7 @@ var init_binary = __esm(() => {
|
|
|
13941
13906
|
return;
|
|
13942
13907
|
if (first.startsWith("claude:") && first.includes("aliased to")) {
|
|
13943
13908
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
13944
|
-
return aliasTarget &&
|
|
13909
|
+
return aliasTarget && existsSync7(aliasTarget) ? aliasTarget : undefined;
|
|
13945
13910
|
}
|
|
13946
13911
|
return first;
|
|
13947
13912
|
},
|
|
@@ -13958,7 +13923,7 @@ var init_binary = __esm(() => {
|
|
|
13958
13923
|
|
|
13959
13924
|
// src/engine/codex-local/binary.ts
|
|
13960
13925
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
13961
|
-
import { existsSync as
|
|
13926
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
13962
13927
|
import { homedir as homedir13 } from "os";
|
|
13963
13928
|
import path8 from "path";
|
|
13964
13929
|
async function findCodexBinary(deps = defaultDeps5) {
|
|
@@ -14029,7 +13994,7 @@ var init_binary2 = __esm(() => {
|
|
|
14029
13994
|
return;
|
|
14030
13995
|
if (first.startsWith("codex:") && first.includes("aliased to")) {
|
|
14031
13996
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
14032
|
-
return aliasTarget &&
|
|
13997
|
+
return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
|
|
14033
13998
|
}
|
|
14034
13999
|
return first;
|
|
14035
14000
|
},
|
|
@@ -14046,7 +14011,7 @@ var init_binary2 = __esm(() => {
|
|
|
14046
14011
|
|
|
14047
14012
|
// src/engine/copilot-local/binary.ts
|
|
14048
14013
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
14049
|
-
import { existsSync as
|
|
14014
|
+
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
14050
14015
|
import { homedir as homedir14 } from "os";
|
|
14051
14016
|
import path9 from "path";
|
|
14052
14017
|
async function findCopilotBinary(deps = defaultDeps6) {
|
|
@@ -14141,7 +14106,7 @@ var init_binary3 = __esm(() => {
|
|
|
14141
14106
|
return;
|
|
14142
14107
|
if (first.startsWith("copilot:") && first.includes("aliased to")) {
|
|
14143
14108
|
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
14144
|
-
return aliasTarget &&
|
|
14109
|
+
return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
|
|
14145
14110
|
}
|
|
14146
14111
|
return first;
|
|
14147
14112
|
},
|
|
@@ -14530,7 +14495,7 @@ var init_dialog_confirm = __esm(() => {
|
|
|
14530
14495
|
|
|
14531
14496
|
// src/tui/component/settings-dialog/actions.ts
|
|
14532
14497
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
14533
|
-
import { join as
|
|
14498
|
+
import { join as join11 } from "path";
|
|
14534
14499
|
function hasRestartableDaemon(orchestrator) {
|
|
14535
14500
|
return orchestrator instanceof RemoteOrchestrator;
|
|
14536
14501
|
}
|
|
@@ -14547,7 +14512,7 @@ async function confirmResetState(dialog, kv, renderer) {
|
|
|
14547
14512
|
return;
|
|
14548
14513
|
kv.clear();
|
|
14549
14514
|
try {
|
|
14550
|
-
unlinkSync2(
|
|
14515
|
+
unlinkSync2(join11(homeDir(), ".kobe", "tasks.json"));
|
|
14551
14516
|
} catch (err) {
|
|
14552
14517
|
if (err.code !== "ENOENT") {
|
|
14553
14518
|
console.error("kobe: failed to delete tasks.json during reset:", err);
|
|
@@ -15984,7 +15949,7 @@ var init_focus = __esm(() => {
|
|
|
15984
15949
|
|
|
15985
15950
|
// src/tui/context/kv.tsx
|
|
15986
15951
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
15987
|
-
import { dirname as
|
|
15952
|
+
import { dirname as dirname7 } from "path";
|
|
15988
15953
|
function loadInitial() {
|
|
15989
15954
|
const statePath2 = kvStatePath();
|
|
15990
15955
|
try {
|
|
@@ -16012,7 +15977,7 @@ var init_kv = __esm(() => {
|
|
|
16012
15977
|
function writeNow(label) {
|
|
16013
15978
|
const statePath2 = kvStatePath();
|
|
16014
15979
|
try {
|
|
16015
|
-
mkdirSync4(
|
|
15980
|
+
mkdirSync4(dirname7(statePath2), {
|
|
16016
15981
|
recursive: true
|
|
16017
15982
|
});
|
|
16018
15983
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -16067,7 +16032,7 @@ var init_kv = __esm(() => {
|
|
|
16067
16032
|
}
|
|
16068
16033
|
const statePath2 = kvStatePath();
|
|
16069
16034
|
try {
|
|
16070
|
-
mkdirSync4(
|
|
16035
|
+
mkdirSync4(dirname7(statePath2), {
|
|
16071
16036
|
recursive: true
|
|
16072
16037
|
});
|
|
16073
16038
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -16103,8 +16068,8 @@ var init_persisted_ui_prefs = __esm(() => {
|
|
|
16103
16068
|
|
|
16104
16069
|
// src/tui/lib/worktree-opener.ts
|
|
16105
16070
|
import { spawn as spawn3 } from "child_process";
|
|
16106
|
-
import { existsSync as
|
|
16107
|
-
import { basename as basename4, delimiter, isAbsolute, join as
|
|
16071
|
+
import { existsSync as existsSync10 } from "fs";
|
|
16072
|
+
import { basename as basename4, delimiter, isAbsolute, join as join12 } from "path";
|
|
16108
16073
|
function executableOnPath(command, env, exists) {
|
|
16109
16074
|
if (isAbsolute(command))
|
|
16110
16075
|
return exists(command);
|
|
@@ -16112,7 +16077,7 @@ function executableOnPath(command, env, exists) {
|
|
|
16112
16077
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16113
16078
|
if (!dir)
|
|
16114
16079
|
continue;
|
|
16115
|
-
if (exists(
|
|
16080
|
+
if (exists(join12(dir, command)))
|
|
16116
16081
|
return true;
|
|
16117
16082
|
}
|
|
16118
16083
|
return false;
|
|
@@ -16132,7 +16097,7 @@ function labelForOverride(command) {
|
|
|
16132
16097
|
function detectWorktreeOpener(deps = {}) {
|
|
16133
16098
|
const env = deps.env ?? process.env;
|
|
16134
16099
|
const platform = deps.platform ?? process.platform;
|
|
16135
|
-
const exists = deps.exists ??
|
|
16100
|
+
const exists = deps.exists ?? existsSync10;
|
|
16136
16101
|
const override = env.KOBE_OPEN_EDITOR?.trim();
|
|
16137
16102
|
if (override) {
|
|
16138
16103
|
return { id: "env", label: labelForOverride(override), command: override, args: [] };
|
|
@@ -17950,7 +17915,7 @@ var exports_host = {};
|
|
|
17950
17915
|
__export(exports_host, {
|
|
17951
17916
|
startTasksPane: () => startTasksPane
|
|
17952
17917
|
});
|
|
17953
|
-
import { existsSync as
|
|
17918
|
+
import { existsSync as existsSync11 } from "fs";
|
|
17954
17919
|
import { TextAttributes as TextAttributes10 } from "@opentui/core";
|
|
17955
17920
|
function TasksShell(props) {
|
|
17956
17921
|
const themeCtx = useTheme();
|
|
@@ -18185,7 +18150,7 @@ function TasksShell(props) {
|
|
|
18185
18150
|
async function openSelectedWorktree(id) {
|
|
18186
18151
|
const task = props.tasks().find((t) => t.id === id);
|
|
18187
18152
|
let worktree = task?.worktreePath;
|
|
18188
|
-
if (!worktree || !
|
|
18153
|
+
if (!worktree || !existsSync11(worktree)) {
|
|
18189
18154
|
if (!props.orch) {
|
|
18190
18155
|
console.error("[kobe tasks] no daemon; cannot materialise worktree");
|
|
18191
18156
|
return;
|
|
@@ -18198,7 +18163,7 @@ function TasksShell(props) {
|
|
|
18198
18163
|
}
|
|
18199
18164
|
await props.reload();
|
|
18200
18165
|
}
|
|
18201
|
-
if (!worktree || !
|
|
18166
|
+
if (!worktree || !existsSync11(worktree))
|
|
18202
18167
|
return;
|
|
18203
18168
|
const opener = detectWorktreeOpener();
|
|
18204
18169
|
if (!opener) {
|
|
@@ -18249,7 +18214,7 @@ function TasksShell(props) {
|
|
|
18249
18214
|
const exists = await sessionExists(name);
|
|
18250
18215
|
if (exists) {
|
|
18251
18216
|
const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
|
|
18252
|
-
if (cwd2 &&
|
|
18217
|
+
if (cwd2 && existsSync11(cwd2)) {
|
|
18253
18218
|
await ensureSession({
|
|
18254
18219
|
name,
|
|
18255
18220
|
cwd: cwd2,
|
|
@@ -18263,7 +18228,7 @@ function TasksShell(props) {
|
|
|
18263
18228
|
return;
|
|
18264
18229
|
}
|
|
18265
18230
|
let cwd = task?.worktreePath;
|
|
18266
|
-
if (!cwd || !
|
|
18231
|
+
if (!cwd || !existsSync11(cwd)) {
|
|
18267
18232
|
if (!props.orch) {
|
|
18268
18233
|
console.error("[kobe tasks] no daemon; cannot materialise worktree");
|
|
18269
18234
|
return;
|
|
@@ -18276,7 +18241,7 @@ function TasksShell(props) {
|
|
|
18276
18241
|
}
|
|
18277
18242
|
await props.reload();
|
|
18278
18243
|
}
|
|
18279
|
-
if (!cwd || !
|
|
18244
|
+
if (!cwd || !existsSync11(cwd))
|
|
18280
18245
|
return;
|
|
18281
18246
|
const init2 = task?.repo ? resolveRepoInit(task.repo, cwd) : {};
|
|
18282
18247
|
const ready = await ensureSession({
|
|
@@ -19727,14 +19692,14 @@ var init_keys2 = __esm(() => {
|
|
|
19727
19692
|
|
|
19728
19693
|
// src/tui/panes/filetree/open-external.ts
|
|
19729
19694
|
import { spawn as spawn5 } from "child_process";
|
|
19730
|
-
import { existsSync as
|
|
19695
|
+
import { existsSync as existsSync12 } from "fs";
|
|
19731
19696
|
import { platform } from "os";
|
|
19732
19697
|
function openExternally(absPath) {
|
|
19733
19698
|
if (!absPath)
|
|
19734
19699
|
return;
|
|
19735
19700
|
const plat = platform();
|
|
19736
19701
|
if (plat === "linux") {
|
|
19737
|
-
if (
|
|
19702
|
+
if (existsSync12("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
|
|
19738
19703
|
spawnDetached("wslview", [absPath], () => {
|
|
19739
19704
|
const child = spawn5("wslpath", ["-w", absPath], { stdio: ["ignore", "pipe", "ignore"] });
|
|
19740
19705
|
let out = "";
|
|
@@ -21175,7 +21140,7 @@ async function ensureRepos(orchestrator) {
|
|
|
21175
21140
|
}
|
|
21176
21141
|
async function startDirectTmux() {
|
|
21177
21142
|
setClientLogContext("gui");
|
|
21178
|
-
Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd)).then((m) => m.
|
|
21143
|
+
Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd)).then((m) => m.ensureGlobalKobeHooks());
|
|
21179
21144
|
if (!await tmuxAvailable()) {
|
|
21180
21145
|
console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
|
|
21181
21146
|
process.exitCode = 1;
|
|
@@ -21769,9 +21734,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
|
|
|
21769
21734
|
var init_pulse = () => {};
|
|
21770
21735
|
|
|
21771
21736
|
// src/tui/lib/sound.ts
|
|
21772
|
-
import { existsSync as
|
|
21737
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
|
|
21773
21738
|
import { tmpdir as tmpdir2 } from "os";
|
|
21774
|
-
import { basename as basename6, isAbsolute as isAbsolute2, join as
|
|
21739
|
+
import { basename as basename6, isAbsolute as isAbsolute2, join as join13, resolve as resolve8 } from "path";
|
|
21775
21740
|
function args(player, file, volume) {
|
|
21776
21741
|
if (player === "ffplay")
|
|
21777
21742
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -21794,13 +21759,13 @@ function pickPlayer() {
|
|
|
21794
21759
|
return cachedPlayer;
|
|
21795
21760
|
const path12 = process.env.PATH ?? "";
|
|
21796
21761
|
const segments = path12.split(":").filter(Boolean);
|
|
21797
|
-
cachedPlayer = PLAYERS.find((p) => segments.some((dir) =>
|
|
21762
|
+
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync13(join13(dir, p)))) ?? null;
|
|
21798
21763
|
return cachedPlayer;
|
|
21799
21764
|
}
|
|
21800
21765
|
async function ensureAsset() {
|
|
21801
21766
|
cachedPath ??= (async () => {
|
|
21802
21767
|
mkdirSync5(DIR, { recursive: true });
|
|
21803
|
-
const dest =
|
|
21768
|
+
const dest = join13(DIR, basename6(pulseAsset));
|
|
21804
21769
|
const out = Bun.file(dest);
|
|
21805
21770
|
if (await out.exists())
|
|
21806
21771
|
return dest;
|
|
@@ -21830,7 +21795,7 @@ var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
|
21830
21795
|
var init_sound = __esm(() => {
|
|
21831
21796
|
init_pulse();
|
|
21832
21797
|
pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
|
|
21833
|
-
DIR =
|
|
21798
|
+
DIR = join13(tmpdir2(), "kobe-sfx");
|
|
21834
21799
|
PLAYERS = [
|
|
21835
21800
|
"ffplay",
|
|
21836
21801
|
"mpv",
|
package/package.json
CHANGED