@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.
- package/README.md +7 -1
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/extensions/header/index.ts +92 -0
- package/extensions/matrix/index.ts +375 -0
- package/extensions/zentui/config.ts +1014 -0
- package/extensions/zentui/extension-status.ts +96 -0
- package/extensions/zentui/fixed-editor/cluster.ts +98 -0
- package/extensions/zentui/fixed-editor/compositor.ts +719 -0
- package/extensions/zentui/fixed-editor/index.ts +223 -0
- package/extensions/zentui/fixed-editor/input.ts +85 -0
- package/extensions/zentui/fixed-editor/pi-compat.ts +296 -0
- package/extensions/zentui/fixed-editor/selection.ts +217 -0
- package/extensions/zentui/fixed-editor/terminal-modes.ts +75 -0
- package/extensions/zentui/fixed-editor/types.ts +37 -0
- package/extensions/zentui/footer-format.ts +279 -0
- package/extensions/zentui/footer.ts +595 -0
- package/extensions/zentui/format.ts +434 -0
- package/extensions/zentui/git.ts +384 -0
- package/extensions/zentui/gradient.ts +70 -0
- package/extensions/zentui/icons.ts +252 -0
- package/extensions/zentui/index.ts +577 -0
- package/extensions/zentui/live-context.ts +75 -0
- package/extensions/zentui/package-version.ts +650 -0
- package/extensions/zentui/project-refresh.ts +104 -0
- package/extensions/zentui/project-state.ts +59 -0
- package/extensions/zentui/prototype-patch-registry.ts +111 -0
- package/extensions/zentui/runtime.ts +841 -0
- package/extensions/zentui/selector-border.ts +77 -0
- package/extensions/zentui/session-lifecycle.ts +60 -0
- package/extensions/zentui/settings-command.ts +897 -0
- package/extensions/zentui/state.ts +55 -0
- package/extensions/zentui/style.ts +332 -0
- package/extensions/zentui/thinking-message.ts +159 -0
- package/extensions/zentui/tool-execution.ts +189 -0
- package/extensions/zentui/ui.ts +618 -0
- package/extensions/zentui/user-message.ts +252 -0
- package/licenses/pi-sakura-cyberdeck-MIT.txt +21 -0
- package/licenses/pi-zentui-MIT.txt +21 -0
- package/manifests/provenance.json +11 -0
- package/manifests/sbom.json +17 -1
- package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
- package/package.json +11 -2
- package/src/runtime/doctor.ts +1 -1
- package/src/runtime/registry.ts +1 -1
- package/src/runtime/rem-head.txt +38 -0
- 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
|
+
}
|