tmux-ide 2.6.1 → 2.8.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/README.md +36 -14
- package/bin/cli.js +4174 -1235
- package/bin/cli.ts +426 -72
- package/package.json +10 -6
- package/packages/contracts/src/__tests__/control.test.ts +154 -0
- package/packages/contracts/src/control.ts +217 -0
- package/packages/contracts/src/index.ts +1 -0
- package/packages/daemon/dist/agent-explain.d.ts +8 -1
- package/packages/daemon/dist/agent-explain.js +19 -3
- package/packages/daemon/dist/control/client.d.ts +23 -0
- package/packages/daemon/dist/control/client.js +105 -0
- package/packages/daemon/dist/control/dispatch.d.ts +34 -0
- package/packages/daemon/dist/control/dispatch.js +83 -0
- package/packages/daemon/dist/control/fanout.d.ts +19 -0
- package/packages/daemon/dist/control/fanout.js +37 -0
- package/packages/daemon/dist/control/frames.d.ts +23 -0
- package/packages/daemon/dist/control/frames.js +37 -0
- package/packages/daemon/dist/control/lifecycle.d.ts +45 -0
- package/packages/daemon/dist/control/lifecycle.js +114 -0
- package/packages/daemon/dist/control/server.d.ts +16 -0
- package/packages/daemon/dist/control/server.js +214 -0
- package/packages/daemon/dist/control/verbs.d.ts +11 -0
- package/packages/daemon/dist/control/verbs.js +91 -0
- package/packages/daemon/dist/doctor.d.ts +18 -0
- package/packages/daemon/dist/doctor.js +105 -15
- package/packages/daemon/dist/lib/agent-discovery.d.ts +27 -2
- package/packages/daemon/dist/lib/agent-discovery.js +29 -14
- package/packages/daemon/dist/lib/app-config.d.ts +106 -0
- package/packages/daemon/dist/lib/app-config.js +104 -5
- package/packages/daemon/dist/lib/manifest-pack.d.ts +79 -0
- package/packages/daemon/dist/lib/manifest-pack.js +232 -0
- package/packages/daemon/dist/lib/state-home.d.ts +2 -0
- package/packages/daemon/dist/lib/state-home.js +12 -0
- package/packages/daemon/dist/lib/tui-binary.d.ts +57 -0
- package/packages/daemon/dist/lib/tui-binary.js +130 -0
- package/packages/daemon/dist/lib/update-check.js +5 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +34 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/PkgInfo +1 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/AppIcon.icns +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +139 -0
- package/packages/daemon/dist/restore.d.ts +35 -8
- package/packages/daemon/dist/restore.js +52 -15
- package/packages/daemon/dist/send.d.ts +33 -1
- package/packages/daemon/dist/send.js +32 -19
- package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/config-tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/field-editor.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +1 -1
- package/packages/daemon/src/agent-explain.ts +34 -6
- package/packages/daemon/src/control/client.ts +128 -0
- package/packages/daemon/src/control/dispatch.ts +107 -0
- package/packages/daemon/src/control/fanout.ts +44 -0
- package/packages/daemon/src/control/frames.ts +40 -0
- package/packages/daemon/src/control/lifecycle.ts +151 -0
- package/packages/daemon/src/control/server.ts +237 -0
- package/packages/daemon/src/control/verbs.ts +118 -0
- package/packages/daemon/src/doctor.ts +113 -28
- package/packages/daemon/src/lib/agent-discovery.ts +53 -13
- package/packages/daemon/src/lib/app-config.ts +194 -5
- package/packages/daemon/src/lib/manifest-pack.ts +255 -0
- package/packages/daemon/src/lib/state-home.ts +13 -0
- package/packages/daemon/src/lib/tui-binary.ts +165 -0
- package/packages/daemon/src/lib/update-check.ts +5 -0
- package/packages/daemon/src/restore.ts +53 -15
- package/packages/daemon/src/send.ts +55 -21
- package/packages/daemon/src/tui/chrome/events.ts +4 -4
- package/packages/daemon/src/tui/chrome/front-door.ts +39 -0
- package/packages/daemon/src/tui/chrome/notify-prefs.ts +58 -0
- package/packages/daemon/src/tui/chrome/notify-state.ts +76 -0
- package/packages/daemon/src/tui/chrome/notify.ts +737 -52
- package/packages/daemon/src/tui/chrome/updater.ts +318 -30
- package/packages/daemon/src/tui/compiled.ts +11 -3
- package/packages/daemon/src/tui/detect/classify.ts +49 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +56 -6
- package/packages/daemon/src/tui/detect/manifest.ts +39 -3
- package/packages/daemon/src/tui/detect/manifests.ts +395 -33
- package/packages/daemon/src/tui/detect/process-tree.ts +33 -3
- package/packages/daemon/src/tui/detect/session-id.ts +503 -0
- package/packages/daemon/src/tui/integrations/opencode.ts +121 -0
- package/packages/daemon/src/tui/main.ts +13 -1
- package/packages/daemon/src/tui/mirror/ack-writer.ts +77 -0
- package/packages/daemon/src/tui/mirror/agent-chip.ts +126 -0
- package/packages/daemon/src/tui/mirror/agent-lifecycle.ts +437 -0
- package/packages/daemon/src/tui/mirror/agent-rows.ts +155 -0
- package/packages/daemon/src/tui/mirror/app-state.ts +342 -0
- package/packages/daemon/src/tui/mirror/app.tsx +7048 -0
- package/packages/daemon/src/tui/mirror/attention.ts +110 -0
- package/packages/daemon/src/tui/mirror/blit.ts +186 -0
- package/packages/daemon/src/tui/mirror/control-client.ts +80 -9
- package/packages/daemon/src/tui/mirror/dialog-model.ts +298 -0
- package/packages/daemon/src/tui/mirror/dialog-stack.ts +367 -0
- package/packages/daemon/src/tui/mirror/diff-model.ts +387 -0
- package/packages/daemon/src/tui/mirror/editor-buffer.ts +117 -0
- package/packages/daemon/src/tui/mirror/file-tree.ts +322 -0
- package/packages/daemon/src/tui/mirror/focus-border.ts +57 -0
- package/packages/daemon/src/tui/mirror/folder-picker.ts +124 -0
- package/packages/daemon/src/tui/mirror/home-model.ts +174 -0
- package/packages/daemon/src/tui/mirror/host-terminal.ts +49 -0
- package/packages/daemon/src/tui/mirror/hosted.ts +205 -0
- package/packages/daemon/src/tui/mirror/input-coalescer.ts +105 -0
- package/packages/daemon/src/tui/mirror/layout-parse.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +210 -0
- package/packages/daemon/src/tui/mirror/palette.ts +564 -0
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +578 -20
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +422 -0
- package/packages/daemon/src/tui/mirror/perf-tap.ts +186 -0
- package/packages/daemon/src/tui/mirror/resize-model.ts +85 -0
- package/packages/daemon/src/tui/mirror/scrollbar-model.ts +88 -0
- package/packages/daemon/src/tui/mirror/search-model.ts +70 -0
- package/packages/daemon/src/tui/mirror/selection.ts +376 -0
- package/packages/daemon/src/tui/mirror/session-mirror.ts +724 -0
- package/packages/daemon/src/tui/mirror/settings-model.ts +425 -0
- package/packages/daemon/src/tui/mirror/sidebar.tsx +218 -0
- package/packages/daemon/src/tui/mirror/size-truth.ts +130 -0
- package/packages/daemon/src/tui/mirror/spans.ts +46 -0
- package/packages/daemon/src/tui/mirror/status-grammar.ts +32 -0
- package/packages/daemon/src/tui/mirror/theme.ts +45 -0
- package/packages/daemon/src/tui/team/entry.ts +34 -7
- package/packages/daemon/src/tui/team/fuzzy.ts +20 -0
- package/packages/daemon/src/tui/team/report.ts +11 -1
- package/packages/daemon/src/tui/team/sessions.ts +184 -17
- package/packages/daemon/src/tui/team/wait.ts +144 -0
- package/scripts/build-macos-notifier.mjs +160 -0
- package/scripts/build-tui.mjs +11 -4
- package/scripts/perf-mirror.mjs +313 -0
- package/scripts/postinstall.js +8 -1
- package/scripts/prepublish-check.mjs +37 -1
- package/scripts/publish-tap.sh +55 -0
- package/skill/SKILL.md +110 -2
- package/templates/AGENTS.md +14 -7
- package/templates/agent-team-monorepo.yml +8 -0
- package/templates/agent-team-nextjs.yml +8 -0
- package/templates/agent-team.yml +10 -0
- package/templates/convex.yml +2 -0
- package/templates/default.yml +11 -5
- package/templates/go.yml +4 -0
- package/templates/missions.yml +6 -0
- package/templates/nextjs.yml +4 -0
- package/templates/python.yml +4 -0
- package/templates/skills/backend.md +5 -12
- package/templates/skills/frontend.md +5 -12
- package/templates/skills/general-worker.md +5 -12
- package/templates/skills/researcher.md +7 -12
- package/templates/skills/reviewer.md +7 -16
- package/templates/vite.yml +4 -0
- package/packages/daemon/src/tui/mirror/viewer.tsx +0 -166
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size truth (M22.8) — is tmux's window the size we pinned, or did a co-attached
|
|
3
|
+
* terminal shrink it out from under us?
|
|
4
|
+
*
|
|
5
|
+
* The mirror pins its virtual control client to the render area
|
|
6
|
+
* (`refresh-client -C`), so when we are the only (or the latest-active) client
|
|
7
|
+
* the window matches our canvas. But tmux's default `window-size latest` lets a
|
|
8
|
+
* second, smaller terminal attached to the same session dictate the window size
|
|
9
|
+
* — and our canvas then renders letterboxed. These PURE helpers detect that
|
|
10
|
+
* mismatch from the pane layout, center the grid inside the canvas, and format
|
|
11
|
+
* the quiet honest hint. No tmux, no render loop — unit-tested in isolation; the
|
|
12
|
+
* app reads the pane geometry and the pinned canvas size and wires the rest.
|
|
13
|
+
*
|
|
14
|
+
* KNOWN GAP, measured (M25.5, tmux 3.7b): when TWO app processes mirror the
|
|
15
|
+
* same session (one per terminal — the non-hosted double-open), the later
|
|
16
|
+
* control client's boot pin wins `window-size latest`, and the other app can
|
|
17
|
+
* NEVER re-win it by interaction — the keys/mouse it forwards travel as
|
|
18
|
+
* control-client `send-keys` commands, which do not update tmux's
|
|
19
|
+
* latest-client
|
|
20
|
+
* bookkeeping (measured: local keystroke and wheel left the window at the
|
|
21
|
+
* other app's pin). The hint + the palette's "Resize to fit" verb are the
|
|
22
|
+
* designed escape; the structural fix is the hosted cockpit (`--detachable`),
|
|
23
|
+
* where one app process serves every terminal and REAL clients attach to the
|
|
24
|
+
* host session, whose native latest handling re-adopts on attach/focus/detach.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface Size {
|
|
28
|
+
cols: number;
|
|
29
|
+
rows: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The minimal pane rectangle the bounding box needs (LivePane is assignable). */
|
|
33
|
+
export interface Rect {
|
|
34
|
+
left: number;
|
|
35
|
+
top: number;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* PURE — the effective window size implied by the pane layout: the bounding box
|
|
42
|
+
* of all pane rects. tmux tiles panes to fill the whole window (edge panes sit
|
|
43
|
+
* flush to the edges, borders live between them), so the maximum right edge
|
|
44
|
+
* (`left + width`) is the window width and the maximum bottom edge
|
|
45
|
+
* (`top + height`) is the window height. Null when there are no panes yet.
|
|
46
|
+
*/
|
|
47
|
+
export function effectiveWindowSize(panes: readonly Rect[]): Size | null {
|
|
48
|
+
if (panes.length === 0) return null;
|
|
49
|
+
let cols = 0;
|
|
50
|
+
let rows = 0;
|
|
51
|
+
for (const p of panes) {
|
|
52
|
+
cols = Math.max(cols, p.left + p.width);
|
|
53
|
+
rows = Math.max(rows, p.top + p.height);
|
|
54
|
+
}
|
|
55
|
+
return { cols, rows };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* PURE — the actual window size when it DIFFERS from what we pinned (another
|
|
60
|
+
* terminal is dictating it), else null. Any difference on either axis counts;
|
|
61
|
+
* equal sizes mean we own the window and there is nothing to surface.
|
|
62
|
+
*/
|
|
63
|
+
export function detectSizeMismatch(pinned: Size, effective: Size): Size | null {
|
|
64
|
+
if (effective.cols === pinned.cols && effective.rows === pinned.rows) return null;
|
|
65
|
+
return { cols: effective.cols, rows: effective.rows };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* PURE — the letterbox offset that centers a `content`-sized grid inside a
|
|
70
|
+
* `canvas`-sized area. Never negative (a window LARGER than the canvas pins to
|
|
71
|
+
* the origin and clips rather than shifting off-screen), floored so cells stay
|
|
72
|
+
* aligned to the grid.
|
|
73
|
+
*/
|
|
74
|
+
export function letterboxOffset(canvas: Size, content: Size): { x: number; y: number } {
|
|
75
|
+
return {
|
|
76
|
+
x: Math.max(0, Math.floor((canvas.cols - content.cols) / 2)),
|
|
77
|
+
y: Math.max(0, Math.floor((canvas.rows - content.rows) / 2)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** An in-flight re-pin (M23.5): we changed our pinned size and tmux has not
|
|
82
|
+
* confirmed the new layout yet. `prev` is the pin BEFORE this re-pin. */
|
|
83
|
+
export interface RepinState {
|
|
84
|
+
prev: Size;
|
|
85
|
+
at: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** How long any mismatch is suppressed after a re-pin (server round-trip +
|
|
89
|
+
* layout-change latency is sub-ms; the margin covers a loaded machine). */
|
|
90
|
+
export const REPIN_GRACE_MS = 400;
|
|
91
|
+
/** How long a mismatch that still equals the PRE-repin pin is suppressed — the
|
|
92
|
+
* unambiguous "tmux hasn't applied our new pin yet" signature can wait longer
|
|
93
|
+
* before we call it another terminal's doing. */
|
|
94
|
+
export const REPIN_STALE_GRACE_MS = 1500;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* PURE — {@link detectSizeMismatch}, gated for the re-pin transition (M23.5,
|
|
98
|
+
* D4b): between our `refresh-client -C` and tmux's `%layout-change` the
|
|
99
|
+
* effective size is legitimately stale, and surfacing it flashed a false
|
|
100
|
+
* "window sized by another terminal" hint plus a letterbox jump-to-center on
|
|
101
|
+
* EVERY grow (measured). Suppress the mismatch while a re-pin is in flight:
|
|
102
|
+
* any mismatch within {@link REPIN_GRACE_MS}, and — for longer, up to
|
|
103
|
+
* {@link REPIN_STALE_GRACE_MS} — one whose effective size still equals the
|
|
104
|
+
* pre-repin pin exactly (that shape can only be the old pin, not a co-attached
|
|
105
|
+
* terminal's choice). Past the grace the honest hint returns.
|
|
106
|
+
*/
|
|
107
|
+
export function detectSizeMismatchWithRepin(
|
|
108
|
+
pinned: Size,
|
|
109
|
+
effective: Size,
|
|
110
|
+
repin: RepinState | null,
|
|
111
|
+
now: number,
|
|
112
|
+
): Size | null {
|
|
113
|
+
const mm = detectSizeMismatch(pinned, effective);
|
|
114
|
+
if (!mm || !repin) return mm;
|
|
115
|
+
const age = now - repin.at;
|
|
116
|
+
if (age < REPIN_GRACE_MS) return null;
|
|
117
|
+
const isPrevPin = effective.cols === repin.prev.cols && effective.rows === repin.prev.rows;
|
|
118
|
+
if (isPrevPin && age < REPIN_STALE_GRACE_MS) return null;
|
|
119
|
+
return mm;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* PURE — the quiet, plain-language hint for a size mismatch: the honest answer
|
|
124
|
+
* ("here is the size another terminal chose"), dimensions in the terminal-native
|
|
125
|
+
* `cols×rows` form. Dismiss-free — the app shows it only while the mismatch
|
|
126
|
+
* exists, so it disappears the moment the sizes agree.
|
|
127
|
+
*/
|
|
128
|
+
export function formatSizeHint(effective: Size): string {
|
|
129
|
+
return `window sized by another terminal — ${effective.cols}×${effective.rows}`;
|
|
130
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Pure horizontal-span geometry shared by the two clickable tab rows — the
|
|
2
|
+
* surface tab bar (F1..F4, startX=0, gap=0) and the per-window strip inside the
|
|
3
|
+
* Terminal surface (startX=SIDEBAR_W+1, gap=1). Both render a row of labels
|
|
4
|
+
* laid out left-to-right and both resolve clicks in the central `route` by
|
|
5
|
+
* x-span math; keeping that math in one tested place is the whole point (the
|
|
6
|
+
* window strip's old inline math drifted from its render and hit the late-mount
|
|
7
|
+
* landmine). Index `i` of the returned array maps to `labels[i]`. */
|
|
8
|
+
export interface Span {
|
|
9
|
+
/** First column (inclusive) this label occupies. */
|
|
10
|
+
start: number;
|
|
11
|
+
/** Cell width — the label's string length (labels are single display-width). */
|
|
12
|
+
width: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Lay `labels` out from `startX`, each `label.length` cells wide, separated by
|
|
16
|
+
* `gap` blank cells. Pure. */
|
|
17
|
+
export function spans(labels: string[], startX: number, gap: number): Span[] {
|
|
18
|
+
const out: Span[] = [];
|
|
19
|
+
let x = startX;
|
|
20
|
+
for (const label of labels) {
|
|
21
|
+
out.push({ start: x, width: label.length });
|
|
22
|
+
x += label.length + gap;
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Lay `labels` out left-to-right anchored to the RIGHT: the last label ends
|
|
28
|
+
* flush at `rightEdge` (its final cell is `rightEdge - 1`), separated by `gap`.
|
|
29
|
+
* For right-aligned header affordance buttons whose PRECEDING content is
|
|
30
|
+
* variable-width — the layout is pinned to the (fixed) right edge, not a left
|
|
31
|
+
* origin, so the render (a flexGrow spacer then the buttons) and the router
|
|
32
|
+
* agree cell-for-cell. Pure. */
|
|
33
|
+
export function spansFromRight(labels: string[], rightEdge: number, gap: number): Span[] {
|
|
34
|
+
const total = labels.reduce((n, l) => n + l.length, 0) + gap * Math.max(0, labels.length - 1);
|
|
35
|
+
return spans(labels, rightEdge - total, gap);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Index of the span containing column `x`, or -1. Gap cells between spans and
|
|
39
|
+
* columns past the last span resolve to -1 (no hit). */
|
|
40
|
+
export function spanHit(list: Span[], x: number): number {
|
|
41
|
+
for (let i = 0; i < list.length; i++) {
|
|
42
|
+
const s = list[i]!;
|
|
43
|
+
if (x >= s.start && x < s.start + s.width) return i;
|
|
44
|
+
}
|
|
45
|
+
return -1;
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified app's status glyph + color grammar — PURE declarative data, hoisted
|
|
3
|
+
* out of app.tsx (M22.2) so every mirror surface shares ONE source of truth: the
|
|
4
|
+
* sidebar fleet tree, the home rollup chips, the AGENTS section, and (post-merge)
|
|
5
|
+
* the pane-chrome agent chips. Keyed by {@link AgentStatus}.
|
|
6
|
+
*
|
|
7
|
+
* This is the MIRROR app's grammar — deliberately distinct from the cockpit's
|
|
8
|
+
* (whose idle glyph is a filled ●) and from chrome's color tokens. Do not swap
|
|
9
|
+
* in either of those: keep every mirror surface in one visual family.
|
|
10
|
+
*/
|
|
11
|
+
import { RGBA } from "@opentui/core";
|
|
12
|
+
import type { AgentStatus } from "../detect/classify.ts";
|
|
13
|
+
|
|
14
|
+
/** Per-state foreground color: blocked red, working amber, done blue, idle
|
|
15
|
+
* green, unknown grey. */
|
|
16
|
+
export const STATUS_COLOR: Record<AgentStatus, RGBA> = {
|
|
17
|
+
blocked: RGBA.fromInts(240, 100, 100, 255),
|
|
18
|
+
working: RGBA.fromInts(235, 200, 100, 255),
|
|
19
|
+
done: RGBA.fromInts(120, 170, 250, 255),
|
|
20
|
+
idle: RGBA.fromInts(120, 200, 140, 255),
|
|
21
|
+
unknown: RGBA.fromInts(110, 110, 130, 255),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Per-state glyph: working/blocked/done share the filled dot (color carries the
|
|
25
|
+
* distinction), idle is hollow, unknown a middot. */
|
|
26
|
+
export const STATUS_GLYPH: Record<AgentStatus, string> = {
|
|
27
|
+
blocked: "●",
|
|
28
|
+
working: "●",
|
|
29
|
+
done: "●",
|
|
30
|
+
idle: "○",
|
|
31
|
+
unknown: "·",
|
|
32
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unified app's color tokens — PURE data, hoisted out of app.tsx (M26) so
|
|
3
|
+
* the surfaces extracted from it (starting with {@link ./sidebar.tsx}) carry
|
|
4
|
+
* their palette with them instead of reaching back into a 21k-line module.
|
|
5
|
+
*
|
|
6
|
+
* Companion to {@link ./status-grammar.ts}: that file owns the per-AGENT-STATE
|
|
7
|
+
* glyph + hue (a status signal), this one owns the CHROME (surface, accent,
|
|
8
|
+
* hover, selection). Keep the two families distinct — focus is an accent signal,
|
|
9
|
+
* agent state is a status signal, and they must never collide on a hue.
|
|
10
|
+
*
|
|
11
|
+
* Node-free on purpose: the web host (docs/tui-web) imports this module
|
|
12
|
+
* verbatim, aliasing @opentui/core to a browser shim for RGBA.
|
|
13
|
+
*/
|
|
14
|
+
import { RGBA } from "@opentui/core";
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_FG = RGBA.fromInts(212, 212, 216, 255);
|
|
17
|
+
export const DEFAULT_BG = RGBA.fromInts(16, 16, 22, 255);
|
|
18
|
+
|
|
19
|
+
/** The sidebar's surface — one lift above DEFAULT_BG so the nav column reads as
|
|
20
|
+
* chrome, not as content. */
|
|
21
|
+
export const SIDEBAR_BG = RGBA.fromInts(22, 22, 30, 255);
|
|
22
|
+
export const ACCENT = RGBA.fromInts(130, 170, 255, 255);
|
|
23
|
+
export const MUTED = RGBA.fromInts(110, 110, 130, 255);
|
|
24
|
+
export const BADGE_BG = RGBA.fromInts(60, 66, 92, 255);
|
|
25
|
+
|
|
26
|
+
/** Focused-pane gutter hairline (M22.7): the ACCENT family, drawn as │/─ glyphs
|
|
27
|
+
* so the gutter stays visually thin (a filled bar read as extra padding — user
|
|
28
|
+
* feedback). Doesn't compete with the blocked chip's red — focus is an accent
|
|
29
|
+
* signal, agent state is a status signal, never the same hue. */
|
|
30
|
+
export const FOCUS_BORDER_FG = RGBA.fromInts(110, 145, 230, 255);
|
|
31
|
+
|
|
32
|
+
/** The selected row/tab. Always wins over HOVER_BG. */
|
|
33
|
+
export const TAB_ACTIVE_BG = RGBA.fromInts(40, 46, 66, 255);
|
|
34
|
+
|
|
35
|
+
/** A single subtle pointer-hover tint, one lift above both DEFAULT_BG (16,16,22)
|
|
36
|
+
* and SIDEBAR_BG (22,22,30) and below TAB_ACTIVE_BG — the active/selected state
|
|
37
|
+
* always wins over hover. Used on every hoverable row/segment. */
|
|
38
|
+
export const HOVER_BG = RGBA.fromInts(30, 34, 48, 255);
|
|
39
|
+
|
|
40
|
+
/** A chip/button under the pointer. */
|
|
41
|
+
export const BUTTON_HOVER_BG = RGBA.fromInts(52, 60, 86, 255);
|
|
42
|
+
|
|
43
|
+
/** The attention flash (M25.1): a just-flipped hidden agent's row pulses this
|
|
44
|
+
* for a beat — the status-strip note's "look here" twin. */
|
|
45
|
+
export const CHIP_ATTN_BG = RGBA.fromInts(92, 44, 48, 255);
|
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Front-door decision for bare `tmux-ide` / `tmux-ide start`:
|
|
3
|
-
*
|
|
2
|
+
* Front-door decision for bare `tmux-ide` / `tmux-ide start`: what does running
|
|
3
|
+
* the command with no subcommand land on?
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Three outcomes:
|
|
6
|
+
* - `"project"` — a single-project `ide.yml` is present here (and `--team`
|
|
7
|
+
* wasn't passed): launch that project, exactly as always (backward compatible).
|
|
8
|
+
* - `"app"` — the M22.6 front-door flip: when there's no project to launch and
|
|
9
|
+
* the user opted in (`app.frontDoor`), bare `tmux-ide` opens the unified app
|
|
10
|
+
* (`tmux-ide app`) — the VS-Code-style "starts anywhere" entry.
|
|
11
|
+
* - `"cockpit"` — the classic team cockpit: the default no-project entry while
|
|
12
|
+
* the flip is off, and always the target of an explicit `--team`.
|
|
13
|
+
*
|
|
14
|
+
* `--team` ALWAYS means the classic cockpit (an explicit request for it), so it
|
|
15
|
+
* overrides both a present `ide.yml` and the front-door flip.
|
|
16
|
+
*/
|
|
17
|
+
export type EntryTarget = "project" | "cockpit" | "app";
|
|
18
|
+
|
|
19
|
+
export interface ResolveEntryOptions {
|
|
20
|
+
/** Whether an `ide.yml` is present in the target directory. */
|
|
21
|
+
hasIdeYml: boolean;
|
|
22
|
+
/** Whether `--team` forced the cockpit. */
|
|
23
|
+
teamFlag: boolean;
|
|
24
|
+
/** The `app.frontDoor` config flag — flip the default no-project entry to the app. */
|
|
25
|
+
frontDoor: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PURE — resolve the bare-invocation target. `--team` wins (always the classic
|
|
30
|
+
* cockpit); otherwise a present `ide.yml` launches the project; otherwise the
|
|
31
|
+
* front-door flag decides between the unified app (opted in) and the classic
|
|
32
|
+
* cockpit (default).
|
|
8
33
|
*/
|
|
9
|
-
export function
|
|
10
|
-
|
|
34
|
+
export function resolveEntry(opts: ResolveEntryOptions): EntryTarget {
|
|
35
|
+
if (opts.teamFlag) return "cockpit";
|
|
36
|
+
if (opts.hasIdeYml) return "project";
|
|
37
|
+
return opts.frontDoor ? "app" : "cockpit";
|
|
11
38
|
}
|
|
@@ -24,6 +24,15 @@ const START_BONUS = 10; // matched char is the first char of the target
|
|
|
24
24
|
const SEPARATOR_BONUS = 8; // matched char follows a separator (word start)
|
|
25
25
|
const CONTIGUOUS_BONUS = 5; // matched char is adjacent to the previous match
|
|
26
26
|
const BASE = 1; // every matched char is worth at least this
|
|
27
|
+
// Whole-alignment extra when the query is a contiguous PREFIX of the target
|
|
28
|
+
// (M24.4): typing a label's start must rank that label above mid-word runs
|
|
29
|
+
// AND above scattered matches that rack up several word-start bonuses —
|
|
30
|
+
// measured: "save" scored 29 on "Save file" but 30 on "swap pane view east"
|
|
31
|
+
// (three word-start hits). Sized to dominate realistic competitors (a
|
|
32
|
+
// non-prefix alignment outscores a prefix only past ~8 all-word-start chars);
|
|
33
|
+
// word-boundary preference WITHIN non-prefix matches is untouched ("web"
|
|
34
|
+
// still anchors on the "web" of `wavyr-website`, not the leading "w").
|
|
35
|
+
const PREFIX_EXTRA = 24;
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
38
|
* Case-insensitive subsequence match of `query` against `target`.
|
|
@@ -106,6 +115,17 @@ export function fuzzyMatch(
|
|
|
106
115
|
}
|
|
107
116
|
if (start === -1) return null;
|
|
108
117
|
|
|
118
|
+
// The exact/prefix tier (M24.4): when the query IS the target's start, that
|
|
119
|
+
// alignment (positions 0..m-1) + PREFIX_EXTRA competes with the DP's best —
|
|
120
|
+
// and effectively always wins, so prefix matches rank first however many
|
|
121
|
+
// word-start bonuses a scattered alignment collected.
|
|
122
|
+
if (t.startsWith(q)) {
|
|
123
|
+
const prefixScore = m * BASE + START_BONUS + (m - 1) * CONTIGUOUS_BONUS + PREFIX_EXTRA;
|
|
124
|
+
if (prefixScore >= bestScore) {
|
|
125
|
+
return { score: prefixScore, positions: Array.from({ length: m }, (_, i) => i) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
const positions: number[] = [];
|
|
110
130
|
let j = start;
|
|
111
131
|
for (let i = 0; i < m && j !== -1; i++) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { AgentStatus } from "../detect/classify.ts";
|
|
12
12
|
import type { TeamProject } from "./projects.ts";
|
|
13
|
-
import type { TeamSession } from "./sessions.ts";
|
|
13
|
+
import type { PaneAgentEntry, TeamSession } from "./sessions.ts";
|
|
14
14
|
|
|
15
15
|
/** The stable JSON shape emitted by `tmux-ide team --json`. */
|
|
16
16
|
export interface FleetJson {
|
|
@@ -32,6 +32,13 @@ export interface FleetJson {
|
|
|
32
32
|
panes: number;
|
|
33
33
|
status: AgentStatus;
|
|
34
34
|
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Per-pane agent detail — one entry per pane the detection layer resolves
|
|
37
|
+
* to a real agent, flat across the session's windows (each entry carries
|
|
38
|
+
* its `windowIndex`). ADDITIVE: always present (possibly empty); existing
|
|
39
|
+
* consumers that ignore it are unaffected.
|
|
40
|
+
*/
|
|
41
|
+
agents: PaneAgentEntry[];
|
|
35
42
|
}>;
|
|
36
43
|
}>;
|
|
37
44
|
}
|
|
@@ -57,6 +64,9 @@ export function toFleetJson(projects: TeamProject[]): FleetJson {
|
|
|
57
64
|
panes: w.panes,
|
|
58
65
|
status: w.status,
|
|
59
66
|
})),
|
|
67
|
+
// A pre-agents TeamSession (older constructor/test) yields `[]` — the
|
|
68
|
+
// contract always exposes the array.
|
|
69
|
+
agents: s.agents ?? [],
|
|
60
70
|
})),
|
|
61
71
|
})),
|
|
62
72
|
};
|