@rosetears/aili-pi 0.1.0 → 0.1.3

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 (58) hide show
  1. package/README.md +15 -6
  2. package/THIRD_PARTY_NOTICES.md +14 -3
  3. package/extensions/header/index.ts +92 -0
  4. package/extensions/matrix/index.ts +375 -0
  5. package/extensions/zentui/config.ts +1014 -0
  6. package/extensions/zentui/extension-status.ts +96 -0
  7. package/extensions/zentui/fixed-editor/cluster.ts +98 -0
  8. package/extensions/zentui/fixed-editor/compositor.ts +719 -0
  9. package/extensions/zentui/fixed-editor/index.ts +223 -0
  10. package/extensions/zentui/fixed-editor/input.ts +85 -0
  11. package/extensions/zentui/fixed-editor/pi-compat.ts +296 -0
  12. package/extensions/zentui/fixed-editor/selection.ts +217 -0
  13. package/extensions/zentui/fixed-editor/terminal-modes.ts +75 -0
  14. package/extensions/zentui/fixed-editor/types.ts +37 -0
  15. package/extensions/zentui/footer-format.ts +279 -0
  16. package/extensions/zentui/footer.ts +595 -0
  17. package/extensions/zentui/format.ts +434 -0
  18. package/extensions/zentui/git.ts +384 -0
  19. package/extensions/zentui/gradient.ts +70 -0
  20. package/extensions/zentui/icons.ts +252 -0
  21. package/extensions/zentui/index.ts +580 -0
  22. package/extensions/zentui/live-context.ts +75 -0
  23. package/extensions/zentui/package-version.ts +650 -0
  24. package/extensions/zentui/project-refresh.ts +104 -0
  25. package/extensions/zentui/project-state.ts +59 -0
  26. package/extensions/zentui/prototype-patch-registry.ts +111 -0
  27. package/extensions/zentui/runtime.ts +841 -0
  28. package/extensions/zentui/selector-border.ts +77 -0
  29. package/extensions/zentui/session-lifecycle.ts +60 -0
  30. package/extensions/zentui/settings-command.ts +897 -0
  31. package/extensions/zentui/state.ts +55 -0
  32. package/extensions/zentui/style.ts +332 -0
  33. package/extensions/zentui/thinking-message.ts +159 -0
  34. package/extensions/zentui/tool-execution.ts +189 -0
  35. package/extensions/zentui/ui.ts +618 -0
  36. package/extensions/zentui/user-message.ts +252 -0
  37. package/licenses/pi-sakura-cyberdeck-MIT.txt +21 -0
  38. package/licenses/pi-zentui-MIT.txt +21 -0
  39. package/manifests/capabilities.json +1 -1
  40. package/manifests/live-verification.json +14 -8
  41. package/manifests/provenance.json +18 -5
  42. package/manifests/sbom.json +19 -3
  43. package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
  44. package/package.json +12 -3
  45. package/scripts/local-package-e2e.ts +1 -1
  46. package/scripts/sync-global-skills.d.mts +13 -0
  47. package/scripts/sync-global-skills.mjs +134 -0
  48. package/src/runtime/credential-guard.ts +58 -0
  49. package/src/runtime/doctor.ts +1 -1
  50. package/src/runtime/index.ts +2 -2
  51. package/src/runtime/path-boundaries.ts +1 -1
  52. package/src/runtime/registry.ts +6 -2
  53. package/src/runtime/rem-head.txt +38 -0
  54. package/src/runtime/rose-context.ts +1 -1
  55. package/src/runtime/subagents.ts +74 -403
  56. package/templates/APPEND_SYSTEM.md +10 -8
  57. package/themes/rem-cyberdeck.json +32 -0
  58. package/upstream/opencode-global-agents.lock.json +34 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Probe widget and lifecycle for the fixed editor compositor.
