@sma1lboy/kobe 0.7.17 → 0.7.18
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 +601 -150
- package/package.json +1 -1
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.
|
|
93
|
+
version: "0.7.18",
|
|
94
94
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
95
95
|
type: "module",
|
|
96
96
|
packageManager: "bun@1.3.13",
|
|
@@ -3918,7 +3918,8 @@ var init_protocol = __esm(() => {
|
|
|
3918
3918
|
"active-task",
|
|
3919
3919
|
"update",
|
|
3920
3920
|
"engine-state",
|
|
3921
|
-
"ui-prefs"
|
|
3921
|
+
"ui-prefs",
|
|
3922
|
+
"keybindings"
|
|
3922
3923
|
];
|
|
3923
3924
|
});
|
|
3924
3925
|
|
|
@@ -5131,6 +5132,16 @@ function kobeCliInvocation() {
|
|
|
5131
5132
|
var init_invocation = () => {};
|
|
5132
5133
|
|
|
5133
5134
|
// src/tmux/session-layout.ts
|
|
5135
|
+
function clampTasksPaneWidth(width) {
|
|
5136
|
+
if (!Number.isFinite(width))
|
|
5137
|
+
return TASKS_PANE_WIDTH;
|
|
5138
|
+
return Math.max(TASKS_PANE_WIDTH_MIN, Math.min(TASKS_PANE_WIDTH_MAX, Math.round(width)));
|
|
5139
|
+
}
|
|
5140
|
+
function clampPanePercent(percent) {
|
|
5141
|
+
if (!Number.isFinite(percent))
|
|
5142
|
+
return null;
|
|
5143
|
+
return Math.max(PANE_PERCENT_MIN, Math.min(PANE_PERCENT_MAX, Math.round(percent)));
|
|
5144
|
+
}
|
|
5134
5145
|
function shellQuote(s) {
|
|
5135
5146
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5136
5147
|
}
|
|
@@ -5201,7 +5212,7 @@ function opsPaneCommand(args) {
|
|
|
5201
5212
|
}
|
|
5202
5213
|
return fallbackOpsScript(args.cwd);
|
|
5203
5214
|
}
|
|
5204
|
-
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
5215
|
+
var TASKS_PANE_WIDTH = 32, TASKS_WIDTH_OPTION = "@kobe_tasks_width", TASKS_PANE_WIDTH_MIN = 16, TASKS_PANE_WIDTH_MAX = 120, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50, RIGHT_COLUMN_WIDTH_OPTION = "@kobe_right_width_pct", OPS_HEIGHT_OPTION = "@kobe_ops_height_pct", PANE_PERCENT_MIN = 10, PANE_PERCENT_MAX = 90;
|
|
5205
5216
|
|
|
5206
5217
|
// src/engine/claude-code-local/hook-adapter.ts
|
|
5207
5218
|
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -6317,8 +6328,10 @@ __export(exports_client, {
|
|
|
6317
6328
|
paneIdByRole: () => paneIdByRole,
|
|
6318
6329
|
newWindow: () => newWindow,
|
|
6319
6330
|
killSession: () => killSession,
|
|
6331
|
+
globalTasksPaneWidth: () => globalTasksPaneWidth,
|
|
6320
6332
|
getSessionOptions: () => getSessionOptions,
|
|
6321
6333
|
getSessionOption: () => getSessionOption,
|
|
6334
|
+
getServerOption: () => getServerOption,
|
|
6322
6335
|
ensureFallbackSession: () => ensureFallbackSession,
|
|
6323
6336
|
currentSessionName: () => currentSessionName,
|
|
6324
6337
|
claudePaneIdStrict: () => claudePaneIdStrict,
|
|
@@ -6452,6 +6465,15 @@ async function getSessionOptions(session, options) {
|
|
|
6452
6465
|
}
|
|
6453
6466
|
return values;
|
|
6454
6467
|
}
|
|
6468
|
+
async function getServerOption(option) {
|
|
6469
|
+
const { code, stdout } = await runTmuxCapturing(["show-options", "-sqv", option]);
|
|
6470
|
+
return code === 0 ? stdout.trim() : "";
|
|
6471
|
+
}
|
|
6472
|
+
async function globalTasksPaneWidth() {
|
|
6473
|
+
const raw = await getServerOption(TASKS_WIDTH_OPTION);
|
|
6474
|
+
const n = Number.parseInt(raw, 10);
|
|
6475
|
+
return Number.isFinite(n) && n > 0 ? clampTasksPaneWidth(n) : TASKS_PANE_WIDTH;
|
|
6476
|
+
}
|
|
6455
6477
|
async function tagPaneRole(paneId, role) {
|
|
6456
6478
|
await runTmux(["set-option", "-p", "-t", paneId, PANE_ROLE_OPTION, role]);
|
|
6457
6479
|
}
|
|
@@ -6551,6 +6573,7 @@ async function ensureFallbackSession() {
|
|
|
6551
6573
|
]);
|
|
6552
6574
|
const mainPane = r0.stdout.trim();
|
|
6553
6575
|
if (mainPane) {
|
|
6576
|
+
const tasksWidth = await globalTasksPaneWidth();
|
|
6554
6577
|
const r1 = await runTmuxCapturing([
|
|
6555
6578
|
"split-window",
|
|
6556
6579
|
"-h",
|
|
@@ -6558,7 +6581,7 @@ async function ensureFallbackSession() {
|
|
|
6558
6581
|
"-t",
|
|
6559
6582
|
mainPane,
|
|
6560
6583
|
"-l",
|
|
6561
|
-
`${
|
|
6584
|
+
`${tasksWidth}`,
|
|
6562
6585
|
"-c",
|
|
6563
6586
|
SAFE_SPAWN_CWD,
|
|
6564
6587
|
"-P",
|
|
@@ -7181,12 +7204,66 @@ var init_handlers = __esm(() => {
|
|
|
7181
7204
|
init_protocol();
|
|
7182
7205
|
});
|
|
7183
7206
|
|
|
7184
|
-
// ../kobe-daemon/src/daemon/
|
|
7185
|
-
import { mkdirSync as mkdirSync2,
|
|
7207
|
+
// ../kobe-daemon/src/daemon/keybindings-watcher.ts
|
|
7208
|
+
import { mkdirSync as mkdirSync2, watch } from "fs";
|
|
7186
7209
|
import { homedir as homedir13 } from "os";
|
|
7187
7210
|
import { basename as basename3, dirname as dirname5, join as join4 } from "path";
|
|
7188
|
-
function
|
|
7189
|
-
return join4(homeDir2, ".
|
|
7211
|
+
function defaultKeybindingsPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir13()) {
|
|
7212
|
+
return join4(homeDir2, ".kobe", "settings", "keybindings.yaml");
|
|
7213
|
+
}
|
|
7214
|
+
function startKeybindingsWatcher(bus, options = {}) {
|
|
7215
|
+
const debounceMs = options.debounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS;
|
|
7216
|
+
if (debounceMs <= 0)
|
|
7217
|
+
return () => {};
|
|
7218
|
+
const filePath = options.path ?? defaultKeybindingsPath();
|
|
7219
|
+
const dir = dirname5(filePath);
|
|
7220
|
+
const baseYaml = basename3(filePath);
|
|
7221
|
+
const baseYml = baseYaml.replace(/\.yaml$/, ".yml");
|
|
7222
|
+
let rev = 0;
|
|
7223
|
+
bus.publish("keybindings", { rev });
|
|
7224
|
+
let timer = null;
|
|
7225
|
+
const bump = () => {
|
|
7226
|
+
timer = null;
|
|
7227
|
+
try {
|
|
7228
|
+
rev += 1;
|
|
7229
|
+
bus.publish("keybindings", { rev });
|
|
7230
|
+
} catch (err) {
|
|
7231
|
+
logDaemonError("keybindings-watcher", err);
|
|
7232
|
+
}
|
|
7233
|
+
};
|
|
7234
|
+
let watcher = null;
|
|
7235
|
+
try {
|
|
7236
|
+
mkdirSync2(dir, { recursive: true });
|
|
7237
|
+
watcher = watch(dir, (_event, filename) => {
|
|
7238
|
+
if (filename !== null && filename !== baseYaml && filename !== baseYml)
|
|
7239
|
+
return;
|
|
7240
|
+
if (timer)
|
|
7241
|
+
clearTimeout(timer);
|
|
7242
|
+
timer = setTimeout(bump, debounceMs);
|
|
7243
|
+
timer.unref?.();
|
|
7244
|
+
});
|
|
7245
|
+
watcher.on("error", (err) => logDaemonError("keybindings-watcher", err));
|
|
7246
|
+
} catch (err) {
|
|
7247
|
+
logDaemonError("keybindings-watcher", err);
|
|
7248
|
+
}
|
|
7249
|
+
return () => {
|
|
7250
|
+
if (timer) {
|
|
7251
|
+
clearTimeout(timer);
|
|
7252
|
+
timer = null;
|
|
7253
|
+
}
|
|
7254
|
+
watcher?.close();
|
|
7255
|
+
watcher = null;
|
|
7256
|
+
};
|
|
7257
|
+
}
|
|
7258
|
+
var DEFAULT_KEYBINDINGS_DEBOUNCE_MS = 200;
|
|
7259
|
+
var init_keybindings_watcher = () => {};
|
|
7260
|
+
|
|
7261
|
+
// ../kobe-daemon/src/daemon/ui-prefs-watcher.ts
|
|
7262
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, watch as watch2 } from "fs";
|
|
7263
|
+
import { homedir as homedir14 } from "os";
|
|
7264
|
+
import { basename as basename4, dirname as dirname6, join as join5 } from "path";
|
|
7265
|
+
function defaultUiPrefsStatePath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir14()) {
|
|
7266
|
+
return join5(homeDir2, ".config", "kobe", "state.json");
|
|
7190
7267
|
}
|
|
7191
7268
|
function readUiPrefsFromStateFile(statePath2) {
|
|
7192
7269
|
let parsed = {};
|
|
@@ -7198,18 +7275,20 @@ function readUiPrefsFromStateFile(statePath2) {
|
|
|
7198
7275
|
const theme = typeof parsed.activeTheme === "string" && parsed.activeTheme.length > 0 ? parsed.activeTheme : "claude";
|
|
7199
7276
|
const transparentBackground = parsed.transparentBackground === true;
|
|
7200
7277
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOT_NAMES.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
7201
|
-
|
|
7278
|
+
const sortMode = parsed.activeSortMode === "recent" ? "recent" : "default";
|
|
7279
|
+
const keysCollapsed = parsed["tasksPane.keysCollapsed"] === true;
|
|
7280
|
+
return { theme, transparentBackground, focusAccent, sortMode, keysCollapsed };
|
|
7202
7281
|
}
|
|
7203
7282
|
function samePrefs(a, b) {
|
|
7204
|
-
return a.theme === b.theme && a.transparentBackground === b.transparentBackground && a.focusAccent === b.focusAccent;
|
|
7283
|
+
return a.theme === b.theme && a.transparentBackground === b.transparentBackground && a.focusAccent === b.focusAccent && a.sortMode === b.sortMode && a.keysCollapsed === b.keysCollapsed;
|
|
7205
7284
|
}
|
|
7206
7285
|
function startUiPrefsWatcher(bus, options = {}) {
|
|
7207
7286
|
const debounceMs = options.debounceMs ?? DEFAULT_UI_PREFS_DEBOUNCE_MS;
|
|
7208
7287
|
if (debounceMs <= 0)
|
|
7209
7288
|
return () => {};
|
|
7210
7289
|
const statePath2 = options.statePath ?? defaultUiPrefsStatePath();
|
|
7211
|
-
const stateDir =
|
|
7212
|
-
const stateFile =
|
|
7290
|
+
const stateDir = dirname6(statePath2);
|
|
7291
|
+
const stateFile = basename4(statePath2);
|
|
7213
7292
|
let last = readUiPrefsFromStateFile(statePath2);
|
|
7214
7293
|
bus.publish("ui-prefs", last);
|
|
7215
7294
|
let timer = null;
|
|
@@ -7227,8 +7306,8 @@ function startUiPrefsWatcher(bus, options = {}) {
|
|
|
7227
7306
|
};
|
|
7228
7307
|
let watcher = null;
|
|
7229
7308
|
try {
|
|
7230
|
-
|
|
7231
|
-
watcher =
|
|
7309
|
+
mkdirSync3(stateDir, { recursive: true });
|
|
7310
|
+
watcher = watch2(stateDir, (_event, filename) => {
|
|
7232
7311
|
if (filename !== null && filename !== stateFile)
|
|
7233
7312
|
return;
|
|
7234
7313
|
if (timer)
|
|
@@ -7257,7 +7336,7 @@ var init_ui_prefs_watcher = __esm(() => {
|
|
|
7257
7336
|
// ../kobe-daemon/src/daemon/server.ts
|
|
7258
7337
|
import { mkdir as mkdir6, readFile as readFile8, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
7259
7338
|
import { createServer } from "net";
|
|
7260
|
-
import { dirname as
|
|
7339
|
+
import { dirname as dirname7 } from "path";
|
|
7261
7340
|
function resolveIdleGraceMs() {
|
|
7262
7341
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
7263
7342
|
if (raw === undefined)
|
|
@@ -7306,8 +7385,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7306
7385
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
7307
7386
|
});
|
|
7308
7387
|
const activity = new DaemonActivityRegistry(bus);
|
|
7309
|
-
await mkdir6(
|
|
7310
|
-
await mkdir6(
|
|
7388
|
+
await mkdir6(dirname7(socketPath), { recursive: true });
|
|
7389
|
+
await mkdir6(dirname7(pidPath), { recursive: true });
|
|
7311
7390
|
await unlink4(socketPath).catch(() => {});
|
|
7312
7391
|
const server = createServer((socket) => {
|
|
7313
7392
|
const client = {
|
|
@@ -7353,6 +7432,10 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7353
7432
|
statePath: defaultUiPrefsStatePath(options.homeDir),
|
|
7354
7433
|
debounceMs: options.uiPrefsDebounceMs ?? DEFAULT_UI_PREFS_DEBOUNCE_MS
|
|
7355
7434
|
});
|
|
7435
|
+
const stopKeybindingsWatcher = startKeybindingsWatcher(bus, {
|
|
7436
|
+
path: defaultKeybindingsPath(options.homeDir),
|
|
7437
|
+
debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
|
|
7438
|
+
});
|
|
7356
7439
|
const serverApi = {
|
|
7357
7440
|
socketPath,
|
|
7358
7441
|
pidPath,
|
|
@@ -7366,6 +7449,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7366
7449
|
clearInterval(updateTimer);
|
|
7367
7450
|
stopAutoTitlePoller();
|
|
7368
7451
|
stopUiPrefsWatcher();
|
|
7452
|
+
stopKeybindingsWatcher();
|
|
7369
7453
|
activity.close();
|
|
7370
7454
|
broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
|
|
7371
7455
|
for (const client of Array.from(clients)) {
|
|
@@ -7485,6 +7569,7 @@ var init_server = __esm(() => {
|
|
|
7485
7569
|
init_activity_registry();
|
|
7486
7570
|
init_auto_title_poller();
|
|
7487
7571
|
init_handlers();
|
|
7572
|
+
init_keybindings_watcher();
|
|
7488
7573
|
init_paths2();
|
|
7489
7574
|
init_protocol();
|
|
7490
7575
|
init_ui_prefs_watcher();
|
|
@@ -7557,14 +7642,14 @@ __export(exports_daemon_process, {
|
|
|
7557
7642
|
connectIfRunning: () => connectIfRunning
|
|
7558
7643
|
});
|
|
7559
7644
|
import { spawn as spawn2 } from "child_process";
|
|
7560
|
-
import { closeSync, existsSync as existsSync5, mkdirSync as
|
|
7561
|
-
import { dirname as
|
|
7645
|
+
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync } from "fs";
|
|
7646
|
+
import { dirname as dirname8, resolve as resolve2 } from "path";
|
|
7562
7647
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7563
7648
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
7564
7649
|
let stdio = "ignore";
|
|
7565
7650
|
let logFd;
|
|
7566
7651
|
try {
|
|
7567
|
-
|
|
7652
|
+
mkdirSync4(dirname8(logPath), { recursive: true });
|
|
7568
7653
|
logFd = openSync(logPath, "a");
|
|
7569
7654
|
stdio = ["ignore", logFd, logFd];
|
|
7570
7655
|
} catch {
|
|
@@ -7631,7 +7716,7 @@ function resolveKobeSpawn(subcommand) {
|
|
|
7631
7716
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
7632
7717
|
return [process.execPath, ...subcommand];
|
|
7633
7718
|
}
|
|
7634
|
-
const dir =
|
|
7719
|
+
const dir = dirname8(here);
|
|
7635
7720
|
const candidates = [
|
|
7636
7721
|
resolve2(dir, "../cli/index.ts"),
|
|
7637
7722
|
resolve2(dir, "../../../kobe/src/cli/index.ts"),
|
|
@@ -7730,13 +7815,13 @@ async function runRepoSubcommand(args) {
|
|
|
7730
7815
|
}
|
|
7731
7816
|
const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
|
|
7732
7817
|
const { existsSync: existsSync6 } = await import("fs");
|
|
7733
|
-
const { join:
|
|
7818
|
+
const { join: join6 } = await import("path");
|
|
7734
7819
|
if (verb === "show") {
|
|
7735
7820
|
const [pathArg] = rest.filter((a) => !a.startsWith("-"));
|
|
7736
7821
|
const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
|
|
7737
7822
|
const override = getRepoInitOverride2(repo);
|
|
7738
|
-
const hasFileScript = existsSync6(
|
|
7739
|
-
const hasFilePrompt = existsSync6(
|
|
7823
|
+
const hasFileScript = existsSync6(join6(repo, ".kobe", "init.sh"));
|
|
7824
|
+
const hasFilePrompt = existsSync6(join6(repo, ".kobe", "init-prompt.md"));
|
|
7740
7825
|
console.log(`repo: ${repo}`);
|
|
7741
7826
|
console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
|
|
7742
7827
|
console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
|
|
@@ -8009,6 +8094,9 @@ var init_daemon_session = __esm(() => {
|
|
|
8009
8094
|
|
|
8010
8095
|
// src/state/keybindings-file.ts
|
|
8011
8096
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
8097
|
+
function resetKeybindingsFileCache() {
|
|
8098
|
+
cached = null;
|
|
8099
|
+
}
|
|
8012
8100
|
function resolveConfigFile() {
|
|
8013
8101
|
const canonical = keybindingsConfigPath();
|
|
8014
8102
|
if (existsSync6(canonical))
|
|
@@ -8399,6 +8487,9 @@ function resolveTmuxKeyEntries(entries) {
|
|
|
8399
8487
|
function tmuxChordOptsFor(id) {
|
|
8400
8488
|
return id.startsWith("tmux.") ? { allowShiftCharacter: true } : {};
|
|
8401
8489
|
}
|
|
8490
|
+
function resetTmuxKeysCache() {
|
|
8491
|
+
cached2 = null;
|
|
8492
|
+
}
|
|
8402
8493
|
function resolveUserTmuxKeys() {
|
|
8403
8494
|
if (cached2)
|
|
8404
8495
|
return cached2;
|
|
@@ -9948,9 +10039,9 @@ var init_schema = () => {};
|
|
|
9948
10039
|
|
|
9949
10040
|
// src/tui/context/theme/loader.ts
|
|
9950
10041
|
import { readFileSync as readFileSync6, readdirSync } from "fs";
|
|
9951
|
-
import { join as
|
|
10042
|
+
import { join as join6 } from "path";
|
|
9952
10043
|
function userThemesDir() {
|
|
9953
|
-
return
|
|
10044
|
+
return join6(kobeStateDir(), "themes");
|
|
9954
10045
|
}
|
|
9955
10046
|
function loadUserThemes() {
|
|
9956
10047
|
const dir = userThemesDir();
|
|
@@ -9964,7 +10055,7 @@ function loadUserThemes() {
|
|
|
9964
10055
|
for (const file of entries) {
|
|
9965
10056
|
if (!file.endsWith(".json"))
|
|
9966
10057
|
continue;
|
|
9967
|
-
const path11 =
|
|
10058
|
+
const path11 = join6(dir, file);
|
|
9968
10059
|
let parsed;
|
|
9969
10060
|
try {
|
|
9970
10061
|
const text = readFileSync6(path11, "utf8");
|
|
@@ -10166,11 +10257,106 @@ async function relaunchEngineInAllWindows(session, cwd, command, remoteKey) {
|
|
|
10166
10257
|
return true;
|
|
10167
10258
|
}
|
|
10168
10259
|
async function healTaskPaneWidths(session) {
|
|
10169
|
-
const
|
|
10170
|
-
|
|
10260
|
+
const target = await globalTasksPaneWidth();
|
|
10261
|
+
const { code, stdout } = await runTmuxCapturing([
|
|
10262
|
+
"list-panes",
|
|
10263
|
+
"-s",
|
|
10264
|
+
"-t",
|
|
10265
|
+
`=${session}`,
|
|
10266
|
+
"-F",
|
|
10267
|
+
"#{pane_id}\t#{@kobe_role}\t#{pane_width}"
|
|
10268
|
+
]);
|
|
10269
|
+
if (code !== 0)
|
|
10270
|
+
return;
|
|
10271
|
+
const mismatched = stdout.split(`
|
|
10272
|
+
`).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "tasks").filter(([, , width]) => Number.parseInt(width?.trim() ?? "", 10) !== target).map(([id]) => id?.trim()).filter((id) => !!id);
|
|
10273
|
+
if (mismatched.length === 0)
|
|
10171
10274
|
return;
|
|
10172
|
-
|
|
10173
|
-
|
|
10275
|
+
await runTmuxSequence(mismatched.map((pane) => ["resize-pane", "-t", pane, "-x", `${target}`]));
|
|
10276
|
+
}
|
|
10277
|
+
async function rightColumnPercents() {
|
|
10278
|
+
const [width, height] = await Promise.all([
|
|
10279
|
+
getServerOption(RIGHT_COLUMN_WIDTH_OPTION),
|
|
10280
|
+
getServerOption(OPS_HEIGHT_OPTION)
|
|
10281
|
+
]);
|
|
10282
|
+
return {
|
|
10283
|
+
widthPct: clampPanePercent(Number.parseInt(width, 10)),
|
|
10284
|
+
heightPct: clampPanePercent(Number.parseInt(height, 10))
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
function rightColumnResizeArgs(geom) {
|
|
10288
|
+
const args = [];
|
|
10289
|
+
if (geom.widthPct !== null)
|
|
10290
|
+
args.push("-x", `${geom.widthPct}%`);
|
|
10291
|
+
if (geom.heightPct !== null)
|
|
10292
|
+
args.push("-y", `${geom.heightPct}%`);
|
|
10293
|
+
return args;
|
|
10294
|
+
}
|
|
10295
|
+
async function globalRightColumnResizeArgs() {
|
|
10296
|
+
return rightColumnResizeArgs(await rightColumnPercents());
|
|
10297
|
+
}
|
|
10298
|
+
async function healRightColumn(session) {
|
|
10299
|
+
const args = await globalRightColumnResizeArgs();
|
|
10300
|
+
if (args.length === 0)
|
|
10301
|
+
return;
|
|
10302
|
+
const { code, stdout } = await runTmuxCapturing([
|
|
10303
|
+
"list-panes",
|
|
10304
|
+
"-s",
|
|
10305
|
+
"-t",
|
|
10306
|
+
`=${session}`,
|
|
10307
|
+
"-F",
|
|
10308
|
+
"#{pane_id}\t#{@kobe_role}"
|
|
10309
|
+
]);
|
|
10310
|
+
if (code !== 0)
|
|
10311
|
+
return;
|
|
10312
|
+
const opsPanes = stdout.split(`
|
|
10313
|
+
`).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "ops").map(([id]) => id?.trim()).filter((id) => !!id);
|
|
10314
|
+
if (opsPanes.length === 0)
|
|
10315
|
+
return;
|
|
10316
|
+
await runTmuxSequence(opsPanes.map((pane) => ["resize-pane", "-t", pane, ...args]));
|
|
10317
|
+
}
|
|
10318
|
+
async function healSessionLayout(session) {
|
|
10319
|
+
if (!await sessionExists(session))
|
|
10320
|
+
return;
|
|
10321
|
+
await healTaskPaneWidths(session);
|
|
10322
|
+
await healRightColumn(session);
|
|
10323
|
+
}
|
|
10324
|
+
async function captureGlobalLayout(session) {
|
|
10325
|
+
const { code, stdout } = await runTmuxCapturing([
|
|
10326
|
+
"list-panes",
|
|
10327
|
+
"-t",
|
|
10328
|
+
`=${session}`,
|
|
10329
|
+
"-F",
|
|
10330
|
+
"#{@kobe_role}\t#{pane_width}\t#{pane_height}\t#{window_width}\t#{window_height}"
|
|
10331
|
+
]);
|
|
10332
|
+
if (code !== 0)
|
|
10333
|
+
return;
|
|
10334
|
+
const rows = stdout.split(`
|
|
10335
|
+
`).map((line) => line.split("\t")).filter((cols) => (cols[0]?.trim() ?? "") !== "");
|
|
10336
|
+
if (rows.length === 0)
|
|
10337
|
+
return;
|
|
10338
|
+
const winW = Number.parseInt(rows[0][3]?.trim() ?? "", 10);
|
|
10339
|
+
const winH = Number.parseInt(rows[0][4]?.trim() ?? "", 10);
|
|
10340
|
+
const sets = [];
|
|
10341
|
+
const tasks = rows.find(([role]) => role?.trim() === "tasks");
|
|
10342
|
+
if (tasks) {
|
|
10343
|
+
const width = Number.parseInt(tasks[1]?.trim() ?? "", 10);
|
|
10344
|
+
if (Number.isFinite(width) && width > 0)
|
|
10345
|
+
sets.push(["set-option", "-s", TASKS_WIDTH_OPTION, `${clampTasksPaneWidth(width)}`]);
|
|
10346
|
+
}
|
|
10347
|
+
const ops = rows.find(([role]) => role?.trim() === "ops");
|
|
10348
|
+
if (ops && Number.isFinite(winW) && winW > 0 && Number.isFinite(winH) && winH > 0) {
|
|
10349
|
+
const opsW = Number.parseInt(ops[1]?.trim() ?? "", 10);
|
|
10350
|
+
const opsH = Number.parseInt(ops[2]?.trim() ?? "", 10);
|
|
10351
|
+
const widthPct = Number.isFinite(opsW) ? clampPanePercent(100 * opsW / winW) : null;
|
|
10352
|
+
const heightPct = Number.isFinite(opsH) ? clampPanePercent(100 * opsH / winH) : null;
|
|
10353
|
+
if (widthPct !== null)
|
|
10354
|
+
sets.push(["set-option", "-s", RIGHT_COLUMN_WIDTH_OPTION, `${widthPct}`]);
|
|
10355
|
+
if (heightPct !== null)
|
|
10356
|
+
sets.push(["set-option", "-s", OPS_HEIGHT_OPTION, `${heightPct}`]);
|
|
10357
|
+
}
|
|
10358
|
+
if (sets.length > 0)
|
|
10359
|
+
await runTmuxSequence(sets);
|
|
10174
10360
|
}
|
|
10175
10361
|
async function healKobePaneVersions(session, cwd, taskId, vendor) {
|
|
10176
10362
|
const rows = await listKobePanes(session);
|
|
@@ -10253,6 +10439,7 @@ function kobeStatusRight(keys) {
|
|
|
10253
10439
|
}
|
|
10254
10440
|
async function buildPanesAround(claudePane, args) {
|
|
10255
10441
|
const envPrefix = inheritedEnvPrefix();
|
|
10442
|
+
const tasksWidth = await globalTasksPaneWidth();
|
|
10256
10443
|
const opsCmd = keepAlive(args.opsCommand ?? envPrefix + opsPaneCommand({
|
|
10257
10444
|
cwd: args.cwd,
|
|
10258
10445
|
taskId: args.taskId,
|
|
@@ -10270,7 +10457,7 @@ async function buildPanesAround(claudePane, args) {
|
|
|
10270
10457
|
"-t",
|
|
10271
10458
|
claudePane,
|
|
10272
10459
|
"-l",
|
|
10273
|
-
`${
|
|
10460
|
+
`${tasksWidth}`,
|
|
10274
10461
|
"-c",
|
|
10275
10462
|
localSpawnCwd(args.cwd),
|
|
10276
10463
|
"-P",
|
|
@@ -10303,6 +10490,11 @@ async function buildPanesAround(claudePane, args) {
|
|
|
10303
10490
|
...ids.ops ? [["set-option", "-p", "-t", ids.ops, "@kobe_role", "ops"]] : [],
|
|
10304
10491
|
...ids.ops ? [["set-option", "-p", "-t", ids.ops, PANE_VERSION_OPTION, CURRENT_VERSION]] : []
|
|
10305
10492
|
]);
|
|
10493
|
+
if (ids.ops) {
|
|
10494
|
+
const rcArgs = await globalRightColumnResizeArgs();
|
|
10495
|
+
if (rcArgs.length > 0)
|
|
10496
|
+
await runTmux(["resize-pane", "-t", ids.ops, ...rcArgs]);
|
|
10497
|
+
}
|
|
10306
10498
|
}
|
|
10307
10499
|
async function newChatTab(session, vendorOverride) {
|
|
10308
10500
|
if (!await sessionExists(session))
|
|
@@ -10350,6 +10542,16 @@ async function openSettingsTab(session) {
|
|
|
10350
10542
|
const command = `${envPrefix}${inv.map(shellQuote).join(" ")} settings`;
|
|
10351
10543
|
await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "settings" });
|
|
10352
10544
|
}
|
|
10545
|
+
async function openHelpTab(session) {
|
|
10546
|
+
if (!await sessionExists(session))
|
|
10547
|
+
return;
|
|
10548
|
+
const sessionOptions = await getSessionOptions(session, ["@kobe_worktree"]);
|
|
10549
|
+
const cwd = sessionOptions["@kobe_worktree"] || process.cwd();
|
|
10550
|
+
const inv = kobeCliInvocation();
|
|
10551
|
+
const envPrefix = inheritedEnvPrefix();
|
|
10552
|
+
const command = `${envPrefix}${inv.map(shellQuote).join(" ")} help-page`;
|
|
10553
|
+
await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "help" });
|
|
10554
|
+
}
|
|
10353
10555
|
async function openNewTaskTab(session, defaultRepo) {
|
|
10354
10556
|
if (!await sessionExists(session))
|
|
10355
10557
|
return;
|
|
@@ -10422,18 +10624,22 @@ __export(exports_tmux, {
|
|
|
10422
10624
|
selectTasksPane: () => selectTasksPane,
|
|
10423
10625
|
refreshKobeWorkspacePanes: () => refreshKobeWorkspacePanes,
|
|
10424
10626
|
quickCreate: () => quickCreate,
|
|
10627
|
+
prepareWindowForAttach: () => prepareWindowForAttach,
|
|
10425
10628
|
openUpdateTab: () => openUpdateTab,
|
|
10426
10629
|
openSettingsTab: () => openSettingsTab,
|
|
10427
10630
|
openNewTaskTab: () => openNewTaskTab,
|
|
10631
|
+
openHelpTab: () => openHelpTab,
|
|
10428
10632
|
newChatTab: () => newChatTab,
|
|
10429
10633
|
kobeStatusRight: () => kobeStatusRight,
|
|
10430
10634
|
killSession: () => killSession,
|
|
10635
|
+
healSessionLayout: () => healSessionLayout,
|
|
10431
10636
|
ensureSession: () => ensureSession,
|
|
10432
10637
|
currentSessionName: () => currentSessionName,
|
|
10433
10638
|
chatTabSwitchBindings: () => chatTabSwitchBindings,
|
|
10434
10639
|
chatTabRenameBinding: () => chatTabRenameBinding,
|
|
10435
10640
|
chatTabCloseBinding: () => chatTabCloseBinding,
|
|
10436
10641
|
chatTabChooseEngineBindings: () => chatTabChooseEngineBindings,
|
|
10642
|
+
captureGlobalLayout: () => captureGlobalLayout,
|
|
10437
10643
|
attachArgv: () => attachArgv,
|
|
10438
10644
|
PANE_VERSION_OPTION: () => PANE_VERSION_OPTION,
|
|
10439
10645
|
CHAT_TAB_STATUS_FORMAT: () => CHAT_TAB_STATUS_FORMAT,
|
|
@@ -10450,6 +10656,13 @@ function positiveInt(value) {
|
|
|
10450
10656
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
10451
10657
|
return Number.isInteger(n) && n > 0 ? n : undefined;
|
|
10452
10658
|
}
|
|
10659
|
+
async function prepareWindowForAttach(session) {
|
|
10660
|
+
const sizeArgs = tmuxInitialSizeArgs();
|
|
10661
|
+
if (sizeArgs.length > 0)
|
|
10662
|
+
await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
|
|
10663
|
+
await healTaskPaneWidths(session);
|
|
10664
|
+
await healRightColumn(session);
|
|
10665
|
+
}
|
|
10453
10666
|
async function ensureSession(opts) {
|
|
10454
10667
|
const inflight = ensureSessionLocks.get(opts.name);
|
|
10455
10668
|
if (inflight)
|
|
@@ -10483,6 +10696,7 @@ async function ensureSessionImpl(opts) {
|
|
|
10483
10696
|
const remoteKey = remoteKeyForRepo(opts.repo);
|
|
10484
10697
|
if (action.kind === "reuse") {
|
|
10485
10698
|
await healTaskPaneWidths(opts.name);
|
|
10699
|
+
await healRightColumn(opts.name);
|
|
10486
10700
|
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10487
10701
|
return true;
|
|
10488
10702
|
}
|
|
@@ -10491,6 +10705,7 @@ async function ensureSessionImpl(opts) {
|
|
|
10491
10705
|
if (opts.vendor)
|
|
10492
10706
|
await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
|
|
10493
10707
|
await healTaskPaneWidths(opts.name);
|
|
10708
|
+
await healRightColumn(opts.name);
|
|
10494
10709
|
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10495
10710
|
return true;
|
|
10496
10711
|
}
|
|
@@ -10544,6 +10759,8 @@ async function ensureSessionImpl(opts) {
|
|
|
10544
10759
|
const chooseEngineTmuxCommand = `run-shell ${shellQuote(chooseEngineCommand)}`;
|
|
10545
10760
|
const focusTasksCommand = `${envStr}${invStr} focus-tasks --session '#{session_name}'`;
|
|
10546
10761
|
const focusTasksTmuxCommand = `run-shell ${shellQuote(focusTasksCommand)}`;
|
|
10762
|
+
const healLayoutCommand = `${envStr}${invStr} heal-layout --session '#{session_name}'`;
|
|
10763
|
+
const healLayoutTmuxCommand = `run-shell -b ${shellQuote(healLayoutCommand)}`;
|
|
10547
10764
|
const userKeys = resolveUserTmuxKeys();
|
|
10548
10765
|
const unbinds = [];
|
|
10549
10766
|
if (userKeys.overridden.has(TMUX_FOCUS_ID)) {
|
|
@@ -10585,6 +10802,7 @@ async function ensureSessionImpl(opts) {
|
|
|
10585
10802
|
})
|
|
10586
10803
|
],
|
|
10587
10804
|
["set-option", "-g", "mouse", "on"],
|
|
10805
|
+
["set-hook", "-g", "window-resized", healLayoutTmuxCommand],
|
|
10588
10806
|
...unbinds,
|
|
10589
10807
|
...b["tmux.detach"] ? [
|
|
10590
10808
|
[
|
|
@@ -10648,12 +10866,12 @@ __export(exports_repo_init, {
|
|
|
10648
10866
|
resolveRepoInit: () => resolveRepoInit
|
|
10649
10867
|
});
|
|
10650
10868
|
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
10651
|
-
import { join as
|
|
10869
|
+
import { join as join7 } from "path";
|
|
10652
10870
|
function repoFileScript(worktreePath) {
|
|
10653
|
-
return existsSync7(
|
|
10871
|
+
return existsSync7(join7(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
|
|
10654
10872
|
}
|
|
10655
10873
|
function repoFilePrompt(worktreePath) {
|
|
10656
|
-
const p =
|
|
10874
|
+
const p = join7(worktreePath, INIT_PROMPT_REL);
|
|
10657
10875
|
if (!existsSync7(p))
|
|
10658
10876
|
return;
|
|
10659
10877
|
try {
|
|
@@ -10675,8 +10893,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
|
|
|
10675
10893
|
var INIT_SCRIPT_REL, INIT_PROMPT_REL;
|
|
10676
10894
|
var init_repo_init = __esm(() => {
|
|
10677
10895
|
init_repos();
|
|
10678
|
-
INIT_SCRIPT_REL =
|
|
10679
|
-
INIT_PROMPT_REL =
|
|
10896
|
+
INIT_SCRIPT_REL = join7(".kobe", "init.sh");
|
|
10897
|
+
INIT_PROMPT_REL = join7(".kobe", "init-prompt.md");
|
|
10680
10898
|
});
|
|
10681
10899
|
|
|
10682
10900
|
// src/tui/panes/sidebar/worktree-changes.ts
|
|
@@ -11717,8 +11935,8 @@ var exports_theme = {};
|
|
|
11717
11935
|
__export(exports_theme, {
|
|
11718
11936
|
runThemeSubcommand: () => runThemeSubcommand
|
|
11719
11937
|
});
|
|
11720
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
11721
|
-
import { basename as
|
|
11938
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
11939
|
+
import { basename as basename5, join as join8, resolve as resolve5 } from "path";
|
|
11722
11940
|
function fail3(message) {
|
|
11723
11941
|
process.stderr.write(`kobe theme: ${message}
|
|
11724
11942
|
`);
|
|
@@ -11749,7 +11967,7 @@ function listThemes() {
|
|
|
11749
11967
|
} else {
|
|
11750
11968
|
for (const f of userFiles) {
|
|
11751
11969
|
const name = f.slice(0, -".json".length);
|
|
11752
|
-
const path11 =
|
|
11970
|
+
const path11 = join8(dir, f);
|
|
11753
11971
|
const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
|
|
11754
11972
|
lines.push(` ${name}${overridesBundled} ${path11}`);
|
|
11755
11973
|
}
|
|
@@ -11771,7 +11989,7 @@ async function readSource(source) {
|
|
|
11771
11989
|
}
|
|
11772
11990
|
const text2 = await res.text();
|
|
11773
11991
|
const cleanPath = source.split(/[?#]/)[0] ?? source;
|
|
11774
|
-
const file2 =
|
|
11992
|
+
const file2 = basename5(cleanPath) || "theme.json";
|
|
11775
11993
|
const defaultName2 = file2.endsWith(".json") ? file2.slice(0, -".json".length) : file2;
|
|
11776
11994
|
return { text: text2, defaultName: defaultName2 };
|
|
11777
11995
|
}
|
|
@@ -11782,7 +12000,7 @@ async function readSource(source) {
|
|
|
11782
12000
|
} catch (err) {
|
|
11783
12001
|
fail3(`failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11784
12002
|
}
|
|
11785
|
-
const file =
|
|
12003
|
+
const file = basename5(abs);
|
|
11786
12004
|
const defaultName = file.endsWith(".json") ? file.slice(0, -".json".length) : file;
|
|
11787
12005
|
return { text, defaultName };
|
|
11788
12006
|
}
|
|
@@ -11838,8 +12056,8 @@ async function addTheme(args) {
|
|
|
11838
12056
|
fail3(`invalid theme name "${name}" (use letters, digits, '.', '_', '-')`);
|
|
11839
12057
|
}
|
|
11840
12058
|
const dir = userThemesDir();
|
|
11841
|
-
|
|
11842
|
-
const dest =
|
|
12059
|
+
mkdirSync5(dir, { recursive: true });
|
|
12060
|
+
const dest = join8(dir, `${name}.json`);
|
|
11843
12061
|
if (existsSync8(dest) && !opts.force) {
|
|
11844
12062
|
fail3(`${dest} already exists (pass --force to overwrite)`);
|
|
11845
12063
|
}
|
|
@@ -11857,7 +12075,7 @@ function removeTheme(args) {
|
|
|
11857
12075
|
if (BUNDLED_NAMES.includes(name)) {
|
|
11858
12076
|
fail3(`"${name}" is a built-in theme and cannot be removed`);
|
|
11859
12077
|
}
|
|
11860
|
-
const dest =
|
|
12078
|
+
const dest = join8(userThemesDir(), `${name}.json`);
|
|
11861
12079
|
if (!existsSync8(dest)) {
|
|
11862
12080
|
fail3(`no user theme named "${name}" (looked for ${dest})`);
|
|
11863
12081
|
}
|
|
@@ -12018,9 +12236,9 @@ var init_feedback_cmd = __esm(() => {
|
|
|
12018
12236
|
});
|
|
12019
12237
|
|
|
12020
12238
|
// src/core/index.ts
|
|
12021
|
-
import { homedir as
|
|
12239
|
+
import { homedir as homedir15 } from "os";
|
|
12022
12240
|
async function createKobeCore(options = {}) {
|
|
12023
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
12241
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir15();
|
|
12024
12242
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
12025
12243
|
await store.load();
|
|
12026
12244
|
const worktrees = new GitWorktreeManager;
|
|
@@ -12138,8 +12356,8 @@ var init_daemon_cmd = __esm(() => {
|
|
|
12138
12356
|
|
|
12139
12357
|
// src/lib/skill-install.ts
|
|
12140
12358
|
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
12141
|
-
import { homedir as
|
|
12142
|
-
import { join as
|
|
12359
|
+
import { homedir as homedir16 } from "os";
|
|
12360
|
+
import { join as join9 } from "path";
|
|
12143
12361
|
function npxSkillsArgv(opts = {}) {
|
|
12144
12362
|
return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
|
|
12145
12363
|
}
|
|
@@ -12147,9 +12365,9 @@ function npxSkillsCommand(opts = {}) {
|
|
|
12147
12365
|
return `npx ${npxSkillsArgv(opts).join(" ")}`;
|
|
12148
12366
|
}
|
|
12149
12367
|
function kobeSkillPaths(opts = {}) {
|
|
12150
|
-
const home = opts.home ??
|
|
12368
|
+
const home = opts.home ?? homedir16();
|
|
12151
12369
|
const cwd = opts.cwd ?? process.cwd();
|
|
12152
|
-
return [
|
|
12370
|
+
return [join9(home, SKILL_REL_PATH), join9(cwd, SKILL_REL_PATH)];
|
|
12153
12371
|
}
|
|
12154
12372
|
function parseSkillVersion(content) {
|
|
12155
12373
|
const m = content.match(/kobe-skill-version:\s*(\d+)/);
|
|
@@ -12210,7 +12428,7 @@ __export(exports_maintenance, {
|
|
|
12210
12428
|
});
|
|
12211
12429
|
import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
12212
12430
|
import { unlink as unlink6 } from "fs/promises";
|
|
12213
|
-
import { join as
|
|
12431
|
+
import { join as join10 } from "path";
|
|
12214
12432
|
import { createInterface as createInterface2 } from "readline";
|
|
12215
12433
|
function isProcessAlive2(pid) {
|
|
12216
12434
|
try {
|
|
@@ -12307,7 +12525,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
12307
12525
|
const socketPath = defaultDaemonSocketPath();
|
|
12308
12526
|
const pidPath = defaultDaemonPidPath();
|
|
12309
12527
|
const logPath = defaultDaemonLogPath();
|
|
12310
|
-
const tasksPath =
|
|
12528
|
+
const tasksPath = join10(kobeStateDir(), "tasks.json");
|
|
12311
12529
|
const statePath2 = kvStatePath();
|
|
12312
12530
|
const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
|
|
12313
12531
|
const status = await probeDaemonStatus(socketPath);
|
|
@@ -12426,7 +12644,7 @@ async function runResetSubcommand(argv) {
|
|
|
12426
12644
|
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
12427
12645
|
const socketPath = defaultDaemonSocketPath();
|
|
12428
12646
|
const pidPath = defaultDaemonPidPath();
|
|
12429
|
-
const tasksPath =
|
|
12647
|
+
const tasksPath = join10(kobeStateDir(), "tasks.json");
|
|
12430
12648
|
const statePath2 = kvStatePath();
|
|
12431
12649
|
console.log("kobe reset will:");
|
|
12432
12650
|
console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
|
|
@@ -12696,15 +12914,15 @@ var GIT_TIMEOUT_MS = 15000;
|
|
|
12696
12914
|
|
|
12697
12915
|
// src/web/notes.ts
|
|
12698
12916
|
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
12699
|
-
import { join as
|
|
12917
|
+
import { join as join11 } from "path";
|
|
12700
12918
|
function notesDir() {
|
|
12701
|
-
return
|
|
12919
|
+
return join11(kobeStateDir(), "notes");
|
|
12702
12920
|
}
|
|
12703
12921
|
function isSafeTaskId(taskId) {
|
|
12704
12922
|
return typeof taskId === "string" && taskId.length > 0 && /^[A-Za-z0-9_-]+$/.test(taskId);
|
|
12705
12923
|
}
|
|
12706
12924
|
function noteFilePath(taskId) {
|
|
12707
|
-
return
|
|
12925
|
+
return join11(notesDir(), `${taskId}.md`);
|
|
12708
12926
|
}
|
|
12709
12927
|
async function handleGet(url) {
|
|
12710
12928
|
const taskId = url.searchParams.get("taskId");
|
|
@@ -12950,7 +13168,7 @@ var init_session = __esm(() => {
|
|
|
12950
13168
|
|
|
12951
13169
|
// ../kobe-web/server/bridge.ts
|
|
12952
13170
|
import { existsSync as existsSync11 } from "fs";
|
|
12953
|
-
import { join as
|
|
13171
|
+
import { join as join12, normalize as normalize2 } from "path";
|
|
12954
13172
|
function sseResponse(register) {
|
|
12955
13173
|
let unregister = null;
|
|
12956
13174
|
let heartbeat = null;
|
|
@@ -13024,10 +13242,10 @@ async function specResponse(url, link, spec) {
|
|
|
13024
13242
|
}
|
|
13025
13243
|
async function staticResponse(pathname, staticDir) {
|
|
13026
13244
|
const rel = pathname === "/" ? "/index.html" : pathname;
|
|
13027
|
-
const resolved = normalize2(
|
|
13245
|
+
const resolved = normalize2(join12(staticDir, rel));
|
|
13028
13246
|
if (!resolved.startsWith(staticDir))
|
|
13029
13247
|
return new Response("forbidden", { status: 403 });
|
|
13030
|
-
const file = Bun.file(existsSync11(resolved) ? resolved :
|
|
13248
|
+
const file = Bun.file(existsSync11(resolved) ? resolved : join12(staticDir, "index.html"));
|
|
13031
13249
|
if (!await file.exists()) {
|
|
13032
13250
|
return new Response("kobe web assets not built \u2014 run `bun --filter kobe-web build`", { status: 503 });
|
|
13033
13251
|
}
|
|
@@ -13424,8 +13642,8 @@ __export(exports_hook_cmd, {
|
|
|
13424
13642
|
parseWorktreeAddPath: () => parseWorktreeAddPath,
|
|
13425
13643
|
ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
|
|
13426
13644
|
});
|
|
13427
|
-
import { homedir as
|
|
13428
|
-
import { join as
|
|
13645
|
+
import { homedir as homedir17 } from "os";
|
|
13646
|
+
import { join as join13, resolve as resolve7 } from "path";
|
|
13429
13647
|
async function readStdinPayload() {
|
|
13430
13648
|
try {
|
|
13431
13649
|
const text = await Promise.race([
|
|
@@ -13545,7 +13763,7 @@ function activityHookAdapters() {
|
|
|
13545
13763
|
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsHooks());
|
|
13546
13764
|
}
|
|
13547
13765
|
function globalSettingsPath() {
|
|
13548
|
-
return
|
|
13766
|
+
return join13(homedir17(), ".claude", "settings.json");
|
|
13549
13767
|
}
|
|
13550
13768
|
function persistedSyncPath(stored) {
|
|
13551
13769
|
if (!stored || stored === "off")
|
|
@@ -13553,7 +13771,7 @@ function persistedSyncPath(stored) {
|
|
|
13553
13771
|
if (stored === "global")
|
|
13554
13772
|
return globalSettingsPath();
|
|
13555
13773
|
if (stored.startsWith("repo:"))
|
|
13556
|
-
return
|
|
13774
|
+
return join13(resolve7(stored.slice(5)), ".claude", "settings.json");
|
|
13557
13775
|
return stored;
|
|
13558
13776
|
}
|
|
13559
13777
|
async function ensureGlobalKobeHooks() {
|
|
@@ -14897,6 +15115,8 @@ class RemoteOrchestrator {
|
|
|
14897
15115
|
setEngineStateSig;
|
|
14898
15116
|
uiPrefsAcc;
|
|
14899
15117
|
setUiPrefsSig;
|
|
15118
|
+
keybindingsRevAcc;
|
|
15119
|
+
setKeybindingsRevSig;
|
|
14900
15120
|
connectionStateAcc;
|
|
14901
15121
|
setConnectionState;
|
|
14902
15122
|
ensureReachable;
|
|
@@ -14910,6 +15130,7 @@ class RemoteOrchestrator {
|
|
|
14910
15130
|
const [daemonVersion, setDaemonVersion] = createSignal(null);
|
|
14911
15131
|
const [engineState, setEngineState] = createSignal(new Map);
|
|
14912
15132
|
const [uiPrefs, setUiPrefs] = createSignal(null);
|
|
15133
|
+
const [keybindingsRev, setKeybindingsRev] = createSignal(null);
|
|
14913
15134
|
const [connectionState, setConnectionState] = createSignal("online");
|
|
14914
15135
|
this.tasksAcc = tasks;
|
|
14915
15136
|
this.setTasks = (next) => setTasks(() => next);
|
|
@@ -14923,6 +15144,8 @@ class RemoteOrchestrator {
|
|
|
14923
15144
|
this.setEngineStateSig = (next) => setEngineState(() => next);
|
|
14924
15145
|
this.uiPrefsAcc = uiPrefs;
|
|
14925
15146
|
this.setUiPrefsSig = (next) => setUiPrefs(() => next);
|
|
15147
|
+
this.keybindingsRevAcc = keybindingsRev;
|
|
15148
|
+
this.setKeybindingsRevSig = (next) => setKeybindingsRev(() => next);
|
|
14926
15149
|
this.connectionStateAcc = connectionState;
|
|
14927
15150
|
this.setConnectionState = (next) => setConnectionState(() => next);
|
|
14928
15151
|
this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
|
|
@@ -15014,6 +15237,9 @@ class RemoteOrchestrator {
|
|
|
15014
15237
|
uiPrefsSignal() {
|
|
15015
15238
|
return this.uiPrefsAcc;
|
|
15016
15239
|
}
|
|
15240
|
+
keybindingsRevSignal() {
|
|
15241
|
+
return this.keybindingsRevAcc;
|
|
15242
|
+
}
|
|
15017
15243
|
listTasks() {
|
|
15018
15244
|
return this.tasksAcc();
|
|
15019
15245
|
}
|
|
@@ -15126,10 +15352,19 @@ class RemoteOrchestrator {
|
|
|
15126
15352
|
this.setUiPrefsSig({
|
|
15127
15353
|
theme: p.theme,
|
|
15128
15354
|
transparentBackground: p.transparentBackground === true,
|
|
15129
|
-
focusAccent: typeof p.focusAccent === "string" ? p.focusAccent : null
|
|
15355
|
+
focusAccent: typeof p.focusAccent === "string" ? p.focusAccent : null,
|
|
15356
|
+
sortMode: p.sortMode === "recent" ? "recent" : "default",
|
|
15357
|
+
keysCollapsed: p.keysCollapsed === true
|
|
15130
15358
|
});
|
|
15131
15359
|
return;
|
|
15132
15360
|
}
|
|
15361
|
+
if (name === "keybindings") {
|
|
15362
|
+
const p = payload;
|
|
15363
|
+
if (typeof p?.rev !== "number")
|
|
15364
|
+
return;
|
|
15365
|
+
this.setKeybindingsRevSig(p.rev);
|
|
15366
|
+
return;
|
|
15367
|
+
}
|
|
15133
15368
|
}
|
|
15134
15369
|
}
|
|
15135
15370
|
function deserializeTask(s) {
|
|
@@ -15523,10 +15758,6 @@ function resolveTheme(theme, mode = "dark") {
|
|
|
15523
15758
|
...out
|
|
15524
15759
|
};
|
|
15525
15760
|
}
|
|
15526
|
-
function withAlpha(color, alpha) {
|
|
15527
|
-
const [r, g, b] = color.toInts();
|
|
15528
|
-
return RGBA.fromInts(r ?? 0, g ?? 0, b ?? 0, alpha);
|
|
15529
|
-
}
|
|
15530
15761
|
var BUNDLED_THEMES, FOCUS_ACCENT_SLOTS, store, setStore, useTheme, ThemeProvider;
|
|
15531
15762
|
var init_theme2 = __esm(() => {
|
|
15532
15763
|
init_solid();
|
|
@@ -15598,8 +15829,7 @@ var init_theme2 = __esm(() => {
|
|
|
15598
15829
|
return {
|
|
15599
15830
|
...v,
|
|
15600
15831
|
background: transparent,
|
|
15601
|
-
backgroundPanel: transparent
|
|
15602
|
-
backgroundDialog: withAlpha(v.backgroundDialog, 128)
|
|
15832
|
+
backgroundPanel: transparent
|
|
15603
15833
|
};
|
|
15604
15834
|
});
|
|
15605
15835
|
createEffect(() => {
|
|
@@ -17673,6 +17903,19 @@ var init_focus = __esm(() => {
|
|
|
17673
17903
|
});
|
|
17674
17904
|
|
|
17675
17905
|
// src/tui/context/keybindings.ts
|
|
17906
|
+
function resetKeymapToDefaults() {
|
|
17907
|
+
for (const row of KobeKeymap) {
|
|
17908
|
+
const def = KEYMAP_DEFAULTS.get(row.id);
|
|
17909
|
+
if (!def)
|
|
17910
|
+
continue;
|
|
17911
|
+
const mutable = row;
|
|
17912
|
+
mutable.keys = [...def.keys];
|
|
17913
|
+
mutable.hint = def.hint ? { ...def.hint } : undefined;
|
|
17914
|
+
}
|
|
17915
|
+
}
|
|
17916
|
+
function bumpKeymapVersion() {
|
|
17917
|
+
setKeymapVersion((v) => v + 1);
|
|
17918
|
+
}
|
|
17676
17919
|
function findBinding(id) {
|
|
17677
17920
|
return KobeKeymap.find((b) => b.id === id);
|
|
17678
17921
|
}
|
|
@@ -17695,15 +17938,16 @@ function bindByIds(handlers) {
|
|
|
17695
17938
|
}
|
|
17696
17939
|
return out;
|
|
17697
17940
|
}
|
|
17698
|
-
var KobeKeymap;
|
|
17941
|
+
var KobeKeymap, KEYMAP_DEFAULTS, keymapVersion, setKeymapVersion;
|
|
17699
17942
|
var init_keybindings2 = __esm(() => {
|
|
17943
|
+
init_dev();
|
|
17700
17944
|
KobeKeymap = [
|
|
17701
17945
|
{
|
|
17702
17946
|
id: "help.open",
|
|
17703
17947
|
scope: "global",
|
|
17704
17948
|
keys: ["f1"],
|
|
17705
17949
|
category: "Global",
|
|
17706
|
-
description: "Show
|
|
17950
|
+
description: "Show keybindings help",
|
|
17707
17951
|
hint: { keys: "F1", label: "help", pin: "right" }
|
|
17708
17952
|
},
|
|
17709
17953
|
{
|
|
@@ -18154,6 +18398,8 @@ var init_keybindings2 = __esm(() => {
|
|
|
18154
18398
|
hint: { keys: "ctrl+[/]", label: "tab" }
|
|
18155
18399
|
}
|
|
18156
18400
|
];
|
|
18401
|
+
KEYMAP_DEFAULTS = new Map(KobeKeymap.map((b) => [b.id, { keys: [...b.keys], hint: b.hint ? { ...b.hint } : undefined }]));
|
|
18402
|
+
[keymapVersion, setKeymapVersion] = createSignal(0);
|
|
18157
18403
|
});
|
|
18158
18404
|
|
|
18159
18405
|
// src/tui/context/keybindings-user.ts
|
|
@@ -18201,6 +18447,15 @@ function applyUserKeybindings() {
|
|
|
18201
18447
|
function userKeybindingsReport() {
|
|
18202
18448
|
return cached3 ?? applyUserKeybindings();
|
|
18203
18449
|
}
|
|
18450
|
+
function reloadUserKeybindings() {
|
|
18451
|
+
cached3 = null;
|
|
18452
|
+
resetKeybindingsFileCache();
|
|
18453
|
+
resetTmuxKeysCache();
|
|
18454
|
+
resetKeymapToDefaults();
|
|
18455
|
+
const report = applyUserKeybindings();
|
|
18456
|
+
bumpKeymapVersion();
|
|
18457
|
+
return report;
|
|
18458
|
+
}
|
|
18204
18459
|
var cached3 = null;
|
|
18205
18460
|
var init_keybindings_user = __esm(() => {
|
|
18206
18461
|
init_keybindings_file();
|
|
@@ -18299,9 +18554,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
|
|
|
18299
18554
|
var init_pulse = () => {};
|
|
18300
18555
|
|
|
18301
18556
|
// src/tui/lib/sound.ts
|
|
18302
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
18557
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6 } from "fs";
|
|
18303
18558
|
import { tmpdir as tmpdir2 } from "os";
|
|
18304
|
-
import { basename as
|
|
18559
|
+
import { basename as basename6, isAbsolute, join as join15, resolve as resolve8 } from "path";
|
|
18305
18560
|
function args(player, file, volume) {
|
|
18306
18561
|
if (player === "ffplay")
|
|
18307
18562
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -18324,13 +18579,13 @@ function pickPlayer() {
|
|
|
18324
18579
|
return cachedPlayer;
|
|
18325
18580
|
const path12 = process.env.PATH ?? "";
|
|
18326
18581
|
const segments = path12.split(":").filter(Boolean);
|
|
18327
|
-
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(
|
|
18582
|
+
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join15(dir, p)))) ?? null;
|
|
18328
18583
|
return cachedPlayer;
|
|
18329
18584
|
}
|
|
18330
18585
|
async function ensureAsset() {
|
|
18331
18586
|
cachedPath ??= (async () => {
|
|
18332
|
-
|
|
18333
|
-
const dest =
|
|
18587
|
+
mkdirSync6(DIR, { recursive: true });
|
|
18588
|
+
const dest = join15(DIR, basename6(pulseAsset));
|
|
18334
18589
|
const out = Bun.file(dest);
|
|
18335
18590
|
if (await out.exists())
|
|
18336
18591
|
return dest;
|
|
@@ -18360,7 +18615,7 @@ var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
|
18360
18615
|
var init_sound = __esm(() => {
|
|
18361
18616
|
init_pulse();
|
|
18362
18617
|
pulseAsset = isAbsolute(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
|
|
18363
|
-
DIR =
|
|
18618
|
+
DIR = join15(tmpdir2(), "kobe-sfx");
|
|
18364
18619
|
PLAYERS = [
|
|
18365
18620
|
"ffplay",
|
|
18366
18621
|
"mpv",
|
|
@@ -18605,6 +18860,18 @@ function UiPrefsSync(props) {
|
|
|
18605
18860
|
if (payload)
|
|
18606
18861
|
applyUiPrefs(target, payload);
|
|
18607
18862
|
});
|
|
18863
|
+
let lastKeybindingsRev = null;
|
|
18864
|
+
createEffect(() => {
|
|
18865
|
+
const rev = prefsOrch()?.keybindingsRevSignal()();
|
|
18866
|
+
if (rev == null)
|
|
18867
|
+
return;
|
|
18868
|
+
if (lastKeybindingsRev === null || rev === lastKeybindingsRev) {
|
|
18869
|
+
lastKeybindingsRev = rev;
|
|
18870
|
+
return;
|
|
18871
|
+
}
|
|
18872
|
+
lastKeybindingsRev = rev;
|
|
18873
|
+
reloadUserKeybindings();
|
|
18874
|
+
});
|
|
18608
18875
|
onCleanup(() => prefsOrch()?.dispose());
|
|
18609
18876
|
return null;
|
|
18610
18877
|
}
|
|
@@ -19107,12 +19374,13 @@ function groupBindings(keymap) {
|
|
|
19107
19374
|
rows: groups.get(cat)
|
|
19108
19375
|
}));
|
|
19109
19376
|
}
|
|
19110
|
-
function HelpDialog() {
|
|
19377
|
+
function HelpDialog(props = {}) {
|
|
19111
19378
|
const dialog = useDialog();
|
|
19112
19379
|
const {
|
|
19113
19380
|
theme
|
|
19114
19381
|
} = useTheme();
|
|
19115
19382
|
const grouped = () => groupBindings(KobeKeymap);
|
|
19383
|
+
const close = () => props.onClose ? props.onClose() : dialog.clear();
|
|
19116
19384
|
const [prefixGlyph, setPrefixGlyph] = createSignal("\u2303B");
|
|
19117
19385
|
onMount(() => {
|
|
19118
19386
|
runTmuxCapturing(["show-options", "-g", "prefix"]).then(({
|
|
@@ -19129,7 +19397,7 @@ function HelpDialog() {
|
|
|
19129
19397
|
useBindings(() => ({
|
|
19130
19398
|
bindings: [{
|
|
19131
19399
|
key: "?",
|
|
19132
|
-
cmd:
|
|
19400
|
+
cmd: close
|
|
19133
19401
|
}]
|
|
19134
19402
|
}));
|
|
19135
19403
|
return (() => {
|
|
@@ -19147,7 +19415,7 @@ function HelpDialog() {
|
|
|
19147
19415
|
setProp(_el$2, "flexShrink", 0);
|
|
19148
19416
|
insertNode(_el$3, createTextNode(`kobe \u2014 keybindings`));
|
|
19149
19417
|
insertNode(_el$5, createTextNode(`esc`));
|
|
19150
|
-
setProp(_el$5, "onMouseUp",
|
|
19418
|
+
setProp(_el$5, "onMouseUp", close);
|
|
19151
19419
|
insertNode(_el$7, _el$8);
|
|
19152
19420
|
setProp(_el$7, "flexShrink", 1);
|
|
19153
19421
|
setProp(_el$7, "flexGrow", 1);
|
|
@@ -19517,7 +19785,7 @@ var init_dialog_confirm = __esm(() => {
|
|
|
19517
19785
|
|
|
19518
19786
|
// src/tui/component/settings-dialog/actions.ts
|
|
19519
19787
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
19520
|
-
import { join as
|
|
19788
|
+
import { join as join16 } from "path";
|
|
19521
19789
|
function hasRestartableDaemon(orchestrator) {
|
|
19522
19790
|
return orchestrator instanceof RemoteOrchestrator;
|
|
19523
19791
|
}
|
|
@@ -19534,7 +19802,7 @@ async function confirmResetState(dialog, kv, renderer) {
|
|
|
19534
19802
|
return;
|
|
19535
19803
|
kv.clear();
|
|
19536
19804
|
try {
|
|
19537
|
-
unlinkSync2(
|
|
19805
|
+
unlinkSync2(join16(homeDir(), ".kobe", "tasks.json"));
|
|
19538
19806
|
} catch (err) {
|
|
19539
19807
|
if (err.code !== "ENOENT") {
|
|
19540
19808
|
console.error("kobe: failed to delete tasks.json during reset:", err);
|
|
@@ -21773,7 +22041,7 @@ var init_task_actions = __esm(() => {
|
|
|
21773
22041
|
// src/tui/lib/worktree-opener.ts
|
|
21774
22042
|
import { spawn as spawn4 } from "child_process";
|
|
21775
22043
|
import { existsSync as existsSync15 } from "fs";
|
|
21776
|
-
import { basename as
|
|
22044
|
+
import { basename as basename7, delimiter, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
21777
22045
|
function executableOnPath(command, env, exists) {
|
|
21778
22046
|
if (isAbsolute2(command))
|
|
21779
22047
|
return exists(command);
|
|
@@ -21781,13 +22049,13 @@ function executableOnPath(command, env, exists) {
|
|
|
21781
22049
|
for (const dir of pathEnv.split(delimiter)) {
|
|
21782
22050
|
if (!dir)
|
|
21783
22051
|
continue;
|
|
21784
|
-
if (exists(
|
|
22052
|
+
if (exists(join17(dir, command)))
|
|
21785
22053
|
return true;
|
|
21786
22054
|
}
|
|
21787
22055
|
return false;
|
|
21788
22056
|
}
|
|
21789
22057
|
function labelForOverride(command) {
|
|
21790
|
-
const name =
|
|
22058
|
+
const name = basename7(command);
|
|
21791
22059
|
if (name === "code")
|
|
21792
22060
|
return "VS Code";
|
|
21793
22061
|
if (name === "cursor")
|
|
@@ -22406,7 +22674,8 @@ function Sidebar(props) {
|
|
|
22406
22674
|
onCleanup(() => r.keyInput.off("keypress", listener2));
|
|
22407
22675
|
});
|
|
22408
22676
|
const [branchTick, setBranchTick] = createSignal(0);
|
|
22409
|
-
setInterval(() => setBranchTick((n) => n + 1), MAIN_BRANCH_POLL_MS);
|
|
22677
|
+
const branchInterval = setInterval(() => setBranchTick((n) => n + 1), MAIN_BRANCH_POLL_MS);
|
|
22678
|
+
onCleanup(() => clearInterval(branchInterval));
|
|
22410
22679
|
const [spinnerFrame, setSpinnerFrame] = createSignal(0);
|
|
22411
22680
|
const spinnerInterval = setInterval(() => setSpinnerFrame((n) => (n + 1) % IN_PROGRESS_SPINNER.length), SPINNER_FRAME_MS);
|
|
22412
22681
|
onCleanup(() => clearInterval(spinnerInterval));
|
|
@@ -23251,7 +23520,7 @@ function TasksShell(props) {
|
|
|
23251
23520
|
});
|
|
23252
23521
|
}
|
|
23253
23522
|
const [moveMode, setMoveMode] = createSignal(false);
|
|
23254
|
-
const [sortMode, setSortMode] = createSignal("default");
|
|
23523
|
+
const [sortMode, setSortMode] = createSignal(kv.get("activeSortMode") === "recent" ? "recent" : "default");
|
|
23255
23524
|
const [updateInfo, setUpdateInfo] = createSignal(null);
|
|
23256
23525
|
const dimensions = useTerminalDimensions();
|
|
23257
23526
|
let activeConfirmed = !props.initialTaskId;
|
|
@@ -23266,6 +23535,11 @@ function TasksShell(props) {
|
|
|
23266
23535
|
}
|
|
23267
23536
|
setSelectedId(active);
|
|
23268
23537
|
});
|
|
23538
|
+
createEffect(() => {
|
|
23539
|
+
const payload = props.orch?.uiPrefsSignal()();
|
|
23540
|
+
if (payload && payload.sortMode !== sortMode())
|
|
23541
|
+
setSortMode(payload.sortMode);
|
|
23542
|
+
});
|
|
23269
23543
|
createEffect(() => {
|
|
23270
23544
|
const info = props.orch?.updateSignal()();
|
|
23271
23545
|
if (info)
|
|
@@ -23333,6 +23607,14 @@ function TasksShell(props) {
|
|
|
23333
23607
|
console.error("[kobe tasks] failed to refresh workspace panes:", err);
|
|
23334
23608
|
}
|
|
23335
23609
|
}
|
|
23610
|
+
async function openHelp() {
|
|
23611
|
+
const session = await currentSessionName();
|
|
23612
|
+
if (session) {
|
|
23613
|
+
await openHelpTab(session);
|
|
23614
|
+
return;
|
|
23615
|
+
}
|
|
23616
|
+
HelpDialog.show(dialog);
|
|
23617
|
+
}
|
|
23336
23618
|
async function openUpdate() {
|
|
23337
23619
|
const info = updateInfo();
|
|
23338
23620
|
if (!info?.hasUpdate) {
|
|
@@ -23404,13 +23686,21 @@ function TasksShell(props) {
|
|
|
23404
23686
|
setSelectedId(id);
|
|
23405
23687
|
await props.reload();
|
|
23406
23688
|
}
|
|
23407
|
-
const keysCollapsed = (
|
|
23408
|
-
const setKeysCollapsed = (next) =>
|
|
23689
|
+
const [keysCollapsed, setKeysCollapsedSig] = createSignal(kv.get("tasksPane.keysCollapsed", false) === true);
|
|
23690
|
+
const setKeysCollapsed = (next) => {
|
|
23691
|
+
setKeysCollapsedSig(next);
|
|
23692
|
+
kv.set("tasksPane.keysCollapsed", next);
|
|
23693
|
+
};
|
|
23694
|
+
createEffect(() => {
|
|
23695
|
+
const payload = props.orch?.uiPrefsSignal()();
|
|
23696
|
+
if (payload && payload.keysCollapsed !== keysCollapsed())
|
|
23697
|
+
setKeysCollapsedSig(payload.keysCollapsed);
|
|
23698
|
+
});
|
|
23409
23699
|
const [searchActive, setSearchActive] = createSignal(false);
|
|
23410
23700
|
useBindings(() => ({
|
|
23411
23701
|
enabled: dialog.stack.length === 0 && !searchActive(),
|
|
23412
23702
|
bindings: bindByIds({
|
|
23413
|
-
"help.open": () =>
|
|
23703
|
+
"help.open": () => void openHelp(),
|
|
23414
23704
|
"task.new": () => void createTask(),
|
|
23415
23705
|
"settings.open.sidebar": () => void openSettings(),
|
|
23416
23706
|
"tasks.update": () => void openUpdate(),
|
|
@@ -23435,6 +23725,9 @@ function TasksShell(props) {
|
|
|
23435
23725
|
async function switchTo(id) {
|
|
23436
23726
|
const name = tmuxSessionName(id);
|
|
23437
23727
|
const task = props.tasks().find((t) => t.id === id);
|
|
23728
|
+
const from = await currentSessionName();
|
|
23729
|
+
if (from && from !== name)
|
|
23730
|
+
await captureGlobalLayout(from);
|
|
23438
23731
|
const exists = await sessionExists(name);
|
|
23439
23732
|
if (exists) {
|
|
23440
23733
|
const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
|
|
@@ -23546,7 +23839,11 @@ function TasksShell(props) {
|
|
|
23546
23839
|
onMoveRequest: (id, delta) => void moveTask(id, delta),
|
|
23547
23840
|
onMoveModeExit: () => setMoveMode(false),
|
|
23548
23841
|
sortMode,
|
|
23549
|
-
onSortModeToggle: () =>
|
|
23842
|
+
onSortModeToggle: () => {
|
|
23843
|
+
const next = sortMode() === "default" ? "recent" : "default";
|
|
23844
|
+
setSortMode(next);
|
|
23845
|
+
kv.set("activeSortMode", next);
|
|
23846
|
+
},
|
|
23550
23847
|
focused: () => dialog.stack.length === 0,
|
|
23551
23848
|
onSearchActiveChange: setSearchActive
|
|
23552
23849
|
}));
|
|
@@ -23578,6 +23875,7 @@ function ShortcutHints(props) {
|
|
|
23578
23875
|
});
|
|
23579
23876
|
});
|
|
23580
23877
|
const tmuxHints = () => {
|
|
23878
|
+
keymapVersion();
|
|
23581
23879
|
const res = resolveUserTmuxKeys();
|
|
23582
23880
|
const b = res.binds;
|
|
23583
23881
|
const out = [];
|
|
@@ -23985,9 +24283,73 @@ var init_host4 = __esm(() => {
|
|
|
23985
24283
|
init_dialog();
|
|
23986
24284
|
});
|
|
23987
24285
|
|
|
23988
|
-
// src/tui/
|
|
24286
|
+
// src/tui/help/host.tsx
|
|
23989
24287
|
var exports_host5 = {};
|
|
23990
24288
|
__export(exports_host5, {
|
|
24289
|
+
startHelpHost: () => startHelpHost
|
|
24290
|
+
});
|
|
24291
|
+
function HelpPage() {
|
|
24292
|
+
const dialog = useDialog();
|
|
24293
|
+
const {
|
|
24294
|
+
theme
|
|
24295
|
+
} = useTheme();
|
|
24296
|
+
function exit() {
|
|
24297
|
+
process.exit(0);
|
|
24298
|
+
}
|
|
24299
|
+
useBindings(() => ({
|
|
24300
|
+
enabled: dialog.stack.length === 0,
|
|
24301
|
+
bindings: [{
|
|
24302
|
+
key: "escape",
|
|
24303
|
+
cmd: exit
|
|
24304
|
+
}, {
|
|
24305
|
+
key: "q",
|
|
24306
|
+
cmd: exit
|
|
24307
|
+
}, {
|
|
24308
|
+
key: "f1",
|
|
24309
|
+
cmd: exit
|
|
24310
|
+
}, {
|
|
24311
|
+
key: "ctrl+c",
|
|
24312
|
+
cmd: exit
|
|
24313
|
+
}]
|
|
24314
|
+
}));
|
|
24315
|
+
return (() => {
|
|
24316
|
+
var _el$ = createElement("box");
|
|
24317
|
+
setProp(_el$, "flexGrow", 1);
|
|
24318
|
+
setProp(_el$, "paddingTop", 1);
|
|
24319
|
+
insert(_el$, createComponent2(HelpDialog, {
|
|
24320
|
+
onClose: exit
|
|
24321
|
+
}));
|
|
24322
|
+
effect((_$p) => setProp(_el$, "backgroundColor", theme.background, _$p));
|
|
24323
|
+
return _el$;
|
|
24324
|
+
})();
|
|
24325
|
+
}
|
|
24326
|
+
async function startHelpHost() {
|
|
24327
|
+
await bootPaneHost({
|
|
24328
|
+
providers: {
|
|
24329
|
+
kv: false,
|
|
24330
|
+
focus: false
|
|
24331
|
+
},
|
|
24332
|
+
setup: () => ({
|
|
24333
|
+
root: () => createComponent2(HelpPage, {})
|
|
24334
|
+
})
|
|
24335
|
+
});
|
|
24336
|
+
}
|
|
24337
|
+
var init_host5 = __esm(() => {
|
|
24338
|
+
init_solid();
|
|
24339
|
+
init_solid();
|
|
24340
|
+
init_solid();
|
|
24341
|
+
init_solid();
|
|
24342
|
+
init_solid();
|
|
24343
|
+
init_help_dialog();
|
|
24344
|
+
init_theme2();
|
|
24345
|
+
init_host_boot();
|
|
24346
|
+
init_keymap();
|
|
24347
|
+
init_dialog();
|
|
24348
|
+
});
|
|
24349
|
+
|
|
24350
|
+
// src/tui/update/host.tsx
|
|
24351
|
+
var exports_host6 = {};
|
|
24352
|
+
__export(exports_host6, {
|
|
23991
24353
|
startUpdateHost: () => startUpdateHost
|
|
23992
24354
|
});
|
|
23993
24355
|
import { spawn as spawn6, spawnSync as spawnSync11 } from "child_process";
|
|
@@ -24342,7 +24704,7 @@ async function startUpdateHost() {
|
|
|
24342
24704
|
})
|
|
24343
24705
|
});
|
|
24344
24706
|
}
|
|
24345
|
-
var
|
|
24707
|
+
var init_host6 = __esm(() => {
|
|
24346
24708
|
init_solid();
|
|
24347
24709
|
init_solid();
|
|
24348
24710
|
init_solid();
|
|
@@ -24753,8 +25115,109 @@ function spawnDetached(cmd, args2, onError) {
|
|
|
24753
25115
|
}
|
|
24754
25116
|
var init_open_external = () => {};
|
|
24755
25117
|
|
|
25118
|
+
// src/tui/panes/filetree/rows.ts
|
|
25119
|
+
function flattenTree(node, expanded, depth, out) {
|
|
25120
|
+
for (const child of node.children) {
|
|
25121
|
+
if (child.isDir) {
|
|
25122
|
+
const isOpen = expanded.has(child.path);
|
|
25123
|
+
out.push({
|
|
25124
|
+
kind: "dir",
|
|
25125
|
+
path: child.path,
|
|
25126
|
+
name: child.name,
|
|
25127
|
+
depth,
|
|
25128
|
+
expanded: isOpen,
|
|
25129
|
+
hasChildren: child.children.length > 0
|
|
25130
|
+
});
|
|
25131
|
+
if (isOpen)
|
|
25132
|
+
flattenTree(child, expanded, depth + 1, out);
|
|
25133
|
+
} else {
|
|
25134
|
+
out.push({ kind: "file", path: child.path, name: child.name, depth });
|
|
25135
|
+
}
|
|
25136
|
+
}
|
|
25137
|
+
}
|
|
25138
|
+
function statusRows(entries) {
|
|
25139
|
+
return entries.map((e) => ({
|
|
25140
|
+
kind: "status",
|
|
25141
|
+
path: e.path,
|
|
25142
|
+
status: e.status,
|
|
25143
|
+
added: e.added,
|
|
25144
|
+
deleted: e.deleted
|
|
25145
|
+
}));
|
|
25146
|
+
}
|
|
25147
|
+
function rowKey(row) {
|
|
25148
|
+
return `${row.kind}\x00${row.path}`;
|
|
25149
|
+
}
|
|
25150
|
+
function rowEquals(a, b) {
|
|
25151
|
+
if (a.kind !== b.kind || a.path !== b.path)
|
|
25152
|
+
return false;
|
|
25153
|
+
switch (a.kind) {
|
|
25154
|
+
case "file": {
|
|
25155
|
+
const o = b;
|
|
25156
|
+
return a.name === o.name && a.depth === o.depth;
|
|
25157
|
+
}
|
|
25158
|
+
case "dir": {
|
|
25159
|
+
const o = b;
|
|
25160
|
+
return a.name === o.name && a.depth === o.depth && a.expanded === o.expanded && a.hasChildren === o.hasChildren;
|
|
25161
|
+
}
|
|
25162
|
+
case "status": {
|
|
25163
|
+
const o = b;
|
|
25164
|
+
return a.status === o.status && a.added === o.added && a.deleted === o.deleted;
|
|
25165
|
+
}
|
|
25166
|
+
}
|
|
25167
|
+
}
|
|
25168
|
+
function reconcileRows(prev, next) {
|
|
25169
|
+
if (prev.length === 0)
|
|
25170
|
+
return next;
|
|
25171
|
+
const prevByKey = new Map;
|
|
25172
|
+
for (const row of prev)
|
|
25173
|
+
prevByKey.set(rowKey(row), row);
|
|
25174
|
+
let allReused = prev.length === next.length;
|
|
25175
|
+
const out = new Array(next.length);
|
|
25176
|
+
for (let i = 0;i < next.length; i++) {
|
|
25177
|
+
const fresh = next[i];
|
|
25178
|
+
const old = prevByKey.get(rowKey(fresh));
|
|
25179
|
+
if (old && rowEquals(old, fresh)) {
|
|
25180
|
+
out[i] = old;
|
|
25181
|
+
if (allReused && prev[i] !== old)
|
|
25182
|
+
allReused = false;
|
|
25183
|
+
} else {
|
|
25184
|
+
out[i] = fresh;
|
|
25185
|
+
allReused = false;
|
|
25186
|
+
}
|
|
25187
|
+
}
|
|
25188
|
+
return allReused ? prev : out;
|
|
25189
|
+
}
|
|
25190
|
+
function sameFileList(a, b) {
|
|
25191
|
+
if (a === b)
|
|
25192
|
+
return true;
|
|
25193
|
+
if (a == null || b == null)
|
|
25194
|
+
return false;
|
|
25195
|
+
if (a.length !== b.length)
|
|
25196
|
+
return false;
|
|
25197
|
+
for (let i = 0;i < a.length; i++) {
|
|
25198
|
+
if (a[i] !== b[i])
|
|
25199
|
+
return false;
|
|
25200
|
+
}
|
|
25201
|
+
return true;
|
|
25202
|
+
}
|
|
25203
|
+
function sameStatusEntries(a, b) {
|
|
25204
|
+
if (a === b)
|
|
25205
|
+
return true;
|
|
25206
|
+
if (a == null || b == null)
|
|
25207
|
+
return false;
|
|
25208
|
+
if (a.length !== b.length)
|
|
25209
|
+
return false;
|
|
25210
|
+
for (let i = 0;i < a.length; i++) {
|
|
25211
|
+
const x = a[i];
|
|
25212
|
+
const y = b[i];
|
|
25213
|
+
if (x.path !== y.path || x.status !== y.status || x.added !== y.added || x.deleted !== y.deleted)
|
|
25214
|
+
return false;
|
|
25215
|
+
}
|
|
25216
|
+
return true;
|
|
25217
|
+
}
|
|
25218
|
+
|
|
24756
25219
|
// src/tui/panes/filetree/FileTree.tsx
|
|
24757
|
-
import { watch as
|
|
25220
|
+
import { watch as watch3 } from "fs";
|
|
24758
25221
|
import { TextAttributes as TextAttributes14 } from "@opentui/core";
|
|
24759
25222
|
function statusToken(s) {
|
|
24760
25223
|
switch (s) {
|
|
@@ -24801,8 +25264,12 @@ function FileTree(props) {
|
|
|
24801
25264
|
const [tab, setTab] = createSignal("all");
|
|
24802
25265
|
const [cursorIndex, setCursorIndex] = createSignal(0);
|
|
24803
25266
|
const [refreshTick, setRefreshTick] = createSignal(0);
|
|
24804
|
-
const [allFiles, setAllFiles] = createSignal(null
|
|
24805
|
-
|
|
25267
|
+
const [allFiles, setAllFiles] = createSignal(null, {
|
|
25268
|
+
equals: sameFileList
|
|
25269
|
+
});
|
|
25270
|
+
const [changes, setChanges] = createSignal(null, {
|
|
25271
|
+
equals: sameStatusEntries
|
|
25272
|
+
});
|
|
24806
25273
|
const [error, setError] = createSignal(null);
|
|
24807
25274
|
const [expandedDirs, setExpandedDirs] = createSignal(new Set);
|
|
24808
25275
|
let fetchSeq = 0;
|
|
@@ -24849,7 +25316,7 @@ function FileTree(props) {
|
|
|
24849
25316
|
let debounceTimer = null;
|
|
24850
25317
|
let watcher = null;
|
|
24851
25318
|
try {
|
|
24852
|
-
watcher =
|
|
25319
|
+
watcher = watch3(path12, {
|
|
24853
25320
|
recursive: true
|
|
24854
25321
|
}, (_event, filename) => {
|
|
24855
25322
|
if (filename == null)
|
|
@@ -24898,53 +25365,19 @@ function FileTree(props) {
|
|
|
24898
25365
|
return null;
|
|
24899
25366
|
return buildTree(files);
|
|
24900
25367
|
});
|
|
24901
|
-
|
|
24902
|
-
|
|
24903
|
-
if (child.isDir) {
|
|
24904
|
-
const isOpen = expanded.has(child.path);
|
|
24905
|
-
out.push({
|
|
24906
|
-
kind: "dir",
|
|
24907
|
-
path: child.path,
|
|
24908
|
-
name: child.name,
|
|
24909
|
-
depth,
|
|
24910
|
-
expanded: isOpen,
|
|
24911
|
-
hasChildren: child.children.length > 0
|
|
24912
|
-
});
|
|
24913
|
-
if (isOpen)
|
|
24914
|
-
flattenTree(child, expanded, depth + 1, out);
|
|
24915
|
-
} else {
|
|
24916
|
-
out.push({
|
|
24917
|
-
kind: "file",
|
|
24918
|
-
path: child.path,
|
|
24919
|
-
name: child.name,
|
|
24920
|
-
depth
|
|
24921
|
-
});
|
|
24922
|
-
}
|
|
24923
|
-
}
|
|
24924
|
-
}
|
|
24925
|
-
const rows = createMemo(() => {
|
|
25368
|
+
const rows = createMemo((prev) => {
|
|
25369
|
+
const next = [];
|
|
24926
25370
|
if (tab() === "all") {
|
|
24927
25371
|
const root = tree();
|
|
24928
|
-
if (root
|
|
24929
|
-
|
|
24930
|
-
|
|
24931
|
-
flattenTree(root, expandedDirs(), 0, out);
|
|
24932
|
-
return out;
|
|
24933
|
-
}
|
|
24934
|
-
if (tab() === "changes") {
|
|
25372
|
+
if (root != null)
|
|
25373
|
+
flattenTree(root, expandedDirs(), 0, next);
|
|
25374
|
+
} else if (tab() === "changes") {
|
|
24935
25375
|
const list2 = changes();
|
|
24936
|
-
if (list2
|
|
24937
|
-
|
|
24938
|
-
return list2.map((e) => ({
|
|
24939
|
-
kind: "status",
|
|
24940
|
-
path: e.path,
|
|
24941
|
-
status: e.status,
|
|
24942
|
-
added: e.added,
|
|
24943
|
-
deleted: e.deleted
|
|
24944
|
-
}));
|
|
25376
|
+
if (list2 != null)
|
|
25377
|
+
next.push(...statusRows(list2));
|
|
24945
25378
|
}
|
|
24946
|
-
return [];
|
|
24947
|
-
});
|
|
25379
|
+
return reconcileRows(prev ?? [], next);
|
|
25380
|
+
}, []);
|
|
24948
25381
|
const statWidths = createMemo(() => {
|
|
24949
25382
|
let added = 0;
|
|
24950
25383
|
let deleted = 0;
|
|
@@ -25631,8 +26064,8 @@ var init_pr_prompt = __esm(() => {
|
|
|
25631
26064
|
});
|
|
25632
26065
|
|
|
25633
26066
|
// src/tui/ops/host.tsx
|
|
25634
|
-
var
|
|
25635
|
-
__export(
|
|
26067
|
+
var exports_host7 = {};
|
|
26068
|
+
__export(exports_host7, {
|
|
25636
26069
|
startOpsPreview: () => startOpsPreview,
|
|
25637
26070
|
startOpsHost: () => startOpsHost
|
|
25638
26071
|
});
|
|
@@ -25738,7 +26171,7 @@ function OpsShell(props) {
|
|
|
25738
26171
|
relPath: rel,
|
|
25739
26172
|
cliInvocation: kobeCliInvocation()
|
|
25740
26173
|
}),
|
|
25741
|
-
name:
|
|
26174
|
+
name: basename8(rel)
|
|
25742
26175
|
});
|
|
25743
26176
|
}
|
|
25744
26177
|
function openFile(rel) {
|
|
@@ -25781,7 +26214,7 @@ function OpsShell(props) {
|
|
|
25781
26214
|
return _el$;
|
|
25782
26215
|
})();
|
|
25783
26216
|
}
|
|
25784
|
-
function
|
|
26217
|
+
function basename8(p) {
|
|
25785
26218
|
const i = p.lastIndexOf("/");
|
|
25786
26219
|
return i >= 0 ? p.slice(i + 1) : p;
|
|
25787
26220
|
}
|
|
@@ -26026,7 +26459,7 @@ async function startOpsPreview(args2) {
|
|
|
26026
26459
|
});
|
|
26027
26460
|
}
|
|
26028
26461
|
var ACTIVITY_POLL_MS = 2500, TURN_STATUS_POLL_MS = 1500, STABLE_POLLS_FOR_DONE = 2, CHAT_TAB_STATE_OPTION2 = "@kobe_tab_state";
|
|
26029
|
-
var
|
|
26462
|
+
var init_host7 = __esm(() => {
|
|
26030
26463
|
init_solid();
|
|
26031
26464
|
init_solid();
|
|
26032
26465
|
init_solid();
|
|
@@ -26124,6 +26557,7 @@ async function startDirectTmux() {
|
|
|
26124
26557
|
if (!task) {
|
|
26125
26558
|
const home = await ensureFallbackSession();
|
|
26126
26559
|
await applyTmuxPaneBorderTheme();
|
|
26560
|
+
await prepareWindowForAttach(home);
|
|
26127
26561
|
if (await attachTmux(attachArgv(home)) === null) {
|
|
26128
26562
|
console.error("kobe: failed to attach to the kobe-home session");
|
|
26129
26563
|
process.exitCode = 1;
|
|
@@ -26151,6 +26585,7 @@ async function startDirectTmux() {
|
|
|
26151
26585
|
return;
|
|
26152
26586
|
}
|
|
26153
26587
|
await applyTmuxPaneBorderTheme();
|
|
26588
|
+
await prepareWindowForAttach(name);
|
|
26154
26589
|
const exitCode = await attachTmux(attachArgv(name));
|
|
26155
26590
|
if (exitCode === null) {
|
|
26156
26591
|
console.error(`kobe: failed to attach to tmux session ${name}`);
|
|
@@ -26576,6 +27011,17 @@ async function main() {
|
|
|
26576
27011
|
await selectTasksPane2(session);
|
|
26577
27012
|
return;
|
|
26578
27013
|
}
|
|
27014
|
+
if (subcommand === "heal-layout") {
|
|
27015
|
+
const flags = parseOpsFlags(rest);
|
|
27016
|
+
const session = flags.session;
|
|
27017
|
+
if (!session) {
|
|
27018
|
+
console.error("kobe heal-layout: --session <name> is required");
|
|
27019
|
+
process.exit(2);
|
|
27020
|
+
}
|
|
27021
|
+
const { healSessionLayout: healSessionLayout2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
27022
|
+
await healSessionLayout2(session);
|
|
27023
|
+
return;
|
|
27024
|
+
}
|
|
26579
27025
|
if (subcommand === "quick-task") {
|
|
26580
27026
|
const flags = parseOpsFlags(rest);
|
|
26581
27027
|
const { startQuickTaskHost: startQuickTaskHost2 } = await Promise.resolve().then(() => (init_host2(), exports_host2));
|
|
@@ -26593,6 +27039,11 @@ async function main() {
|
|
|
26593
27039
|
await startSettingsHost2();
|
|
26594
27040
|
return;
|
|
26595
27041
|
}
|
|
27042
|
+
if (subcommand === "help-page") {
|
|
27043
|
+
const { startHelpHost: startHelpHost2 } = await Promise.resolve().then(() => (init_host5(), exports_host5));
|
|
27044
|
+
await startHelpHost2();
|
|
27045
|
+
return;
|
|
27046
|
+
}
|
|
26596
27047
|
if (subcommand === "new-task") {
|
|
26597
27048
|
const flags = parseOpsFlags(rest);
|
|
26598
27049
|
const { startNewTaskHost: startNewTaskHost2 } = await Promise.resolve().then(() => (init_host(), exports_host));
|
|
@@ -26600,7 +27051,7 @@ async function main() {
|
|
|
26600
27051
|
return;
|
|
26601
27052
|
}
|
|
26602
27053
|
if (subcommand === "update-page") {
|
|
26603
|
-
const { startUpdateHost: startUpdateHost2 } = await Promise.resolve().then(() => (
|
|
27054
|
+
const { startUpdateHost: startUpdateHost2 } = await Promise.resolve().then(() => (init_host6(), exports_host6));
|
|
26604
27055
|
await startUpdateHost2();
|
|
26605
27056
|
return;
|
|
26606
27057
|
}
|
|
@@ -26611,11 +27062,11 @@ async function main() {
|
|
|
26611
27062
|
process.exit(2);
|
|
26612
27063
|
}
|
|
26613
27064
|
if (flags.preview) {
|
|
26614
|
-
const { startOpsPreview: startOpsPreview2 } = await Promise.resolve().then(() => (
|
|
27065
|
+
const { startOpsPreview: startOpsPreview2 } = await Promise.resolve().then(() => (init_host7(), exports_host7));
|
|
26615
27066
|
await startOpsPreview2({ worktree: flags.worktree, relPath: flags.preview });
|
|
26616
27067
|
return;
|
|
26617
27068
|
}
|
|
26618
|
-
const { startOpsHost: startOpsHost2 } = await Promise.resolve().then(() => (
|
|
27069
|
+
const { startOpsHost: startOpsHost2 } = await Promise.resolve().then(() => (init_host7(), exports_host7));
|
|
26619
27070
|
await startOpsHost2({
|
|
26620
27071
|
taskId: flags.taskId ?? "",
|
|
26621
27072
|
worktree: flags.worktree,
|