@gigaflow/gmux 0.15.0
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/CHANGELOG.md +654 -0
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/dist/adapters/claude-code.d.ts +52 -0
- package/dist/adapters/claude-code.js +315 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +39 -0
- package/dist/adapters/codex.js +233 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/jsonl.d.ts +40 -0
- package/dist/adapters/jsonl.js +92 -0
- package/dist/adapters/jsonl.js.map +1 -0
- package/dist/adapters/registry.d.ts +11 -0
- package/dist/adapters/registry.js +21 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +43 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli/border-client.d.ts +8 -0
- package/dist/cli/border-client.js +11 -0
- package/dist/cli/border-client.js.map +1 -0
- package/dist/cli/commands/__ask-cancel.d.ts +35 -0
- package/dist/cli/commands/__ask-cancel.js +91 -0
- package/dist/cli/commands/__ask-cancel.js.map +1 -0
- package/dist/cli/commands/__ask-refresh.d.ts +40 -0
- package/dist/cli/commands/__ask-refresh.js +54 -0
- package/dist/cli/commands/__ask-refresh.js.map +1 -0
- package/dist/cli/commands/__ask-run.d.ts +43 -0
- package/dist/cli/commands/__ask-run.js +126 -0
- package/dist/cli/commands/__ask-run.js.map +1 -0
- package/dist/cli/commands/__ask-send.d.ts +50 -0
- package/dist/cli/commands/__ask-send.js +138 -0
- package/dist/cli/commands/__ask-send.js.map +1 -0
- package/dist/cli/commands/__preview-card.d.ts +31 -0
- package/dist/cli/commands/__preview-card.js +72 -0
- package/dist/cli/commands/__preview-card.js.map +1 -0
- package/dist/cli/commands/ask.d.ts +50 -0
- package/dist/cli/commands/ask.js +240 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/auto.d.ts +12 -0
- package/dist/cli/commands/auto.js +28 -0
- package/dist/cli/commands/auto.js.map +1 -0
- package/dist/cli/commands/cockpit.d.ts +14 -0
- package/dist/cli/commands/cockpit.js +52 -0
- package/dist/cli/commands/cockpit.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +75 -0
- package/dist/cli/commands/daemon.js +258 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +159 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/grep.d.ts +2 -0
- package/dist/cli/commands/grep.js +52 -0
- package/dist/cli/commands/grep.js.map +1 -0
- package/dist/cli/commands/index-cmd.d.ts +2 -0
- package/dist/cli/commands/index-cmd.js +29 -0
- package/dist/cli/commands/index-cmd.js.map +1 -0
- package/dist/cli/commands/ls.d.ts +30 -0
- package/dist/cli/commands/ls.js +95 -0
- package/dist/cli/commands/ls.js.map +1 -0
- package/dist/cli/commands/overlay.d.ts +26 -0
- package/dist/cli/commands/overlay.js +216 -0
- package/dist/cli/commands/overlay.js.map +1 -0
- package/dist/cli/commands/pick.d.ts +59 -0
- package/dist/cli/commands/pick.js +254 -0
- package/dist/cli/commands/pick.js.map +1 -0
- package/dist/cli/commands/picker-rows.d.ts +17 -0
- package/dist/cli/commands/picker-rows.js +47 -0
- package/dist/cli/commands/picker-rows.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +25 -0
- package/dist/cli/commands/resume.js +88 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/run.d.ts +13 -0
- package/dist/cli/commands/run.js +78 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +37 -0
- package/dist/cli/commands/setup.js +200 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/show.d.ts +2 -0
- package/dist/cli/commands/show.js +24 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/summarize.d.ts +2 -0
- package/dist/cli/commands/summarize.js +59 -0
- package/dist/cli/commands/summarize.js.map +1 -0
- package/dist/cli/commands/tmux.d.ts +11 -0
- package/dist/cli/commands/tmux.js +90 -0
- package/dist/cli/commands/tmux.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +2 -0
- package/dist/cli/commands/watch.js +76 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/format.d.ts +74 -0
- package/dist/cli/format.js +242 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/gmux-render.d.ts +11 -0
- package/dist/cli/gmux-render.js +60 -0
- package/dist/cli/gmux-render.js.map +1 -0
- package/dist/cli/main.d.ts +8 -0
- package/dist/cli/main.js +145 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/overlay-ask.d.ts +13 -0
- package/dist/cli/overlay-ask.js +32 -0
- package/dist/cli/overlay-ask.js.map +1 -0
- package/dist/cli/overlay.d.ts +26 -0
- package/dist/cli/overlay.js +149 -0
- package/dist/cli/overlay.js.map +1 -0
- package/dist/cli/picker.d.ts +308 -0
- package/dist/cli/picker.js +642 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/preview.d.ts +87 -0
- package/dist/cli/preview.js +292 -0
- package/dist/cli/preview.js.map +1 -0
- package/dist/cli/tmux-label.d.ts +50 -0
- package/dist/cli/tmux-label.js +146 -0
- package/dist/cli/tmux-label.js.map +1 -0
- package/dist/core/errors.d.ts +54 -0
- package/dist/core/errors.js +91 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fingerprint.d.ts +21 -0
- package/dist/core/fingerprint.js +59 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/gmux-types.d.ts +102 -0
- package/dist/core/gmux-types.js +13 -0
- package/dist/core/gmux-types.js.map +1 -0
- package/dist/core/pane-state.d.ts +5 -0
- package/dist/core/pane-state.js +15 -0
- package/dist/core/pane-state.js.map +1 -0
- package/dist/core/paths.d.ts +68 -0
- package/dist/core/paths.js +106 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/proc-tree.d.ts +9 -0
- package/dist/core/proc-tree.js +33 -0
- package/dist/core/proc-tree.js.map +1 -0
- package/dist/core/text.d.ts +34 -0
- package/dist/core/text.js +126 -0
- package/dist/core/text.js.map +1 -0
- package/dist/core/types.d.ts +322 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -0
- package/dist/services/ask-transcript.d.ts +256 -0
- package/dist/services/ask-transcript.js +544 -0
- package/dist/services/ask-transcript.js.map +1 -0
- package/dist/services/ask.d.ts +54 -0
- package/dist/services/ask.js +161 -0
- package/dist/services/ask.js.map +1 -0
- package/dist/services/auto-summarize.d.ts +188 -0
- package/dist/services/auto-summarize.js +415 -0
- package/dist/services/auto-summarize.js.map +1 -0
- package/dist/services/concurrency.d.ts +2 -0
- package/dist/services/concurrency.js +16 -0
- package/dist/services/concurrency.js.map +1 -0
- package/dist/services/config.d.ts +131 -0
- package/dist/services/config.js +325 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/daemon-client.d.ts +9 -0
- package/dist/services/daemon-client.js +34 -0
- package/dist/services/daemon-client.js.map +1 -0
- package/dist/services/daemon-socket.d.ts +30 -0
- package/dist/services/daemon-socket.js +76 -0
- package/dist/services/daemon-socket.js.map +1 -0
- package/dist/services/daemon.d.ts +43 -0
- package/dist/services/daemon.js +106 -0
- package/dist/services/daemon.js.map +1 -0
- package/dist/services/distill.d.ts +39 -0
- package/dist/services/distill.js +93 -0
- package/dist/services/distill.js.map +1 -0
- package/dist/services/guardian.d.ts +26 -0
- package/dist/services/guardian.js +71 -0
- package/dist/services/guardian.js.map +1 -0
- package/dist/services/index-store.d.ts +36 -0
- package/dist/services/index-store.js +125 -0
- package/dist/services/index-store.js.map +1 -0
- package/dist/services/log-rotation.d.ts +3 -0
- package/dist/services/log-rotation.js +28 -0
- package/dist/services/log-rotation.js.map +1 -0
- package/dist/services/pane-links.d.ts +10 -0
- package/dist/services/pane-links.js +47 -0
- package/dist/services/pane-links.js.map +1 -0
- package/dist/services/pane-process.d.ts +95 -0
- package/dist/services/pane-process.js +183 -0
- package/dist/services/pane-process.js.map +1 -0
- package/dist/services/pane-registry.d.ts +24 -0
- package/dist/services/pane-registry.js +40 -0
- package/dist/services/pane-registry.js.map +1 -0
- package/dist/services/provider-process.d.ts +37 -0
- package/dist/services/provider-process.js +105 -0
- package/dist/services/provider-process.js.map +1 -0
- package/dist/services/providers.d.ts +76 -0
- package/dist/services/providers.js +104 -0
- package/dist/services/providers.js.map +1 -0
- package/dist/services/resolve.d.ts +3 -0
- package/dist/services/resolve.js +18 -0
- package/dist/services/resolve.js.map +1 -0
- package/dist/services/resources.d.ts +17 -0
- package/dist/services/resources.js +113 -0
- package/dist/services/resources.js.map +1 -0
- package/dist/services/search.d.ts +29 -0
- package/dist/services/search.js +135 -0
- package/dist/services/search.js.map +1 -0
- package/dist/services/semantic-gate.d.ts +13 -0
- package/dist/services/semantic-gate.js +23 -0
- package/dist/services/semantic-gate.js.map +1 -0
- package/dist/services/semantic.d.ts +113 -0
- package/dist/services/semantic.js +216 -0
- package/dist/services/semantic.js.map +1 -0
- package/dist/services/sensors.d.ts +92 -0
- package/dist/services/sensors.js +246 -0
- package/dist/services/sensors.js.map +1 -0
- package/dist/services/summarize.d.ts +84 -0
- package/dist/services/summarize.js +230 -0
- package/dist/services/summarize.js.map +1 -0
- package/dist/services/tmux-gateway.d.ts +17 -0
- package/dist/services/tmux-gateway.js +10 -0
- package/dist/services/tmux-gateway.js.map +1 -0
- package/dist/services/tmux-resolve.d.ts +54 -0
- package/dist/services/tmux-resolve.js +0 -0
- package/dist/services/tmux-resolve.js.map +1 -0
- package/dist/services/tmux.d.ts +32 -0
- package/dist/services/tmux.js +107 -0
- package/dist/services/tmux.js.map +1 -0
- package/dist/services/views.d.ts +7 -0
- package/dist/services/views.js +18 -0
- package/dist/services/views.js.map +1 -0
- package/dist/services/watch.d.ts +36 -0
- package/dist/services/watch.js +98 -0
- package/dist/services/watch.js.map +1 -0
- package/dist/services/workspace.d.ts +19 -0
- package/dist/services/workspace.js +88 -0
- package/dist/services/workspace.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preview pane — both halves of it.
|
|
3
|
+
*
|
|
4
|
+
* fzf has exactly ONE preview pane and cannot split it. So gmux renders the card
|
|
5
|
+
* and the chat into one command's output and owns the boundary itself: fzf does
|
|
6
|
+
* not gain a pane, it gains a longer string.
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* ┌─ sessions ─────┬─ preview ──────────────────┐
|
|
10
|
+
* │ > webshop 2h │ 4998a936 webshop 2h ago │
|
|
11
|
+
* │ api 4h │ WHERE IT LANDED … │ ← formatCard, clipped
|
|
12
|
+
* │ docs 1d │ ── ask ────────────────────│ ← the divider
|
|
13
|
+
* │ │ you │
|
|
14
|
+
* │ │ why did this one fail? │ ← the transcript
|
|
15
|
+
* │ │ gmux │
|
|
16
|
+
* │ │ The run died in apply_… │
|
|
17
|
+
* └────────────────┴────────────────────────────┘
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **No conversation ⇒ the card gets the whole pane, byte-identical to today.**
|
|
21
|
+
* That is the deal, not a nicety: a picker run where nobody presses ctrl-o must
|
|
22
|
+
* render exactly what it rendered before this file existed. It needs no flag and
|
|
23
|
+
* no branch — a missing transcript reads as zero events, `splitPreview` hands the
|
|
24
|
+
* card every row, and `formatPreview` returns `formatCard` and nothing else.
|
|
25
|
+
*
|
|
26
|
+
* **Nothing here calls a model, and nothing here may ever learn how.** The
|
|
27
|
+
* preview command re-runs on every cursor move, so a model call in this file is
|
|
28
|
+
* a model call per keystroke. The chat renders from the transcript file or it
|
|
29
|
+
* does not render.
|
|
30
|
+
*
|
|
31
|
+
* **Colour is an accent, never the message.** The preview's stdout is a pipe,
|
|
32
|
+
* not a tty, so `format.ts`'s gated `dim`/`cyan` are no-ops here and the CARD
|
|
33
|
+
* stays monochrome, exactly as it shipped. The divider and the `you`/`gmux`
|
|
34
|
+
* speakers use the `paneCyan`/`paneBold` variants instead, which fzf renders
|
|
35
|
+
* with `--ansi` — so the seam between card and chat pops in colour. But the
|
|
36
|
+
* divider is still carried by GLYPHS and the speakers by LAYOUT, so `NO_COLOR`,
|
|
37
|
+
* `TERM=dumb` or a colourless pane loses only the accent, never the structure.
|
|
38
|
+
*/
|
|
39
|
+
import type { SessionView } from "../core/types.js";
|
|
40
|
+
import type { AskTranscript } from "../services/ask-transcript.js";
|
|
41
|
+
/** Row budget for one render. Sums to the pane height. */
|
|
42
|
+
export interface PreviewSplit {
|
|
43
|
+
cardRows: number;
|
|
44
|
+
dividerRows: 0 | 1;
|
|
45
|
+
chatRows: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How the pane's rows are divided.
|
|
49
|
+
*
|
|
50
|
+
* `paneRows` arrives from `$FZF_PREVIEW_LINES` via a shell and a commander flag,
|
|
51
|
+
* which makes it environment-controlled text. Treat it as hostile: anything
|
|
52
|
+
* non-positive is the guess, and no arithmetic below may go negative or throw.
|
|
53
|
+
*/
|
|
54
|
+
export declare function splitPreview(paneRows: number, hasChat: boolean): PreviewSplit;
|
|
55
|
+
/** `Math.max(0, …)`: `String.repeat` throws RangeError on a negative count, and
|
|
56
|
+
* the width came from the environment. A narrow pane must not crash the pane.
|
|
57
|
+
* The colour is zero display columns, so the rule is still exactly `width`. */
|
|
58
|
+
export declare function askDivider(width: number): string;
|
|
59
|
+
/**
|
|
60
|
+
* Is there a conversation at all?
|
|
61
|
+
*
|
|
62
|
+
* A question is the only thing that makes one. `meta` alone is a transcript the
|
|
63
|
+
* sender created and nothing more, and it must still render the empty state —
|
|
64
|
+
* otherwise a `no-provider` send would put a divider over an empty half.
|
|
65
|
+
*/
|
|
66
|
+
export declare function hasChatContent(transcript: AskTranscript | null): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The chat half: the last `rows` wrapped rows of the thread.
|
|
69
|
+
*
|
|
70
|
+
* **Auto-tail.** New text appears at the bottom, old text slides up. That is what
|
|
71
|
+
* makes it read as a chat rather than a document, it composes with the refresh
|
|
72
|
+
* loop for free, and it means the common case needs no scrolling at all. There is
|
|
73
|
+
* deliberately no scroll: fzf's own `shift-up` scrolls the WHOLE preview, which
|
|
74
|
+
* in split mode drags the card off the top and reveals nothing, because there is
|
|
75
|
+
* only one pane. If you hit the ceiling, `cat` the transcript.
|
|
76
|
+
*
|
|
77
|
+
* `now` is a parameter so the `thinking… Ns` count is testable without a clock.
|
|
78
|
+
*/
|
|
79
|
+
export declare function formatChat(transcript: AskTranscript, rows: number, width: number, now?: Date): string;
|
|
80
|
+
/**
|
|
81
|
+
* One preview command's whole output: the card, and the chat if there is one.
|
|
82
|
+
*
|
|
83
|
+
* **The empty state returns `formatCard` and NOTHING else** — not "the card plus
|
|
84
|
+
* an empty tail", not "the card with a trailing divider". Byte-identical, because
|
|
85
|
+
* that is what decision 4 promises to everyone who never presses ctrl-o.
|
|
86
|
+
*/
|
|
87
|
+
export declare function formatPreview(view: SessionView, transcript: AskTranscript | null, split: PreviewSplit, width: number, now?: Date): string;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The preview pane — both halves of it.
|
|
3
|
+
*
|
|
4
|
+
* fzf has exactly ONE preview pane and cannot split it. So gmux renders the card
|
|
5
|
+
* and the chat into one command's output and owns the boundary itself: fzf does
|
|
6
|
+
* not gain a pane, it gains a longer string.
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* ┌─ sessions ─────┬─ preview ──────────────────┐
|
|
10
|
+
* │ > webshop 2h │ 4998a936 webshop 2h ago │
|
|
11
|
+
* │ api 4h │ WHERE IT LANDED … │ ← formatCard, clipped
|
|
12
|
+
* │ docs 1d │ ── ask ────────────────────│ ← the divider
|
|
13
|
+
* │ │ you │
|
|
14
|
+
* │ │ why did this one fail? │ ← the transcript
|
|
15
|
+
* │ │ gmux │
|
|
16
|
+
* │ │ The run died in apply_… │
|
|
17
|
+
* └────────────────┴────────────────────────────┘
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* **No conversation ⇒ the card gets the whole pane, byte-identical to today.**
|
|
21
|
+
* That is the deal, not a nicety: a picker run where nobody presses ctrl-o must
|
|
22
|
+
* render exactly what it rendered before this file existed. It needs no flag and
|
|
23
|
+
* no branch — a missing transcript reads as zero events, `splitPreview` hands the
|
|
24
|
+
* card every row, and `formatPreview` returns `formatCard` and nothing else.
|
|
25
|
+
*
|
|
26
|
+
* **Nothing here calls a model, and nothing here may ever learn how.** The
|
|
27
|
+
* preview command re-runs on every cursor move, so a model call in this file is
|
|
28
|
+
* a model call per keystroke. The chat renders from the transcript file or it
|
|
29
|
+
* does not render.
|
|
30
|
+
*
|
|
31
|
+
* **Colour is an accent, never the message.** The preview's stdout is a pipe,
|
|
32
|
+
* not a tty, so `format.ts`'s gated `dim`/`cyan` are no-ops here and the CARD
|
|
33
|
+
* stays monochrome, exactly as it shipped. The divider and the `you`/`gmux`
|
|
34
|
+
* speakers use the `paneCyan`/`paneBold` variants instead, which fzf renders
|
|
35
|
+
* with `--ansi` — so the seam between card and chat pops in colour. But the
|
|
36
|
+
* divider is still carried by GLYPHS and the speakers by LAYOUT, so `NO_COLOR`,
|
|
37
|
+
* `TERM=dumb` or a colourless pane loses only the accent, never the structure.
|
|
38
|
+
*/
|
|
39
|
+
import { wrapText } from "../core/text.js";
|
|
40
|
+
import { formatCard, indent, paneBold, paneCyan, paneDim } from "./format.js";
|
|
41
|
+
/** Below this the card is a fragment and the chat is unreadable. See COLLAPSED. */
|
|
42
|
+
const MIN_SPLIT = 15;
|
|
43
|
+
/** Enough card to be a card: label, meta, blank, a heading and two lines under it. */
|
|
44
|
+
const CARD_MIN = 6;
|
|
45
|
+
/** Enough chat to read as one: two speakers, two bodies, the blank between them. */
|
|
46
|
+
const CHAT_MIN = 8;
|
|
47
|
+
const DIVIDER_ROWS = 1;
|
|
48
|
+
/**
|
|
49
|
+
* What we assume when fzf did not tell us the pane height.
|
|
50
|
+
*
|
|
51
|
+
* A guess rather than a refusal to split, deliberately: the chat auto-tails, so
|
|
52
|
+
* an over-guess costs a little fzf scrolling, while refusing shows no chat at all
|
|
53
|
+
* to someone who just asked for one.
|
|
54
|
+
*/
|
|
55
|
+
const ASSUMED_PANE_ROWS = 24;
|
|
56
|
+
/**
|
|
57
|
+
* How the pane's rows are divided.
|
|
58
|
+
*
|
|
59
|
+
* `paneRows` arrives from `$FZF_PREVIEW_LINES` via a shell and a commander flag,
|
|
60
|
+
* which makes it environment-controlled text. Treat it as hostile: anything
|
|
61
|
+
* non-positive is the guess, and no arithmetic below may go negative or throw.
|
|
62
|
+
*/
|
|
63
|
+
export function splitPreview(paneRows, hasChat) {
|
|
64
|
+
const rows = Number.isFinite(paneRows) && paneRows > 0 ? Math.floor(paneRows) : ASSUMED_PANE_ROWS;
|
|
65
|
+
// The empty state, and the whole of it. `cardRows` is the pane because there
|
|
66
|
+
// is nothing to share it with.
|
|
67
|
+
if (!hasChat)
|
|
68
|
+
return { cardRows: rows, dividerRows: 0, chatRows: 0 };
|
|
69
|
+
if (rows < MIN_SPLIT) {
|
|
70
|
+
// COLLAPSED — the answer to "what about a 20-line terminal?", where the pane
|
|
71
|
+
// is 14 rows and two 7-row halves are useless to everybody. The card drops to
|
|
72
|
+
// `formatCard`'s first line (`bold(sessionLabel)`) and the chat takes the
|
|
73
|
+
// rest: you still know which session "this" is, which is the entire point of
|
|
74
|
+
// the focus model, and the chat stays readable. A better failure than an
|
|
75
|
+
// 8-row card fragment.
|
|
76
|
+
const chatRows = Math.max(0, rows - 1 - DIVIDER_ROWS);
|
|
77
|
+
const dividerRows = chatRows > 0 ? 1 : 0;
|
|
78
|
+
return { cardRows: rows - chatRows - dividerRows, dividerRows, chatRows };
|
|
79
|
+
}
|
|
80
|
+
const chatRows = Math.min(Math.max(Math.floor(rows / 2), CHAT_MIN), rows - DIVIDER_ROWS - CARD_MIN);
|
|
81
|
+
return { cardRows: rows - chatRows - DIVIDER_ROWS, dividerRows: 1, chatRows };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `"── ask "` is 7 display columns, so the rendered rule is exactly `width`.
|
|
85
|
+
*
|
|
86
|
+
* Labelled because `format.ts`'s idiom is named sections (`WHERE IT LANDED`,
|
|
87
|
+
* `NEXT STEP`) and an anonymous rule would be the only unnamed boundary on
|
|
88
|
+
* screen. `paneCyan` — gmux's own accent, the `gmux` speaker's colour — so the seam
|
|
89
|
+
* between the monochrome card and the chat is the one line that pops, which is
|
|
90
|
+
* the whole reason it is coloured rather than dim. `paneCyan` not `cyan`: the
|
|
91
|
+
* pane is a pipe, so the gated `cyan` is a no-op here; `paneCyan` drops only the
|
|
92
|
+
* TTY test and still honours `NO_COLOR`. The unicode needs no ASCII fallback —
|
|
93
|
+
* `format.ts` already ships `⚠ ○ ◐ ·` unconditionally.
|
|
94
|
+
*/
|
|
95
|
+
const DIVIDER_LABEL = "── ask ";
|
|
96
|
+
/** `Math.max(0, …)`: `String.repeat` throws RangeError on a negative count, and
|
|
97
|
+
* the width came from the environment. A narrow pane must not crash the pane.
|
|
98
|
+
* The colour is zero display columns, so the rule is still exactly `width`. */
|
|
99
|
+
export function askDivider(width) {
|
|
100
|
+
const fill = Number.isFinite(width) ? Math.floor(width) - DIVIDER_LABEL.length : 0;
|
|
101
|
+
return paneCyan(`${DIVIDER_LABEL}${"─".repeat(Math.max(0, fill))}`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Is there a conversation at all?
|
|
105
|
+
*
|
|
106
|
+
* A question is the only thing that makes one. `meta` alone is a transcript the
|
|
107
|
+
* sender created and nothing more, and it must still render the empty state —
|
|
108
|
+
* otherwise a `no-provider` send would put a divider over an empty half.
|
|
109
|
+
*/
|
|
110
|
+
export function hasChatContent(transcript) {
|
|
111
|
+
return transcript !== null && transcript.events.some((event) => event.t === "question");
|
|
112
|
+
}
|
|
113
|
+
function foldBySeq(events) {
|
|
114
|
+
const questions = [];
|
|
115
|
+
const chunks = new Map();
|
|
116
|
+
const settled = new Map();
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
switch (event.t) {
|
|
119
|
+
case "question":
|
|
120
|
+
questions.push(event);
|
|
121
|
+
break;
|
|
122
|
+
case "chunk": {
|
|
123
|
+
const existing = chunks.get(event.seq);
|
|
124
|
+
if (existing)
|
|
125
|
+
existing.push(event.text);
|
|
126
|
+
else
|
|
127
|
+
chunks.set(event.seq, [event.text]);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "end":
|
|
131
|
+
case "aborted":
|
|
132
|
+
settled.set(event.seq, { t: event.t });
|
|
133
|
+
break;
|
|
134
|
+
case "error":
|
|
135
|
+
settled.set(event.seq, { t: "error", message: event.message });
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Keyed by `seq`, never file order — the same rule `foldCompletedTurns` folds
|
|
142
|
+
// by, and for the same reason: correctness here must not depend on a lock in
|
|
143
|
+
// another module.
|
|
144
|
+
questions.sort((a, b) => a.seq - b.seq);
|
|
145
|
+
return { questions, chunks, settled };
|
|
146
|
+
}
|
|
147
|
+
/** Elapsed whole seconds since a question landed. Computed at render time and
|
|
148
|
+
* never stored — a number written into the transcript is stale on arrival. */
|
|
149
|
+
function elapsedSeconds(at, now) {
|
|
150
|
+
const started = Date.parse(at);
|
|
151
|
+
if (!Number.isFinite(started))
|
|
152
|
+
return 0;
|
|
153
|
+
return Math.max(0, Math.floor((now.getTime() - started) / 1000));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The answer slot, in every state a turn can be in.
|
|
157
|
+
*
|
|
158
|
+
* The in-flight render is specified rather than implied because it IS the UX for
|
|
159
|
+
* most of a turn's life: the provider buffers, so `thinking… 14s` is what the
|
|
160
|
+
* pane says for ~9–20s and the answer then lands in one paint.
|
|
161
|
+
*/
|
|
162
|
+
function answerText(seq, question, folded, now) {
|
|
163
|
+
const partial = (folded.chunks.get(seq) ?? []).join("").trim();
|
|
164
|
+
const settled = folded.settled.get(seq);
|
|
165
|
+
if (!settled) {
|
|
166
|
+
return partial === ""
|
|
167
|
+
? `thinking… ${elapsedSeconds(question.at, now)}s (esc to cancel)`
|
|
168
|
+
: partial;
|
|
169
|
+
}
|
|
170
|
+
if (settled.t === "error")
|
|
171
|
+
return settled.message ?? "the answer failed";
|
|
172
|
+
if (settled.t === "aborted") {
|
|
173
|
+
// The human still SEES an aborted turn here; only the model's view loses it
|
|
174
|
+
// (`foldCompletedTurns` drops it, so "ok but briefly" after an esc has no
|
|
175
|
+
// antecedent — which beats telling the model it failed and being apologized
|
|
176
|
+
// at instead of answered).
|
|
177
|
+
return partial === "" ? "(cancelled)" : `${partial}\n(cancelled)`;
|
|
178
|
+
}
|
|
179
|
+
return partial;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* `you` / `gmux`, with the body indented under it.
|
|
183
|
+
*
|
|
184
|
+
* Legible with colour off, which the pane requires: the speaker sits on its own
|
|
185
|
+
* line above an indented body, so the LAYOUT distinguishes them and the colour
|
|
186
|
+
* only accelerates it. `paneBold` is the section-heading idiom; `paneCyan` is
|
|
187
|
+
* already gmux's own colour (the `where` column, the `○` marker) — the forced
|
|
188
|
+
* variants, because the pane is a pipe and the gated ones would be no-ops here.
|
|
189
|
+
*/
|
|
190
|
+
function speaker(head, body, width) {
|
|
191
|
+
const wrapped = body
|
|
192
|
+
.split("\n")
|
|
193
|
+
.flatMap((line) => wrapText(line, Math.max(1, Math.floor(width) - 2)));
|
|
194
|
+
return [head, ...indent(wrapped.join("\n")).split("\n")];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The `· re:` suffix marks a CHANGE of focus, and nothing else.
|
|
198
|
+
*
|
|
199
|
+
* Focus re-points per question, so a thread of five questions can span five
|
|
200
|
+
* sessions and an answer rendered under a cursor that has since moved reads as
|
|
201
|
+
* being about the wrong one. But stamping every question with the same id when
|
|
202
|
+
* focus never moved is noise on the half of the pane with least room for it. The
|
|
203
|
+
* change is the only moment it carries information — plus the first question,
|
|
204
|
+
* which has nothing to differ from and everything to establish.
|
|
205
|
+
*/
|
|
206
|
+
function questionHead(question, previousFocus, first) {
|
|
207
|
+
const focus = question.focus;
|
|
208
|
+
if (!focus || (!first && focus === previousFocus))
|
|
209
|
+
return paneBold("you");
|
|
210
|
+
return `${paneBold("you")} ${paneDim(`· re: ${focus.slice(0, 8)}`)}`;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The chat half: the last `rows` wrapped rows of the thread.
|
|
214
|
+
*
|
|
215
|
+
* **Auto-tail.** New text appears at the bottom, old text slides up. That is what
|
|
216
|
+
* makes it read as a chat rather than a document, it composes with the refresh
|
|
217
|
+
* loop for free, and it means the common case needs no scrolling at all. There is
|
|
218
|
+
* deliberately no scroll: fzf's own `shift-up` scrolls the WHOLE preview, which
|
|
219
|
+
* in split mode drags the card off the top and reveals nothing, because there is
|
|
220
|
+
* only one pane. If you hit the ceiling, `cat` the transcript.
|
|
221
|
+
*
|
|
222
|
+
* `now` is a parameter so the `thinking… Ns` count is testable without a clock.
|
|
223
|
+
*/
|
|
224
|
+
export function formatChat(transcript, rows, width, now = new Date()) {
|
|
225
|
+
const folded = foldBySeq(transcript.events);
|
|
226
|
+
const lines = [];
|
|
227
|
+
let previousFocus = null;
|
|
228
|
+
for (const [index, question] of folded.questions.entries()) {
|
|
229
|
+
if (index > 0)
|
|
230
|
+
lines.push("");
|
|
231
|
+
lines.push(...speaker(questionHead(question, previousFocus, index === 0), question.text, width));
|
|
232
|
+
lines.push("");
|
|
233
|
+
lines.push(...speaker(paneCyan("gmux"), answerText(question.seq, question, folded, now), width));
|
|
234
|
+
previousFocus = question.focus;
|
|
235
|
+
}
|
|
236
|
+
const budget = Number.isFinite(rows) && rows > 0 ? Math.floor(rows) : 0;
|
|
237
|
+
return lines.slice(-budget || lines.length).join("\n");
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The card, clipped at the divider, counted in DISPLAY rows.
|
|
241
|
+
*
|
|
242
|
+
* `--preview-window=…,wrap` breaks a card line at the pane's right edge, and
|
|
243
|
+
* `formatCard` emits lines up to 647 columns — so a divider placed after
|
|
244
|
+
* `cardRows` *logical* lines would be shoved off the pane it exists to split.
|
|
245
|
+
* Breaking at the same column fzf would gives the same picture and puts the
|
|
246
|
+
* divider where we said it goes.
|
|
247
|
+
*
|
|
248
|
+
* The card is clipped and not rewritten: measured, it renders 23–83 rows against
|
|
249
|
+
* a 14–41 row pane, so it already overflows the FULL pane by 1.2x–6x at every
|
|
250
|
+
* realistic size. The split does not break the card; the card was already
|
|
251
|
+
* clipped, and fixing that is `gmux show`'s bug, not this pane's.
|
|
252
|
+
*/
|
|
253
|
+
function clipToRows(lines, rows, width) {
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const line of lines) {
|
|
256
|
+
if (out.length >= rows)
|
|
257
|
+
break;
|
|
258
|
+
for (const piece of hardWrap(line, width)) {
|
|
259
|
+
if (out.length >= rows)
|
|
260
|
+
break;
|
|
261
|
+
out.push(piece);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
function hardWrap(line, width) {
|
|
267
|
+
const w = Math.floor(width);
|
|
268
|
+
if (!Number.isFinite(w) || w <= 0 || line.length <= w)
|
|
269
|
+
return [line];
|
|
270
|
+
const pieces = [];
|
|
271
|
+
for (let i = 0; i < line.length; i += w)
|
|
272
|
+
pieces.push(line.slice(i, i + w));
|
|
273
|
+
return pieces;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* One preview command's whole output: the card, and the chat if there is one.
|
|
277
|
+
*
|
|
278
|
+
* **The empty state returns `formatCard` and NOTHING else** — not "the card plus
|
|
279
|
+
* an empty tail", not "the card with a trailing divider". Byte-identical, because
|
|
280
|
+
* that is what decision 4 promises to everyone who never presses ctrl-o.
|
|
281
|
+
*/
|
|
282
|
+
export function formatPreview(view, transcript, split, width, now = new Date()) {
|
|
283
|
+
const card = formatCard(view, now);
|
|
284
|
+
if (transcript === null || split.chatRows <= 0 || !hasChatContent(transcript))
|
|
285
|
+
return card;
|
|
286
|
+
return [
|
|
287
|
+
...clipToRows(card.split("\n"), split.cardRows, width),
|
|
288
|
+
askDivider(width),
|
|
289
|
+
formatChat(transcript, split.chatRows, width, now),
|
|
290
|
+
].join("\n");
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/cli/preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pane-border-label HUD: each pane's session headline, written into its tmux
|
|
3
|
+
* pane title and shown on the pane border. The `gmux watch` service repaints these
|
|
4
|
+
* on a loop; `Alt-g` toggles the service.
|
|
5
|
+
*
|
|
6
|
+
* Labels live in a per-pane `@gm_label` option, NOT `#{pane_title}`: a running
|
|
7
|
+
* agent sets its pane title via OSC escape sequences (a progress spinner), which
|
|
8
|
+
* would clobber a title we wrote. A `@`-prefixed user option is ours alone.
|
|
9
|
+
*/
|
|
10
|
+
import type { PaneEntry, PaneState } from "../core/gmux-types.js";
|
|
11
|
+
import type { SessionRecord, SessionView, TmuxPane } from "../core/types.js";
|
|
12
|
+
/**
|
|
13
|
+
* The label for a pane: `project — headline`, `project — gmux summaries loading…`
|
|
14
|
+
* while a refresh is in flight, `○ project` when resolved but not yet summarised,
|
|
15
|
+
* or empty for a pane with no resolvable agent. Not truncated — tmux clips it to
|
|
16
|
+
* the pane's width at render.
|
|
17
|
+
*/
|
|
18
|
+
export declare function paneLabel(view: SessionView | null, refreshing?: boolean): string;
|
|
19
|
+
/** Phase 0 state glyph, shown in the daemon-driven border label ahead of the project name. */
|
|
20
|
+
export declare function stateGlyph(state: PaneState): string;
|
|
21
|
+
/**
|
|
22
|
+
* The daemon-driven border label for one pane: `<glyph> <project> — <headline>`.
|
|
23
|
+
* Zero sensing — reads only the model entry. Falls back to the pane's command
|
|
24
|
+
* when the cwd is empty, and to the raw state name until semantics arrive.
|
|
25
|
+
*/
|
|
26
|
+
export declare function snapshotLabel(entry: PaneEntry): string;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a set of panes and write each one's `@gm_label`. Returns the resolved
|
|
29
|
+
* session records, so the caller can feed exactly the on-screen sessions to the
|
|
30
|
+
* summariser. No model calls here — labels render from the cache.
|
|
31
|
+
*/
|
|
32
|
+
export declare function labelPanes(panes: readonly TmuxPane[]): Promise<SessionRecord[]>;
|
|
33
|
+
/** Label every pane in a window (or all windows when `windowId` is null). */
|
|
34
|
+
export declare function paintLabels(windowId: string | null): Promise<SessionRecord[]>;
|
|
35
|
+
/** Show the label borders across all windows, driven purely by the global option. */
|
|
36
|
+
export declare function enableBorder(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Hide the label borders. Sets the global off, then clears any per-window
|
|
39
|
+
* override (older versions set `pane-border-status` per window, which would
|
|
40
|
+
* override the global and keep the border up) and wipes every pane's `@gm_label`,
|
|
41
|
+
* so no headline lingers regardless of theme or leftover settings.
|
|
42
|
+
*/
|
|
43
|
+
export declare function disableBorder(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* `Alt-g`: toggle the background label agent. On when off (enable the borders,
|
|
46
|
+
* paint an immediate first frame for the current window, start the watcher); off
|
|
47
|
+
* when on (stop the watcher, hide the borders). "On" is the service running, not
|
|
48
|
+
* the border option — a theme may set the border for its own reasons.
|
|
49
|
+
*/
|
|
50
|
+
export declare function toggleWatch(windowId: string): Promise<"on" | "off">;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pane-border-label HUD: each pane's session headline, written into its tmux
|
|
3
|
+
* pane title and shown on the pane border. The `gmux watch` service repaints these
|
|
4
|
+
* on a loop; `Alt-g` toggles the service.
|
|
5
|
+
*
|
|
6
|
+
* Labels live in a per-pane `@gm_label` option, NOT `#{pane_title}`: a running
|
|
7
|
+
* agent sets its pane title via OSC escape sequences (a progress spinner), which
|
|
8
|
+
* would clobber a title we wrote. A `@`-prefixed user option is ours alone.
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { basename } from "node:path";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
import { inProgressIds } from "../services/auto-summarize.js";
|
|
14
|
+
import { prunePaneLinks } from "../services/pane-links.js";
|
|
15
|
+
import { listAllPanes, listPanes } from "../services/tmux.js";
|
|
16
|
+
import { resolvePanesLive } from "../services/tmux-resolve.js";
|
|
17
|
+
import { attachSummaries, loadCachedRecords } from "../services/views.js";
|
|
18
|
+
import { isWatchRunning, startWatch, stopWatch, } from "../services/watch.js";
|
|
19
|
+
const run = promisify(execFile);
|
|
20
|
+
/**
|
|
21
|
+
* Active pane emphasised, label centred. Forces its own foreground colour so it
|
|
22
|
+
* doesn't inherit a themed, dimmed `pane-border-style` (which would leave every
|
|
23
|
+
* inactive pane's label invisible).
|
|
24
|
+
*/
|
|
25
|
+
const PANE_BORDER_FORMAT = "#[align=centre]#[fg=colour252]#{?pane_active,#[reverse],} #{@gm_label} #[default]";
|
|
26
|
+
/**
|
|
27
|
+
* The label for a pane: `project — headline`, `project — gmux summaries loading…`
|
|
28
|
+
* while a refresh is in flight, `○ project` when resolved but not yet summarised,
|
|
29
|
+
* or empty for a pane with no resolvable agent. Not truncated — tmux clips it to
|
|
30
|
+
* the pane's width at render.
|
|
31
|
+
*/
|
|
32
|
+
export function paneLabel(view, refreshing = false) {
|
|
33
|
+
if (!view)
|
|
34
|
+
return "";
|
|
35
|
+
const project = view.record.project ?? view.record.harness;
|
|
36
|
+
if (refreshing)
|
|
37
|
+
return `${project} — gmux summaries loading…`;
|
|
38
|
+
if (!view.summary)
|
|
39
|
+
return `○ ${project}`;
|
|
40
|
+
return `${project} — ${view.summary.headline}`;
|
|
41
|
+
}
|
|
42
|
+
/** Phase 0 state glyph, shown in the daemon-driven border label ahead of the project name. */
|
|
43
|
+
export function stateGlyph(state) {
|
|
44
|
+
switch (state) {
|
|
45
|
+
case "working": return "●";
|
|
46
|
+
case "waiting": return "◔";
|
|
47
|
+
case "error": return "✗";
|
|
48
|
+
case "done": return "✓";
|
|
49
|
+
case "idle": return "○";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The daemon-driven border label for one pane: `<glyph> <project> — <headline>`.
|
|
54
|
+
* Zero sensing — reads only the model entry. Falls back to the pane's command
|
|
55
|
+
* when the cwd is empty, and to the raw state name until semantics arrive.
|
|
56
|
+
*/
|
|
57
|
+
export function snapshotLabel(entry) {
|
|
58
|
+
const name = entry.identity.cwd ? basename(entry.identity.cwd) : entry.identity.command;
|
|
59
|
+
const text = entry.semantics?.label ?? entry.state;
|
|
60
|
+
return `${stateGlyph(entry.state)} ${name} — ${text}`;
|
|
61
|
+
}
|
|
62
|
+
async function tmux(args) {
|
|
63
|
+
const { stdout } = await run("tmux", args);
|
|
64
|
+
return stdout;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a set of panes and write each one's `@gm_label`. Returns the resolved
|
|
68
|
+
* session records, so the caller can feed exactly the on-screen sessions to the
|
|
69
|
+
* summariser. No model calls here — labels render from the cache.
|
|
70
|
+
*/
|
|
71
|
+
export async function labelPanes(panes) {
|
|
72
|
+
const links = await prunePaneLinks(panes.map((p) => p.paneId));
|
|
73
|
+
const records = await loadCachedRecords();
|
|
74
|
+
const resolved = await resolvePanesLive(panes, records, links);
|
|
75
|
+
const present = resolved
|
|
76
|
+
.map((r) => r.record)
|
|
77
|
+
.filter((r) => r !== null);
|
|
78
|
+
const views = await attachSummaries(present);
|
|
79
|
+
const bySession = new Map(views.map((v) => [v.record.sessionId, v]));
|
|
80
|
+
const refreshing = await inProgressIds();
|
|
81
|
+
for (const { pane, record } of resolved) {
|
|
82
|
+
const view = record
|
|
83
|
+
? bySession.get(record.sessionId) ?? { record, summary: null }
|
|
84
|
+
: null;
|
|
85
|
+
const isRefreshing = record ? refreshing.has(record.sessionId) : false;
|
|
86
|
+
await tmux(["set-option", "-p", "-t", pane.paneId, "@gm_label", paneLabel(view, isRefreshing)]);
|
|
87
|
+
}
|
|
88
|
+
return present;
|
|
89
|
+
}
|
|
90
|
+
/** Label every pane in a window (or all windows when `windowId` is null). */
|
|
91
|
+
export async function paintLabels(windowId) {
|
|
92
|
+
const panes = windowId === null ? await listAllPanes() : await listPanes(windowId);
|
|
93
|
+
return labelPanes(panes);
|
|
94
|
+
}
|
|
95
|
+
async function windowIds() {
|
|
96
|
+
const out = await tmux(["list-windows", "-a", "-F", "#{window_id}"]);
|
|
97
|
+
return out.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
/** Clear any per-window `pane-border-status` so the global option governs every window. */
|
|
100
|
+
async function clearWindowOverrides() {
|
|
101
|
+
for (const w of await windowIds()) {
|
|
102
|
+
await tmux(["set-option", "-w", "-u", "-t", w, "pane-border-status"]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Show the label borders across all windows, driven purely by the global option. */
|
|
106
|
+
export async function enableBorder() {
|
|
107
|
+
await clearWindowOverrides();
|
|
108
|
+
await tmux(["set-option", "-g", "pane-border-format", PANE_BORDER_FORMAT]);
|
|
109
|
+
await tmux(["set-option", "-g", "pane-border-status", "top"]);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Hide the label borders. Sets the global off, then clears any per-window
|
|
113
|
+
* override (older versions set `pane-border-status` per window, which would
|
|
114
|
+
* override the global and keep the border up) and wipes every pane's `@gm_label`,
|
|
115
|
+
* so no headline lingers regardless of theme or leftover settings.
|
|
116
|
+
*/
|
|
117
|
+
export async function disableBorder() {
|
|
118
|
+
await tmux(["set-option", "-g", "pane-border-status", "off"]);
|
|
119
|
+
try {
|
|
120
|
+
await clearWindowOverrides();
|
|
121
|
+
for (const pane of await listAllPanes()) {
|
|
122
|
+
await tmux(["set-option", "-p", "-u", "-t", pane.paneId, "@gm_label"]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Best-effort cleanup; the global 'off' has already hidden the borders.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `Alt-g`: toggle the background label agent. On when off (enable the borders,
|
|
131
|
+
* paint an immediate first frame for the current window, start the watcher); off
|
|
132
|
+
* when on (stop the watcher, hide the borders). "On" is the service running, not
|
|
133
|
+
* the border option — a theme may set the border for its own reasons.
|
|
134
|
+
*/
|
|
135
|
+
export async function toggleWatch(windowId) {
|
|
136
|
+
if (await isWatchRunning()) {
|
|
137
|
+
await stopWatch();
|
|
138
|
+
await disableBorder();
|
|
139
|
+
return "off";
|
|
140
|
+
}
|
|
141
|
+
await enableBorder();
|
|
142
|
+
await paintLabels(windowId); // instant first paint, before the loop's first tick
|
|
143
|
+
await startWatch();
|
|
144
|
+
return "on";
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=tmux-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-label.js","sourceRoot":"","sources":["../../src/cli/tmux-label.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* Harness-engineering rule: an error message carries the fix inline. A user —
|
|
5
|
+
* or an agent — should never have to guess the next command. `fix` is printed
|
|
6
|
+
* directly beneath the message and included in `--json` output.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GmuxError extends Error {
|
|
9
|
+
/** The exact command or action that resolves this error. */
|
|
10
|
+
readonly fix: string | undefined;
|
|
11
|
+
/** Process exit code. */
|
|
12
|
+
readonly exitCode: number;
|
|
13
|
+
constructor(message: string, options?: {
|
|
14
|
+
fix?: string;
|
|
15
|
+
exitCode?: number;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** No session matched the given id or prefix. */
|
|
19
|
+
export declare class SessionNotFoundError extends GmuxError {
|
|
20
|
+
constructor(id: string);
|
|
21
|
+
}
|
|
22
|
+
/** A prefix matched more than one session. */
|
|
23
|
+
export declare class AmbiguousSessionError extends GmuxError {
|
|
24
|
+
constructor(id: string, matches: string[]);
|
|
25
|
+
}
|
|
26
|
+
/** A required external binary is missing. */
|
|
27
|
+
export declare class MissingDependencyError extends GmuxError {
|
|
28
|
+
constructor(binary: string, fix: string);
|
|
29
|
+
}
|
|
30
|
+
/** A summary provider returned something unusable. */
|
|
31
|
+
export declare class SummaryProviderError extends GmuxError {
|
|
32
|
+
constructor(provider: string, detail: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The chat provider behind `gmux ask` failed or said nothing.
|
|
36
|
+
*
|
|
37
|
+
* The fix names the provider that actually failed. It used to hardcode
|
|
38
|
+
* `claude -p`, which told a user who had configured Codex to go and test a
|
|
39
|
+
* binary they may not even have — non-negotiable #5 asks for the fix to the
|
|
40
|
+
* problem in front of you, not to the common case.
|
|
41
|
+
*/
|
|
42
|
+
export declare class AskProviderError extends GmuxError {
|
|
43
|
+
constructor(provider: string, detail: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* No provider is configured — because the user chose "none" in `gmux setup`.
|
|
47
|
+
*
|
|
48
|
+
* Distinct from a missing binary, and it must stay distinct: this is a choice
|
|
49
|
+
* being honored, not a fault. The fix says how to change your mind, not how to
|
|
50
|
+
* install something.
|
|
51
|
+
*/
|
|
52
|
+
export declare class NoProviderError extends GmuxError {
|
|
53
|
+
constructor(what: string);
|
|
54
|
+
}
|