@oh-my-pi/pi-tui 18.8.7 → 18.8.8

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +28 -64
  2. package/dist/types/chat/execution-shared.d.ts +6 -5
  3. package/dist/types/keybinding-matchers.d.ts +3 -0
  4. package/dist/types/overlays/annotation-overlay.d.ts +12 -0
  5. package/dist/types/overlays/annotation-types.d.ts +2 -0
  6. package/dist/types/overlays/hook-selector.d.ts +9 -4
  7. package/dist/types/overlays/model-browser.d.ts +2 -2
  8. package/dist/types/overlays/model-hub.d.ts +21 -4
  9. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  10. package/dist/types/overlays/usage-dashboard.d.ts +3 -1
  11. package/dist/types/status-line/types.d.ts +1 -1
  12. package/dist/types/theme/symbols.d.ts +1 -1
  13. package/dist/types/tools/eval.d.ts +1 -1
  14. package/dist/types/tools/native-view.d.ts +47 -0
  15. package/dist/types/tools/renderer.d.ts +15 -2
  16. package/dist/types/tui.d.ts +20 -0
  17. package/dist/types/vim.d.ts +19 -5
  18. package/package.json +9 -9
  19. package/src/chat/execution-shared.ts +42 -25
  20. package/src/chat/tool-execution.ts +9 -2
  21. package/src/components/editor.ts +182 -5
  22. package/src/keybinding-matchers.ts +6 -2
  23. package/src/overlays/agent-hub.ts +7 -3
  24. package/src/overlays/annotation-overlay.ts +87 -6
  25. package/src/overlays/annotation-types.ts +2 -0
  26. package/src/overlays/hook-selector.ts +12 -5
  27. package/src/overlays/model-browser.ts +3 -3
  28. package/src/overlays/model-hub.ts +107 -27
  29. package/src/overlays/session-observer-registry.ts +10 -2
  30. package/src/overlays/usage-dashboard.ts +70 -23
  31. package/src/prompt/composer.ts +1 -0
  32. package/src/status-line/segments.ts +3 -0
  33. package/src/status-line/types.ts +1 -1
  34. package/src/theme/symbols.ts +5 -0
  35. package/src/tools/bash.ts +82 -31
  36. package/src/tools/eval.ts +149 -123
  37. package/src/tools/native-view.ts +102 -1
  38. package/src/tools/renderer.ts +15 -2
  39. package/src/tui.ts +68 -0
  40. package/src/vim.ts +333 -18
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.8.8] - 2026-10-10
6
+
7
+ ### Breaking Changes
8
+
9
+ - `ModelHubCallbacks.onCompactionPointChange` now receives a `confirmed` flag and returns a `CompactionPointChangeResult` (`{ kind: "error" }` or `{ kind: "confirm" }`) instead of an error string; a `confirm` result shows a warning that a second Enter on the same input accepts ([#15048](https://github.com/can1357/oh-my-pi/pull/15048) by [@H4vC](https://github.com/H4vC))
10
+ - `ModelCompactionPoint.percent` is replaced by `basis`, a short explanation of why the model compacts there (`fixed`, `85% of 400K base`, `80% of window`) that the model hub preview shows next to the trigger ([#15065](https://github.com/can1357/oh-my-pi/pull/15065) by [@H4vC](https://github.com/H4vC))
11
+
12
+ ### Added
13
+
14
+ - Added an `inline` option to `HookSelectorComponent` that keeps a selector in the composer slot in Tern instead of opening it as a sheet over the screen ([#15058](https://github.com/can1357/oh-my-pi/pull/15058) by [@H4vC](https://github.com/H4vC))
15
+ - Added `ModelHubCallbacks.previewCompactionPoint`, whose line the compaction limit field shows while you type instead of the input syntax ([#15065](https://github.com/can1357/oh-my-pi/pull/15065) by [@H4vC](https://github.com/H4vC))
16
+ - Added Vim find and till motions (`f`, `F`, `t`, `T`) in the prompt editor, with `;` and `,` to repeat them ([#15100](https://github.com/can1357/oh-my-pi/pull/15100) by [@Shadorain](https://github.com/Shadorain))
17
+ - Added Vim replace (`r`, `R`) in the prompt editor ([#15100](https://github.com/can1357/oh-my-pi/pull/15100) by [@Shadorain](https://github.com/Shadorain))
18
+ - Added Vim bracket and quote matching (`%`), paragraph motions (`{`, `}`), line join (`J`), and indent (`>>`, `<<`) in the prompt editor ([#15100](https://github.com/can1357/oh-my-pi/pull/15100) by [@Shadorain](https://github.com/Shadorain))
19
+ - Added an external-editor key (Ctrl+G by default) to the annotation overlay for the note draft and, when the host provides `onExternalEditor`, the source being annotated; an edited text source is returned as `editedText` on the paste result ([#15151](https://github.com/can1357/oh-my-pi/pull/15151) by [@Shadorain](https://github.com/Shadorain))
20
+ - Added `TUI.releaseHeldInput()`: on a terminal expected to speak TSP, a `deferInput` start now holds keystrokes (TSP events and the cell-size reply still apply; Ctrl+C/Ctrl+D release early) until the app calls it once its session is wired; such terminals also skip the sixel probe ([#15120](https://github.com/can1357/oh-my-pi/pull/15120) by [@H4vC](https://github.com/H4vC))
21
+ - Added `TUI.replaceHeldFocus(previous, next)`, which `Composer.setEditor()` calls so a swapped-in editor inherits held startup keys; only keys for the start-time focus owner are now held, and a dialog that takes focus gets its input live ([#15122](https://github.com/can1357/oh-my-pi/pull/15122) by [@H4vC](https://github.com/H4vC))
22
+ - Added `RenderResultOptions.elapsedMs` and `cancelled`, `NativeToolHead.command` and `NativeToolView.preview: "children"` for native describe hooks
23
+
24
+ ### Changed
25
+
26
+ - Tern draws each bash run, eval cell and `!`/`$` run as one box: the command or code, its output, status lines and a foot with state, time and facts; images sit below the box and the bash head shows the intent instead of the command
27
+
28
+ ### Fixed
29
+
30
+ - Fixed the Agents hub Time column rewinding and freezing for running subagents between progress updates ([#15140](https://github.com/can1357/oh-my-pi/pull/15140) by [@H4vC](https://github.com/H4vC))
31
+
5
32
  ## [18.8.7] - 2026-10-09
6
33
 
7
34
  ### Added
@@ -2977,67 +3004,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
2977
3004
 
2978
3005
  - Decode Kitty CSI-u printable sequences in the editor so shifted symbol keys (e.g., `@`, `?`) work in terminals that enable Kitty keyboard protocol ([#779](https://github.com/badlogic/pi-mono/pull/779) by [@iamd3vil](https://github.com/iamd3vil))
2979
3006
 
2980
- ## [0.47.0] - 2026-01-16
2981
-
2982
- ### Breaking Changes
2983
-
2984
- - `Editor` constructor now requires `TUI` as first parameter: `new Editor(tui, theme)`. This enables automatic vertical scrolling when content exceeds terminal height. ([#732](https://github.com/badlogic/pi-mono/issues/732))
2985
-
2986
- ### Added
2987
-
2988
- - Hardware cursor positioning for IME support in `Editor` and `Input` components. The terminal cursor now follows the text cursor position, enabling proper IME candidate window placement for CJK input. ([#719](https://github.com/badlogic/pi-mono/pull/719))
2989
- - `Focusable` interface for components that need hardware cursor positioning. Implement `focused: boolean` and emit `CURSOR_MARKER` in render output when focused.
2990
- - `CURSOR_MARKER` constant and `isFocusable()` type guard exported from the package
2991
- - Editor now supports Page Up/Down keys (Fn+Up/Down on MacBook) for scrolling through large content ([#732](https://github.com/badlogic/pi-mono/issues/732))
2992
- - Expanded keymap coverage for terminal compatibility: added support for Home/End keys in tmux, additional modifier combinations, and improved key sequence parsing ([#752](https://github.com/badlogic/pi-mono/pull/752) by [@richardgill](https://github.com/richardgill))
2993
-
2994
- ### Fixed
2995
-
2996
- - Editor no longer corrupts terminal display when text exceeds screen height. Content now scrolls vertically with indicators showing lines above/below the viewport. Max height is 30% of terminal (minimum 5 lines). ([#732](https://github.com/badlogic/pi-mono/issues/732))
2997
- - `visibleWidth()` and `extractAnsiCode()` now handle APC escape sequences (`ESC _ ... BEL`), fixing width calculation and string slicing for strings containing cursor markers
2998
- - SelectList now handles multi-line descriptions by replacing newlines with spaces ([#728](https://github.com/badlogic/pi-mono/pull/728) by [@richardgill](https://github.com/richardgill))
2999
-
3000
- ## [0.46.0] - 2026-01-15
3001
-
3002
- ### Fixed
3003
-
3004
- - Keyboard shortcuts (Ctrl+C, Ctrl+D, etc.) now work on non-Latin keyboard layouts (Russian, Ukrainian, Bulgarian, etc.) in terminals supporting Kitty keyboard protocol with alternate key reporting ([#718](https://github.com/badlogic/pi-mono/pull/718) by [@dannote](https://github.com/dannote))
3005
-
3006
- ## [0.45.6] - 2026-01-13
3007
-
3008
- ### Added
3009
-
3010
- - `OverlayOptions` API for overlay positioning and sizing with CSS-like values: `width`, `maxHeight`, `row`, `col` accept numbers (absolute) or percentage strings (e.g., `"50%"`). Also supports `minWidth`, `anchor`, `offsetX`, `offsetY`, `margin`. ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3011
- - `OverlayOptions.visible` callback for responsive overlays - receives terminal dimensions, return false to hide ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3012
- - `showOverlay()` now returns `OverlayHandle` with `hide()`, `setHidden(boolean)`, `isHidden()` for programmatic visibility control ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3013
- - New exported types: `OverlayAnchor`, `OverlayHandle`, `OverlayMargin`, `OverlayOptions`, `SizeValue` ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3014
- - `truncateToWidth()` now accepts optional `pad` parameter to pad result with spaces to exactly `maxWidth` ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3015
-
3016
- ### Fixed
3017
-
3018
- - Overlay compositing crash when rendered lines exceed terminal width due to complex ANSI/OSC sequences (e.g., hyperlinks in subagent output) ([#667](https://github.com/badlogic/pi-mono/pull/667) by [@nicobailon](https://github.com/nicobailon))
3019
-
3020
- ## [0.44.0] - 2026-01-12
3021
-
3022
- ### Added
3023
-
3024
- - `SettingsListOptions` with `enableSearch` for fuzzy filtering in `SettingsList` ([#643](https://github.com/badlogic/pi-mono/pull/643) by [@ninlds](https://github.com/ninlds))
3025
- - `pageUp` and `pageDown` key support with `selectPageUp`/`selectPageDown` editor actions ([#662](https://github.com/badlogic/pi-mono/pull/662) by [@aliou](https://github.com/aliou))
3026
-
3027
- ### Fixed
3028
-
3029
- - Numbered list items showing "1." for all items when code blocks break list continuity ([#660](https://github.com/badlogic/pi-mono/pull/660) by [@ogulcancelik](https://github.com/ogulcancelik))
3030
-
3031
- ## [0.43.0] - 2026-01-11
3032
-
3033
- ### Added
3034
-
3035
- - `fuzzyFilter()` and `fuzzyMatch()` utilities for fuzzy text matching
3036
- - Slash command autocomplete now uses fuzzy matching instead of prefix matching
3037
-
3038
- ### Fixed
3039
-
3040
- - Cursor now moves to end of content on exit, preventing status line from being overwritten ([#629](https://github.com/badlogic/pi-mono/pull/629) by [@tallshort](https://github.com/tallshort))
3041
- - Reset ANSI styles after each rendered line to prevent style leakage
3042
-
3043
- Older entries are archived in [packages/tui/CHANGELOG.md@4787659281ba](https://github.com/can1357/oh-my-pi/blob/4787659281ba47f5a3707bbf5ee61742e9373c42/packages/tui/CHANGELOG.md).
3007
+ Older entries are archived in [packages/tui/CHANGELOG.md@01e0b5c26e26](https://github.com/can1357/oh-my-pi/blob/01e0b5c26e267dff08c305f51d1ab56ec5a2f05d/packages/tui/CHANGELOG.md).
@@ -78,7 +78,7 @@ export interface ExecutionToolInput {
78
78
  /** Tool name for the head icon (`bash`, `eval`). */
79
79
  readonly name: string;
80
80
  readonly title: string;
81
- /** The command / cell source, shown once in the head. */
81
+ /** The command / cell source: the run box's input and the head's Copy command. */
82
82
  readonly command: string;
83
83
  readonly lang: string;
84
84
  /** Excluded from context (`!!`/`$$`). */
@@ -96,10 +96,11 @@ export interface ExecutionToolInput {
96
96
  readonly artifactError?: OutputArtifactError;
97
97
  }
98
98
  /**
99
- * A user `!`/`$` run as the agent's `tool` frame (§6.6): the command in the
100
- * head with a `you` badge (plus `not sent` when excluded from context), the
101
- * timer, exit chip and cancelled note; the body is the output as an `ansi`
102
- * mini terminal following its tail, then one quiet truncation line.
99
+ * A user `!`/`$` run as the agent's `tool` frame (§6.6): a head with a `you`
100
+ * badge (plus `not sent` when excluded from context) and the command for Copy
101
+ * command; the body is one run box — the source, the output as an `ansi` mini
102
+ * terminal following its tail, and the foot (state, time, truncation) — with
103
+ * any images below it. Timer, exit chip and cancelled note stay head data.
103
104
  */
104
105
  export declare function describeExecutionTool(input: ExecutionToolInput): NativeNode;
105
106
  /**
@@ -1,3 +1,4 @@
1
+ import type { KeyId } from "./app-keybindings.js";
1
2
  /**
2
3
  * Match the coding-agent interrupt key.
3
4
  *
@@ -18,6 +19,8 @@ export declare function matchesSelectDown(data: string): boolean;
18
19
  export declare function matchesSelectPageUp(data: string): boolean;
19
20
  /** Match the generic selector page-down keybinding. */
20
21
  export declare function matchesSelectPageDown(data: string): boolean;
22
+ /** Effective external-editor key: the first bound key, or Ctrl+G when unbound. */
23
+ export declare function appExternalEditorKey(): KeyId;
21
24
  /** Match the external-editor keybinding, falling back to Ctrl+G when unbound. */
22
25
  export declare function matchesAppExternalEditor(data: string): boolean;
23
26
  /**
@@ -10,6 +10,8 @@ export interface AnnotationOverlayCallbacks {
10
10
  onWarning?(message: string): void;
11
11
  /** Open the draft in the host's editor and invoke `commit` with its result. */
12
12
  onAnnotationExternalEditor?(draft: string, commit: (text: string | null) => void): void | Promise<void>;
13
+ /** Open the current source in the host's editor. The overlay stays open. */
14
+ onExternalEditor?(): void | Promise<void>;
13
15
  }
14
16
  export interface CodeReviewOverlayCallbacks extends AnnotationOverlayCallbacks {
15
17
  onComplete(result: CodeReviewOverlayResult | undefined): void;
@@ -29,6 +31,16 @@ export declare class AnnotationOverlay implements Focusable {
29
31
  dispose(): void;
30
32
  getAnnotations(): CodeReviewAnnotation[];
31
33
  getTextAnnotations(): TextReviewAnnotation[];
34
+ /** Repo-relative path of the diff file under the cursor, if this is a code review. */
35
+ reviewFilePath(): string | undefined;
36
+ /** Current text-source bytes, including edits applied by {@link replaceTextSource}. */
37
+ textSourceText(): string | undefined;
38
+ /**
39
+ * Replace the text under review. Line notes whose quote still sits on the
40
+ * same row stay; a quote that moved to exactly one row is retargeted;
41
+ * every other line note is dropped. Returns how many were dropped.
42
+ */
43
+ replaceTextSource(text: string): number;
32
44
  handleInput(data: string): void;
33
45
  describe(): NativeNode;
34
46
  handleNativeEvent(event: NativeUiEvent): void;
@@ -81,4 +81,6 @@ export type TextReviewAnnotation = {
81
81
  export interface TextReviewOverlayResult {
82
82
  action: "paste";
83
83
  annotations: TextReviewAnnotation[];
84
+ /** Source text after an external-editor edit; absent when the source was not edited. */
85
+ editedText?: string;
84
86
  }
@@ -57,6 +57,10 @@ export interface HookSelectorOptions {
57
57
  /** Number of leading options (original order) that receive a selection
58
58
  * marker. Defaults to every option when {@link selectionMarker} is set. */
59
59
  markableCount?: number;
60
+ /** Describe as a card in the dock (the composer slot) instead of a picker
61
+ * sheet, so dock content above it (e.g. a `/omfg` candidate rule) stays
62
+ * visible and the transcript stays scrollable on native surfaces. */
63
+ inline?: boolean;
60
64
  }
61
65
  export interface HookSelectorOption {
62
66
  label: string;
@@ -70,10 +74,11 @@ export declare class HookSelectorComponent extends OverlayPanel {
70
74
  render(width: number): readonly string[];
71
75
  dispose(): void;
72
76
  /**
73
- * A plain option list is a picker sheet. Otherwise a card headed by the
74
- * title (plus the countdown `elapsed` when a timeout runs), the extra title
75
- * lines, the slider as `tabs`, the options as a `list` keyed by original
76
- * option index, the search query while typing, and the key hints.
77
+ * A plain option list is a picker sheet. Otherwise (inline, slider, or
78
+ * countdown) a card headed by the title (plus the countdown `elapsed` when a
79
+ * timeout runs), the extra title lines, the slider as `tabs`, the options as
80
+ * a `list` keyed by original option index, the search query while typing,
81
+ * and the key hints.
77
82
  */
78
83
  describe(cx: DescribeContext): NativeNode;
79
84
  /** Pointer pick of an option does what highlighting it and pressing Enter does; a tab pick moves the slider. */
@@ -41,8 +41,8 @@ export interface ResolvedModelRoleValue {
41
41
  export interface ModelCompactionPoint {
42
42
  /** Context tokens that trigger auto-compaction; undefined when auto-compaction is off or the window is unknown. */
43
43
  tokens: number | undefined;
44
- /** The configured percentage of the window, when the trigger is percent-based. */
45
- percent?: number;
44
+ /** Why it triggers there, in a few words: `fixed`, `85% of 400K base`, `80% of window`. */
45
+ basis?: string;
46
46
  /** What sets it: the matching `compaction.modelThresholds` key, `global`, or `default`. */
47
47
  source: string;
48
48
  /** The model's own exact entry as editable text (`90k`, `80%`); absent when it has none. */
@@ -65,13 +65,30 @@ export interface ModelHubCallbacks {
65
65
  /** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
66
66
  onCycleOrderChange?: (order: string[]) => void;
67
67
  /**
68
- * Persist typed text as `model`'s own compaction point: a token count (`90000`,
69
- * `90k`, `1M`), a percentage (`80%`), or empty to reset. Returns an error
70
- * message for input it rejects, which keeps the field open.
68
+ * Persist typed text as `model`'s own compaction limit: a token base the
69
+ * policy scales (`400k`), a fixed trigger (`f400k`), a percentage (`80%`), or
70
+ * empty to reset. Rejected input returns `{ kind: "error" }`; input that
71
+ * needs acknowledgement (one that opens the extended window) returns
72
+ * `{ kind: "confirm" }` until called again with the same text and
73
+ * `confirmed`. Either keeps the field open.
71
74
  */
72
- onCompactionPointChange?: (model: Model, input: string) => string | undefined;
75
+ onCompactionPointChange?: (model: Model, input: string, confirmed: boolean) => CompactionPointChangeResult | undefined;
76
+ /**
77
+ * One short line on where `model` would compact with the typed limit
78
+ * (`compacts at 340K · 85% of 400K base`), shown beside the field while
79
+ * typing; undefined for input that does not parse or fit.
80
+ */
81
+ previewCompactionPoint?: (model: Model, input: string) => string | undefined;
73
82
  onCancel: () => void;
74
83
  }
84
+ /** Why {@link ModelHubCallbacks.onCompactionPointChange} kept the field open. */
85
+ export type CompactionPointChangeResult = {
86
+ kind: "error";
87
+ message: string;
88
+ } | {
89
+ kind: "confirm";
90
+ message: string;
91
+ };
75
92
  export interface ModelHubOptions {
76
93
  /** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
77
94
  initialProviderId?: string;
@@ -58,6 +58,8 @@ export interface ObservableSession {
58
58
  lastUpdate: number;
59
59
  /** Latest progress snapshot from the subagent executor */
60
60
  progress?: AgentProgress;
61
+ /** `Date.now()` when {@link progress} arrived; its `durationMs` was current then. */
62
+ progressAt?: number;
61
63
  }
62
64
  /** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
63
65
  export type SessionObserverChangeKind = "main" | "reset" | "lifecycle" | "progress";
@@ -35,8 +35,10 @@ export interface ProviderCard {
35
35
  unavailableAccounts: string[];
36
36
  /** Window rows in provider-declared order (e.g. 5h → weekly → monthly); the fullest CARD_MAX_WINDOWS lead. */
37
37
  windows: CardWindowRow[];
38
- /** True when every account reports no limits (e.g. enterprise plans). */
38
+ /** True when every account reports no limits (e.g. enterprise plans, providers without a quota API). */
39
39
  unlimited: boolean;
40
+ /** Distinct report notes across accounts (e.g. why a provider exposes no quota windows). */
41
+ notes: string[];
40
42
  /** True when nothing is used anywhere (or there are no limits): collapses to a tick. */
41
43
  idle: boolean;
42
44
  resetCredits?: {
@@ -112,7 +112,7 @@ export interface SegmentContext {
112
112
  } | null;
113
113
  /** Modal editing state, or null when `tui.vimMode` is off. */
114
114
  vim: {
115
- mode: "insert" | "normal" | "visual" | "visual-line";
115
+ mode: "insert" | "normal" | "visual" | "visual-line" | "replace";
116
116
  /** Half-typed operator/count (`"2d"`), empty when nothing is pending. */
117
117
  pending: string;
118
118
  /** Lines spanned by the active Visual selection; 0 outside Visual modes. */
@@ -2,7 +2,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
2
2
  /**
3
3
  * All available symbol keys organized by category.
4
4
  */
5
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
5
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "icon.vimReplace" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
6
6
  export type SymbolMap = Record<SymbolKey, string>;
7
7
  /**
8
8
  * Icon vocabulary for slash-command autocomplete type indicators. Each name
@@ -95,7 +95,7 @@ export declare const evalToolRenderer: {
95
95
  }, options: RenderResultOptions & {
96
96
  renderContext?: EvalRenderContext;
97
97
  }, uiTheme: Theme, _args?: EvalRenderArgs): Component;
98
- describeCall(args: EvalRenderArgs, _options: RenderResultOptions): NativeToolView;
98
+ describeCall(args: EvalRenderArgs, options: RenderResultOptions): NativeToolView;
99
99
  describeResult(result: {
100
100
  content: Array<{
101
101
  type: string;
@@ -72,6 +72,53 @@ export declare function truncationNotice(meta: OutputMeta | undefined): NativeNo
72
72
  * truncation / artifact-capture notice from `meta`; undefined when empty.
73
73
  */
74
74
  export declare function footnoteText(parts: readonly string[], meta?: OutputMeta): NativeNode | undefined;
75
+ /** Drawn lines of a run's input (command, cell code) before its "N more lines" button. */
76
+ export declare const RUN_INPUT_PREVIEW_LINES = 6;
77
+ /**
78
+ * What a run's foot names: `running` (live clock), `background` (an async job
79
+ * outlives the call), and the settled outcomes. A failure with an exit code
80
+ * reads `exit N`; without one, `Failed`.
81
+ */
82
+ export type RunState = "running" | "background" | "done" | "failed" | "cancelled" | "timed-out";
83
+ /** Inputs of {@link runFoot}. */
84
+ export interface RunFootInput {
85
+ readonly state: RunState;
86
+ /** Exit code of a `failed` run; a non-zero code names the state `exit N`. */
87
+ readonly exitCode?: number;
88
+ /** Milliseconds run so far (`running`) or in total; the time is omitted when undefined. */
89
+ readonly elapsedMs?: number;
90
+ /** Quiet facts (job id, service state, artifact), muted and ` · `-joined. */
91
+ readonly facts?: readonly string[];
92
+ /** Truncation / artifact-capture notices, appended to the facts in `warning`. */
93
+ readonly meta?: OutputMeta;
94
+ }
95
+ /**
96
+ * The foot line of a run box (`omp.run.foot`): the state word in its tone,
97
+ * the run time (a live clock while running, frozen once settled) and the
98
+ * quiet facts. Glyphs are the terminal's styling, never text.
99
+ */
100
+ export declare function runFoot(input: RunFootInput): NativeNode;
101
+ /** A run's input section: the command or cell source, clamped to {@link RUN_INPUT_PREVIEW_LINES}. */
102
+ export declare function runInput(source: string, p: {
103
+ role: string;
104
+ lang: string;
105
+ wrap: boolean;
106
+ }): NativeNode;
107
+ /** A run's terminal output: follows its tail, clamped to the last `previewLines` lines while folded. */
108
+ export declare function runOutput(output: string, p: {
109
+ role: string;
110
+ previewLines: number;
111
+ key?: string;
112
+ }): NativeNode;
113
+ /**
114
+ * One run as one box (`omp.run`): input, output, status lines and foot in
115
+ * order. `tone` tints the box: `error` for a failed run, `warning` for a
116
+ * timed-out one.
117
+ */
118
+ export declare function runBox(children: readonly (NativeChild | undefined | false)[], p: {
119
+ key: string;
120
+ tone?: TspTone;
121
+ }): NativeNode;
75
122
  /** A diagnostic's severity glyph: `x-circle` error, `warn` warning, `info`, `lightbulb` hint, in the severity tone. */
76
123
  export declare function diagnosticGlyph(severity: ParsedDiagnostic["severity"]): NativeNode;
77
124
  /**
@@ -25,6 +25,14 @@ export interface RenderResultOptions {
25
25
  * Streamed `xd://` previews stay queued until this is set.
26
26
  */
27
27
  executionStarted?: boolean;
28
+ /**
29
+ * Milliseconds since this call began executing (`tool_execution_start`),
30
+ * or its total once settled; undefined before it starts. Native describe
31
+ * hooks only.
32
+ */
33
+ elapsedMs?: number;
34
+ /** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
35
+ cancelled?: boolean;
28
36
  }
29
37
  /** Render options for a result, plus the tool-specific context the transcript threads through. */
30
38
  export type RenderResultContextOptions = RenderResultOptions & {
@@ -85,6 +93,8 @@ export interface NativeToolHead {
85
93
  readonly targetKind?: TspToolProps["targetKind"];
86
94
  /** Language for `command` targets. */
87
95
  readonly lang?: string;
96
+ /** The raw command line the head's Copy command button copies (set implicitly by a `command` target). */
97
+ readonly command?: string;
88
98
  /** `file://` link for path targets. */
89
99
  readonly href?: string;
90
100
  /** Short facts after the target (`+8 −1`, `5 matches · 2 files`). */
@@ -111,10 +121,13 @@ export interface NativeToolView {
111
121
  readonly body?: readonly NativeChild[];
112
122
  /** Tone override; the frame otherwise derives it from the call status. */
113
123
  readonly tone?: TspTone;
114
- /** Body clamp while collapsed; `{tail}` keeps the end (terminal output). Defaults to the transcript's preview size. */
124
+ /**
125
+ * Body clamp while collapsed; `{tail}` keeps the end (terminal output). Defaults to the transcript's preview size.
126
+ * `children`: the body stays whole while collapsed and its own `ansi`/`code` previews clamp instead.
127
+ */
115
128
  readonly preview?: TspPreview | {
116
129
  readonly tail: number;
117
- } | "none";
130
+ } | "none" | "children";
118
131
  /** Starts expanded whatever the transcript's expand state (the todo checklist); the user can still fold it. */
119
132
  readonly open?: boolean;
120
133
  /** Render frameless (`frame:"inline"`): a head line plus a disclosed body. */
@@ -78,6 +78,10 @@ export interface TUIStartOptions {
78
78
  * Paint without owning stdin: the terminal stays in cooked mode (kernel
79
79
  * echo + line editing at the hardware cursor) until {@link TUI.enableInput}
80
80
  * switches to raw input and replays the kernel-buffered keystrokes.
81
+ *
82
+ * A terminal expected to speak TSP needs raw input from the start, so it
83
+ * gets it; its keystrokes are held instead (TSP events and the cell-size
84
+ * reply still apply) until {@link TUI.releaseHeldInput} replays them.
81
85
  */
82
86
  deferInput?: boolean;
83
87
  }
@@ -469,6 +473,22 @@ export declare class TUI extends Container {
469
473
  * path. Idempotent; no-op when input was never deferred.
470
474
  */
471
475
  enableInput(): void;
476
+ /**
477
+ * Replay the keystrokes held since a TSP `deferInput` start through the
478
+ * normal input path, then deliver input live. Call once the app's key
479
+ * handlers are installed so a hotkey pressed during startup still fires.
480
+ * Only keys typed while the start-time focus owner had focus are held. The
481
+ * hold survives a stop/start until released. Idempotent; no-op when nothing
482
+ * is held.
483
+ */
484
+ releaseHeldInput(): void;
485
+ /**
486
+ * Hand held-key ownership from `previous` to `next` when the app replaces
487
+ * the component focused at start (a swapped-in custom editor), so keys
488
+ * typed into the replacement stay queued behind the held ones instead of
489
+ * overtaking them. No-op unless `previous` owns the held keys.
490
+ */
491
+ replaceHeldFocus(previous: Component, next: Component): void;
472
492
  addStartListener(listener: StartListener): () => void;
473
493
  addInputListener(listener: InputListener): () => void;
474
494
  removeInputListener(listener: InputListener): void;
@@ -8,8 +8,8 @@
8
8
  * This is a usable subset of Vim, not a reimplementation of it (see issue #3299): Normal and Visual
9
9
  * modes, the common motions, and operators built from those motions.
10
10
  */
11
- export type VimMode = "insert" | "normal" | "visual" | "visual-line";
12
- export type VimOperator = "d" | "y" | "c";
11
+ export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
12
+ export type VimOperator = "d" | "y" | "c" | ">" | "<";
13
13
  export interface VimPosition {
14
14
  line: number;
15
15
  col: number;
@@ -50,6 +50,20 @@ export type VimCommand = {
50
50
  count: number;
51
51
  } | {
52
52
  kind: "undo";
53
+ } | {
54
+ kind: "replace";
55
+ from: VimPosition;
56
+ to: VimPosition;
57
+ text: string;
58
+ } | {
59
+ kind: "join";
60
+ fromLine: number;
61
+ lines: number;
62
+ } | {
63
+ kind: "indent";
64
+ fromLine: number;
65
+ toLine: number;
66
+ out: boolean;
53
67
  };
54
68
  /** Start offset of the grapheme after `col`, clamped to `text.length`. */
55
69
  export declare function nextGraphemeStart(text: string, col: number): number;
@@ -62,11 +76,11 @@ export declare class VimState {
62
76
  mode: VimMode;
63
77
  /** Fixed end of a Visual selection; the cursor is the moving end. */
64
78
  anchor: VimPosition | null;
65
- /** True while a count, operator, `g`, or text-object prefix is half-typed — Escape cancels it. */
79
+ /** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
66
80
  get pending(): boolean;
67
81
  /**
68
- * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty when
69
- * nothing is pending. Hosts render this next to the mode so a partially entered operator is
82
+ * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
83
+ * when nothing is pending. Hosts render this next to the mode so a partially entered operator is
70
84
  * visible instead of silently swallowing the next keystroke.
71
85
  */
72
86
  get pendingText(): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "18.8.7",
4
+ "version": "18.8.8",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -41,14 +41,14 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.8.7",
45
- "@oh-my-pi/pi-agent-core": "18.8.7",
46
- "@oh-my-pi/pi-ai": "18.8.7",
47
- "@oh-my-pi/pi-catalog": "18.8.7",
48
- "@oh-my-pi/pi-natives": "18.8.7",
49
- "@oh-my-pi/pi-utils": "18.8.7",
50
- "@oh-my-pi/pi-wire": "18.8.7",
51
- "@oh-my-pi/snapcompact": "18.8.7"
44
+ "@oh-my-pi/omptype": "18.8.8",
45
+ "@oh-my-pi/pi-agent-core": "18.8.8",
46
+ "@oh-my-pi/pi-ai": "18.8.8",
47
+ "@oh-my-pi/pi-catalog": "18.8.8",
48
+ "@oh-my-pi/pi-natives": "18.8.8",
49
+ "@oh-my-pi/pi-utils": "18.8.8",
50
+ "@oh-my-pi/pi-wire": "18.8.8",
51
+ "@oh-my-pi/snapcompact": "18.8.8"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"