@rosetears/aili-pi 0.1.0 → 0.1.1

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 (46) hide show
  1. package/README.md +7 -1
  2. package/THIRD_PARTY_NOTICES.md +11 -0
  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 +577 -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/provenance.json +11 -0
  40. package/manifests/sbom.json +17 -1
  41. package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
  42. package/package.json +11 -2
  43. package/src/runtime/doctor.ts +1 -1
  44. package/src/runtime/registry.ts +1 -1
  45. package/src/runtime/rem-head.txt +38 -0
  46. package/themes/rem-cyberdeck.json +32 -0
@@ -0,0 +1,96 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+ import type {
3
+ ExtensionStatusColorMode,
4
+ ExtensionStatusPlacement,
5
+ PolishedTuiConfig,
6
+ } from "./config.js";
7
+ import { getExtensionStatusColorMode, getExtensionStatusPlacement } from "./config.js";
8
+
9
+ export type ExtensionStatusSegment = {
10
+ key: string;
11
+ text: string;
12
+ placement: ExtensionStatusPlacement;
13
+ colorMode: ExtensionStatusColorMode;
14
+ };
15
+
16
+ export type ExtensionStatusSegmentsByPlacement = {
17
+ left: ExtensionStatusSegment[];
18
+ middle: ExtensionStatusSegment[];
19
+ right: ExtensionStatusSegment[];
20
+ };
21
+
22
+ const safeSgrPattern = /\x1b\[[0-9;:]*m/g;
23
+ const sgrPlaceholderPattern = /__ZENTUI_SGR_(\d+)__/g;
24
+
25
+ function compareKeys(a: ExtensionStatusSegment, b: ExtensionStatusSegment): number {
26
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
27
+ }
28
+
29
+ function normalizeStatusWhitespace(value: string): string {
30
+ return value
31
+ .replace(/[\r\n\t\f\v]+/g, " ")
32
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, "")
33
+ .replace(/\s+/g, " ")
34
+ .trim();
35
+ }
36
+
37
+ export function sanitizeExtensionStatusText(value: string): string {
38
+ return normalizeStatusWhitespace(stripVTControlCharacters(value));
39
+ }
40
+
41
+ function hasVisibleStatusText(value: string): boolean {
42
+ return sanitizeExtensionStatusText(value).length > 0;
43
+ }
44
+
45
+ function formatQuotaWindowLabel(key: string, text: string): string {
46
+ // pi-quota-status uses the compact upstream label `Wk`. Rem Cyberdeck
47
+ // presents the same weekly Codex window as the more explicit `7d`.
48
+ return key === "pi-quota-status" ? text.replace(/\bWk\b/g, "7d") : text;
49
+ }
50
+
51
+ export function sanitizeExtensionStatusOriginalText(value: string): string {
52
+ const safeSequences: string[] = [];
53
+ const protectedValue = value.replace(safeSgrPattern, (sequence) => {
54
+ const index = safeSequences.push(sequence) - 1;
55
+ return `__ZENTUI_SGR_${index}__`;
56
+ });
57
+ const cleaned = normalizeStatusWhitespace(stripVTControlCharacters(protectedValue));
58
+ const restored = cleaned.replace(sgrPlaceholderPattern, (_match, indexText: string) => {
59
+ const index = Number.parseInt(indexText, 10);
60
+ return safeSequences[index] ?? "";
61
+ });
62
+
63
+ return hasVisibleStatusText(restored) ? restored : "";
64
+ }
65
+
66
+ export function collectExtensionStatusSegments(
67
+ statuses: ReadonlyMap<string, string>,
68
+ config: PolishedTuiConfig,
69
+ ): ExtensionStatusSegmentsByPlacement {
70
+ const segments: ExtensionStatusSegmentsByPlacement = {
71
+ left: [],
72
+ middle: [],
73
+ right: [],
74
+ };
75
+
76
+ for (const [key, value] of statuses.entries()) {
77
+ const placement = getExtensionStatusPlacement(config, key);
78
+ if (placement === "off") continue;
79
+
80
+ const colorMode = getExtensionStatusColorMode(config, key);
81
+ const text = formatQuotaWindowLabel(
82
+ key,
83
+ colorMode === "original"
84
+ ? sanitizeExtensionStatusOriginalText(value)
85
+ : sanitizeExtensionStatusText(value),
86
+ );
87
+ if (!text) continue;
88
+
89
+ segments[placement].push({ key, text, placement, colorMode });
90
+ }
91
+
92
+ segments.left.sort(compareKeys);
93
+ segments.middle.sort(compareKeys);
94
+ segments.right.sort(compareKeys);
95
+ return segments;
96
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Cluster rendering for the fixed editor.
3
+ *
4
+ * Pi-specific cluster discovery and validation live in pi-compat.ts. This module
5
+ * only renders the already-verified pinned components.
6
+ *
7
+ * @internal
8
+ */
9
+
10
+ import { CURSOR_MARKER, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
+
12
+ import type { PiFixedCluster, PiRenderableCapability } from "./pi-compat.js";
13
+ import type { ClusterRender } from "./types.js";
14
+
15
+ export type FixedCluster = PiFixedCluster;
16
+
17
+ function renderComponent(component: PiRenderableCapability | null, width: number): string[] {
18
+ if (!component) return [];
19
+ const lines = component.render.call(component.target, width);
20
+ // Strip only trailing blank lines — internal blank lines (e.g. editor
21
+ // padding in copy-friendly mode) must be preserved.
22
+ let end = lines.length;
23
+ while (end > 0 && visibleWidth(lines[end - 1]) === 0) end--;
24
+ return lines.slice(0, Math.max(end, 1));
25
+ }
26
+
27
+ /**
28
+ * Cap editor lines to `maxLines`, keeping the cursor row visible.
29
+ * If the cursor marker is found, the window centers on it; otherwise
30
+ * the last `maxLines` are kept.
31
+ */
32
+ export function capEditorLines(lines: string[], maxLines: number): string[] {
33
+ if (maxLines <= 0) return [];
34
+ if (lines.length <= maxLines) return lines;
35
+
36
+ const cursorRow = lines.findIndex((line) => line.includes(CURSOR_MARKER));
37
+ if (cursorRow !== -1) {
38
+ const start = Math.max(0, Math.min(cursorRow - maxLines + 1, lines.length - maxLines));
39
+ return lines.slice(start, start + maxLines);
40
+ }
41
+ return lines.slice(lines.length - maxLines);
42
+ }
43
+
44
+ function sanitizeLines(lines: string[], width: number): string[] {
45
+ return lines.map((line) =>
46
+ visibleWidth(line) > width ? truncateToWidth(line, width, "", true) : line,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Render the full cluster (status + widgets + editor + footer) and extract
52
+ * the cursor position from the CURSOR_MARKER.
53
+ */
54
+ export function renderCluster(
55
+ cluster: FixedCluster,
56
+ width: number,
57
+ maxHeight: number,
58
+ ): ClusterRender {
59
+ const w = Math.max(1, width);
60
+ const maxRows = Math.max(1, maxHeight - 1);
61
+
62
+ const statusLines = sanitizeLines(renderComponent(cluster.status, w), w);
63
+ const aboveLines = sanitizeLines(renderComponent(cluster.aboveWidget, w), w);
64
+ const editorSource = sanitizeLines(renderComponent(cluster.editor, w), w);
65
+ const belowLines = sanitizeLines(renderComponent(cluster.belowWidget, w), w);
66
+ const footerLines = sanitizeLines(renderComponent(cluster.footer, w), w);
67
+
68
+ const editorLines = capEditorLines(editorSource, maxRows);
69
+ let remaining = maxRows - editorLines.length;
70
+
71
+ const footer = footerLines.slice(-remaining);
72
+ remaining -= footer.length;
73
+
74
+ const below = belowLines.slice(-remaining);
75
+ remaining -= below.length;
76
+
77
+ const above = aboveLines.slice(-remaining);
78
+ remaining -= above.length;
79
+
80
+ const status = statusLines.slice(-remaining);
81
+
82
+ let allLines = [...status, ...above, ...editorLines, ...below, ...footer];
83
+
84
+ // Strip leading blank lines (e.g. empty status line above the editor border).
85
+ let start = 0;
86
+ while (start < allLines.length - 1 && visibleWidth(allLines[start]) === 0) start++;
87
+ allLines = allLines.slice(start);
88
+
89
+ let cursor: { row: number; col: number } | null = null;
90
+ const cleaned = allLines.map((line, row) => {
91
+ const markerIndex = line.indexOf(CURSOR_MARKER);
92
+ if (markerIndex === -1) return line;
93
+ cursor ??= { row, col: visibleWidth(line.slice(0, markerIndex)) };
94
+ return line.slice(0, markerIndex) + line.slice(markerIndex + CURSOR_MARKER.length);
95
+ });
96
+
97
+ return { lines: cleaned, cursor };
98
+ }