agileflow 4.0.0-alpha.17 → 4.0.0-alpha.19
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 +1 -1
- package/src/cli/commands/launch.js +28 -20
- package/src/runtime/launch/tabs.js +8 -0
- package/src/runtime/launch/tmux.js +50 -14
package/package.json
CHANGED
|
@@ -1182,25 +1182,29 @@ async function maybeOfferAutoRestore(prefs) {
|
|
|
1182
1182
|
if (ap !== bp) return bp - ap;
|
|
1183
1183
|
return 0;
|
|
1184
1184
|
});
|
|
1185
|
-
//
|
|
1186
|
-
//
|
|
1187
|
-
//
|
|
1188
|
-
//
|
|
1189
|
-
//
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1185
|
+
// Smart toggle at the top of the picker: one entry that flips the
|
|
1186
|
+
// current selection state. If everything's selected, checking it
|
|
1187
|
+
// deselects all; if anything's unselected, checking it selects all.
|
|
1188
|
+
// Visually separated from session entries by a dash divider line
|
|
1189
|
+
// so it doesn't blend in with real options.
|
|
1190
|
+
const TOGGLE_ALL = "__toggle_all__";
|
|
1191
|
+
const DIVIDER = "__divider__";
|
|
1192
1192
|
const allNames = sorted.map((s) => s.name);
|
|
1193
1193
|
const sessionOptions = sorted.map((s) => ({
|
|
1194
1194
|
value: s.name,
|
|
1195
|
-
label: `${s.pinned ? "
|
|
1195
|
+
label: `${s.pinned ? "* " : " "}${s.name}`,
|
|
1196
1196
|
hint: `${s.cli} — ${s.cwd}${s.worktree && s.worktree.branch ? ` [wt ${s.worktree.branch}]` : ""}`,
|
|
1197
1197
|
}));
|
|
1198
1198
|
const options = [
|
|
1199
|
-
{ value: SELECT_ALL, label: "✅ Select all", hint: "check every session" },
|
|
1200
1199
|
{
|
|
1201
|
-
value:
|
|
1202
|
-
label: "
|
|
1203
|
-
hint: "
|
|
1200
|
+
value: TOGGLE_ALL,
|
|
1201
|
+
label: "[ select all / deselect all ]",
|
|
1202
|
+
hint: "toggles every session below",
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
value: DIVIDER,
|
|
1206
|
+
label: "─────────────────────────────",
|
|
1207
|
+
hint: "",
|
|
1204
1208
|
},
|
|
1205
1209
|
...sessionOptions,
|
|
1206
1210
|
];
|
|
@@ -1228,14 +1232,18 @@ async function maybeOfferAutoRestore(prefs) {
|
|
|
1228
1232
|
}
|
|
1229
1233
|
/** @type {string[]} */
|
|
1230
1234
|
let chosen = Array.isArray(selection) ? selection : [];
|
|
1231
|
-
//
|
|
1232
|
-
//
|
|
1233
|
-
//
|
|
1234
|
-
|
|
1235
|
-
const
|
|
1236
|
-
chosen = chosen.filter((v) => v !==
|
|
1237
|
-
if (
|
|
1238
|
-
|
|
1235
|
+
// Strip the synthetic divider always (it's never a real choice).
|
|
1236
|
+
// Resolve the toggle: if the user checked it, flip the current
|
|
1237
|
+
// selection state — everything selected goes to nothing, anything
|
|
1238
|
+
// partial or empty goes to everything.
|
|
1239
|
+
const toggled = chosen.includes(TOGGLE_ALL);
|
|
1240
|
+
chosen = chosen.filter((v) => v !== TOGGLE_ALL && v !== DIVIDER);
|
|
1241
|
+
if (toggled) {
|
|
1242
|
+
const allSelected =
|
|
1243
|
+
chosen.length === allNames.length &&
|
|
1244
|
+
allNames.every((n) => chosen.includes(n));
|
|
1245
|
+
chosen = allSelected ? [] : [...allNames];
|
|
1246
|
+
}
|
|
1239
1247
|
if (chosen.length === 0) {
|
|
1240
1248
|
prompts.outro(
|
|
1241
1249
|
"Skipped. Run `agileflow launch restore` later to bring them back.",
|
|
@@ -308,6 +308,14 @@ const TAB_KEYBINDS = [
|
|
|
308
308
|
action: ["select-window", "-t", `:${n}`],
|
|
309
309
|
hint: `Alt+${n} → switch to tab ${n}`,
|
|
310
310
|
})),
|
|
311
|
+
// Alt+0 jumps to the LAST tab — matches Chrome's Ctrl+9 muscle
|
|
312
|
+
// memory (the 0 key sits naturally to the right of 9). tmux's
|
|
313
|
+
// `:$` is the alias for the last window in the session.
|
|
314
|
+
{
|
|
315
|
+
key: "M-0",
|
|
316
|
+
action: ["select-window", "-t", ":$"],
|
|
317
|
+
hint: "Alt+0 → switch to last tab",
|
|
318
|
+
},
|
|
311
319
|
// Browser-style cycling. Bind M-S-Tab AND M-BTab — terminals split
|
|
312
320
|
// on which escape sequence they emit for Shift+Tab, so binding both
|
|
313
321
|
// covers either path. Note: some window managers (GNOME, KDE)
|
|
@@ -241,24 +241,60 @@ function applyTabFormat(sessionName, runner, opts = {}) {
|
|
|
241
241
|
`#[bg=${PILL_BG},fg=${ACCENT}] #h ` +
|
|
242
242
|
`#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_CLOSE}`;
|
|
243
243
|
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
244
|
+
// Reduce escape-time so pressing Esc inside command-prompt (Alt+n
|
|
245
|
+
// worktree-name prompt, etc.) cancels immediately. tmux's default
|
|
246
|
+
// is 500ms — it waits for a follow-up key in case Esc is the start
|
|
247
|
+
// of an Alt+key sequence. 25ms is the common "snappy Esc" value used
|
|
248
|
+
// by tmux power-user configs (Oh My Tmux, gpakosz/.tmux, etc.).
|
|
249
|
+
runner.runSync(["set-option", "-sg", "escape-time", "25"]);
|
|
250
|
+
|
|
251
|
+
// Apply each option with the correct tmux scope. Session-scope opts
|
|
252
|
+
// (status-style, status-left/right, status-justify) take
|
|
253
|
+
// `-t <session>`. Window-scope opts (window-status-format,
|
|
254
|
+
// window-status-current-format, window-status-separator) take
|
|
255
|
+
// `-wg` so every window in every session picks them up. Earlier
|
|
256
|
+
// code applied ALL options with `-t session`, which made tmux
|
|
257
|
+
// silently ignore the window-scope ones — the visible symptom
|
|
258
|
+
// was tmux's default green status bar surviving on every session.
|
|
247
259
|
const ops = [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
260
|
+
{
|
|
261
|
+
scope: "session",
|
|
262
|
+
option: "status-style",
|
|
263
|
+
value: `bg=${BG},fg=${theme.inactiveFg}`,
|
|
264
|
+
},
|
|
265
|
+
{ scope: "session", option: "status-justify", value: "centre" },
|
|
266
|
+
{ scope: "session", option: "status-left", value: statusLeft },
|
|
267
|
+
{ scope: "session", option: "status-left-length", value: "100" },
|
|
268
|
+
{ scope: "session", option: "status-right", value: statusRight },
|
|
269
|
+
{ scope: "session", option: "status-right-length", value: "100" },
|
|
270
|
+
// Number windows from 1 so Alt+1 maps to the first tab (matches
|
|
271
|
+
// Chrome's Ctrl+1 mental model). Tmux default is base-index 0,
|
|
272
|
+
// which means Alt+1 with no other tabs open does nothing.
|
|
273
|
+
{ scope: "session", option: "base-index", value: "1" },
|
|
274
|
+
// Keep tab indices contiguous after a close — without this,
|
|
275
|
+
// closing window 2 leaves indices 1, 3, 4 and Alt+2 becomes
|
|
276
|
+
// dead. tmux renumbers on close so Alt+1..N always works.
|
|
277
|
+
{ scope: "session", option: "renumber-windows", value: "on" },
|
|
278
|
+
{ scope: "window-global", option: "window-status-separator", value: "" },
|
|
279
|
+
{
|
|
280
|
+
scope: "window-global",
|
|
281
|
+
option: "window-status-format",
|
|
282
|
+
value: inactiveFormat,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
scope: "window-global",
|
|
286
|
+
option: "window-status-current-format",
|
|
287
|
+
value: activeFormat,
|
|
288
|
+
},
|
|
257
289
|
];
|
|
258
290
|
let lastResult = { status: 0, stderr: "" };
|
|
259
291
|
const failures = [];
|
|
260
|
-
for (const
|
|
261
|
-
const
|
|
292
|
+
for (const { scope, option, value } of ops) {
|
|
293
|
+
const args =
|
|
294
|
+
scope === "session"
|
|
295
|
+
? ["set-option", "-t", sessionName, option, value]
|
|
296
|
+
: ["set-option", "-wg", option, value];
|
|
297
|
+
const r = runner.runSync(args);
|
|
262
298
|
lastResult = r;
|
|
263
299
|
if (r.status !== 0) {
|
|
264
300
|
failures.push({ option, stderr: (r.stderr || "").trim() });
|