@sma1lboy/kobe 0.6.2 → 0.6.4

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.
Files changed (3) hide show
  1. package/README.md +14 -22
  2. package/dist/cli/index.js +140 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -78,28 +78,20 @@ Once you're in, the keys you'll use most:
78
78
 
79
79
  | Key | What it does |
80
80
  | ------------------ | -------------------------------------------------------------- |
81
- | `ctrl+h` / `ctrl+j` / `ctrl+k` / `ctrl+l` | Jump straight to a pane (sidebar, workspace, files, terminal) |
82
- | `tab` | Cycle focus to the next pane |
83
- | `ctrl+q` | Detach back to the sidebar (your task keeps streaming) |
84
- | `ctrl+o` | Open the active task's worktree in your editor |
85
- | `F1` | Show the full keybinding help dialog |
86
- | `ctrl+,` | Open Settings (theme, transparent background, dev reset) |
87
- | `q` | Quit (with confirm) |
88
-
89
- Inside the sidebar, with a task highlighted: `n` creates a task, `j/k` moves,
90
- `enter` opens, `r` renames, `a` archives, `d` deletes, `s` opens Settings, and
91
- `[` / `]` switches between the working session and the archives view.
92
-
93
- Inside the chat composer:
94
-
95
- - `enter` to send, `shift+enter` for a newline.
96
- - `shift+tab` toggles the per-task tool-permission mode between `default` and `plan`. `default` is the trusted-bypass mode — kobe forwards it to `claude` as `--permission-mode bypassPermissions` because `claude -p` has no interactive permission protocol, so the only meaningful choice is "auto-deny outside cwd" or "auto-approve everything." `plan` forwards unchanged.
97
- - Click the model label in the footer to pick the model for this task (opus / sonnet / haiku).
98
- - Type `/` to open the slash-command dropdown. Bundled `claude-code` commands and your own `.claude/{commands,skills}/*.md` are merged in.
99
- - A `Create PR` chip on the chat header injects a PR-instructions prompt into the active task and routes the resulting PR through the orchestrator.
100
-
101
- A given task can host **multiple chat tabs** on the same worktree — useful when
102
- you want a parallel sub-conversation without losing the main thread.
81
+ | `ctrl+h` / `ctrl+j` / `ctrl+k` / `ctrl+l` | Move between tmux panes (Tasks, engine, Ops files, shell) |
82
+ | `ctrl+q` | Detach back to your launching shell; the task tmux session keeps running |
83
+ | `ctrl+t` | Open a same-engine ChatTab window on the current task/worktree |
84
+ | `ctrl+shift+t` or tmux `prefix T` | Pick an engine, then open a new ChatTab window |
85
+ | `ctrl+[` / `ctrl+]` | Switch to previous / next ChatTab window |
86
+ | `ctrl+w` | Close the current ChatTab window; the final window is protected |
87
+ | `F2` | Rename the current ChatTab window |
88
+ | tmux `prefix f` | Open the Tasks pane's new-task dialog from anywhere in the session |
89
+
90
+ Inside the Tasks pane, with a task highlighted: `n` creates a task, `j/k` moves, `enter` opens, `r` renames, `b` renames the branch, `v` cycles the engine, `o` opens the worktree in your editor, `a` archives/unarchives, `d` deletes, `s` opens Settings, and `[` / `]` switches between Working session and Archives. Archive/delete also kill the task's cached tmux session when one exists.
91
+
92
+ Inside the Ops files pane: `[` / `]` switches All / Changes, `enter` opens the preview, `a` injects an `@file` mention into the engine pane, `p` injects the create-PR prompt, `r` refreshes, and `o` opens a file externally when that makes sense.
93
+
94
+ A given task can host **multiple ChatTab tmux windows** on the same worktree — useful when you want a parallel sub-conversation without losing the main thread.
103
95
 
104
96
  ## Opening tasks in your editor
105
97
 