3
+ *
4
+ * A "probe" widget is registered via `ctx.ui.setWidget` with
5
+ * `placement: "aboveEditor"`. On first render it provides the TUI instance,
6
+ * which the compositor needs to patch internal methods.
7
+ *
8
+ * @internal
9
+ */
10
+
11
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
12
+ import { type Component, type TUI, visibleWidth } from "@earendil-works/pi-tui";
13
+
14
+ import type { PolishedTuiConfig } from "../config.js";
15
+ import type { SessionLifecycle } from "../session-lifecycle.js";
16
+ import { renderStyleForSourceOrFallback } from "../style.js";
17
+ import { TerminalSplitCompositor } from "./compositor.js";
18
+ import { inspectPiTui } from "./pi-compat.js";
19
+
20
+ let compositor: TerminalSplitCompositor | null = null;
21
+ let didWarnUnsupported = false;
22
+ let copyNoticeTimer: ReturnType<typeof setTimeout> | null = null;
23
+ let storedCtx: ExtensionContext | null = null;
24
+ let cancelProbeInstall: (() => void) | null = null;
25
+ const COPY_NOTICE_KEY = "zentui-copy-notice";
26
+ const COPY_NOTICE_MS = 2500;
27
+
28
+ function clearCopyNotice(ctx: ExtensionContext): void {
29
+ if (copyNoticeTimer) {
30
+ clearTimeout(copyNoticeTimer);
31
+ copyNoticeTimer = null;
32
+ }
33
+ if (!ctx.hasUI || typeof ctx.ui.setWidget !== "function") return;
34
+ ctx.ui.setWidget(COPY_NOTICE_KEY, undefined);
35
+ }
36
+
37
+ /** Centered bordered box showing the copy notice. */
38
+ class CopyNoticeComponent implements Component {
39
+ private readonly text: string;
40
+ private readonly border: string;
41
+
42
+ constructor(text: string, border: string) {
43
+ this.text = text;
44
+ this.border = border;
45
+ }
46
+
47
+ render(width: number): string[] {
48
+ const inner = " ".repeat(2) + this.text + " ".repeat(2);
49
+ const innerWidth = visibleWidth(inner);
50
+ const leftPad = Math.max(0, Math.floor((width - innerWidth - 2) / 2));
51
+ const pad = " ".repeat(leftPad);
52
+ const bar = "─".repeat(innerWidth);
53
+ return [
54
+ `${pad}${this.border}┌${bar}┐`,
55
+ `${pad}${this.border}│${inner}│`,
56
+ `${pad}${this.border}└${bar}┘`,
57
+ ];
58
+ }
59
+
60
+ invalidate(): void {}
61
+ }
62
+
63
+ function showCopyNotice(ctx: ExtensionContext, getConfig: () => PolishedTuiConfig): void {
64
+ if (!ctx.hasUI || typeof ctx.ui.setWidget !== "function") return;
65
+ const config = getConfig();
66
+ ctx.ui.setWidget(COPY_NOTICE_KEY, (_tui, theme) => {
67
+ const text = renderStyleForSourceOrFallback(
68
+ theme,
69
+ config.colorSources.editor,
70
+ undefined,
71
+ { terminal: "yellow", theme: "warning" },
72
+ "Copied to clipboard",
73
+ );
74
+ const border = renderStyleForSourceOrFallback(
75
+ theme,
76
+ config.colorSources.editor,
77
+ config.colors.editorBorder,
78
+ { terminal: "yellow", theme: "border" },
79
+ "",
80
+ );
81
+ return new CopyNoticeComponent(text, border);
82
+ });
83
+ if (copyNoticeTimer) clearTimeout(copyNoticeTimer);
84
+ copyNoticeTimer = setTimeout(() => {
85
+ copyNoticeTimer = null;
86
+ if (storedCtx !== ctx) return;
87
+ if (!ctx.hasUI || typeof ctx.ui.setWidget !== "function") return;
88
+ ctx.ui.setWidget(COPY_NOTICE_KEY, undefined);
89
+ }, COPY_NOTICE_MS);
90
+ }
91
+
92
+ /**
93
+ * Minimal component that triggers a callback on first render, then returns [].
94
+ */
95
+ class ProbeComponent implements Component {
96
+ private readonly onInstall: () => void;
97
+ private hasQueuedInstall = false;
98
+
99
+ constructor(onInstall: () => void) {
100
+ this.onInstall = onInstall;
101
+ }
102
+
103
+ render(): string[] {
104
+ if (!this.hasQueuedInstall) {
105
+ this.hasQueuedInstall = true;
106
+ this.onInstall();
107
+ }
108
+ return [];
109
+ }
110
+
111
+ invalidate(): void {
112
+ this.hasQueuedInstall = false;
113
+ }
114
+ }
115
+
116
+ function warnUnsupported(ctx: ExtensionContext): void {
117
+ if (didWarnUnsupported || !ctx.hasUI) return;
118
+ didWarnUnsupported = true;
119
+ console.warn(
120
+ "[zentui] Fixed editor: unsupported Pi TUI layout — falling back to normal rendering.",
121
+ );
122
+ }
123
+
124
+ function installFromProbe(
125
+ ctx: ExtensionContext,
126
+ tui: TUI,
127
+ getConfig: () => PolishedTuiConfig,
128
+ ): void {
129
+ if (compositor) return;
130
+ try {
131
+ const config = getConfig();
132
+ if (!config.fixedEditor?.enabled) return;
133
+
134
+ const capabilities = inspectPiTui(tui);
135
+ if (!capabilities) {
136
+ warnUnsupported(ctx);
137
+ return;
138
+ }
139
+
140
+ const next = new TerminalSplitCompositor(
141
+ capabilities,
142
+ () => ({
143
+ enabled: getConfig().fixedEditor?.enabled ?? false,
144
+ mouseScroll: getConfig().fixedEditor?.mouseScroll ?? false,
145
+ copyNotice: getConfig().fixedEditor?.copyNotice ?? true,
146
+ }),
147
+ ctx.hasUI ? () => showCopyNotice(ctx, getConfig) : undefined,
148
+ ctx.hasUI ? () => clearCopyNotice(ctx) : undefined,
149
+ );
150
+
151
+ if (!next.install()) {
152
+ warnUnsupported(ctx);
153
+ return;
154
+ }
155
+
156
+ compositor = next;
157
+ } catch {
158
+ warnUnsupported(ctx);
159
+ }
160
+ }
161
+
162
+ const WIDGET_KEY = "zentui-fixed-editor-probe";
163
+
164
+ /**
165
+ * Register the fixed-editor probe widget.
166
+ * Call from session_start after editor + footer install.
167
+ * Only activates when `fixedEditor.enabled` is true.
168
+ */
169
+ export function installFixedEditorProbe(
170
+ ctx: ExtensionContext,
171
+ getConfig: () => PolishedTuiConfig,
172
+ lifecycle: SessionLifecycle,
173
+ ): void {
174
+ if (!lifecycle.isCurrent() || !ctx.hasUI) return;
175
+ if (typeof ctx.ui.setWidget !== "function") return;
176
+ didWarnUnsupported = false;
177
+ storedCtx = ctx;
178
+ cancelProbeInstall?.();
179
+ cancelProbeInstall = null;
180
+
181
+ ctx.ui.setWidget(
182
+ WIDGET_KEY,
183
+ (tui: TUI) =>
184
+ new ProbeComponent(() => {
185
+ cancelProbeInstall = lifecycle.queueMicrotask(() => {
186
+ cancelProbeInstall = lifecycle.queueMicrotask(() => {
187
+ cancelProbeInstall = null;
188
+ installFromProbe(ctx, tui, getConfig);
189
+ });
190
+ });
191
+ }),
192
+ { placement: "aboveEditor" },
193
+ );
194
+ }
195
+
196
+ /**
197
+ * Dispose the compositor if active.
198
+ * Call from session_shutdown and cleanupUi.
199
+ */
200
+ export function disposeFixedEditor(ctx?: ExtensionContext): void {
201
+ cancelProbeInstall?.();
202
+ cancelProbeInstall = null;
203
+ compositor?.dispose();
204
+ compositor = null;
205
+ if (copyNoticeTimer) {
206
+ clearTimeout(copyNoticeTimer);
207
+ copyNoticeTimer = null;
208
+ }
209
+ storedCtx = null;
210
+ if (ctx) clearCopyNotice(ctx);
211
+ }
212
+
213
+ /**
214
+ * Remove the probe widget without disposing an active compositor.
215
+ * Useful for full UI cleanup.
216
+ */
217
+ export function removeFixedEditorProbe(ctx: ExtensionContext): void {
218
+ cancelProbeInstall?.();
219
+ cancelProbeInstall = null;
220
+ if (!ctx.hasUI) return;
221
+ if (typeof ctx.ui.setWidget !== "function") return;
222
+ ctx.ui.setWidget(WIDGET_KEY, undefined);
223
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pure functions for parsing terminal input into scroll commands.
3
+ *
4
+ * These are fully unit-testable with no I/O or side effects.
5
+ */
6
+
7
+ import { isKeyRelease, matchesKey } from "@earendil-works/pi-tui";
8
+
9
+ import type { KeyboardScrollInput, MouseEvent, MouseScrollInput } from "./types.js";
10
+
11
+ /** Regex matching SGR mouse format `\x1b[<code;col;row M|m`. */
12
+ const SGR_MOUSE_RE = /\u001b\[<(\d+);(\d+);(\d+)([Mm])/;
13
+
14
+ /** Kitty protocol variants for PgUp/PgDn ( CSI 5;~u / CSI 6;~u etc). */
15
+ const KITTY_PAGE_UP_RE = /^\u001b\[5;9(?::[12])?~$|^\u001b\[57421;9(?::[12])?u$|^\u001b\[1;6A$/;
16
+ const KITTY_PAGE_DOWN_RE = /^\u001b\[6;9(?::[12])?~$|^\u001b\[57422;9(?::[12])?u$|^\u001b\[1;6B$/;
17
+
18
+ /** Mouse wheel button codes (SGR). */
19
+ const WHEEL_UP = 64;
20
+ const WHEEL_DOWN = 65;
21
+ const SCROLL_AMOUNT = 3;
22
+
23
+ /**
24
+ * Parse an SGR mouse sequence for wheel scroll.
25
+ * Returns `undefined` if the data is not a wheel event.
26
+ */
27
+ export function parseMouseScroll(data: string): MouseScrollInput | undefined {
28
+ const ev = parseMouseEvent(data);
29
+ if (!ev) return undefined;
30
+ if (ev.button === "wheel-up") return { direction: "up", amount: SCROLL_AMOUNT };
31
+ if (ev.button === "wheel-down") return { direction: "down", amount: SCROLL_AMOUNT };
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * Parse any SGR mouse event (press, drag, release, wheel).
37
+ * Returns `undefined` if the data is not a mouse sequence.
38
+ */
39
+ export function parseMouseEvent(data: string): MouseEvent | undefined {
40
+ const match = SGR_MOUSE_RE.exec(data);
41
+ if (!match) return undefined;
42
+ const code = Number(match[1]);
43
+ const col = Number(match[2]);
44
+ const row = Number(match[3]);
45
+ const isRelease = match[4] === "m";
46
+ const isMotion = (code & 32) !== 0;
47
+ const baseButton = code & ~(4 | 8 | 16 | 32);
48
+
49
+ const button: MouseEvent["button"] =
50
+ baseButton === 0
51
+ ? "left"
52
+ : baseButton === 1
53
+ ? "middle"
54
+ : baseButton === 2
55
+ ? "right"
56
+ : baseButton === WHEEL_UP
57
+ ? "wheel-up"
58
+ : baseButton === WHEEL_DOWN
59
+ ? "wheel-down"
60
+ : "other";
61
+
62
+ const action: MouseEvent["action"] = isRelease ? "release" : isMotion ? "drag" : "press";
63
+
64
+ return { button, action, col, row };
65
+ }
66
+
67
+ /**
68
+ * Parse keyboard input for scroll commands.
69
+ * Handles PgUp/PgDn, Ctrl+Shift+↑/↓, and Enter (jump-to-bottom).
70
+ * Returns `undefined` if the input is not a scroll command.
71
+ */
72
+ export function parseKeyboardScroll(data: string): KeyboardScrollInput | undefined {
73
+ if (isKeyRelease(data)) return undefined;
74
+
75
+ if (matchesKey(data, "pageUp") || KITTY_PAGE_UP_RE.test(data)) return { action: "pageUp" };
76
+ if (matchesKey(data, "pageDown") || KITTY_PAGE_DOWN_RE.test(data)) return { action: "pageDown" };
77
+ if (matchesKey(data, "enter") || matchesKey(data, "return")) return { action: "jumpBottom" };
78
+
79
+ return undefined;
80
+ }
81
+
82
+ /** Clamp scroll offset to [0, maxOffset]. */
83
+ export function clampScrollOffset(offset: number, maxOffset: number): number {
84
+ return Math.max(0, Math.min(offset, maxOffset));
85
+ }
@@ -0,0 +1,296 @@
1
+ /** Verified private Pi TUI capabilities required by the experimental fixed editor. @internal */
2
+ export type PiMethodCapability = {
3
+ target: Record<PropertyKey, unknown>;
4
+ key: "render" | "doRender" | "write";
5
+ method: (...args: unknown[]) => unknown;
6
+ ownDescriptor: PropertyDescriptor | undefined;
7
+ };
8
+
9
+ export type PiRenderableCapability = {
10
+ target: Record<PropertyKey, unknown>;
11
+ render: (width: number) => string[];
12
+ ownDescriptor: PropertyDescriptor | undefined;
13
+ };
14
+
15
+ export type PiFixedCluster = {
16
+ status: PiRenderableCapability | null;
17
+ aboveWidget: PiRenderableCapability | null;
18
+ editor: PiRenderableCapability;
19
+ belowWidget: PiRenderableCapability | null;
20
+ footer: PiRenderableCapability | null;
21
+ };
22
+
23
+ export type PiFixedEditorCapabilities = {
24
+ tui: Record<PropertyKey, unknown>;
25
+ terminal: Record<PropertyKey, unknown>;
26
+ cluster: PiFixedCluster;
27
+ renderMethod: PiMethodCapability;
28
+ doRenderMethod: PiMethodCapability;
29
+ writeMethod: PiMethodCapability;
30
+ rowsOwnDescriptor: PropertyDescriptor | undefined;
31
+ readRawRows: () => number;
32
+ getColumns: () => number;
33
+ hasVisibleOverlay: () => boolean;
34
+ getCursorBookkeeping: () => { hardwareCursorRow: number; previousViewportTop: number };
35
+ addInputListener: (
36
+ listener: (data: string) => { consume?: boolean; data?: string } | undefined,
37
+ ) => unknown;
38
+ removeInputListener: (
39
+ listener: (data: string) => { consume?: boolean; data?: string } | undefined,
40
+ ) => void;
41
+ requestRender?: (force?: boolean) => void;
42
+ };
43
+
44
+ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
45
+ return (typeof value === "object" && value !== null) || typeof value === "function";
46
+ }
47
+
48
+ function ownDescriptor(target: object, key: PropertyKey): PropertyDescriptor | undefined {
49
+ return Object.getOwnPropertyDescriptor(target, key);
50
+ }
51
+
52
+ function descriptorInChain(target: object, key: PropertyKey): PropertyDescriptor | undefined {
53
+ let current: object | null = target;
54
+ while (current) {
55
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
56
+ if (descriptor) return descriptor;
57
+ current = Object.getPrototypeOf(current);
58
+ }
59
+ return undefined;
60
+ }
61
+
62
+ function writableMethod(
63
+ target: Record<PropertyKey, unknown>,
64
+ key: PiMethodCapability["key"],
65
+ ): PiMethodCapability | undefined {
66
+ const method = Reflect.get(target, key);
67
+ if (typeof method !== "function") return undefined;
68
+ const descriptor = ownDescriptor(target, key);
69
+ if (descriptor) {
70
+ if (!("value" in descriptor) || descriptor.writable !== true) return undefined;
71
+ } else if (!Object.isExtensible(target)) {
72
+ return undefined;
73
+ }
74
+ return {
75
+ target,
76
+ key,
77
+ method: method as (...args: unknown[]) => unknown,
78
+ ownDescriptor: descriptor,
79
+ };
80
+ }
81
+
82
+ function renderable(value: unknown): PiRenderableCapability | undefined {
83
+ if (!isRecord(value)) return undefined;
84
+ const method = writableMethod(value, "render");
85
+ if (!method) return undefined;
86
+ return {
87
+ target: value,
88
+ render: method.method as (width: number) => string[],
89
+ ownDescriptor: method.ownDescriptor,
90
+ };
91
+ }
92
+
93
+ function isEditorLike(value: unknown): boolean {
94
+ return (
95
+ isRecord(value) &&
96
+ typeof Reflect.get(value, "getText") === "function" &&
97
+ typeof Reflect.get(value, "setText") === "function" &&
98
+ typeof Reflect.get(value, "handleInput") === "function"
99
+ );
100
+ }
101
+
102
+ function containerChildren(value: unknown): unknown[] | undefined {
103
+ if (!isRecord(value)) return undefined;
104
+ const children = Reflect.get(value, "children");
105
+ return Array.isArray(children) ? children : undefined;
106
+ }
107
+
108
+ export function findEditorContainerIndex(
109
+ children: unknown[],
110
+ focusedComponent?: unknown,
111
+ ): number | undefined {
112
+ if (focusedComponent && isRecord(focusedComponent)) {
113
+ const focusedIndex = children.findIndex(
114
+ (child) => renderable(child) && containerChildren(child)?.includes(focusedComponent),
115
+ );
116
+ if (focusedIndex !== -1) return focusedIndex;
117
+ }
118
+ const index = children.findIndex(
119
+ (child) => renderable(child) && containerChildren(child)?.some(isEditorLike),
120
+ );
121
+ return index === -1 ? undefined : index;
122
+ }
123
+
124
+ function clusterCapability(children: unknown[], editorIndex: number): PiFixedCluster | undefined {
125
+ const editor = renderable(children[editorIndex]);
126
+ if (!editor) return undefined;
127
+ const optional = (index: number): PiRenderableCapability | null | undefined => {
128
+ if (index < 0 || index >= children.length) return null;
129
+ return renderable(children[index]);
130
+ };
131
+ const status = optional(editorIndex - 2);
132
+ const aboveWidget = optional(editorIndex - 1);
133
+ const belowWidget = optional(editorIndex + 1);
134
+ const footer = optional(editorIndex + 2);
135
+ if (
136
+ status === undefined ||
137
+ aboveWidget === undefined ||
138
+ belowWidget === undefined ||
139
+ footer === undefined
140
+ ) {
141
+ return undefined;
142
+ }
143
+ return { status, aboveWidget, editor, belowWidget, footer };
144
+ }
145
+
146
+ function readRowsValue(
147
+ terminal: Record<PropertyKey, unknown>,
148
+ descriptor: PropertyDescriptor,
149
+ ): unknown {
150
+ return descriptor.get
151
+ ? descriptor.get.call(terminal)
152
+ : "value" in descriptor
153
+ ? descriptor.value
154
+ : Reflect.get(terminal, "rows");
155
+ }
156
+
157
+ function rowsReader(
158
+ terminal: Record<PropertyKey, unknown>,
159
+ descriptor: PropertyDescriptor,
160
+ fallback: number,
161
+ ): () => number {
162
+ return () => {
163
+ try {
164
+ const value = readRowsValue(terminal, descriptor);
165
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
166
+ } catch {
167
+ return fallback;
168
+ }
169
+ };
170
+ }
171
+
172
+ function inspectPiTuiUnsafe(value: unknown): PiFixedEditorCapabilities | undefined {
173
+ if (!isRecord(value)) return undefined;
174
+ const terminalValue = Reflect.get(value, "terminal");
175
+ if (!isRecord(terminalValue)) return undefined;
176
+
177
+ const renderMethod = writableMethod(value, "render");
178
+ const doRenderMethod = writableMethod(value, "doRender");
179
+ const writeMethod = writableMethod(terminalValue, "write");
180
+ if (!renderMethod || !doRenderMethod || !writeMethod) return undefined;
181
+
182
+ const addInputListenerValue = Reflect.get(value, "addInputListener");
183
+ const removeInputListenerValue = Reflect.get(value, "removeInputListener");
184
+ if (
185
+ typeof addInputListenerValue !== "function" ||
186
+ typeof removeInputListenerValue !== "function"
187
+ ) {
188
+ return undefined;
189
+ }
190
+ const children = Reflect.get(value, "children");
191
+ if (!Array.isArray(children) || children.length < 3) return undefined;
192
+ const editorIndex = findEditorContainerIndex(children, Reflect.get(value, "focusedComponent"));
193
+ if (editorIndex === undefined) return undefined;
194
+ const cluster = clusterCapability(children, editorIndex);
195
+ if (!cluster) return undefined;
196
+
197
+ const rowsOwnDescriptor = ownDescriptor(terminalValue, "rows");
198
+ if (
199
+ rowsOwnDescriptor
200
+ ? rowsOwnDescriptor.configurable !== true
201
+ : !Object.isExtensible(terminalValue)
202
+ ) {
203
+ return undefined;
204
+ }
205
+ const rowsDescriptor = descriptorInChain(terminalValue, "rows");
206
+ if (!rowsDescriptor) return undefined;
207
+ const initialRows = readRowsValue(terminalValue, rowsDescriptor);
208
+ if (typeof initialRows !== "number" || !Number.isFinite(initialRows)) return undefined;
209
+ const columns = Reflect.get(terminalValue, "columns");
210
+ if (typeof columns !== "number" || !Number.isFinite(columns)) return undefined;
211
+
212
+ const hasOverlayValue = Reflect.get(value, "hasOverlay");
213
+ const overlayStackValue = Reflect.get(value, "overlayStack");
214
+ if (typeof hasOverlayValue !== "function" && !Array.isArray(overlayStackValue)) return undefined;
215
+ const hardwareCursorRow = Reflect.get(value, "hardwareCursorRow");
216
+ const previousViewportTop = Reflect.get(value, "previousViewportTop");
217
+ if (
218
+ typeof hardwareCursorRow !== "number" ||
219
+ !Number.isFinite(hardwareCursorRow) ||
220
+ typeof previousViewportTop !== "number" ||
221
+ !Number.isFinite(previousViewportTop)
222
+ ) {
223
+ return undefined;
224
+ }
225
+
226
+ const requestRenderValue = Reflect.get(value, "requestRender");
227
+ return {
228
+ tui: value,
229
+ terminal: terminalValue,
230
+ cluster,
231
+ renderMethod,
232
+ doRenderMethod,
233
+ writeMethod,
234
+ rowsOwnDescriptor,
235
+ readRawRows: rowsReader(terminalValue, rowsDescriptor, initialRows),
236
+ getColumns: () => {
237
+ try {
238
+ const current = Reflect.get(terminalValue, "columns");
239
+ return typeof current === "number" && Number.isFinite(current) ? current : columns;
240
+ } catch {
241
+ return columns;
242
+ }
243
+ },
244
+ hasVisibleOverlay: () => {
245
+ try {
246
+ if (
247
+ typeof hasOverlayValue === "function" &&
248
+ Reflect.apply(hasOverlayValue, value, []) === true
249
+ ) {
250
+ return true;
251
+ }
252
+ const stack = Reflect.get(value, "overlayStack");
253
+ return (
254
+ Array.isArray(stack) && stack.some((entry) => isRecord(entry) && entry.hidden !== true)
255
+ );
256
+ } catch {
257
+ return true;
258
+ }
259
+ },
260
+ getCursorBookkeeping: () => {
261
+ try {
262
+ const currentHardwareCursorRow = Reflect.get(value, "hardwareCursorRow");
263
+ const currentViewportTop = Reflect.get(value, "previousViewportTop");
264
+ return {
265
+ hardwareCursorRow:
266
+ typeof currentHardwareCursorRow === "number" &&
267
+ Number.isFinite(currentHardwareCursorRow)
268
+ ? currentHardwareCursorRow
269
+ : hardwareCursorRow,
270
+ previousViewportTop:
271
+ typeof currentViewportTop === "number" && Number.isFinite(currentViewportTop)
272
+ ? currentViewportTop
273
+ : previousViewportTop,
274
+ };
275
+ } catch {
276
+ return { hardwareCursorRow, previousViewportTop };
277
+ }
278
+ },
279
+ addInputListener: (listener) => Reflect.apply(addInputListenerValue, value, [listener]),
280
+ removeInputListener: (listener) => {
281
+ Reflect.apply(removeInputListenerValue, value, [listener]);
282
+ },
283
+ requestRender:
284
+ typeof requestRenderValue === "function"
285
+ ? (force) => Reflect.apply(requestRenderValue, value, [force])
286
+ : undefined,
287
+ };
288
+ }
289
+
290
+ export function inspectPiTui(value: unknown): PiFixedEditorCapabilities | undefined {
291
+ try {
292
+ return inspectPiTuiUnsafe(value);
293
+ } catch {
294
+ return undefined;
295
+ }
296
+ }