@visual-z/xeno 0.1.4 → 0.2.0

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 (146) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +143 -33
  3. package/docs/install.md +18 -6
  4. package/docs/providers.md +147 -0
  5. package/package.json +8 -2
  6. package/src/apps/cli.ts +42 -14
  7. package/src/apps/commands.ts +32 -4
  8. package/src/apps/completions.ts +9 -6
  9. package/src/apps/export-cli.ts +24 -0
  10. package/src/apps/extension-ui.ts +330 -0
  11. package/src/apps/input.ts +16 -1
  12. package/src/apps/model-catalog.ts +42 -0
  13. package/src/apps/project-trust.ts +49 -0
  14. package/src/apps/render.ts +1 -0
  15. package/src/apps/resource-browser.ts +75 -0
  16. package/src/apps/resources.ts +25 -0
  17. package/src/apps/session-browser.ts +113 -0
  18. package/src/apps/session-picker.ts +55 -0
  19. package/src/apps/shell.ts +1177 -378
  20. package/src/apps/startup.ts +400 -268
  21. package/src/core/action-defaults.ts +306 -0
  22. package/src/core/attachment-input.ts +17 -0
  23. package/src/core/catalog-merge.ts +19 -0
  24. package/src/core/event-validation.ts +19 -2
  25. package/src/core/events.ts +45 -2
  26. package/src/core/export.ts +6 -2
  27. package/src/core/extension-flags.ts +14 -0
  28. package/src/core/images.ts +46 -0
  29. package/src/core/json-event.ts +2 -0
  30. package/src/core/keymap.ts +43 -119
  31. package/src/core/model-replay.ts +22 -0
  32. package/src/core/model-scope.ts +53 -0
  33. package/src/core/packages.ts +47 -0
  34. package/src/core/project.ts +21 -3
  35. package/src/core/prompts.ts +33 -5
  36. package/src/core/queue.ts +18 -0
  37. package/src/core/resource-selection.ts +36 -0
  38. package/src/{runtime → core}/resume.ts +15 -22
  39. package/src/core/session-archive.ts +4 -0
  40. package/src/core/session-browser.ts +82 -0
  41. package/src/core/session-entry.ts +56 -0
  42. package/src/core/session-position.ts +26 -0
  43. package/src/core/settings.ts +169 -154
  44. package/src/core/theme.ts +15 -6
  45. package/src/core/thinking.ts +26 -0
  46. package/src/core/tokens.ts +2 -0
  47. package/src/core/transcript.ts +15 -5
  48. package/src/runtime/auth.ts +116 -11
  49. package/src/runtime/catalog.ts +54 -151
  50. package/src/runtime/clipboard.ts +114 -0
  51. package/src/runtime/extension-loader.ts +37 -0
  52. package/src/runtime/extension-ui.ts +55 -0
  53. package/src/runtime/extensions.ts +273 -0
  54. package/src/runtime/images.ts +76 -0
  55. package/src/runtime/loop.ts +213 -83
  56. package/src/runtime/model-config.ts +232 -0
  57. package/src/runtime/model-data.LICENSE +27 -0
  58. package/src/runtime/model-data.ts +21 -0
  59. package/src/runtime/model-replay.ts +10 -0
  60. package/src/runtime/model-scope.ts +20 -0
  61. package/src/runtime/model-sources.ts +151 -0
  62. package/src/runtime/model.ts +74 -11
  63. package/src/runtime/models-fetch.ts +6 -104
  64. package/src/runtime/oauth.ts +4 -3
  65. package/src/runtime/openai-responses.ts +12 -4
  66. package/src/runtime/openai.ts +13 -4
  67. package/src/runtime/package-manager.ts +125 -0
  68. package/src/runtime/pi-provider.ts +235 -0
  69. package/src/runtime/progress.ts +26 -0
  70. package/src/runtime/provider-access.ts +21 -0
  71. package/src/runtime/provider-protocols.ts +29 -0
  72. package/src/runtime/provider-request.ts +73 -0
  73. package/src/runtime/provider.ts +26 -6
  74. package/src/runtime/rich-text.ts +36 -0
  75. package/src/runtime/tools.ts +41 -5
  76. package/src/store/command.ts +7 -1
  77. package/src/store/context-files.ts +1 -1
  78. package/src/store/log.ts +65 -0
  79. package/src/store/models.ts +28 -9
  80. package/src/store/observations.ts +4 -2
  81. package/src/store/packages.ts +98 -0
  82. package/src/store/recovery.ts +8 -0
  83. package/src/store/resource-index.ts +170 -0
  84. package/src/store/resources.ts +75 -142
  85. package/src/store/session-archive.ts +162 -0
  86. package/src/store/session-browser.ts +68 -0
  87. package/src/store/session-position.ts +27 -0
  88. package/src/store/sessions.ts +87 -66
  89. package/src/store/trust.ts +13 -0
  90. package/src/tui/chat-viewport.ts +47 -0
  91. package/src/tui/editor.ts +62 -15
  92. package/src/tui/host.ts +50 -9
  93. package/src/tui/input.ts +8 -2
  94. package/src/tui/select.ts +24 -6
  95. package/src/tui/settings.ts +4 -0
  96. package/src/tui/system-theme.ts +700 -0
  97. package/src/tui/transcript.ts +63 -11
  98. package/src/tui/tree.ts +161 -95
  99. package/src/vendor/pi-rich/LICENSE.upstream +21 -0
  100. package/src/vendor/pi-rich/UPSTREAM.json +17 -0
  101. package/src/vendor/pi-rich/clipboard-command.ts +44 -0
  102. package/src/vendor/pi-rich/exif-orientation.ts +182 -0
  103. package/src/vendor/pi-rich/html.ts +51 -0
  104. package/src/vendor/pi-rich/image-convert.ts +49 -0
  105. package/src/vendor/pi-rich/image-resize-core.ts +164 -0
  106. package/src/vendor/pi-rich/image-resize-worker.ts +42 -0
  107. package/src/vendor/pi-rich/image-resize.ts +123 -0
  108. package/src/vendor/pi-rich/mime.ts +116 -0
  109. package/src/vendor/pi-rich/photon.ts +139 -0
  110. package/src/vendor/pi-rich/syntax-highlight.ts +214 -0
  111. package/src/vendor/pi-tui/UPSTREAM.json +50 -0
  112. package/src/vendor/pi-tui/VENDOR.md +7 -5
  113. package/src/vendor/pi-tui/autocomplete.ts +35 -10
  114. package/src/vendor/pi-tui/colors.ts +367 -0
  115. package/src/vendor/pi-tui/components/box.ts +8 -9
  116. package/src/vendor/pi-tui/components/editor.ts +4 -2
  117. package/src/vendor/pi-tui/components/markdown.ts +8 -2
  118. package/src/vendor/pi-tui/index.ts +31 -1
  119. package/src/vendor/pi-tui/native/UPSTREAM.json +23 -0
  120. package/src/vendor/pi-tui/native/clipboard.h +208 -0
  121. package/src/vendor/pi-tui/native/darwin/README.md +22 -0
  122. package/src/vendor/pi-tui/native/darwin/build.sh +69 -0
  123. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  124. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  125. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +132 -0
  126. package/src/vendor/pi-tui/native/linux/README.md +23 -0
  127. package/src/vendor/pi-tui/native/linux/build.sh +65 -0
  128. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  129. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  130. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +70 -0
  131. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +274 -0
  132. package/src/vendor/pi-tui/native/napi.h +91 -0
  133. package/src/vendor/pi-tui/native/win32/README.md +31 -0
  134. package/src/vendor/pi-tui/native/win32/build.mjs +232 -0
  135. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  136. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  137. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +241 -0
  138. package/src/vendor/pi-tui/native-platform.ts +2 -0
  139. package/src/vendor/pi-tui/oklab.ts +233 -0
  140. package/src/vendor/pi-tui/terminal-colors.ts +26 -8
  141. package/src/vendor/pi-tui/terminal-image.ts +43 -8
  142. package/src/vendor/pi-tui/terminal.ts +16 -9
  143. package/src/vendor/pi-tui/tui-alt-screen.ts +26 -18
  144. package/src/vendor/pi-tui/tui.ts +112 -75
  145. package/src/vendor/pi-tui/utils.ts +89 -49
  146. 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-21
6
- Commit: `3390bd93630965a12a0a1a5c36ce890ec22f7e1d`
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 modules (`native-*.ts`) are optional: `isNativeModifierPressed`
22
- returns false when no helper loads, so the prebuilt `.node` binaries from
23
- upstream are deliberately NOT copied.
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
- return PATH_DELIMITERS.has(text[index - 1] ?? "") || tokenStartRegex.test(text.slice(0, index));
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 filtered = fuzzyFilter(commandItems, prefix, (item) =>
336
- !prefix.startsWith("skill:") && item.name.startsWith("skill:")
337
- ? item.name.slice("skill:".length)
338
- : item.name,
339
- ).map((item) => ({
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 tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
509
+ const token = stripLeadingWrappers(lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1));
485
510
 
486
- if (text[tokenStart] === "@") {
487
- return text.slice(tokenStart);
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) {