package/dist/cli/index.js CHANGED
@@ -576,7 +576,7 @@ var init_package = __esm(() => {
576
576
  package_default = {
577
577
  $schema: "https://json.schemastore.org/package.json",
578
578
  name: "@sma1lboy/kobe",
579
- version: "0.6.2",
579
+ version: "0.6.4",
580
580
  description: "TUI orchestrator for Claude Code (codename)",
581
581
  type: "module",
582
582
  packageManager: "bun@1.3.13",
@@ -4786,6 +4786,7 @@ __export(exports_tmux, {
4786
4786
  killSession: () => killSession,
4787
4787
  ensureSession: () => ensureSession,
4788
4788
  attachArgv: () => attachArgv,
4789
+ PANE_VERSION_OPTION: () => PANE_VERSION_OPTION,
4789
4790
  CHAT_TAB_SWITCH_BINDINGS: () => CHAT_TAB_SWITCH_BINDINGS,
4790
4791
  CHAT_TAB_STATUS_FORMAT: () => CHAT_TAB_STATUS_FORMAT,
4791
4792
  CHAT_TAB_STATUS_CURRENT_FORMAT: () => CHAT_TAB_STATUS_CURRENT_FORMAT,
@@ -4826,6 +4827,7 @@ async function ensureSessionImpl(opts) {
4826
4827
  const claudeAlive = await claudePaneIdStrict(opts.name) !== "";
4827
4828
  if (claudeAlive && worktreeOk && vendorOk) {
4828
4829
  await healTaskPaneWidths(opts.name);
4830
+ await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
4829
4831
  return true;
4830
4832
  }
4831
4833
  if (worktreeOk && !vendorOk && opts.command.length > 0) {
@@ -4833,11 +4835,13 @@ async function ensureSessionImpl(opts) {
4833
4835
  if (opts.vendor)
4834
4836
  await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
4835
4837
  await healTaskPaneWidths(opts.name);
4838
+ await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
4836
4839
  return true;
4837
4840
  }
4838
4841
  }
4839
4842
  if (worktreeOk && vendorOk && await windowCount(opts.name) > 1) {
4840
4843
  await healTaskPaneWidths(opts.name);
4844
+ await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
4841
4845
  return true;
4842
4846
  }
4843
4847
  await runTmux(["kill-session", "-t", `=${opts.name}`]);
@@ -4936,6 +4940,68 @@ async function healTaskPaneWidths(session) {
4936
4940
  `).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "tasks").map(([id]) => id?.trim()).filter((id) => !!id);
4937
4941
  await runTmuxSequence(taskPanes.map((pane) => ["resize-pane", "-t", pane, "-x", `${TASKS_PANE_WIDTH}`]));
4938
4942
  }
4943
+ function parseKobePaneRows(stdout) {
4944
+ const rows = [];
4945
+ for (const raw of stdout.split(`
4946
+ `)) {
4947
+ const line = raw.trim();
4948
+ if (!line)
4949
+ continue;
4950
+ const [windowId, paneId, role, version] = line.split("\t");
4951
+ if (!windowId || !paneId || !role)
4952
+ continue;
4953
+ rows.push({ windowId: windowId.trim(), paneId: paneId.trim(), role: role.trim(), version: version?.trim() ?? "" });
4954
+ }
4955
+ return rows;
4956
+ }
4957
+ async function healKobePaneVersions(session, cwd, taskId, vendor) {
4958
+ const inv = kobeCliInvocation();
4959
+ const envPrefix = inheritedEnvPrefix();
4960
+ const { code, stdout } = await runTmuxCapturing([
4961
+ "list-panes",
4962
+ "-s",
4963
+ "-t",
4964
+ `=${session}`,
4965
+ "-F",
4966
+ `#{window_id} #{pane_id} #{@kobe_role} #{${PANE_VERSION_OPTION}}`
4967
+ ]);
4968
+ if (code !== 0)
4969
+ return;
4970
+ const byWindow = new Map;
4971
+ for (const row of parseKobePaneRows(stdout)) {
4972
+ const panes = byWindow.get(row.windowId) ?? [];
4973
+ panes.push(row);
4974
+ byWindow.set(row.windowId, panes);
4975
+ }
4976
+ const commands = [];
4977
+ for (const panes of byWindow.values()) {
4978
+ const claudePane = panes.find((pane) => pane.role === "claude")?.paneId;
4979
+ const tasksPane = panes.find((pane) => pane.role === "tasks");
4980
+ const opsPane = panes.find((pane) => pane.role === "ops");
4981
+ if (tasksPane && tasksPane.version !== CURRENT_VERSION) {
4982
+ commands.push(["respawn-pane", "-k", "-t", tasksPane.paneId, "-c", cwd, keepAlive(envPrefix + tasksPaneCommand(inv))], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
4983
+ }
4984
+ if (opsPane && claudePane && opsPane.version !== CURRENT_VERSION) {
4985
+ commands.push([
4986
+ "respawn-pane",
4987
+ "-k",
4988
+ "-t",
4989
+ opsPane.paneId,
4990
+ "-c",
4991
+ cwd,
4992
+ keepAlive(envPrefix + opsPaneCommand({
4993
+ cwd,
4994
+ taskId,
4995
+ claudePaneId: claudePane,
4996
+ cliInvocation: inv,
4997
+ vendor
4998
+ }))
4999
+ ], ["set-option", "-p", "-t", opsPane.paneId, "@kobe_role", "ops"], ["set-option", "-p", "-t", opsPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
5000
+ }
5001
+ }
5002
+ if (commands.length > 0)
5003
+ await runTmuxSequence(commands);
5004
+ }
4939
5005
  function inheritedEnvPrefix() {
4940
5006
  const parts = [];
4941
5007
  for (const key of ["KOBE_HOME_DIR", "KOBE_DAEMON_SOCKET_PATH", "KOBE_TMUX_SOCKET"]) {
@@ -4993,7 +5059,9 @@ async function buildPanesAround(claudePane, args) {
4993
5059
  `).map((line) => line.trim()).filter(Boolean).map((line) => line.split("=", 2)));
4994
5060
  await runTmuxSequence([
4995
5061
  ...ids.tasks ? [["set-option", "-p", "-t", ids.tasks, "@kobe_role", "tasks"]] : [],
4996
- ...ids.ops ? [["set-option", "-p", "-t", ids.ops, "@kobe_role", "ops"]] : []
5062
+ ...ids.tasks ? [["set-option", "-p", "-t", ids.tasks, PANE_VERSION_OPTION, CURRENT_VERSION]] : [],
5063
+ ...ids.ops ? [["set-option", "-p", "-t", ids.ops, "@kobe_role", "ops"]] : [],
5064
+ ...ids.ops ? [["set-option", "-p", "-t", ids.ops, PANE_VERSION_OPTION, CURRENT_VERSION]] : []
4997
5065
  ]);
4998
5066
  }
4999
5067
  async function newChatTab(session, vendorOverride) {
@@ -5048,12 +5116,13 @@ async function quickCreate(session) {
5048
5116
  await runTmux(["select-pane", "-t", tasksPane]);
5049
5117
  await sendKeys(tasksPane, "n");
5050
5118
  }
5051
- 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", 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;
5119
+ 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;
5052
5120
  var init_tmux = __esm(() => {
5053
5121
  init_invocation();
5054
5122
  init_interactive_command();
5055
5123
  init_client2();
5056
5124
  init_vendor();
5125
+ init_version();
5057
5126
  init_client2();
5058
5127
  CHAT_TAB_SWITCH_BINDINGS = [
5059
5128
  ["bind-key", "-n", "C-[", "previous-window"],
@@ -13681,6 +13750,7 @@ function TasksShell(props) {
13681
13750
  const dialog = useDialog();
13682
13751
  const kv = useKV();
13683
13752
  const [selectedId, setSelectedId] = createSignal(props.tasks()[0]?.id ?? null);
13753
+ const [updateInfo, setUpdateInfo] = createSignal(null);
13684
13754
  createEffect(() => {
13685
13755
  const active = props.orch?.activeTaskSignal()();
13686
13756
  if (active)
@@ -13690,6 +13760,10 @@ function TasksShell(props) {
13690
13760
  themeCtx.setTransparentBackground(props.transparent);
13691
13761
  if (props.focusAccent)
13692
13762
  themeCtx.setFocusAccent(props.focusAccent);
13763
+ checkLatestVersion().then((info) => {
13764
+ if (info)
13765
+ setUpdateInfo(info);
13766
+ }).catch(() => {});
13693
13767
  });
13694
13768
  async function createTask() {
13695
13769
  const repos = getSavedRepos();
@@ -13971,15 +14045,23 @@ function TasksShell(props) {
13971
14045
  onArchiveRequest: (id) => void archiveTask(id),
13972
14046
  focused: () => dialog.stack.length === 0
13973
14047
  }));
13974
- insert(_el$, createComponent2(ShortcutHints, {}), null);
14048
+ insert(_el$, createComponent2(ShortcutHints, {
14049
+ updateInfo
14050
+ }), null);
13975
14051
  effect((_$p) => setProp(_el$, "backgroundColor", theme.background, _$p));
13976
14052
  return _el$;
13977
14053
  })();
13978
14054
  }
13979
- function ShortcutHints() {
14055
+ function ShortcutHints(props) {
13980
14056
  const {
13981
14057
  theme
13982
14058
  } = useTheme();
14059
+ const updateLabel = createMemo(() => {
14060
+ const info = props.updateInfo();
14061
+ if (!info)
14062
+ return `v${CURRENT_VERSION}`;
14063
+ return info.hasUpdate ? `v${info.latest} available` : `v${CURRENT_VERSION} latest`;
14064
+ });
13983
14065
  const HINTS = [{
13984
14066
  k: "\u23CE",
13985
14067
  label: "open"
@@ -13992,6 +14074,9 @@ function ShortcutHints() {
13992
14074
  }, {
13993
14075
  k: "O",
13994
14076
  label: "open wt"
14077
+ }, {
14078
+ k: "[/]",
14079
+ label: "views"
13995
14080
  }, {
13996
14081
  k: "A/D",
13997
14082
  label: "archive/delete"
@@ -14010,6 +14095,15 @@ function ShortcutHints() {
14010
14095
  }, {
14011
14096
  k: "\u2303\u21E7T",
14012
14097
  label: "engine tab"
14098
+ }, {
14099
+ k: "Prefix T",
14100
+ label: "engine tab"
14101
+ }, {
14102
+ k: "Prefix F",
14103
+ label: "new task"
14104
+ }, {
14105
+ k: "F2",
14106
+ label: "rename tab"
14013
14107
  }, {
14014
14108
  k: "\u2303W",
14015
14109
  label: "close tab"
@@ -14018,55 +14112,72 @@ function ShortcutHints() {
14018
14112
  label: "detach"
14019
14113
  }];
14020
14114
  return (() => {
14021
- var _el$3 = createElement("box"), _el$4 = createElement("text");
14115
+ var _el$3 = createElement("box"), _el$4 = createElement("text"), _el$6 = createElement("box"), _el$7 = createElement("text"), _el$8 = createElement("text");
14022
14116
  insertNode(_el$3, _el$4);
14117
+ insertNode(_el$3, _el$6);
14118
+ insertNode(_el$3, _el$8);
14023
14119
  setProp(_el$3, "flexShrink", 0);
14024
14120
  setProp(_el$3, "flexDirection", "column");
14025
14121
  setProp(_el$3, "paddingLeft", 1);
14026
14122
  setProp(_el$3, "paddingRight", 1);
14027
14123
  setProp(_el$3, "paddingTop", 1);
14028
14124
  setProp(_el$3, "gap", 0);
14029
- insertNode(_el$4, createTextNode(`\u2500\u2500 keys \u2500\u2500`));
14125
+ insertNode(_el$4, createTextNode(`\u2500\u2500 system \u2500\u2500`));
14030
14126
  setProp(_el$4, "wrapMode", "none");
14127
+ insertNode(_el$6, _el$7);
14128
+ setProp(_el$6, "flexDirection", "row");
14129
+ setProp(_el$6, "gap", 1);
14130
+ setProp(_el$7, "wrapMode", "none");
14131
+ insert(_el$7, updateLabel);
14132
+ insertNode(_el$8, createTextNode(`\u2500\u2500 keys \u2500\u2500`));
14133
+ setProp(_el$8, "wrapMode", "none");
14031
14134
  insert(_el$3, createComponent2(For, {
14032
14135
  each: HINTS,
14033
14136
  children: (h) => (() => {
14034
- var _el$6 = createElement("box"), _el$7 = createElement("box"), _el$8 = createElement("text"), _el$9 = createTextNode(`[`), _el$0 = createTextNode(`]`), _el$1 = createElement("text");
14035
- insertNode(_el$6, _el$7);
14036
- insertNode(_el$6, _el$1);
14037
- setProp(_el$6, "flexDirection", "row");
14038
- setProp(_el$6, "gap", 1);
14039
- insertNode(_el$7, _el$8);
14040
- setProp(_el$7, "width", 10);
14041
- setProp(_el$7, "flexShrink", 0);
14042
- insertNode(_el$8, _el$9);
14043
- insertNode(_el$8, _el$0);
14044
- setProp(_el$8, "wrapMode", "none");
14045
- insert(_el$8, () => h.k, _el$0);
14046
- setProp(_el$1, "wrapMode", "none");
14047
- insert(_el$1, () => h.label);
14137
+ var _el$0 = createElement("box"), _el$1 = createElement("box"), _el$10 = createElement("text"), _el$11 = createTextNode(`[`), _el$12 = createTextNode(`]`), _el$13 = createElement("text");
14138
+ insertNode(_el$0, _el$1);
14139
+ insertNode(_el$0, _el$13);
14140
+ setProp(_el$0, "flexDirection", "row");
14141
+ setProp(_el$0, "gap", 1);
14142
+ insertNode(_el$1, _el$10);
14143
+ setProp(_el$1, "width", 10);
14144
+ setProp(_el$1, "flexShrink", 0);
14145
+ insertNode(_el$10, _el$11);
14146
+ insertNode(_el$10, _el$12);
14147
+ setProp(_el$10, "wrapMode", "none");
14148
+ insert(_el$10, () => h.k, _el$12);
14149
+ setProp(_el$13, "wrapMode", "none");
14150
+ insert(_el$13, () => h.label);
14048
14151
  effect((_p$) => {
14049
- var _v$3 = theme.accent, _v$4 = TextAttributes9.BOLD, _v$5 = theme.textMuted;
14050
- _v$3 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$3, _p$.e));
14051
- _v$4 !== _p$.t && (_p$.t = setProp(_el$8, "attributes", _v$4, _p$.t));
14052
- _v$5 !== _p$.a && (_p$.a = setProp(_el$1, "fg", _v$5, _p$.a));
14152
+ var _v$7 = theme.accent, _v$8 = TextAttributes9.BOLD, _v$9 = theme.textMuted;
14153
+ _v$7 !== _p$.e && (_p$.e = setProp(_el$10, "fg", _v$7, _p$.e));
14154
+ _v$8 !== _p$.t && (_p$.t = setProp(_el$10, "attributes", _v$8, _p$.t));
14155
+ _v$9 !== _p$.a && (_p$.a = setProp(_el$13, "fg", _v$9, _p$.a));
14053
14156
  return _p$;
14054
14157
  }, {
14055
14158
  e: undefined,
14056
14159
  t: undefined,
14057
14160
  a: undefined
14058
14161
  });
14059
- return _el$6;
14162
+ return _el$0;
14060
14163
  })()
14061
14164
  }), null);
14062
14165
  effect((_p$) => {
14063
- var _v$ = theme.textMuted, _v$2 = TextAttributes9.DIM;
14166
+ var _v$ = theme.textMuted, _v$2 = TextAttributes9.DIM, _v$3 = props.updateInfo()?.hasUpdate ? theme.warning : theme.textMuted, _v$4 = props.updateInfo()?.hasUpdate ? TextAttributes9.BOLD : TextAttributes9.DIM, _v$5 = theme.textMuted, _v$6 = TextAttributes9.DIM;
14064
14167
  _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
14065
14168
  _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "attributes", _v$2, _p$.t));
14169
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$7, "fg", _v$3, _p$.a));
14170
+ _v$4 !== _p$.o && (_p$.o = setProp(_el$7, "attributes", _v$4, _p$.o));
14171
+ _v$5 !== _p$.i && (_p$.i = setProp(_el$8, "fg", _v$5, _p$.i));
14172
+ _v$6 !== _p$.n && (_p$.n = setProp(_el$8, "attributes", _v$6, _p$.n));
14066
14173
  return _p$;
14067
14174
  }, {
14068
14175
  e: undefined,
14069
- t: undefined
14176
+ t: undefined,
14177
+ a: undefined,
14178
+ o: undefined,
14179
+ i: undefined,
14180
+ n: undefined
14070
14181
  });
14071
14182
  return _el$3;
14072
14183
  })();
@@ -14165,6 +14276,7 @@ var init_host = __esm(() => {
14165
14276
  init_store();
14166
14277
  init_repos();
14167
14278
  init_vendor();
14279
+ init_version();
14168
14280
  init_new_task_dialog();
14169
14281
  init_rename_task_dialog();
14170
14282
  init_settings_dialog();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@sma1lboy/kobe",
4
- "version": "0.6.2",
4
+ "version": "0.6.4",
5
5
  "description": "TUI orchestrator for Claude Code (codename)",
6
6
  "type": "module",
7
7
  "packageManager": "bun@1.3.13",