@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,841 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readdirSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const execFileAsync = promisify(execFile);
7
+ const VERSION_TIMEOUT_MS = 2500;
8
+
9
+ export type RuntimeMetadata = {
10
+ name: string;
11
+ symbol: string;
12
+ style: string;
13
+ };
14
+
15
+ export type RuntimeInfo = Pick<RuntimeMetadata, "name" | "symbol" | "style"> & {
16
+ version?: string;
17
+ };
18
+
19
+ export type RuntimeReadResult = { kind: "ok"; runtime?: RuntimeInfo } | { kind: "error" };
20
+
21
+ type RuntimeCacheEntry = {
22
+ fingerprint: string;
23
+ runtime: RuntimeInfo | undefined;
24
+ };
25
+
26
+ const RUNTIME_CACHE_MAX = 32;
27
+ const runtimeInfoCache = new Map<string, RuntimeCacheEntry>();
28
+
29
+ export function clearRuntimeInfoCache(): void {
30
+ runtimeInfoCache.clear();
31
+ }
32
+
33
+ function topLevelFingerprint(entries: readonly string[]): string {
34
+ return entries.slice().sort().join("\0");
35
+ }
36
+
37
+ function cacheRuntimeInfo(
38
+ cwd: string,
39
+ fingerprint: string,
40
+ runtime: RuntimeInfo | undefined,
41
+ ): void {
42
+ // Drop prior fingerprints for the same cwd so stale marker sets do not linger.
43
+ for (const key of runtimeInfoCache.keys()) {
44
+ if (key === cwd || key.startsWith(`${cwd}\0`)) runtimeInfoCache.delete(key);
45
+ }
46
+ const key = `${cwd}\0${fingerprint}`;
47
+ runtimeInfoCache.set(key, { fingerprint, runtime });
48
+ while (runtimeInfoCache.size > RUNTIME_CACHE_MAX) {
49
+ const oldest = runtimeInfoCache.keys().next().value;
50
+ if (oldest === undefined) break;
51
+ runtimeInfoCache.delete(oldest);
52
+ }
53
+ }
54
+
55
+ type RuntimeEnvironment = Record<string, string | undefined>;
56
+
57
+ type DetectionSpec = {
58
+ extensions?: readonly string[];
59
+ files?: readonly string[];
60
+ folders?: readonly string[];
61
+ env?: (env: RuntimeEnvironment) => boolean;
62
+ excludedFiles?: readonly string[];
63
+ };
64
+
65
+ type VersionCommand = {
66
+ command: string;
67
+ args?: readonly string[];
68
+ pattern?: RegExp;
69
+ };
70
+
71
+ type RuntimeDef = RuntimeMetadata & {
72
+ priority: number;
73
+ detect: DetectionSpec;
74
+ version: (cwd: string) => Promise<string | undefined>;
75
+ };
76
+
77
+ // --- Detection utilities ---
78
+
79
+ function hasAnyFile(cwd: string, names: readonly string[]): boolean {
80
+ return names.some((name) => existsSync(join(cwd, name)));
81
+ }
82
+
83
+ function hasAnyFolder(cwd: string, names: readonly string[]): boolean {
84
+ return names.some((name) => {
85
+ try {
86
+ return statSync(join(cwd, name)).isDirectory();
87
+ } catch {
88
+ return false;
89
+ }
90
+ });
91
+ }
92
+
93
+ function entryExtensions(entry: string): string[] {
94
+ const baseName = entry.split(/[\\/]/).pop() ?? entry;
95
+ if (!baseName || baseName.startsWith(".")) return [];
96
+
97
+ const firstDot = baseName.indexOf(".");
98
+ if (firstDot === -1) return [];
99
+
100
+ const extensions = [baseName.slice(firstDot + 1)];
101
+ const lastDot = baseName.lastIndexOf(".");
102
+ if (lastDot !== firstDot) extensions.push(baseName.slice(lastDot + 1));
103
+ return extensions;
104
+ }
105
+
106
+ function hasAnyExtension(entries: readonly string[], extensions: readonly string[]): boolean {
107
+ const extensionSet = new Set(extensions);
108
+ return entries.some((entry) =>
109
+ entryExtensions(entry).some((extension) => extensionSet.has(extension)),
110
+ );
111
+ }
112
+
113
+ function matchesDetection(
114
+ cwd: string,
115
+ entries: string[],
116
+ spec: DetectionSpec,
117
+ env: RuntimeEnvironment,
118
+ ): boolean {
119
+ if (spec.excludedFiles && hasAnyFile(cwd, spec.excludedFiles)) return false;
120
+ return Boolean(
121
+ (spec.files && hasAnyFile(cwd, spec.files)) ||
122
+ (spec.folders && hasAnyFolder(cwd, spec.folders)) ||
123
+ (spec.extensions && hasAnyExtension(entries, spec.extensions)) ||
124
+ spec.env?.(env),
125
+ );
126
+ }
127
+
128
+ // --- Version utilities ---
129
+
130
+ async function runVersion(
131
+ command: string,
132
+ args: readonly string[] = [],
133
+ cwd?: string,
134
+ ): Promise<string | undefined> {
135
+ try {
136
+ const { stdout, stderr } = await execFileAsync(command, [...args], {
137
+ cwd,
138
+ timeout: VERSION_TIMEOUT_MS,
139
+ });
140
+ const text =
141
+ `${typeof stdout === "string" ? stdout : String(stdout)}\n${typeof stderr === "string" ? stderr : String(stderr)}`.trim();
142
+ return text || undefined;
143
+ } catch {
144
+ return undefined;
145
+ }
146
+ }
147
+
148
+ function prefixVersion(version: string | undefined): string | undefined {
149
+ if (!version) return undefined;
150
+ return version.startsWith("v") ? version : `v${version}`;
151
+ }
152
+
153
+ function extractVersion(output: string | undefined, pattern?: RegExp): string | undefined {
154
+ if (!output) return undefined;
155
+ const match = output.match(
156
+ pattern ?? /(?:version\s*)?v?([0-9]+(?:\.[0-9A-Za-z][0-9A-Za-z.+_-]*)*)/i,
157
+ );
158
+ return prefixVersion(match?.[1]);
159
+ }
160
+
161
+ function versionFromCommands(
162
+ commands: readonly VersionCommand[],
163
+ ): () => Promise<string | undefined> {
164
+ return async () => {
165
+ for (const { command, args = [], pattern } of commands) {
166
+ const version = extractVersion(await runVersion(command, args), pattern);
167
+ if (version) return version;
168
+ }
169
+ return undefined;
170
+ };
171
+ }
172
+
173
+ function noVersion(): Promise<undefined> {
174
+ return Promise.resolve(undefined);
175
+ }
176
+
177
+ // --- Priority levels ---
178
+ // 10 = build-system runtimes (checked first to avoid language false-positives)
179
+ // 50 = common runtimes (bun, deno, node, python, etc.)
180
+ // 100 = default (everything else, checked in definition order)
181
+
182
+ const PRIORITY_BUILD_SYSTEM = 10;
183
+ const PRIORITY_COMMON = 50;
184
+ const PRIORITY_DEFAULT = 100;
185
+
186
+ // --- Runtime definitions ---
187
+
188
+ const runtimes: RuntimeDef[] = [
189
+ {
190
+ name: "xmake",
191
+ symbol: "",
192
+ style: "bold green",
193
+ priority: PRIORITY_BUILD_SYSTEM,
194
+ detect: { files: ["xmake.lua"] },
195
+ version: versionFromCommands([{ command: "xmake", args: ["--version"] }]),
196
+ },
197
+ {
198
+ name: "maven",
199
+ symbol: "",
200
+ style: "bold bright-cyan",
201
+ priority: PRIORITY_BUILD_SYSTEM,
202
+ detect: { files: ["pom.xml"] },
203
+ version: versionFromCommands([
204
+ { command: "mvn", args: ["--version"], pattern: /Apache Maven\s+([0-9][^\s]*)/i },
205
+ ]),
206
+ },
207
+ {
208
+ name: "gradle",
209
+ symbol: "",
210
+ style: "bold bright-cyan",
211
+ priority: PRIORITY_BUILD_SYSTEM,
212
+ detect: { files: ["build.gradle", "build.gradle.kts"], folders: ["gradle"] },
213
+ version: versionFromCommands([
214
+ { command: "gradle", args: ["--version"], pattern: /Gradle\s+([0-9][^\s]*)/i },
215
+ ]),
216
+ },
217
+ {
218
+ name: "bun",
219
+ symbol: "",
220
+ style: "bold red",
221
+ priority: PRIORITY_COMMON,
222
+ detect: { files: ["bun.lock", "bun.lockb"] },
223
+ version: async () => prefixVersion(await runVersion("bun", ["--version"])),
224
+ },
225
+ {
226
+ name: "deno",
227
+ symbol: "",
228
+ style: "green bold",
229
+ priority: PRIORITY_COMMON,
230
+ detect: { files: ["deno.json", "deno.jsonc", "deno.lock"] },
231
+ version: async () =>
232
+ extractVersion(await runVersion("deno", ["--version"]), /deno\s+([0-9][^\s]*)/i),
233
+ },
234
+ {
235
+ name: "lua",
236
+ symbol: "",
237
+ style: "bold blue",
238
+ priority: PRIORITY_COMMON,
239
+ detect: {
240
+ extensions: ["lua"],
241
+ files: [
242
+ "stylua.toml",
243
+ ".stylua.toml",
244
+ ".luarc.json",
245
+ ".luarc.jsonc",
246
+ "init.lua",
247
+ ".lua-version",
248
+ ],
249
+ folders: ["lua"],
250
+ excludedFiles: ["xmake.lua"],
251
+ },
252
+ version: async () => {
253
+ const lua = await runVersion("lua", ["-v"]);
254
+ const luaMatch = lua?.match(/Lua\s+([0-9][^\s]*)/i);
255
+ if (luaMatch?.[1]) return prefixVersion(luaMatch[1]);
256
+ const luajit = await runVersion("luajit", ["-v"]);
257
+ const luajitMatch = luajit?.match(/LuaJIT\s+([0-9][^\s]*)/i);
258
+ return prefixVersion(luajitMatch?.[1]);
259
+ },
260
+ },
261
+ {
262
+ name: "nodejs",
263
+ symbol: "",
264
+ style: "bold green",
265
+ priority: PRIORITY_COMMON,
266
+ detect: {
267
+ files: ["package.json", ".node-version", ".nvmrc"],
268
+ excludedFiles: ["bunfig.toml", "bun.lock", "bun.lockb"],
269
+ },
270
+ version: async () => prefixVersion(await runVersion("node", ["--version"])),
271
+ },
272
+ {
273
+ name: "python",
274
+ symbol: "",
275
+ style: "yellow bold",
276
+ priority: PRIORITY_COMMON,
277
+ detect: {
278
+ files: [
279
+ "requirements.txt",
280
+ ".python-version",
281
+ "pyproject.toml",
282
+ "Pipfile",
283
+ "setup.py",
284
+ "setup.cfg",
285
+ ],
286
+ },
287
+ version: async () => {
288
+ const python3 = await runVersion("python3", ["--version"]);
289
+ const python3Match = python3?.match(/Python\s+([0-9][^\s]*)/i);
290
+ if (python3Match?.[1]) return prefixVersion(python3Match[1]);
291
+ const python = await runVersion("python", ["--version"]);
292
+ const pythonMatch = python?.match(/Python\s+([0-9][^\s]*)/i);
293
+ return prefixVersion(pythonMatch?.[1]);
294
+ },
295
+ },
296
+ {
297
+ name: "golang",
298
+ symbol: "",
299
+ style: "bold cyan",
300
+ priority: PRIORITY_COMMON,
301
+ detect: { files: ["go.mod"] },
302
+ version: async () =>
303
+ extractVersion(await runVersion("go", ["version"]), /go version go([0-9][^\s]*)/i),
304
+ },
305
+ {
306
+ name: "rust",
307
+ symbol: "󱘗",
308
+ style: "bold red",
309
+ priority: PRIORITY_COMMON,
310
+ detect: { files: ["Cargo.toml"] },
311
+ version: async () =>
312
+ extractVersion(await runVersion("rustc", ["--version"]), /rustc\s+([0-9][^\s]*)/i),
313
+ },
314
+ {
315
+ name: "java",
316
+ symbol: "",
317
+ style: "red dimmed",
318
+ priority: PRIORITY_COMMON,
319
+ detect: { files: [".java-version"] },
320
+ version: async () => {
321
+ const output = await runVersion("java", ["-version"]);
322
+ const quoted = output?.match(/"([0-9][^"]*)"/);
323
+ if (quoted?.[1]) return prefixVersion(quoted[1]);
324
+ const plain = output?.match(/version\s+([0-9][^\s]*)/i);
325
+ return prefixVersion(plain?.[1]);
326
+ },
327
+ },
328
+ {
329
+ name: "ruby",
330
+ symbol: "",
331
+ style: "bold red",
332
+ priority: PRIORITY_COMMON,
333
+ detect: { files: ["Gemfile", ".ruby-version"] },
334
+ version: async () =>
335
+ extractVersion(await runVersion("ruby", ["--version"]), /ruby\s+([0-9][^\s]*)/i),
336
+ },
337
+ {
338
+ name: "php",
339
+ symbol: "",
340
+ style: "147 bold",
341
+ priority: PRIORITY_COMMON,
342
+ detect: { files: ["composer.json"] },
343
+ version: async () =>
344
+ extractVersion(await runVersion("php", ["--version"]), /PHP\s+([0-9][^\s]*)/i),
345
+ },
346
+ {
347
+ name: "buf",
348
+ symbol: "",
349
+ style: "bold blue",
350
+ priority: PRIORITY_DEFAULT,
351
+ detect: { files: ["buf.yaml", "buf.gen.yaml", "buf.work.yaml"] },
352
+ version: versionFromCommands([{ command: "buf", args: ["--version"] }]),
353
+ },
354
+ {
355
+ name: "cmake",
356
+ symbol: "",
357
+ style: "bold blue",
358
+ priority: PRIORITY_DEFAULT,
359
+ detect: { files: ["CMakeLists.txt", "CMakeCache.txt"] },
360
+ version: versionFromCommands([{ command: "cmake", args: ["--version"] }]),
361
+ },
362
+ {
363
+ name: "cpp",
364
+ symbol: "",
365
+ style: "bold 149",
366
+ priority: PRIORITY_DEFAULT,
367
+ detect: { extensions: ["cpp", "cc", "cxx", "c++", "hpp", "hh", "hxx", "h++", "tcc"] },
368
+ version: versionFromCommands([
369
+ { command: "c++", args: ["--version"] },
370
+ { command: "g++", args: ["--version"] },
371
+ { command: "clang++", args: ["--version"] },
372
+ ]),
373
+ },
374
+ {
375
+ name: "c",
376
+ symbol: "",
377
+ style: "bold 149",
378
+ priority: PRIORITY_DEFAULT,
379
+ detect: { extensions: ["c", "h"] },
380
+ version: versionFromCommands([
381
+ { command: "cc", args: ["--version"] },
382
+ { command: "gcc", args: ["--version"] },
383
+ { command: "clang", args: ["--version"] },
384
+ ]),
385
+ },
386
+ {
387
+ name: "cobol",
388
+ symbol: "",
389
+ style: "bold blue",
390
+ priority: PRIORITY_DEFAULT,
391
+ detect: { extensions: ["cbl", "cob", "CBL", "COB"] },
392
+ version: versionFromCommands([{ command: "cobc", args: ["--version"] }]),
393
+ },
394
+ {
395
+ name: "conda",
396
+ symbol: "",
397
+ style: "bold green",
398
+ priority: PRIORITY_DEFAULT,
399
+ detect: { env: (env) => Boolean(env.CONDA_DEFAULT_ENV?.trim()) && !env.PIXI_ENVIRONMENT_NAME },
400
+ version: noVersion,
401
+ },
402
+ {
403
+ name: "crystal",
404
+ symbol: "",
405
+ style: "bold red",
406
+ priority: PRIORITY_DEFAULT,
407
+ detect: { extensions: ["cr"], files: ["shard.yml"] },
408
+ version: versionFromCommands([{ command: "crystal", args: ["--version"] }]),
409
+ },
410
+ {
411
+ name: "dart",
412
+ symbol: "",
413
+ style: "bold blue",
414
+ priority: PRIORITY_DEFAULT,
415
+ detect: {
416
+ extensions: ["dart"],
417
+ files: ["pubspec.yaml", "pubspec.yml", "pubspec.lock"],
418
+ folders: [".dart_tool"],
419
+ },
420
+ version: versionFromCommands([{ command: "dart", args: ["--version"] }]),
421
+ },
422
+ {
423
+ name: "dotnet",
424
+ symbol: "",
425
+ style: "bold blue",
426
+ priority: PRIORITY_DEFAULT,
427
+ detect: {
428
+ extensions: ["csproj", "fsproj", "xproj"],
429
+ files: [
430
+ "global.json",
431
+ "project.json",
432
+ "Directory.Build.props",
433
+ "Directory.Build.targets",
434
+ "Packages.props",
435
+ ],
436
+ },
437
+ version: versionFromCommands([{ command: "dotnet", args: ["--version"] }]),
438
+ },
439
+ {
440
+ name: "elixir",
441
+ symbol: "",
442
+ style: "bold purple",
443
+ priority: PRIORITY_DEFAULT,
444
+ detect: { files: ["mix.exs"] },
445
+ version: versionFromCommands([
446
+ { command: "elixir", args: ["--version"], pattern: /Elixir\s+([0-9][^\s]*)/i },
447
+ ]),
448
+ },
449
+ {
450
+ name: "elm",
451
+ symbol: "",
452
+ style: "cyan bold",
453
+ priority: PRIORITY_DEFAULT,
454
+ detect: {
455
+ extensions: ["elm"],
456
+ files: ["elm.json", "elm-package.json", ".elm-version"],
457
+ folders: ["elm-stuff"],
458
+ },
459
+ version: versionFromCommands([{ command: "elm", args: ["--version"] }]),
460
+ },
461
+ {
462
+ name: "erlang",
463
+ symbol: "",
464
+ style: "bold red",
465
+ priority: PRIORITY_DEFAULT,
466
+ detect: { files: ["rebar.config", "erlang.mk"] },
467
+ version: versionFromCommands([{ command: "erl", args: ["-version"] }]),
468
+ },
469
+ {
470
+ name: "fennel",
471
+ symbol: "",
472
+ style: "bold green",
473
+ priority: PRIORITY_DEFAULT,
474
+ detect: { extensions: ["fnl"] },
475
+ version: versionFromCommands([{ command: "fennel", args: ["--version"] }]),
476
+ },
477
+ {
478
+ name: "fortran",
479
+ symbol: "",
480
+ style: "bold purple",
481
+ priority: PRIORITY_DEFAULT,
482
+ detect: {
483
+ extensions: [
484
+ "f",
485
+ "F",
486
+ "for",
487
+ "FOR",
488
+ "ftn",
489
+ "FTN",
490
+ "f77",
491
+ "F77",
492
+ "f90",
493
+ "F90",
494
+ "f95",
495
+ "F95",
496
+ "f03",
497
+ "F03",
498
+ "f08",
499
+ "F08",
500
+ "f18",
501
+ "F18",
502
+ ],
503
+ files: ["fpm.toml"],
504
+ },
505
+ version: versionFromCommands([
506
+ { command: "gfortran", args: ["--version"] },
507
+ { command: "flang", args: ["--version"] },
508
+ { command: "flang-new", args: ["--version"] },
509
+ ]),
510
+ },
511
+ {
512
+ name: "gleam",
513
+ symbol: "",
514
+ style: "bold #FFAFF3",
515
+ priority: PRIORITY_DEFAULT,
516
+ detect: { extensions: ["gleam"], files: ["gleam.toml"] },
517
+ version: versionFromCommands([{ command: "gleam", args: ["--version"] }]),
518
+ },
519
+ {
520
+ name: "guix_shell",
521
+ symbol: "",
522
+ style: "yellow bold",
523
+ priority: PRIORITY_DEFAULT,
524
+ detect: { env: (env) => Boolean(env.GUIX_ENVIRONMENT?.trim()) },
525
+ version: noVersion,
526
+ },
527
+ {
528
+ name: "haskell",
529
+ symbol: "",
530
+ style: "bold purple",
531
+ priority: PRIORITY_DEFAULT,
532
+ detect: { extensions: ["hs", "cabal", "hs-boot"], files: ["stack.yaml", "cabal.project"] },
533
+ version: versionFromCommands([{ command: "ghc", args: ["--numeric-version"] }]),
534
+ },
535
+ {
536
+ name: "haxe",
537
+ symbol: "",
538
+ style: "bold fg:202",
539
+ priority: PRIORITY_DEFAULT,
540
+ detect: {
541
+ extensions: ["hx", "hxml"],
542
+ files: ["haxelib.json", "hxformat.json", ".haxerc"],
543
+ folders: [".haxelib", "haxe_libraries"],
544
+ },
545
+ version: versionFromCommands([{ command: "haxe", args: ["--version"] }]),
546
+ },
547
+ {
548
+ name: "helm",
549
+ symbol: "",
550
+ style: "bold white",
551
+ priority: PRIORITY_DEFAULT,
552
+ detect: { files: ["helmfile.yaml", "Chart.yaml"] },
553
+ version: versionFromCommands([{ command: "helm", args: ["version", "--short"] }]),
554
+ },
555
+ {
556
+ name: "julia",
557
+ symbol: "",
558
+ style: "bold purple",
559
+ priority: PRIORITY_DEFAULT,
560
+ detect: { extensions: ["jl"], files: ["Project.toml", "Manifest.toml"] },
561
+ version: versionFromCommands([{ command: "julia", args: ["--version"] }]),
562
+ },
563
+ {
564
+ name: "kotlin",
565
+ symbol: "",
566
+ style: "bold blue",
567
+ priority: PRIORITY_DEFAULT,
568
+ detect: { extensions: ["kt", "kts"] },
569
+ version: versionFromCommands([{ command: "kotlin", args: ["-version"] }]),
570
+ },
571
+ {
572
+ name: "meson",
573
+ symbol: "󰔷",
574
+ style: "blue bold",
575
+ priority: PRIORITY_DEFAULT,
576
+ detect: { env: (env) => env.MESON_DEVENV === "1" && Boolean(env.MESON_PROJECT_NAME?.trim()) },
577
+ version: noVersion,
578
+ },
579
+ {
580
+ name: "mojo",
581
+ symbol: "󰈸",
582
+ style: "bold 208",
583
+ priority: PRIORITY_DEFAULT,
584
+ detect: { extensions: ["mojo", "🔥"] },
585
+ version: versionFromCommands([{ command: "mojo", args: ["--version"] }]),
586
+ },
587
+ {
588
+ name: "nim",
589
+ symbol: "",
590
+ style: "bold yellow",
591
+ priority: PRIORITY_DEFAULT,
592
+ detect: { extensions: ["nim", "nims", "nimble"], files: ["nim.cfg"] },
593
+ version: versionFromCommands([{ command: "nim", args: ["--version"] }]),
594
+ },
595
+ {
596
+ name: "nix_shell",
597
+ symbol: "",
598
+ style: "bold blue",
599
+ priority: PRIORITY_DEFAULT,
600
+ detect: { env: (env) => env.IN_NIX_SHELL === "pure" || env.IN_NIX_SHELL === "impure" },
601
+ version: noVersion,
602
+ },
603
+ {
604
+ name: "ocaml",
605
+ symbol: "",
606
+ style: "bold yellow",
607
+ priority: PRIORITY_DEFAULT,
608
+ detect: {
609
+ extensions: ["opam", "ml", "mli", "re", "rei"],
610
+ files: ["dune", "dune-project", "jbuild", "jbuild-ignore", ".merlin"],
611
+ folders: ["_opam", "esy.lock"],
612
+ },
613
+ version: versionFromCommands([{ command: "ocaml", args: ["-version"] }]),
614
+ },
615
+ {
616
+ name: "odin",
617
+ symbol: "󰟢",
618
+ style: "bold bright-blue",
619
+ priority: PRIORITY_DEFAULT,
620
+ detect: { extensions: ["odin"] },
621
+ version: versionFromCommands([{ command: "odin", args: ["version"] }]),
622
+ },
623
+ {
624
+ name: "opa",
625
+ symbol: "",
626
+ style: "bold blue",
627
+ priority: PRIORITY_DEFAULT,
628
+ detect: { extensions: ["rego"] },
629
+ version: versionFromCommands([{ command: "opa", args: ["version"] }]),
630
+ },
631
+ {
632
+ name: "perl",
633
+ symbol: "",
634
+ style: "bold 149",
635
+ priority: PRIORITY_DEFAULT,
636
+ detect: {
637
+ extensions: ["pl", "pm", "pod"],
638
+ files: [
639
+ "Makefile.PL",
640
+ "Build.PL",
641
+ "cpanfile",
642
+ "cpanfile.snapshot",
643
+ "META.json",
644
+ "META.yml",
645
+ ".perl-version",
646
+ ],
647
+ },
648
+ version: versionFromCommands([{ command: "perl", args: ["--version"] }]),
649
+ },
650
+ {
651
+ name: "pixi",
652
+ symbol: "󰏗",
653
+ style: "yellow bold",
654
+ priority: PRIORITY_DEFAULT,
655
+ detect: {
656
+ files: ["pixi.toml", "pixi.lock"],
657
+ env: (env) => Boolean(env.PIXI_ENVIRONMENT_NAME?.trim()),
658
+ },
659
+ version: versionFromCommands([{ command: "pixi", args: ["--version"] }]),
660
+ },
661
+ {
662
+ name: "pulumi",
663
+ symbol: "",
664
+ style: "bold 5",
665
+ priority: PRIORITY_DEFAULT,
666
+ detect: { files: ["Pulumi.yaml", "Pulumi.yml"] },
667
+ version: versionFromCommands([{ command: "pulumi", args: ["version"] }]),
668
+ },
669
+ {
670
+ name: "purescript",
671
+ symbol: "",
672
+ style: "bold white",
673
+ priority: PRIORITY_DEFAULT,
674
+ detect: { extensions: ["purs"], files: ["spago.dhall", "spago.yaml", "spago.lock"] },
675
+ version: versionFromCommands([{ command: "purs", args: ["--version"] }]),
676
+ },
677
+ {
678
+ name: "raku",
679
+ symbol: "󱖊",
680
+ style: "bold 149",
681
+ priority: PRIORITY_DEFAULT,
682
+ detect: { extensions: ["p6", "pm6", "pod6", "raku", "rakumod"], files: ["META6.json"] },
683
+ version: versionFromCommands([{ command: "raku", args: ["--version"] }]),
684
+ },
685
+ {
686
+ name: "red",
687
+ symbol: "󱍼",
688
+ style: "red bold",
689
+ priority: PRIORITY_DEFAULT,
690
+ detect: { extensions: ["red", "reds"] },
691
+ version: versionFromCommands([{ command: "red", args: ["--version"] }]),
692
+ },
693
+ {
694
+ name: "rlang",
695
+ symbol: "󰟔",
696
+ style: "blue bold",
697
+ priority: PRIORITY_DEFAULT,
698
+ detect: {
699
+ extensions: ["R", "Rd", "Rmd", "Rproj", "Rsx"],
700
+ files: ["DESCRIPTION"],
701
+ folders: [".Rproj.user"],
702
+ },
703
+ version: versionFromCommands([{ command: "R", args: ["--version"] }]),
704
+ },
705
+ {
706
+ name: "scala",
707
+ symbol: "",
708
+ style: "red dimmed",
709
+ priority: PRIORITY_DEFAULT,
710
+ detect: {
711
+ extensions: ["sbt", "scala"],
712
+ files: [".scalaenv", ".sbtenv", "build.sbt"],
713
+ folders: [".metals"],
714
+ },
715
+ version: versionFromCommands([{ command: "scala", args: ["-version"] }]),
716
+ },
717
+ {
718
+ name: "solidity",
719
+ symbol: "",
720
+ style: "bold blue",
721
+ priority: PRIORITY_DEFAULT,
722
+ detect: { extensions: ["sol"] },
723
+ version: versionFromCommands([{ command: "solc", args: ["--version"] }]),
724
+ },
725
+ {
726
+ name: "spack",
727
+ symbol: "",
728
+ style: "bold blue",
729
+ priority: PRIORITY_DEFAULT,
730
+ detect: { env: (env) => Boolean(env.SPACK_ENV?.trim()) },
731
+ version: noVersion,
732
+ },
733
+ {
734
+ name: "swift",
735
+ symbol: "",
736
+ style: "bold 202",
737
+ priority: PRIORITY_DEFAULT,
738
+ detect: { extensions: ["swift"], files: ["Package.swift"] },
739
+ version: versionFromCommands([
740
+ { command: "swift", args: ["--version"], pattern: /Swift version\s+([0-9][^\s]*)/i },
741
+ ]),
742
+ },
743
+ {
744
+ name: "terraform",
745
+ symbol: "",
746
+ style: "bold 105",
747
+ priority: PRIORITY_DEFAULT,
748
+ detect: { extensions: ["tf", "tfplan", "tfstate"], folders: [".terraform"] },
749
+ version: versionFromCommands([
750
+ { command: "terraform", args: ["version"] },
751
+ { command: "tofu", args: ["version"] },
752
+ ]),
753
+ },
754
+ {
755
+ name: "typst",
756
+ symbol: "",
757
+ style: "bold #0093A7",
758
+ priority: PRIORITY_DEFAULT,
759
+ detect: { extensions: ["typ"], files: ["template.typ"] },
760
+ version: versionFromCommands([{ command: "typst", args: ["--version"] }]),
761
+ },
762
+ {
763
+ name: "vagrant",
764
+ symbol: "",
765
+ style: "cyan bold",
766
+ priority: PRIORITY_DEFAULT,
767
+ detect: { files: ["Vagrantfile"] },
768
+ version: versionFromCommands([{ command: "vagrant", args: ["--version"] }]),
769
+ },
770
+ {
771
+ name: "vlang",
772
+ symbol: "",
773
+ style: "blue bold",
774
+ priority: PRIORITY_DEFAULT,
775
+ detect: { extensions: ["v"], files: ["v.mod", "vpkg.json", ".vpkg-lock.json"] },
776
+ version: versionFromCommands([{ command: "v", args: ["version"] }]),
777
+ },
778
+ {
779
+ name: "zig",
780
+ symbol: "",
781
+ style: "bold yellow",
782
+ priority: PRIORITY_DEFAULT,
783
+ detect: { extensions: ["zig"], files: ["build.zig"] },
784
+ version: versionFromCommands([{ command: "zig", args: ["version"] }]),
785
+ },
786
+ ];
787
+
788
+ // Sort once at module load — stable sort preserves definition order within same priority
789
+ const sortedRuntimes = [...runtimes].sort((a, b) => a.priority - b.priority);
790
+
791
+ export const runtimeMetadata: RuntimeMetadata[] = runtimes.map(({ name, symbol, style }) => ({
792
+ name,
793
+ symbol,
794
+ style,
795
+ }));
796
+
797
+ export function detectRuntime(
798
+ cwd: string,
799
+ entries: string[],
800
+ env: RuntimeEnvironment = process.env,
801
+ ): RuntimeDef | undefined {
802
+ for (const runtime of sortedRuntimes) {
803
+ if (matchesDetection(cwd, entries, runtime.detect, env)) return runtime;
804
+ }
805
+ return undefined;
806
+ }
807
+
808
+ export async function readRuntimeInfo(cwd: string): Promise<RuntimeReadResult> {
809
+ let entries: string[];
810
+ try {
811
+ entries = readdirSync(cwd);
812
+ } catch {
813
+ // readdir failure is treated as a transient error so last-good can be kept.
814
+ return { kind: "error" };
815
+ }
816
+
817
+ const fingerprint = topLevelFingerprint(entries);
818
+ const cacheKey = `${cwd}\0${fingerprint}`;
819
+ const cached = runtimeInfoCache.get(cacheKey);
820
+ if (cached && cached.fingerprint === fingerprint) {
821
+ return { kind: "ok", runtime: cached.runtime };
822
+ }
823
+
824
+ try {
825
+ const runtime = detectRuntime(cwd, entries);
826
+ if (!runtime) {
827
+ cacheRuntimeInfo(cwd, fingerprint, undefined);
828
+ return { kind: "ok", runtime: undefined };
829
+ }
830
+ const info: RuntimeInfo = {
831
+ name: runtime.name,
832
+ symbol: runtime.symbol,
833
+ style: runtime.style,
834
+ version: await runtime.version(cwd),
835
+ };
836
+ cacheRuntimeInfo(cwd, fingerprint, info);
837
+ return { kind: "ok", runtime: info };
838
+ } catch {
839
+ return { kind: "error" };
840
+ }
841
+ }