agileflow 4.0.0-alpha.13 → 4.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "4.0.0-alpha.13",
3
+ "version": "4.0.0-alpha.15",
4
4
  "description": "AI-driven agile development toolkit for Claude Code — skills-first architecture with opt-in plugins (v4)",
5
5
  "keywords": [
6
6
  "agile",
@@ -273,19 +273,18 @@ const TAB_KEYBINDS = [
273
273
  hint: "Alt+, → rename current tab",
274
274
  },
275
275
  {
276
- // confirm-before runs the command on `y` and does nothing on `n`.
277
- // We pass session+index as positional args so the callback targets
278
- // the exact window the user pressed Alt+w on — without this, the
279
- // callback would re-probe display-message and could close the
280
- // wrong tab if focus moved during the confirmation prompt.
276
+ // Direct close no confirm-before. The earlier confirm-before
277
+ // path was brittle under tmux's bind-key parsing (deferred-command
278
+ // quoting issues caused the keybind to silently no-op on some
279
+ // tmux builds). Alt+Shift+T (Alt+T) restores the last closed
280
+ // tab in the same cwd, so the close is effectively reversible —
281
+ // matches Chrome's instant-close + Ctrl+Shift+T undo.
281
282
  key: "M-w",
282
283
  action: [
283
- "confirm-before",
284
- "-p",
285
- "kill tab #W? (y/n)",
286
- "run-shell '%AGILEFLOW% launch __close-window #{session_name} #{window_index}'",
284
+ "run-shell",
285
+ "%AGILEFLOW% launch __close-window #{session_name} #{window_index}",
287
286
  ],
288
- hint: "Alt+w → close current tab (with confirm)",
287
+ hint: "Alt+w → close current tab (Alt+Shift+T to undo)",
289
288
  },
290
289
  {
291
290
  // tmux's built-in window picker. -Z zooms (full-screen the picker),
@@ -198,33 +198,81 @@ function detectTmuxVersion(runner) {
198
198
  */
199
199
  function applyTabFormat(sessionName, runner, opts = {}) {
200
200
  const theme = { ...tabs.DEFAULT_TAB_THEME, ...(opts.theme || {}) };
201
- const format = tabs.buildTabFormat({
202
- tmuxVersion: opts.tmuxVersion,
203
- theme: opts.theme,
204
- });
205
- // Override tmux's default green status-style so the strip's dark
206
- // background isn't broken up by tmux's stock green bar. Also clear
207
- // status-left / status-right they default to session info + clock
208
- // on green; the tab strip already shows what the user needs.
209
- runner.runSync([
210
- "set-option",
211
- "-t",
212
- sessionName,
213
- "status-style",
214
- `bg=${theme.stripBg} fg=${theme.inactiveFg}`,
215
- ]);
216
- runner.runSync(["set-option", "-t", sessionName, "status-left", ""]);
217
- runner.runSync(["set-option", "-t", sessionName, "status-right", ""]);
218
- const result = runner.runSync([
219
- "set-option",
220
- "-t",
221
- sessionName,
222
- "status-format[0]",
223
- format,
224
- ]);
201
+ // Use the standard per-window formats instead of overriding the full
202
+ // status-format[0] — broader tmux compatibility, and tmux silently
203
+ // ignores invalid status-format expressions on some versions, which
204
+ // makes overrides hard to debug. window-status-format works on every
205
+ // tmux 2.0+.
206
+ // jakobwesthoff/tmux-from-scratch inspired styling: active tab is a
207
+ // rounded "pill" (Powerline half-round glyphs) over a lighter
208
+ // background, with brand-orange accent on the index:name separator
209
+ // colon. Inactive tabs are plain text with the same accent colon.
210
+ // Session name appears as a rounded pill on the left, hostname as
211
+ // a rounded pill on the right.
212
+ //
213
+ // Requires a Nerd Font / Powerline-patched font in the user's
214
+ // terminal. Without one the glyphs render as tofu boxes; users on
215
+ // a vanilla font can switch back via a future `tabStyle: "flat"`
216
+ // pref.
217
+ const HALF_ROUND_OPEN = ""; //
218
+ const HALF_ROUND_CLOSE = ""; //
219
+ const TRIANGLE_OPEN = ""; //
220
+ const TRIANGLE_CLOSE = ""; //
221
+ const BG = theme.stripBg;
222
+ const PILL_BG = theme.activeNameBg;
223
+ const ACCENT = theme.activeBg;
224
+ const FG = theme.activeNameFg;
225
+
226
+ const inactiveFormat = ` #I#[fg=${ACCENT}]:#[fg=default]#W `;
227
+ const activeFormat =
228
+ `#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_OPEN}` +
229
+ `#[bg=${PILL_BG},fg=${FG},bold]#I#[fg=${ACCENT}]:#[fg=${FG},nobold]#W` +
230
+ `#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_CLOSE}`;
231
+ const statusLeft =
232
+ `#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_OPEN}` +
233
+ `#[bg=${PILL_BG},fg=${ACCENT}] #S ` +
234
+ `#[fg=${PILL_BG},bg=${BG}]${TRIANGLE_CLOSE}`;
235
+ const statusRight =
236
+ `#[fg=${PILL_BG},bg=${BG}]${TRIANGLE_OPEN}` +
237
+ `#[bg=${PILL_BG},fg=${ACCENT}] #h ` +
238
+ `#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_CLOSE}`;
239
+
240
+ // Apply each option and collect failures. If ANYTHING fails we surface
241
+ // it on stderr so users debugging "why doesn't my tab strip look right"
242
+ // can see the tmux complaint instead of staring at default formatting.
243
+ const ops = [
244
+ ["status-style", `bg=${BG},fg=${theme.inactiveFg}`],
245
+ ["status-justify", "centre"],
246
+ ["status-left", statusLeft],
247
+ ["status-left-length", "100"],
248
+ ["status-right", statusRight],
249
+ ["status-right-length", "100"],
250
+ ["window-status-separator", ""],
251
+ ["window-status-format", inactiveFormat],
252
+ ["window-status-current-format", activeFormat],
253
+ ];
254
+ let lastResult = { status: 0, stderr: "" };
255
+ const failures = [];
256
+ for (const [option, value] of ops) {
257
+ const r = runner.runSync(["set-option", "-t", sessionName, option, value]);
258
+ lastResult = r;
259
+ if (r.status !== 0) {
260
+ failures.push({ option, stderr: (r.stderr || "").trim() });
261
+ }
262
+ }
263
+ if (failures.length > 0) {
264
+ // eslint-disable-next-line no-console
265
+ console.error(
266
+ `agileflow launch: tab strip styling — ${failures.length} of ${ops.length} options rejected by tmux:`,
267
+ );
268
+ for (const f of failures) {
269
+ // eslint-disable-next-line no-console
270
+ console.error(` ${f.option}: ${f.stderr || "(no error message)"}`);
271
+ }
272
+ }
225
273
  return {
226
- applied: result.status === 0,
227
- stderr: result.stderr || "",
274
+ applied: lastResult.status === 0,
275
+ stderr: lastResult.stderr || "",
228
276
  };
229
277
  }
230
278
 
@@ -330,15 +378,15 @@ const KEYBIND_PRESET_BINDINGS = {
330
378
  },
331
379
  {
332
380
  // Prompt for a worktree name, then spawn. tmux's command-prompt
333
- // substitutes %% with the user's input. Single-quoting the
334
- // run-shell command keeps the shell parser happy when the name
335
- // contains odd characters; the inner double-quotes around %%
336
- // protect against shell word-splitting.
381
+ // natively cancels on Escape (or Ctrl+G) without firing the
382
+ // deferred command the prompt label calls that out so users
383
+ // know they can back out. The agileflow CLI also bails cleanly
384
+ // if `%%` came in empty (user hit Enter with no input).
337
385
  key: "M-n",
338
386
  action: [
339
387
  "command-prompt",
340
388
  "-p",
341
- "worktree name:",
389
+ "worktree name (esc to cancel):",
342
390
  "run-shell '%AGILEFLOW% launch new \"%%\"'",
343
391
  ],
344
392
  hint: "Alt+n → prompt for a name, create a worktree, spawn there",