@visual-z/xeno 0.1.4 → 0.2.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/CHANGELOG.md +25 -0
- package/README.md +143 -33
- package/docs/install.md +23 -6
- package/docs/providers.md +147 -0
- package/package.json +8 -2
- package/src/apps/cli.ts +45 -18
- package/src/apps/commands.ts +32 -4
- package/src/apps/completions.ts +9 -6
- package/src/apps/export-cli.ts +24 -0
- package/src/apps/extension-ui.ts +330 -0
- package/src/apps/input.ts +16 -1
- package/src/apps/model-catalog.ts +42 -0
- package/src/apps/project-trust.ts +49 -0
- package/src/apps/render.ts +1 -0
- package/src/apps/resource-browser.ts +75 -0
- package/src/apps/resources.ts +25 -0
- package/src/apps/session-browser.ts +113 -0
- package/src/apps/session-picker.ts +55 -0
- package/src/apps/shell.ts +1177 -378
- package/src/apps/startup-input.ts +94 -0
- package/src/apps/startup.ts +413 -295
- package/src/core/action-defaults.ts +306 -0
- package/src/core/attachment-input.ts +17 -0
- package/src/core/catalog-merge.ts +19 -0
- package/src/core/event-validation.ts +19 -2
- package/src/core/events.ts +45 -2
- package/src/core/export.ts +6 -2
- package/src/core/extension-flags.ts +14 -0
- package/src/core/images.ts +46 -0
- package/src/core/json-event.ts +2 -0
- package/src/core/keymap.ts +43 -119
- package/src/core/model-replay.ts +22 -0
- package/src/core/model-scope.ts +53 -0
- package/src/core/packages.ts +47 -0
- package/src/core/project.ts +21 -3
- package/src/core/prompts.ts +33 -5
- package/src/core/queue.ts +18 -0
- package/src/core/resource-selection.ts +36 -0
- package/src/{runtime → core}/resume.ts +15 -22
- package/src/core/session-archive.ts +4 -0
- package/src/core/session-browser.ts +82 -0
- package/src/core/session-entry.ts +56 -0
- package/src/core/session-position.ts +26 -0
- package/src/core/settings.ts +169 -154
- package/src/core/theme.ts +15 -6
- package/src/core/thinking.ts +26 -0
- package/src/core/tokens.ts +2 -0
- package/src/core/transcript.ts +15 -5
- package/src/runtime/auth.ts +116 -11
- package/src/runtime/catalog.ts +54 -151
- package/src/runtime/clipboard.ts +114 -0
- package/src/runtime/extension-loader.ts +37 -0
- package/src/runtime/extension-ui.ts +55 -0
- package/src/runtime/extensions.ts +273 -0
- package/src/runtime/images.ts +76 -0
- package/src/runtime/loop.ts +213 -83
- package/src/runtime/model-config.ts +232 -0
- package/src/runtime/model-data.LICENSE +27 -0
- package/src/runtime/model-data.ts +21 -0
- package/src/runtime/model-replay.ts +10 -0
- package/src/runtime/model-scope.ts +20 -0
- package/src/runtime/model-sources.ts +151 -0
- package/src/runtime/model.ts +74 -11
- package/src/runtime/models-fetch.ts +6 -104
- package/src/runtime/oauth.ts +4 -3
- package/src/runtime/openai-responses.ts +12 -4
- package/src/runtime/openai.ts +13 -4
- package/src/runtime/package-manager.ts +125 -0
- package/src/runtime/pi-provider.ts +235 -0
- package/src/runtime/progress.ts +26 -0
- package/src/runtime/provider-access.ts +21 -0
- package/src/runtime/provider-protocols.ts +29 -0
- package/src/runtime/provider-request.ts +73 -0
- package/src/runtime/provider.ts +26 -6
- package/src/runtime/rich-text.ts +36 -0
- package/src/runtime/tools.ts +41 -5
- package/src/store/command.ts +7 -1
- package/src/store/context-files.ts +1 -1
- package/src/store/log.ts +65 -0
- package/src/store/models.ts +28 -9
- package/src/store/observations.ts +4 -2
- package/src/store/packages.ts +98 -0
- package/src/store/recovery.ts +8 -0
- package/src/store/resource-index.ts +170 -0
- package/src/store/resources.ts +75 -142
- package/src/store/session-archive.ts +162 -0
- package/src/store/session-browser.ts +68 -0
- package/src/store/session-position.ts +27 -0
- package/src/store/sessions.ts +87 -66
- package/src/store/trust.ts +13 -0
- package/src/tui/chat-viewport.ts +47 -0
- package/src/tui/editor.ts +62 -15
- package/src/tui/host.ts +50 -9
- package/src/tui/input.ts +8 -2
- package/src/tui/select.ts +24 -6
- package/src/tui/settings.ts +4 -0
- package/src/tui/system-theme.ts +700 -0
- package/src/tui/transcript.ts +63 -11
- package/src/tui/tree.ts +161 -95
- package/src/vendor/pi-rich/LICENSE.upstream +21 -0
- package/src/vendor/pi-rich/UPSTREAM.json +17 -0
- package/src/vendor/pi-rich/clipboard-command.ts +44 -0
- package/src/vendor/pi-rich/exif-orientation.ts +182 -0
- package/src/vendor/pi-rich/html.ts +51 -0
- package/src/vendor/pi-rich/image-convert.ts +49 -0
- package/src/vendor/pi-rich/image-resize-core.ts +164 -0
- package/src/vendor/pi-rich/image-resize-worker.ts +42 -0
- package/src/vendor/pi-rich/image-resize.ts +123 -0
- package/src/vendor/pi-rich/mime.ts +116 -0
- package/src/vendor/pi-rich/photon.ts +139 -0
- package/src/vendor/pi-rich/syntax-highlight.ts +214 -0
- package/src/vendor/pi-tui/UPSTREAM.json +50 -0
- package/src/vendor/pi-tui/VENDOR.md +7 -5
- package/src/vendor/pi-tui/autocomplete.ts +35 -10
- package/src/vendor/pi-tui/colors.ts +367 -0
- package/src/vendor/pi-tui/components/box.ts +8 -9
- package/src/vendor/pi-tui/components/editor.ts +4 -2
- package/src/vendor/pi-tui/components/markdown.ts +8 -2
- package/src/vendor/pi-tui/index.ts +31 -1
- package/src/vendor/pi-tui/native/UPSTREAM.json +23 -0
- package/src/vendor/pi-tui/native/clipboard.h +208 -0
- package/src/vendor/pi-tui/native/darwin/README.md +22 -0
- package/src/vendor/pi-tui/native/darwin/build.sh +69 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +132 -0
- package/src/vendor/pi-tui/native/linux/README.md +23 -0
- package/src/vendor/pi-tui/native/linux/build.sh +65 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +70 -0
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +274 -0
- package/src/vendor/pi-tui/native/napi.h +91 -0
- package/src/vendor/pi-tui/native/win32/README.md +31 -0
- package/src/vendor/pi-tui/native/win32/build.mjs +232 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +241 -0
- package/src/vendor/pi-tui/native-platform.ts +2 -0
- package/src/vendor/pi-tui/oklab.ts +233 -0
- package/src/vendor/pi-tui/terminal-colors.ts +26 -8
- package/src/vendor/pi-tui/terminal-image.ts +43 -8
- package/src/vendor/pi-tui/terminal.ts +16 -9
- package/src/vendor/pi-tui/tui-alt-screen.ts +26 -18
- package/src/vendor/pi-tui/tui.ts +112 -75
- package/src/vendor/pi-tui/utils.ts +89 -49
- package/src/vendor/pi-tui/wheel-scroll.ts +82 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
const IMAGE_TYPE_SNIFF_BYTES = 4100;
|
|
4
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
5
|
+
|
|
6
|
+
export function detectSupportedImageMimeType(buffer: Uint8Array): string | null {
|
|
7
|
+
if (startsWith(buffer, [0xff, 0xd8, 0xff])) {
|
|
8
|
+
return buffer[3] === 0xf7 ? null : "image/jpeg";
|
|
9
|
+
}
|
|
10
|
+
if (startsWith(buffer, PNG_SIGNATURE)) {
|
|
11
|
+
return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : null;
|
|
12
|
+
}
|
|
13
|
+
if (startsWithAscii(buffer, 0, "GIF87a") || startsWithAscii(buffer, 0, "GIF89a")) {
|
|
14
|
+
return "image/gif";
|
|
15
|
+
}
|
|
16
|
+
if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP")) {
|
|
17
|
+
return "image/webp";
|
|
18
|
+
}
|
|
19
|
+
if (startsWithAscii(buffer, 0, "BM") && isBmp(buffer)) {
|
|
20
|
+
return "image/bmp";
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {
|
|
26
|
+
const fileHandle = await open(filePath, "r");
|
|
27
|
+
try {
|
|
28
|
+
const buffer = Buffer.alloc(IMAGE_TYPE_SNIFF_BYTES);
|
|
29
|
+
const { bytesRead } = await fileHandle.read(buffer, 0, IMAGE_TYPE_SNIFF_BYTES, 0);
|
|
30
|
+
return detectSupportedImageMimeType(buffer.subarray(0, bytesRead));
|
|
31
|
+
} finally {
|
|
32
|
+
await fileHandle.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isPng(buffer: Uint8Array): boolean {
|
|
37
|
+
return (
|
|
38
|
+
buffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, "IHDR")
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isAnimatedPng(buffer: Uint8Array): boolean {
|
|
43
|
+
let offset = PNG_SIGNATURE.length;
|
|
44
|
+
while (offset + 8 <= buffer.length) {
|
|
45
|
+
const chunkLength = readUint32BE(buffer, offset);
|
|
46
|
+
const chunkTypeOffset = offset + 4;
|
|
47
|
+
if (startsWithAscii(buffer, chunkTypeOffset, "acTL")) return true;
|
|
48
|
+
if (startsWithAscii(buffer, chunkTypeOffset, "IDAT")) return false;
|
|
49
|
+
|
|
50
|
+
const nextOffset = offset + 8 + chunkLength + 4;
|
|
51
|
+
if (nextOffset <= offset || nextOffset > buffer.length) return false;
|
|
52
|
+
offset = nextOffset;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isBmp(buffer: Uint8Array): boolean {
|
|
58
|
+
if (buffer.length < 26) return false;
|
|
59
|
+
|
|
60
|
+
const declaredFileSize = readUint32LE(buffer, 2);
|
|
61
|
+
const pixelDataOffset = readUint32LE(buffer, 10);
|
|
62
|
+
const dibHeaderSize = readUint32LE(buffer, 14);
|
|
63
|
+
if (declaredFileSize !== 0 && declaredFileSize < 26) return false;
|
|
64
|
+
if (pixelDataOffset < 14 + dibHeaderSize) return false;
|
|
65
|
+
if (declaredFileSize !== 0 && pixelDataOffset >= declaredFileSize) return false;
|
|
66
|
+
|
|
67
|
+
let colorPlanes: number;
|
|
68
|
+
let bitsPerPixel: number;
|
|
69
|
+
if (dibHeaderSize === 12) {
|
|
70
|
+
colorPlanes = readUint16LE(buffer, 22);
|
|
71
|
+
bitsPerPixel = readUint16LE(buffer, 24);
|
|
72
|
+
} else if (dibHeaderSize >= 40 && dibHeaderSize <= 124) {
|
|
73
|
+
if (buffer.length < 30) return false;
|
|
74
|
+
colorPlanes = readUint16LE(buffer, 26);
|
|
75
|
+
bitsPerPixel = readUint16LE(buffer, 28);
|
|
76
|
+
} else {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return colorPlanes === 1 && [1, 4, 8, 16, 24, 32].includes(bitsPerPixel);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readUint16LE(buffer: Uint8Array, offset: number): number {
|
|
84
|
+
return (buffer[offset] ?? 0) + ((buffer[offset + 1] ?? 0) << 8);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readUint32BE(buffer: Uint8Array, offset: number): number {
|
|
88
|
+
return (
|
|
89
|
+
(buffer[offset] ?? 0) * 0x1000000 +
|
|
90
|
+
((buffer[offset + 1] ?? 0) << 16) +
|
|
91
|
+
((buffer[offset + 2] ?? 0) << 8) +
|
|
92
|
+
(buffer[offset + 3] ?? 0)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readUint32LE(buffer: Uint8Array, offset: number): number {
|
|
97
|
+
return (
|
|
98
|
+
(buffer[offset] ?? 0) +
|
|
99
|
+
((buffer[offset + 1] ?? 0) << 8) +
|
|
100
|
+
((buffer[offset + 2] ?? 0) << 16) +
|
|
101
|
+
(buffer[offset + 3] ?? 0) * 0x1000000
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function startsWith(buffer: Uint8Array, bytes: number[]): boolean {
|
|
106
|
+
if (buffer.length < bytes.length) return false;
|
|
107
|
+
return bytes.every((byte, index) => buffer[index] === byte);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function startsWithAscii(buffer: Uint8Array, offset: number, text: string): boolean {
|
|
111
|
+
if (buffer.length < offset + text.length) return false;
|
|
112
|
+
for (let index = 0; index < text.length; index++) {
|
|
113
|
+
if (buffer[offset + index] !== text.charCodeAt(index)) return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photon image processing wrapper.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
|
|
5
|
+
* 1. Node.js (development, npm run build)
|
|
6
|
+
* 2. Bun compiled binaries (standalone distribution)
|
|
7
|
+
*
|
|
8
|
+
* The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm')
|
|
9
|
+
* which bakes the build machine's absolute path into Bun compiled binaries.
|
|
10
|
+
*
|
|
11
|
+
* Solution:
|
|
12
|
+
* 1. Patch fs.readFileSync to redirect missing photon_rs_bg.wasm reads
|
|
13
|
+
* 2. Copy photon_rs_bg.wasm next to the executable in build:binary
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { PathOrFileDescriptor } from "fs";
|
|
17
|
+
import { createRequire } from "module";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const fs = require("fs") as typeof import("fs");
|
|
23
|
+
|
|
24
|
+
// Re-export types from the main package
|
|
25
|
+
export type { PhotonImage as PhotonImageType } from "@silvia-odwyer/photon-node";
|
|
26
|
+
|
|
27
|
+
type ReadFileSync = typeof fs.readFileSync;
|
|
28
|
+
|
|
29
|
+
const WASM_FILENAME = "photon_rs_bg.wasm";
|
|
30
|
+
|
|
31
|
+
// Lazy-loaded photon module
|
|
32
|
+
let photonModule: typeof import("@silvia-odwyer/photon-node") | null = null;
|
|
33
|
+
let loadPromise: Promise<typeof import("@silvia-odwyer/photon-node") | null> | null = null;
|
|
34
|
+
|
|
35
|
+
function pathOrNull(file: PathOrFileDescriptor): string | null {
|
|
36
|
+
if (typeof file === "string") {
|
|
37
|
+
return file;
|
|
38
|
+
}
|
|
39
|
+
if (file instanceof URL) {
|
|
40
|
+
return fileURLToPath(file);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getFallbackWasmPaths(): string[] {
|
|
46
|
+
const execDir = path.dirname(process.execPath);
|
|
47
|
+
return [
|
|
48
|
+
path.join(execDir, WASM_FILENAME),
|
|
49
|
+
path.join(execDir, "photon", WASM_FILENAME),
|
|
50
|
+
path.join(process.cwd(), WASM_FILENAME),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function patchPhotonWasmRead(): () => void {
|
|
55
|
+
const originalReadFileSync: ReadFileSync = fs.readFileSync.bind(fs);
|
|
56
|
+
const fallbackPaths = getFallbackWasmPaths();
|
|
57
|
+
const mutableFs = fs as { readFileSync: ReadFileSync };
|
|
58
|
+
|
|
59
|
+
const patchedReadFileSync: ReadFileSync = ((...args: Parameters<ReadFileSync>) => {
|
|
60
|
+
const [file, options] = args;
|
|
61
|
+
const resolvedPath = pathOrNull(file);
|
|
62
|
+
|
|
63
|
+
if (resolvedPath?.endsWith(WASM_FILENAME)) {
|
|
64
|
+
try {
|
|
65
|
+
return originalReadFileSync(...args);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const err = error as NodeJS.ErrnoException;
|
|
68
|
+
if (err?.code && err.code !== "ENOENT") {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const fallbackPath of fallbackPaths) {
|
|
73
|
+
if (!fs.existsSync(fallbackPath)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (options === undefined) {
|
|
77
|
+
return originalReadFileSync(fallbackPath);
|
|
78
|
+
}
|
|
79
|
+
return originalReadFileSync(fallbackPath, options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return originalReadFileSync(...args);
|
|
87
|
+
}) as ReadFileSync;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
mutableFs.readFileSync = patchedReadFileSync;
|
|
91
|
+
} catch {
|
|
92
|
+
Object.defineProperty(fs, "readFileSync", {
|
|
93
|
+
value: patchedReadFileSync,
|
|
94
|
+
writable: true,
|
|
95
|
+
configurable: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
try {
|
|
101
|
+
mutableFs.readFileSync = originalReadFileSync;
|
|
102
|
+
} catch {
|
|
103
|
+
Object.defineProperty(fs, "readFileSync", {
|
|
104
|
+
value: originalReadFileSync,
|
|
105
|
+
writable: true,
|
|
106
|
+
configurable: true,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Load the photon module asynchronously.
|
|
114
|
+
* Returns cached module on subsequent calls.
|
|
115
|
+
*/
|
|
116
|
+
export async function loadPhoton(): Promise<typeof import("@silvia-odwyer/photon-node") | null> {
|
|
117
|
+
if (photonModule) {
|
|
118
|
+
return photonModule;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (loadPromise) {
|
|
122
|
+
return loadPromise;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
loadPromise = (async () => {
|
|
126
|
+
const restoreReadFileSync = patchPhotonWasmRead();
|
|
127
|
+
try {
|
|
128
|
+
photonModule = await import("@silvia-odwyer/photon-node");
|
|
129
|
+
return photonModule;
|
|
130
|
+
} catch {
|
|
131
|
+
photonModule = null;
|
|
132
|
+
return photonModule;
|
|
133
|
+
} finally {
|
|
134
|
+
restoreReadFileSync();
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
|
|
138
|
+
return loadPromise;
|
|
139
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import hljs from "highlight.js/lib/core.js";
|
|
2
|
+
import bash from "highlight.js/lib/languages/bash.js";
|
|
3
|
+
import c from "highlight.js/lib/languages/c.js";
|
|
4
|
+
import cpp from "highlight.js/lib/languages/cpp.js";
|
|
5
|
+
import csharp from "highlight.js/lib/languages/csharp.js";
|
|
6
|
+
import dart from "highlight.js/lib/languages/dart.js";
|
|
7
|
+
import go from "highlight.js/lib/languages/go.js";
|
|
8
|
+
import groovy from "highlight.js/lib/languages/groovy.js";
|
|
9
|
+
import java from "highlight.js/lib/languages/java.js";
|
|
10
|
+
import javascript from "highlight.js/lib/languages/javascript.js";
|
|
11
|
+
import json from "highlight.js/lib/languages/json.js";
|
|
12
|
+
import kotlin from "highlight.js/lib/languages/kotlin.js";
|
|
13
|
+
import lua from "highlight.js/lib/languages/lua.js";
|
|
14
|
+
import nix from "highlight.js/lib/languages/nix.js";
|
|
15
|
+
import perl from "highlight.js/lib/languages/perl.js";
|
|
16
|
+
import php from "highlight.js/lib/languages/php.js";
|
|
17
|
+
import python from "highlight.js/lib/languages/python.js";
|
|
18
|
+
import ruby from "highlight.js/lib/languages/ruby.js";
|
|
19
|
+
import rust from "highlight.js/lib/languages/rust.js";
|
|
20
|
+
import scala from "highlight.js/lib/languages/scala.js";
|
|
21
|
+
import swift from "highlight.js/lib/languages/swift.js";
|
|
22
|
+
import typescript from "highlight.js/lib/languages/typescript.js";
|
|
23
|
+
import { decodeHtmlEntityAt } from "./html.ts";
|
|
24
|
+
|
|
25
|
+
const eagerLanguages = {
|
|
26
|
+
python,
|
|
27
|
+
java,
|
|
28
|
+
go,
|
|
29
|
+
javascript,
|
|
30
|
+
json,
|
|
31
|
+
cpp,
|
|
32
|
+
typescript,
|
|
33
|
+
php,
|
|
34
|
+
ruby,
|
|
35
|
+
c,
|
|
36
|
+
csharp,
|
|
37
|
+
nix,
|
|
38
|
+
bash,
|
|
39
|
+
rust,
|
|
40
|
+
scala,
|
|
41
|
+
kotlin,
|
|
42
|
+
swift,
|
|
43
|
+
dart,
|
|
44
|
+
groovy,
|
|
45
|
+
perl,
|
|
46
|
+
lua,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
for (const [name, language] of Object.entries(eagerLanguages)) {
|
|
50
|
+
hljs.registerLanguage(name, language);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let allLanguagesPromise: Promise<void> | undefined;
|
|
54
|
+
|
|
55
|
+
export function loadAllHighlightLanguages(): Promise<void> {
|
|
56
|
+
if (!allLanguagesPromise) {
|
|
57
|
+
allLanguagesPromise = new Promise((resolve) => {
|
|
58
|
+
setImmediate(() => {
|
|
59
|
+
void import("highlight.js/lib/index.js").then(
|
|
60
|
+
() => resolve(),
|
|
61
|
+
() => {
|
|
62
|
+
// Eager languages and plaintext fallback remain available.
|
|
63
|
+
resolve();
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return allLanguagesPromise;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type HighlightFormatter = (text: string) => string;
|
|
73
|
+
export type HighlightTheme = Partial<Record<string, HighlightFormatter>>;
|
|
74
|
+
|
|
75
|
+
export interface HighlightOptions {
|
|
76
|
+
language?: string;
|
|
77
|
+
ignoreIllegals?: boolean;
|
|
78
|
+
languageSubset?: string[];
|
|
79
|
+
theme?: HighlightTheme;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SPAN_CLOSE = "</span>";
|
|
83
|
+
const HIGHLIGHT_CLASS_PREFIX = "hljs-";
|
|
84
|
+
|
|
85
|
+
function getScopeFromSpanTag(tag: string): string | undefined {
|
|
86
|
+
const match = /\sclass\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(tag);
|
|
87
|
+
const classValue = match?.[1] ?? match?.[2];
|
|
88
|
+
if (!classValue) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const className of classValue.split(/\s+/)) {
|
|
93
|
+
if (className.startsWith(HIGHLIGHT_CLASS_PREFIX)) {
|
|
94
|
+
return className.slice(HIGHLIGHT_CLASS_PREFIX.length);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getScopeFormatter(scope: string, theme: HighlightTheme): HighlightFormatter | undefined {
|
|
102
|
+
const exact = theme[scope];
|
|
103
|
+
if (exact) {
|
|
104
|
+
return exact;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const dotIndex = scope.indexOf(".");
|
|
108
|
+
if (dotIndex !== -1) {
|
|
109
|
+
const prefixFormatter = theme[scope.slice(0, dotIndex)];
|
|
110
|
+
if (prefixFormatter) {
|
|
111
|
+
return prefixFormatter;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const dashIndex = scope.indexOf("-");
|
|
116
|
+
if (dashIndex !== -1) {
|
|
117
|
+
const prefixFormatter = theme[scope.slice(0, dashIndex)];
|
|
118
|
+
if (prefixFormatter) {
|
|
119
|
+
return prefixFormatter;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getActiveFormatter(scopes: Array<string | undefined>, theme: HighlightTheme): HighlightFormatter | undefined {
|
|
127
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
128
|
+
const scope = scopes[i];
|
|
129
|
+
if (!scope) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const formatter = getScopeFormatter(scope, theme);
|
|
133
|
+
if (formatter) {
|
|
134
|
+
return formatter;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return theme.default;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isSpanOpenTagStart(html: string, index: number): boolean {
|
|
141
|
+
if (!html.startsWith("<span", index)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const nextChar = html[index + "<span".length];
|
|
145
|
+
return nextChar === ">" || nextChar === " " || nextChar === "\t" || nextChar === "\n" || nextChar === "\r";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function renderHighlightedHtml(html: string, theme: HighlightTheme = {}): string {
|
|
149
|
+
let output = "";
|
|
150
|
+
let textBuffer = "";
|
|
151
|
+
const scopes: Array<string | undefined> = [];
|
|
152
|
+
|
|
153
|
+
const flushText = () => {
|
|
154
|
+
if (!textBuffer) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const formatter = getActiveFormatter(scopes, theme);
|
|
158
|
+
output += formatter ? formatter(textBuffer) : textBuffer;
|
|
159
|
+
textBuffer = "";
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
let index = 0;
|
|
163
|
+
while (index < html.length) {
|
|
164
|
+
if (isSpanOpenTagStart(html, index)) {
|
|
165
|
+
const tagEndIndex = html.indexOf(">", index + 5);
|
|
166
|
+
if (tagEndIndex !== -1) {
|
|
167
|
+
flushText();
|
|
168
|
+
const tag = html.slice(index, tagEndIndex + 1);
|
|
169
|
+
const scope = getScopeFromSpanTag(tag);
|
|
170
|
+
scopes.push(scope);
|
|
171
|
+
index = tagEndIndex + 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (html.startsWith(SPAN_CLOSE, index)) {
|
|
177
|
+
flushText();
|
|
178
|
+
if (scopes.length > 0) {
|
|
179
|
+
scopes.pop();
|
|
180
|
+
}
|
|
181
|
+
index += SPAN_CLOSE.length;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (html[index] === "&") {
|
|
186
|
+
const decoded = decodeHtmlEntityAt(html, index);
|
|
187
|
+
if (decoded) {
|
|
188
|
+
textBuffer += decoded.text;
|
|
189
|
+
index += decoded.length;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
textBuffer += html[index];
|
|
195
|
+
index++;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
flushText();
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function highlight(code: string, options: HighlightOptions = {}): string {
|
|
203
|
+
const html = options.language
|
|
204
|
+
? hljs.highlight(code, {
|
|
205
|
+
language: options.language,
|
|
206
|
+
ignoreIllegals: options.ignoreIllegals,
|
|
207
|
+
}).value
|
|
208
|
+
: hljs.highlightAuto(code, options.languageSubset).value;
|
|
209
|
+
return renderHighlightedHtml(html, options.theme);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function supportsLanguage(name: string): boolean {
|
|
213
|
+
return hljs.getLanguage(name) !== undefined;
|
|
214
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"commit": "02eed88fd8912e54a804ddebd409e2e4c08ac5ef",
|
|
3
|
+
"files": {
|
|
4
|
+
"alt-screen-search.ts": "2ed3821323723515523075ac143ce5e25fdcd7c7",
|
|
5
|
+
"autocomplete.ts": "3ff45bf17a5def8d0bdf81e9eb1af264f1c5f768",
|
|
6
|
+
"colors.ts": "070a261344e307988f45e7c86d1728db5a00b8f3",
|
|
7
|
+
"components/alt-screen-flash.ts": "e4b823cd2bdd54696895cac93807cc6fcddaddd4",
|
|
8
|
+
"components/box.ts": "7c880d9145eff2c26db7969937223bb3776acced",
|
|
9
|
+
"components/cancellable-loader.ts": "7822cb04219440703a9100e02679725b3326fa1c",
|
|
10
|
+
"components/editor.ts": "c7fda44eb8288a10f518791f931cff4fc36c62b5",
|
|
11
|
+
"components/h-stack.ts": "07f8f8ffd055789d69266152f62ee97390bacf01",
|
|
12
|
+
"components/image.ts": "91d68016aae5d292e22a3e892631b7ed53b13e64",
|
|
13
|
+
"components/input.ts": "1bc26453098639991c22515cc94fee89b53f289d",
|
|
14
|
+
"components/loader.ts": "0c59e73a83bb4bd9978818b16bf556da53f48123",
|
|
15
|
+
"components/markdown.ts": "30c40c1715dfc374687b538d447988a81feb237f",
|
|
16
|
+
"components/mouse-region.ts": "a77ded0c1e59167fe2f0643e87531b95ed9e544d",
|
|
17
|
+
"components/scroll-view.ts": "b8f8a51ff0f8f93aabce0df072ee29c5a47c82b3",
|
|
18
|
+
"components/select-list.ts": "f4f4354dd850939e2e84ea8ce8a755206d3e8e43",
|
|
19
|
+
"components/settings-list.ts": "b1b8366bf349b30a1d93073a4cc1d0e2bfa9dc8a",
|
|
20
|
+
"components/spacer.ts": "7abe1551ca79a17bbb03d6b62f3ec18c50274a75",
|
|
21
|
+
"components/stack.ts": "68bde0368455646144a1dd3942b56a48e9d4f45a",
|
|
22
|
+
"components/text.ts": "7a50e2721028d544f641ab5f66ef1b752ff6d1f7",
|
|
23
|
+
"components/truncated-text.ts": "c26b8892967a8462a8828cfed4ea572a73af1602",
|
|
24
|
+
"components/v-stack.ts": "ce7b9292d6dfd0981650a1cf927f223b926f9714",
|
|
25
|
+
"editor-component.ts": "595645033a074d17b8190b73709c2623d3e479b5",
|
|
26
|
+
"fuzzy.ts": "1b5352c1c592956ea614a2ec3630cb6f606ccd83",
|
|
27
|
+
"index.ts": "93fb8b72777a3a4f8423cf4e7d5f74b8dbd982f0",
|
|
28
|
+
"keybindings.ts": "d6afb5ec39642c098b7d95e10ab6b57568e514b4",
|
|
29
|
+
"keys.ts": "8b6f8a0a708cc9a1c9b795ff1ec9766c4209114b",
|
|
30
|
+
"kill-ring.ts": "2292f91aa36baa1bf762df2890bdbed8ca17eb19",
|
|
31
|
+
"latex.ts": "060fd052d820b42903896e53053614e1c578a995",
|
|
32
|
+
"layout-node.ts": "a3c06b24564efaa35321f190641a80e95ce7c7ab",
|
|
33
|
+
"layout.ts": "897c51de2ffbc4c543e87399eaa4e8b6421449ef",
|
|
34
|
+
"native-modifiers.ts": "8cdc60d484717431bd5f1577b36f189275aa06d3",
|
|
35
|
+
"native-module-path.ts": "b75117feb7384da268cfd7d3a8c8fc6af0aeb10e",
|
|
36
|
+
"native-platform.ts": "3a6580a445d8a816225ed824d30e48a93268a761",
|
|
37
|
+
"oklab.ts": "83ddda42fca5bcd485464c74029c2f3db7a5cc6d",
|
|
38
|
+
"stdin-buffer.ts": "cc10c61a55196eb90d9da3e1858e48cab27e925f",
|
|
39
|
+
"terminal-colors.ts": "6a972a95bb5c3c0de65657f4861cdb3ad3579c41",
|
|
40
|
+
"terminal-image.ts": "a84d22c575599c55e5ea96c528e641e54df8511c",
|
|
41
|
+
"terminal.ts": "cce2dd116ca55b2c9f3add30b080a395e8f1f4bb",
|
|
42
|
+
"tui-alt-screen.ts": "bf8affb13ba1d3625711268c8b6f0956c9e5d90e",
|
|
43
|
+
"tui-main-screen.ts": "c079dd776984b23f5e0b4a833ac5b67fb31ff337",
|
|
44
|
+
"tui.ts": "7968a9e792375b9e8701ca4dbe89d18735aa387b",
|
|
45
|
+
"undo-stack.ts": "5b9a7e9cee03a08b5d7ee637b92834a3a28d7fd2",
|
|
46
|
+
"utils.ts": "f537ad7e37dcf35db1259aa05d43f1b45bdef7c7",
|
|
47
|
+
"wheel-scroll.ts": "a6c0018bd4caa9b5bc197d05d548bfdca4036172",
|
|
48
|
+
"word-navigation.ts": "7c7eced2b6ac3ff5b49418824c3ac00a1f0204a3"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Source: https://github.com/earendil-works/pi — `packages/tui/src`
|
|
4
4
|
License: MIT (see `LICENSE.upstream`), Copyright (c) 2025 Mario Zechner
|
|
5
|
-
Copied: 2026-09-
|
|
6
|
-
Commit: `
|
|
5
|
+
Copied: 2026-09-30
|
|
6
|
+
Commit: `02eed88fd8912e54a804ddebd409e2e4c08ac5ef`
|
|
7
|
+
All 45 source files verified against upstream Git blob hashes in `UPSTREAM.json`.
|
|
7
8
|
|
|
8
9
|
The application also follows coding-agent's CustomEditor, searchable model
|
|
9
10
|
selector and interactive-mode composition. Those product behaviors are adapted
|
|
@@ -18,6 +19,7 @@ adapts this to xeno and is the only thing allowed to import from here.
|
|
|
18
19
|
checks, because those exist to keep *our* code's layering decidable and
|
|
19
20
|
upstream organises its code its own way.
|
|
20
21
|
|
|
21
|
-
Native
|
|
22
|
-
|
|
23
|
-
upstream
|
|
22
|
+
Native sources and all six upstream prebuilt `.node` binaries are included
|
|
23
|
+
under `native/`, verified by `native/UPSTREAM.json` at the same commit.
|
|
24
|
+
The upstream resolver loads them directly; unavailable platform capabilities
|
|
25
|
+
retain its optional fallback. No install script or startup download is required.
|
|
@@ -7,6 +7,8 @@ import { autocompleteBoundaryRegex, autocompleteSeparatorRegex } from "./utils.t
|
|
|
7
7
|
|
|
8
8
|
const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
|
|
9
9
|
const tokenStartRegex = new RegExp(`${autocompleteBoundaryRegex.source}$`, "u");
|
|
10
|
+
// Opening wrappers that may precede a path in prose, mapped to their closing counterpart.
|
|
11
|
+
const PATH_WRAPPERS: Record<string, string> = { "(": ")", "[": "]", "{": "}", "<": ">", "`": "`" };
|
|
10
12
|
|
|
11
13
|
function toDisplayPath(value: string): string {
|
|
12
14
|
return value.replace(/\\/g, "/");
|
|
@@ -56,6 +58,20 @@ function findLastDelimiter(text: string): number {
|
|
|
56
58
|
return lastDelimiter;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// Strip opening wrappers before a path, e.g. "(~/Dev" -> "~/Dev" or "`src/ma" -> "src/ma".
|
|
62
|
+
// Keep a wrapper if the token also contains its closer, e.g. "app/[slug]/pa" or "(group)/pa".
|
|
63
|
+
function stripLeadingWrappers(token: string): string {
|
|
64
|
+
let result = token;
|
|
65
|
+
while (result.length > 0) {
|
|
66
|
+
const closer = PATH_WRAPPERS[result[0]!];
|
|
67
|
+
if (!closer || result.includes(closer, 1)) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
result = result.slice(1);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
function findUnclosedQuoteStart(text: string): number | null {
|
|
60
76
|
let inQuotes = false;
|
|
61
77
|
let quoteStart = -1;
|
|
@@ -73,7 +89,11 @@ function findUnclosedQuoteStart(text: string): number | null {
|
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
function isTokenStart(text: string, index: number): boolean {
|
|
76
|
-
|
|
92
|
+
let start = index;
|
|
93
|
+
while (start > 0 && PATH_WRAPPERS[text[start - 1]!]) {
|
|
94
|
+
start -= 1;
|
|
95
|
+
}
|
|
96
|
+
return PATH_DELIMITERS.has(text[start - 1] ?? "") || tokenStartRegex.test(text.slice(0, start));
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
function extractQuotedPrefix(text: string): string | null {
|
|
@@ -332,11 +352,16 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
332
352
|
};
|
|
333
353
|
});
|
|
334
354
|
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
355
|
+
const bareNameMatches = fuzzyFilter(commandItems, prefix, (item) =>
|
|
356
|
+
item.name.startsWith("skill:") ? item.name.slice("skill:".length) : item.name,
|
|
357
|
+
);
|
|
358
|
+
const bareNameMatchSet = new Set(bareNameMatches);
|
|
359
|
+
const fullNameOnlyMatches = fuzzyFilter(
|
|
360
|
+
commandItems.filter((item) => item.name.startsWith("skill:") && !bareNameMatchSet.has(item)),
|
|
361
|
+
prefix,
|
|
362
|
+
(item) => item.name,
|
|
363
|
+
);
|
|
364
|
+
const filtered = [...bareNameMatches, ...fullNameOnlyMatches].map((item) => ({
|
|
340
365
|
value: item.name,
|
|
341
366
|
label: item.label,
|
|
342
367
|
...(item.description && { description: item.description }),
|
|
@@ -481,10 +506,10 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
481
506
|
}
|
|
482
507
|
|
|
483
508
|
const lastDelimiterIndex = findLastDelimiter(text);
|
|
484
|
-
const
|
|
509
|
+
const token = stripLeadingWrappers(lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1));
|
|
485
510
|
|
|
486
|
-
if (
|
|
487
|
-
return
|
|
511
|
+
if (token.startsWith("@")) {
|
|
512
|
+
return token;
|
|
488
513
|
}
|
|
489
514
|
|
|
490
515
|
return null;
|
|
@@ -498,7 +523,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
498
523
|
}
|
|
499
524
|
|
|
500
525
|
const lastDelimiterIndex = findLastDelimiter(text);
|
|
501
|
-
const pathPrefix = lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1);
|
|
526
|
+
const pathPrefix = stripLeadingWrappers(lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1));
|
|
502
527
|
|
|
503
528
|
// For forced extraction (Tab key), always return something
|
|
504
529
|
if (forceExtract) {
|