@xzzpig/pi-starline 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Starline
2
2
 
3
- A Starship-inspired statusline and Opencode-style TUI for [Pi](https://pi.dev).
3
+ A Starship-inspired statusline, Opencode-style editor chrome, and fullscreen mouse control for [Pi](https://pi.dev).
4
4
 
5
5
  > Starline is a fork of [pi-zentui](https://github.com/lmilojevicc/pi-zentui) by Luka, renamed and released on its own. It has diverged well past upstream and adds a pill footer style, a colour palette with `$ref` expansion, `model`/`thinking` footer segments, a git host icon, per-segment display options, configurable editor cursor styles, and mouse selection and copying.
6
6
 
@@ -10,10 +10,12 @@ A Starship-inspired statusline and Opencode-style TUI for [Pi](https://pi.dev).
10
10
 
11
11
  ## What is this?
12
12
 
13
- Starline brings two popular aesthetics to Pi:
13
+ Starline makes Pi's fullscreen mode feel like a finished terminal app — three
14
+ pieces of one product, not a theme:
14
15
 
15
- - **[Starship](https://starship.rs/) footer** — shows your current directory, git branch, git status indicators, and runtime/version detection in a compact, icon-rich format
16
- - **[Opencode](https://github.com/opencode-ai/opencode) editor** — clean bordered input box with accent rail, copy-friendly mode, and model/provider display inside the editor frame
16
+ - **See the state** — a Starship-inspired statusline and pill footer: current directory, git branch and status, runtime/version detection, context usage and token counts in a compact, icon-rich format
17
+ - **Type comfortably** — Opencode-style editor chrome: a clean bordered input box with accent rail and copy-friendly mode, model and provider inside the frame, paste collapse, and a `ctrl+g` hint pointing at your `$EDITOR` when a draft outgrows the box
18
+ - **Drive with the mouse** — fullscreen mouse control: select and copy anywhere, click a tool box to expand just it, click to move the caret, wheel to scroll the draft — [see Mouse](#mouse-fullscreen-control)
17
19
 
18
20
  ## Features
19
21
 
@@ -37,7 +39,26 @@ Starline brings two popular aesthetics to Pi:
37
39
  - Configurable model, provider, and thinking-level indicator colors
38
40
  - Prompt-box-style user messages matching the Starline input chrome
39
41
  - Copy-friendly mode hides editor and previous-message rail glyphs so terminal selection copies less chrome
40
- - **Fixed editor** (experimental, opt-in): Pin the editor and footer at the bottom of the terminal while the transcript scrolls above
42
+
43
+ ### Mouse (fullscreen control)
44
+
45
+ The second half of the product: the statusline tells you where you are, the
46
+ mouse lets you act on what you see. Starline drives Pi 0.84's own renderer
47
+ (`tuiMode: "fullscreen"`) by patching its methods directly — the extension API
48
+ stops at high-level hooks, and the selection hint, click-to-expand and clean-copy all
49
+ need to reach into the renderer itself. That is also why they live in Starline:
50
+ they sit on the same prototype patches the footer and editor already use, and
51
+ Pi keeps its core deliberately minimal — behaviours belong in extensions.
52
+
53
+ - **Select and copy anywhere.** Drag to select in the transcript or the input box; double-click a word (paths and kebab-case stay whole), triple-click a line. Releasing copies — or set Pi's `fullscreenCopyOnSelect: false` and the selection stays highlighted, waiting for the copy key ([Pi #7720](https://github.com/earendil-works/pi/issues/7720), shipped in Pi 0.84.4).
54
+ - **Clean copies.** Transcript selections drop Starline's rails, rule rows and pi-toolbox frame borders before they reach the clipboard; editor selections copy the draft's text, not the painted rows.
55
+ - **Selection hint.** A selection that is not auto-copying shows exactly how many characters the copy key (`ctrl+x` by default, whatever `app.message.copy` is bound to) will copy, and a selection inside the editor names your editor — `ctrl+g to edit in nvim`.
56
+ - **Click to expand one tool box.** A click on a box's hint row expands just that box (MCP boxes included), and the `(ctrl+o to collapse)` anchor row closes it again.
57
+ - **Click to move the caret** in the input box; **backspace/delete remove a selected range**; the **wheel scrolls the draft** over the input box, and a draft taller than the box keeps the `ctrl+g` external-editor hint on screen.
58
+
59
+ 0.2.x's `fixedEditor` config block was renamed to `mouse` (migrated
60
+ automatically, see [the changelog](CHANGELOG.md)); the fixed editor itself is
61
+ gone, superseded by Pi's fullscreen mode.
41
62
 
42
63
  ### Git Status Icons
43
64
 
@@ -130,6 +151,9 @@ Detects Starship Nerd Font runtime/language modules, uses the Starship Nerd Font
130
151
 
131
152
  ## Install
132
153
 
154
+ Starline drives Pi 0.84's alternate-screen renderer, so it needs **Pi ≥ 0.84.4**.
155
+ If you are on a distribution that bundles an older Pi, upgrade it or wait for it to catch up.
156
+
133
157
  ```bash
134
158
  # From npm (local fork @xzzpig/pi-starline)
135
159
  pi install npm:@xzzpig/pi-starline
@@ -150,7 +174,7 @@ Settings live in `~/.pi/agent/starline.json`. The file is optional — anything
150
174
  {
151
175
  "footerStyle": "pill",
152
176
  "colors": { "gitBranch": "bold purple" },
153
- "mouse": { "copyOnSelect": false }
177
+ "mouse": { "clickToExpandTools": false }
154
178
  }
155
179
  ```
156
180
 
@@ -158,29 +182,9 @@ Settings live in `~/.pi/agent/starline.json`. The file is optional — anything
158
182
 
159
183
  **[Full configuration reference →](https://github.com/Andy8647/pi-starline/blob/main/docs/configuration.md)** — every option with its default: the pill footer, the colour palette and its `$name` references, footer format templates, per-segment display options, icon overrides, and editor styling.
160
184
 
161
- ## Mouse
162
-
163
- Starline reads and drives Pi 0.84's own renderer, so mouse handling works in
164
- both TUI modes — use Pi's fullscreen mode (`tuiMode: "fullscreen"`) for the
165
- sticky editor and footer.
166
-
167
- - **Select and copy anywhere.** Drag to select in the transcript or the input
168
- box; double-click a word (paths and kebab-case stay whole), triple-click a
169
- line. Releasing copies — or hold the selection for `ctrl+c` with
170
- `mouse.copyOnSelect: false`. Transcript copies drop Starline's rails, rule
171
- rows and pi-toolbox frame borders.
172
- - **Click a tool box's hint row to expand just that box**, and collapse it via
173
- the `(ctrl+o to collapse)` anchor row (pi-toolbox 0.2.2+).
174
- - **Click to move the caret** in the input box; **backspace/delete remove a
175
- selected range**; the **wheel scrolls the draft** over the input box.
176
-
177
- 0.2.x's `fixedEditor` config block was renamed to `mouse` (migrated
178
- automatically, see [the changelog](CHANGELOG.md)); the fixed editor itself is
179
- gone, superseded by Pi's fullscreen mode.
180
-
181
185
  ## Requirements
182
186
 
183
- - [Pi](https://pi.dev) coding agent 0.80.3 or newer
187
+ - [Pi](https://pi.dev) coding agent 0.84.4 or newer
184
188
  - A [Nerd Font](https://www.nerdfonts.com/) for icons (or set `icons.mode` to `"ascii"`)
185
189
 
186
190
  ## Development
@@ -109,12 +109,8 @@ export type MouseConfig = {
109
109
  wheelRouting: boolean;
110
110
  /** Show a "copied to clipboard" notice for a copy Starline itself performs. */
111
111
  copyNotice: boolean;
112
- /** Copy on mouse release. When false the highlight waits for ctrl+c. */
113
- copyOnSelect: boolean;
114
112
  /** Clicking a tool box's `ctrl+o to expand` hint expands that one box. */
115
113
  clickToExpandTools: boolean;
116
- /** Double/triple-click word selection stops at path separators. */
117
- pathAwareWords: boolean;
118
114
  /** Copy transcript selections without the painted rails, rules and frames. */
119
115
  transcriptCleanCopy: boolean;
120
116
  };
@@ -123,7 +119,6 @@ const FIXED_EDITOR_KEY_MAP: Record<string, keyof MouseConfig> = {
123
119
  enabled: "enabled",
124
120
  mouseScroll: "wheelRouting",
125
121
  copyNotice: "copyNotice",
126
- copyOnSelect: "copyOnSelect",
127
122
  clickToExpandTools: "clickToExpandTools",
128
123
  };
129
124
 
@@ -415,9 +410,7 @@ export const defaultConfig: PolishedTuiConfig = {
415
410
  enabled: true,
416
411
  wheelRouting: true,
417
412
  copyNotice: true,
418
- copyOnSelect: true,
419
413
  clickToExpandTools: true,
420
- pathAwareWords: true,
421
414
  transcriptCleanCopy: true,
422
415
  },
423
416
  };
@@ -831,18 +824,10 @@ function normalizeMouseConfig(record: Record<string, unknown>): MouseConfig {
831
824
  : defaultConfig.mouse.wheelRouting,
832
825
  copyNotice:
833
826
  typeof record.copyNotice === "boolean" ? record.copyNotice : defaultConfig.mouse.copyNotice,
834
- copyOnSelect:
835
- typeof record.copyOnSelect === "boolean"
836
- ? record.copyOnSelect
837
- : defaultConfig.mouse.copyOnSelect,
838
827
  clickToExpandTools:
839
828
  typeof record.clickToExpandTools === "boolean"
840
829
  ? record.clickToExpandTools
841
830
  : defaultConfig.mouse.clickToExpandTools,
842
- pathAwareWords:
843
- typeof record.pathAwareWords === "boolean"
844
- ? record.pathAwareWords
845
- : defaultConfig.mouse.pathAwareWords,
846
831
  transcriptCleanCopy:
847
832
  typeof record.transcriptCleanCopy === "boolean"
848
833
  ? record.transcriptCleanCopy
@@ -1304,11 +1289,9 @@ export function saveMousePatch(patch: Partial<MouseConfig>, path = configPath):
1304
1289
  ...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
1305
1290
  ...(patch.wheelRouting !== undefined ? { wheelRouting: patch.wheelRouting } : {}),
1306
1291
  ...(patch.copyNotice !== undefined ? { copyNotice: patch.copyNotice } : {}),
1307
- ...(patch.copyOnSelect !== undefined ? { copyOnSelect: patch.copyOnSelect } : {}),
1308
1292
  ...(patch.clickToExpandTools !== undefined
1309
1293
  ? { clickToExpandTools: patch.clickToExpandTools }
1310
1294
  : {}),
1311
- ...(patch.pathAwareWords !== undefined ? { pathAwareWords: patch.pathAwareWords } : {}),
1312
1295
  ...(patch.transcriptCleanCopy !== undefined
1313
1296
  ? { transcriptCleanCopy: patch.transcriptCleanCopy }
1314
1297
  : {}),
@@ -255,6 +255,18 @@ export default function (pi: ExtensionAPI) {
255
255
  const installMousePatches = () => {
256
256
  uninstallMouse();
257
257
  if (!getCurrentConfig().mouse.enabled) return;
258
+ // `TuiAltScreen` only ships with pi-tui >= 0.84.0 (the peer range below
259
+ // already says so). This guard is for environments that bypass peer
260
+ // resolution: fail with a readable hint instead of a TypeError on
261
+ // `TuiAltScreen.prototype`.
262
+ if (!TuiAltScreen) {
263
+ console.warn(
264
+ "starline: mouse support needs @earendil-works/pi-tui >= 0.84.0 (" +
265
+ "TuiAltScreen is not exported by the installed pi-tui). Upgrade Pi, " +
266
+ "or wait for your distribution to bundle a newer one.",
267
+ );
268
+ return;
269
+ }
258
270
  // Repaints are not wired from here: each patch asks its own receiver —
259
271
  // the live renderer it is running inside — to render. Handing it the
260
272
  // extension's `refresh` instead tied the pending hint to the footer's
@@ -14,15 +14,20 @@
14
14
  * install this table exists to prevent. The check being stricter than a plain
15
15
  * call needs (it also demands writable and configurable) costs nothing, because
16
16
  * a class method declared the ordinary way is both.
17
+ *
18
+ * Since 0.84.4 the peer floor guarantees `copyActiveSelectionToClipboard`,
19
+ * `getCopyOnSelect` and `hasActiveSelection` exist; the probe still runs so a
20
+ * future Pi that moves them degrades feature-by-feature instead of breaking
21
+ * the whole install.
17
22
  */
18
23
 
19
24
  export type MouseCapability =
20
25
  | "handleViewportInput"
21
26
  | "routeWheel"
22
27
  | "handleSelectionMouseEvent"
23
- | "copySelectionToClipboard"
24
- | "getWordSelection"
25
- | "getSelectionSourceLine"
28
+ | "copyActiveSelectionToClipboard"
29
+ | "getCopyOnSelect"
30
+ | "hasActiveSelection"
26
31
  | "getSelectionBounds"
27
32
  | "getSelectionColumns"
28
33
  | "flash"
@@ -30,8 +35,7 @@ export type MouseCapability =
30
35
  | "requestRender";
31
36
 
32
37
  export type MouseFeature =
33
- | "selectionPendingMode"
34
- | "pathAwareWords"
38
+ | "selectionHint"
35
39
  | "clickToExpandTools"
36
40
  | "editorWheelScroll"
37
41
  | "editorClickToCaret"
@@ -42,9 +46,9 @@ const CAPABILITIES: readonly MouseCapability[] = [
42
46
  "handleViewportInput",
43
47
  "routeWheel",
44
48
  "handleSelectionMouseEvent",
45
- "copySelectionToClipboard",
46
- "getWordSelection",
47
- "getSelectionSourceLine",
49
+ "copyActiveSelectionToClipboard",
50
+ "getCopyOnSelect",
51
+ "hasActiveSelection",
48
52
  "getSelectionBounds",
49
53
  "getSelectionColumns",
50
54
  "flash",
@@ -61,25 +65,20 @@ const CAPABILITIES: readonly MouseCapability[] = [
61
65
  * is worse than no table.
62
66
  */
63
67
  const REQUIREMENTS: Record<MouseFeature, readonly MouseCapability[]> = {
64
- // Without ctrl+c interception the pending mode strands a highlight the user
65
- // cannot copy, which is worse than copy-on-release. It also reads the
66
- // selection directly (`getSelectionBounds`, `getSelectionColumns`) to build
67
- // an exact character count, and raises its own notice (`flash`), so all
68
- // three must be reachable too. `requestRender` is what puts the pending hint
69
- // on screen the moment the button comes up: arming without it would leave
70
- // the user staring at a highlight with nothing telling them ctrl+c copies it.
71
- selectionPendingMode: [
72
- "copySelectionToClipboard",
73
- "handleViewportInput",
68
+ // Derived from Pi 0.84.4's own select-without-copy state: the hint shows
69
+ // only while the renderer is NOT auto-copying (`getCopyOnSelect` false) and
70
+ // something is actually selected (`hasActiveSelection`). The character
71
+ // count is computed from the live selection through `getSelectionBounds`
72
+ // and `getSelectionColumns`, matching the columns the copy itself would
73
+ // slice — the hint promises what ctrl+x delivers. No release interception,
74
+ // no pending state: Pi's `copyOnSelect: false` already keeps the selection
75
+ // highlighted, and the hint rides Pi's own repaints.
76
+ selectionHint: [
77
+ "getCopyOnSelect",
78
+ "hasActiveSelection",
74
79
  "getSelectionBounds",
75
80
  "getSelectionColumns",
76
- "flash",
77
- "requestRender",
78
81
  ],
79
- // Also reads the line under the pointer through the receiver's own
80
- // `getSelectionSourceLine`, so the patch has a real line to hand
81
- // `wordRangeAt` — without it there's nothing to compute a range over.
82
- pathAwareWords: ["getWordSelection", "getSelectionSourceLine"],
83
82
  // The press it acts on arrives through `handleSelectionMouseEvent`, and it
84
83
  // asks `hasOverlay` the same question Pi's own press path asks before
85
84
  // resolving a scroll view (`tui-alt-screen.js:684`) — without it, a click on
@@ -123,11 +122,12 @@ const REQUIREMENTS: Record<MouseFeature, readonly MouseCapability[]> = {
123
122
  "hasOverlay",
124
123
  "requestRender",
125
124
  ],
126
- // It wraps `copySelectionToClipboard`, reads the selection through
127
- // `getSelectionBounds` to find out whether it is the editor's, and raises
128
- // Pi's own "Copied!" through `flash` when it answers the copy itself.
129
- // `hasOverlay` again: a selection dropped on a dialog must not be read as
130
- // text from the draft behind it.
125
+ // It wraps `copyActiveSelectionToClipboard` — the method Pi 0.84.4's
126
+ // Ctrl+X handler reaches (`handleCopyCommand` with `preferSelection`), so
127
+ // the clean copy answers exactly the copies that key performs — and reads
128
+ // the selection through `getSelectionBounds` to find out whether it is the
129
+ // editor's. `hasOverlay` again: a selection dropped on a dialog must not be
130
+ // read as text from the draft behind it.
131
131
  //
132
132
  // The clipboard write goes through `terminal.write`, which is not listed
133
133
  // because it is not on this prototype — `terminal` is a plain instance field
@@ -135,18 +135,18 @@ const REQUIREMENTS: Record<MouseFeature, readonly MouseCapability[]> = {
135
135
  // It is checked at the call site instead, and a terminal that cannot be
136
136
  // written to makes the copy fall through to Pi rather than disabling the
137
137
  // feature at install time.
138
- editorBufferCopy: ["copySelectionToClipboard", "getSelectionBounds", "hasOverlay", "flash"],
139
- // Shares the `copySelectionToClipboard` patch with the two features above
140
- // it. It reads the selection through `getSelectionBounds` to find the rows,
141
- // through `getSelectionColumns` to slice the cleaned text along the same
142
- // grapheme-aligned columns Pi would have used, asks `hasOverlay` before
138
+ editorBufferCopy: ["copyActiveSelectionToClipboard", "getSelectionBounds", "hasOverlay", "flash"],
139
+ // Shares the `copyActiveSelectionToClipboard` patch with the two features
140
+ // above it. It reads the selection through `getSelectionBounds` to find the
141
+ // rows, through `getSelectionColumns` to slice the cleaned text along the
142
+ // same grapheme-aligned columns Pi would have used, asks `hasOverlay` before
143
143
  // trusting those rows (an overlay is composited over a layout that still
144
- // contains the transcript), and raises Pi's own "Copied!" through `flash`
144
+ // contains the transcript), and raises its own "Copied!" through `flash`
145
145
  // when it answers the copy itself. The clipboard write goes through
146
146
  // `terminal.write`, unlisted for the same reason as editorBufferCopy's — it
147
147
  // is an instance field, checked at the call site instead.
148
148
  transcriptCleanCopy: [
149
- "copySelectionToClipboard",
149
+ "copyActiveSelectionToClipboard",
150
150
  "getSelectionBounds",
151
151
  "getSelectionColumns",
152
152
  "hasOverlay",
@@ -397,9 +397,9 @@ type EditableEditor = {
397
397
  * range back a letter at a time. So one snapshot is pushed up front through the
398
398
  * editor's own `pushUndoSnapshot`, and the editor's own is shadowed for the
399
399
  * duration of the loop and put back in a `finally` — the same shadow-and-restore
400
- * shape `copyWithNotice` uses on `flash` and `installPasteCollapse` uses on
401
- * `handlePaste`. This is a plain assignment on Starline's own editor instance,
402
- * never on anything reached through Pi's renderer Proxy.
400
+ * shape `installPasteCollapse` uses on `handlePaste`. This is a plain
401
+ * assignment on Starline's own editor instance, never on anything reached
402
+ * through Pi's renderer Proxy.
403
403
  *
404
404
  * The loop is bounded twice over: by the character count of the range, and by a
405
405
  * no-progress check, so an editor that stops deleting cannot spin.