@alisio/alisio-code 0.1.0-alpha.9 → 0.1.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 (56) hide show
  1. package/README.md +95 -16
  2. package/dist/analysis.d.ts +27 -0
  3. package/dist/analysis.js +145 -0
  4. package/dist/banner.js +2 -1
  5. package/dist/builtin.d.ts +3 -1
  6. package/dist/builtin.js +0 -24
  7. package/dist/main.js +133 -6
  8. package/dist/serve.d.ts +15 -0
  9. package/dist/serve.js +99 -0
  10. package/dist/tui/agent-manager.d.ts +105 -0
  11. package/dist/tui/agent-manager.js +269 -0
  12. package/dist/tui/agents.d.ts +5 -0
  13. package/dist/tui/agents.js +5 -0
  14. package/dist/tui/app.d.ts +42 -1
  15. package/dist/tui/app.js +1895 -97
  16. package/dist/tui/artifact-preview.d.ts +52 -0
  17. package/dist/tui/artifact-preview.js +212 -0
  18. package/dist/tui/artifacts.d.ts +54 -0
  19. package/dist/tui/artifacts.js +207 -0
  20. package/dist/tui/btw.d.ts +57 -0
  21. package/dist/tui/btw.js +76 -0
  22. package/dist/tui/components.d.ts +174 -12
  23. package/dist/tui/components.js +962 -66
  24. package/dist/tui/connect-input.d.ts +2 -0
  25. package/dist/tui/connect-input.js +1 -1
  26. package/dist/tui/exit.d.ts +10 -0
  27. package/dist/tui/exit.js +15 -0
  28. package/dist/tui/git-branch.d.ts +48 -0
  29. package/dist/tui/git-branch.js +99 -0
  30. package/dist/tui/goal.d.ts +75 -0
  31. package/dist/tui/goal.js +171 -0
  32. package/dist/tui/modes.d.ts +71 -0
  33. package/dist/tui/modes.js +90 -0
  34. package/dist/tui/open-path.d.ts +38 -0
  35. package/dist/tui/open-path.js +80 -0
  36. package/dist/tui/plan-review.d.ts +48 -0
  37. package/dist/tui/plan-review.js +76 -0
  38. package/dist/tui/queue.d.ts +2 -0
  39. package/dist/tui/queue.js +5 -1
  40. package/dist/tui/scroll.d.ts +8 -0
  41. package/dist/tui/scroll.js +12 -0
  42. package/dist/tui/settings-menu.d.ts +40 -0
  43. package/dist/tui/settings-menu.js +132 -0
  44. package/dist/tui/settings.d.ts +194 -0
  45. package/dist/tui/settings.js +482 -0
  46. package/dist/tui/state.d.ts +343 -7
  47. package/dist/tui/state.js +823 -54
  48. package/dist/tui/tasks.d.ts +123 -0
  49. package/dist/tui/tasks.js +369 -0
  50. package/dist/tui/theme.d.ts +42 -1
  51. package/dist/tui/theme.js +516 -1
  52. package/dist/tui/viewer-state.d.ts +7 -0
  53. package/dist/tui/viewer-state.js +36 -0
  54. package/dist/version.d.ts +7 -5
  55. package/dist/version.js +44 -8
  56. package/package.json +7 -9
package/dist/tui/theme.js CHANGED
@@ -17,8 +17,521 @@ export const style = {
17
17
  userBg: sgr("48;5;236", 49),
18
18
  };
19
19
  export const levelColor = (level) => level === "ok" ? style.green : level === "warn" ? style.yellow : style.red;
20
+ const TS_KEYWORDS = new Set([
21
+ "abstract",
22
+ "as",
23
+ "async",
24
+ "await",
25
+ "break",
26
+ "case",
27
+ "catch",
28
+ "class",
29
+ "const",
30
+ "constructor",
31
+ "continue",
32
+ "debugger",
33
+ "declare",
34
+ "default",
35
+ "delete",
36
+ "do",
37
+ "else",
38
+ "enum",
39
+ "export",
40
+ "extends",
41
+ "false",
42
+ "finally",
43
+ "for",
44
+ "from",
45
+ "function",
46
+ "get",
47
+ "if",
48
+ "implements",
49
+ "import",
50
+ "in",
51
+ "instanceof",
52
+ "interface",
53
+ "is",
54
+ "keyof",
55
+ "let",
56
+ "namespace",
57
+ "new",
58
+ "never",
59
+ "null",
60
+ "of",
61
+ "override",
62
+ "private",
63
+ "protected",
64
+ "public",
65
+ "readonly",
66
+ "return",
67
+ "satisfies",
68
+ "set",
69
+ "static",
70
+ "super",
71
+ "switch",
72
+ "this",
73
+ "throw",
74
+ "true",
75
+ "try",
76
+ "type",
77
+ "typeof",
78
+ "unknown",
79
+ "using",
80
+ "var",
81
+ "void",
82
+ "while",
83
+ "with",
84
+ "yield",
85
+ ]);
86
+ const PYTHON_KEYWORDS = new Set([
87
+ "and",
88
+ "as",
89
+ "assert",
90
+ "async",
91
+ "await",
92
+ "break",
93
+ "case",
94
+ "class",
95
+ "continue",
96
+ "def",
97
+ "del",
98
+ "elif",
99
+ "else",
100
+ "except",
101
+ "False",
102
+ "finally",
103
+ "for",
104
+ "from",
105
+ "global",
106
+ "if",
107
+ "import",
108
+ "in",
109
+ "is",
110
+ "lambda",
111
+ "match",
112
+ "None",
113
+ "nonlocal",
114
+ "not",
115
+ "or",
116
+ "pass",
117
+ "raise",
118
+ "return",
119
+ "True",
120
+ "try",
121
+ "while",
122
+ "with",
123
+ "yield",
124
+ ]);
125
+ const SHELL_KEYWORDS = new Set([
126
+ "alias",
127
+ "break",
128
+ "case",
129
+ "continue",
130
+ "do",
131
+ "done",
132
+ "elif",
133
+ "else",
134
+ "esac",
135
+ "exit",
136
+ "export",
137
+ "fi",
138
+ "for",
139
+ "function",
140
+ "if",
141
+ "in",
142
+ "local",
143
+ "readonly",
144
+ "return",
145
+ "select",
146
+ "set",
147
+ "shift",
148
+ "source",
149
+ "then",
150
+ "time",
151
+ "true",
152
+ "false",
153
+ "until",
154
+ "while",
155
+ ]);
156
+ const CSS_KEYWORDS = new Set([
157
+ "@charset",
158
+ "@font-face",
159
+ "@import",
160
+ "@keyframes",
161
+ "@layer",
162
+ "@media",
163
+ "@namespace",
164
+ "@page",
165
+ "@supports",
166
+ "important",
167
+ ]);
168
+ /** Standalone words highlighted in HTML (tags themselves are matched structurally). */
169
+ const HTML_WORDS = new Set([]);
170
+ /** Rules used for TS/JS and as the fallback for unknown language tags. */
171
+ const TS_RULES = {
172
+ keywords: TS_KEYWORDS,
173
+ lineComment: "//",
174
+ blockComment: ["/*", "*/"],
175
+ strings: ['"', "'", "`"],
176
+ };
177
+ const LANG_RULES = {
178
+ ts: TS_RULES,
179
+ js: TS_RULES,
180
+ json: {
181
+ keywords: new Set(["true", "false", "null"]),
182
+ lineComment: "//",
183
+ blockComment: ["/*", "*/"],
184
+ strings: ['"', "'"],
185
+ keyString: true,
186
+ },
187
+ python: {
188
+ keywords: PYTHON_KEYWORDS,
189
+ lineComment: "#",
190
+ strings: ['"', "'"],
191
+ triple: ['"""', "'''"],
192
+ defNames: true,
193
+ decorators: true,
194
+ prefixedStrings: true,
195
+ },
196
+ bash: {
197
+ keywords: SHELL_KEYWORDS,
198
+ lineComment: "#",
199
+ strings: ['"', "'"],
200
+ dollarVars: true,
201
+ eqKey: true,
202
+ },
203
+ sh: {
204
+ keywords: SHELL_KEYWORDS,
205
+ lineComment: "#",
206
+ strings: ['"', "'"],
207
+ dollarVars: true,
208
+ eqKey: true,
209
+ },
210
+ yaml: {
211
+ keywords: new Set(["true", "false", "yes", "no", "on", "off", "null"]),
212
+ lineComment: "#",
213
+ strings: ['"', "'"],
214
+ colonKey: true,
215
+ },
216
+ css: {
217
+ keywords: CSS_KEYWORDS,
218
+ blockComment: ["/*", "*/"],
219
+ strings: ['"', "'"],
220
+ colonKey: true,
221
+ hexColors: true,
222
+ atWord: true,
223
+ },
224
+ html: {
225
+ keywords: HTML_WORDS,
226
+ blockComment: ["<!--", "-->"],
227
+ strings: ['"', "'"],
228
+ html: true,
229
+ eqKey: true,
230
+ hexColors: true,
231
+ },
232
+ md: { keywords: new Set(), strings: ["`"], headings: true },
233
+ };
234
+ const LANG_ALIASES = {
235
+ typescript: "ts",
236
+ tsx: "ts",
237
+ javascript: "js",
238
+ jsx: "js",
239
+ node: "js",
240
+ jsonc: "json",
241
+ json5: "json",
242
+ shell: "bash",
243
+ zsh: "bash",
244
+ python3: "python",
245
+ py: "python",
246
+ yml: "yaml",
247
+ markdown: "md",
248
+ };
249
+ /** Unknown or missing language tags fall back to TypeScript rules (closest to plain code). */
250
+ const rulesFor = (lang) => {
251
+ const base = (lang ?? "")
252
+ .trim()
253
+ .toLowerCase()
254
+ .split(/[\s.]+/)[0] ?? "";
255
+ return LANG_RULES[LANG_ALIASES[base] ?? base] ?? TS_RULES;
256
+ };
257
+ /** Strips ANSI escapes, OSC sequences and control characters; normalizes tabs. */
258
+ export function sanitizeCode(code) {
259
+ return code
260
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
261
+ .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "")
262
+ .replace(/\x1b[()][0-9A-Za-z]/g, "")
263
+ .replace(/\x1b/g, "")
264
+ .replace(/[\u0000-\u0008\u000B\u000C\u000D\u000E-\u001F\u007F]/g, "")
265
+ .replace(/\t/g, " ");
266
+ }
267
+ function scanLine(line, rules, state) {
268
+ const out = [];
269
+ const len = line.length;
270
+ const push = (text, style) => {
271
+ if (text)
272
+ out.push({ text, style });
273
+ };
274
+ /** Finds the unescaped closing delimiter of a string starting at `from`. */
275
+ const scanString = (from, delimiter) => {
276
+ let j = from;
277
+ while (j < len) {
278
+ if (line[j] === "\\") {
279
+ j += 2;
280
+ continue;
281
+ }
282
+ if (line.startsWith(delimiter, j))
283
+ return { end: j + delimiter.length, closed: true };
284
+ j++;
285
+ }
286
+ return { end: len, closed: false };
287
+ };
288
+ let i = 0;
289
+ // Continue a multi-line string opened on a previous line.
290
+ if (state.inString) {
291
+ const end = line.indexOf(state.inString);
292
+ if (end < 0) {
293
+ push(line, "string");
294
+ return out;
295
+ }
296
+ push(line.slice(0, end + state.inString.length), "string");
297
+ i = end + state.inString.length;
298
+ state.inString = undefined;
299
+ }
300
+ // Continue a block comment opened on a previous line.
301
+ if (state.inComment) {
302
+ const end = line.indexOf(state.inComment);
303
+ if (end < 0) {
304
+ push(line, "comment");
305
+ return out;
306
+ }
307
+ push(line.slice(0, end + state.inComment.length), "comment");
308
+ i = end + state.inComment.length;
309
+ state.inComment = undefined;
310
+ }
311
+ while (i < len) {
312
+ const rem = line.slice(i);
313
+ // Markdown headings: color the `#` marker run (approximate; only at the start of a line).
314
+ if (rules.headings && /^#{1,6}[ \t]/.test(rem)) {
315
+ const m = /^#{1,6}/.exec(rem);
316
+ push(m?.[0] ?? "", "keyword");
317
+ push(rem.slice(m?.[0].length ?? 0), "plain");
318
+ break;
319
+ }
320
+ const ws = /^ +/.exec(rem);
321
+ if (ws) {
322
+ push(ws[0], "plain");
323
+ i += ws[0].length;
324
+ continue;
325
+ }
326
+ if (rules.triple) {
327
+ const delim = rules.triple.find((d) => rem.startsWith(d));
328
+ if (delim) {
329
+ const end = line.indexOf(delim, i + delim.length);
330
+ if (end >= 0) {
331
+ push(line.slice(i, end + delim.length), "string");
332
+ i = end + delim.length;
333
+ }
334
+ else {
335
+ push(line.slice(i), "string");
336
+ state.inString = delim;
337
+ i = len;
338
+ }
339
+ continue;
340
+ }
341
+ }
342
+ // Escape-aware quoted strings, with optional Python-style prefixes (`f"..."`, `r"..."`).
343
+ const anyQuote = () => {
344
+ for (const d of rules.strings) {
345
+ if (rem.startsWith(d)) {
346
+ const scan = scanString(i + d.length, d);
347
+ return { quote: d, from: i, closed: scan.closed, end: scan.end };
348
+ }
349
+ }
350
+ if (rules.prefixedStrings) {
351
+ const pm = /^(?:[fF][rR]?|[rR][fF]?|[bB]|[uU])(["'])/.exec(rem);
352
+ if (pm?.[1] && rules.strings.includes(pm[1])) {
353
+ const scan = scanString(i + pm[0].length, pm[1]);
354
+ return { quote: pm[1], from: i, closed: scan.closed, end: scan.end };
355
+ }
356
+ }
357
+ return undefined;
358
+ };
359
+ const str = anyQuote();
360
+ if (str) {
361
+ const key = rules.keyString && /^\s*:/.test(line.slice(str.end));
362
+ push(line.slice(str.from, str.end), key ? "property" : "string");
363
+ i = str.end;
364
+ if (!str.closed && str.quote === "`")
365
+ state.inString = str.quote;
366
+ continue;
367
+ }
368
+ if (rules.lineComment && rem.startsWith(rules.lineComment)) {
369
+ push(rem, "comment");
370
+ break;
371
+ }
372
+ if (rules.blockComment && rem.startsWith(rules.blockComment[0])) {
373
+ const end = line.indexOf(rules.blockComment[1], i + rules.blockComment[0].length);
374
+ if (end >= 0) {
375
+ push(line.slice(i, end + rules.blockComment[1].length), "comment");
376
+ i = end + rules.blockComment[1].length;
377
+ }
378
+ else {
379
+ push(line.slice(i), "comment");
380
+ state.inComment = rules.blockComment[1];
381
+ i = len;
382
+ }
383
+ continue;
384
+ }
385
+ if (rules.dollarVars && rem.startsWith("$")) {
386
+ const m = /^\$(?:\{[^}]*\}|\d+|[A-Za-z_][\w]*)/.exec(rem);
387
+ if (m) {
388
+ push(m[0], "number");
389
+ i += m[0].length;
390
+ continue;
391
+ }
392
+ }
393
+ if (rules.decorators && rem.startsWith("@")) {
394
+ const m = /^@[A-Za-z_][\w]*/.exec(rem);
395
+ if (m) {
396
+ push("@", "plain");
397
+ push(m[0].slice(1), "function");
398
+ i += m[0].length;
399
+ continue;
400
+ }
401
+ }
402
+ if (rules.atWord && rem.startsWith("@")) {
403
+ const m = /^@[A-Za-z-][\w-]*/.exec(rem);
404
+ if (m) {
405
+ push(m[0], "keyword");
406
+ i += m[0].length;
407
+ continue;
408
+ }
409
+ }
410
+ if (rules.html) {
411
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rem);
412
+ if (tag && tag[0].length > 1) {
413
+ push(tag[0], "keyword");
414
+ i += tag[0].length;
415
+ continue;
416
+ }
417
+ }
418
+ if (rules.hexColors && rem.startsWith("#")) {
419
+ const m = /^#[0-9a-fA-F]{3,8}\b/.exec(rem);
420
+ if (m) {
421
+ push(m[0], "number");
422
+ i += m[0].length;
423
+ continue;
424
+ }
425
+ }
426
+ const num = /^(?:0[xX][0-9a-fA-F]+|0[bB][01]+|\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(rem);
427
+ if (num) {
428
+ push(num[0], "number");
429
+ i += num[0].length;
430
+ continue;
431
+ }
432
+ const word = /^[A-Za-z_$][\w$]*/.exec(rem);
433
+ if (word) {
434
+ const w = word[0];
435
+ const rest = line.slice(i + w.length);
436
+ const prevWord = out.at(-1)?.text;
437
+ if (rules.keywords.has(w))
438
+ push(w, "keyword");
439
+ else if (rules.defNames && (prevWord === "def" || prevWord === "class"))
440
+ push(w, "function");
441
+ else if (/^\(/.test(rest))
442
+ push(w, "function");
443
+ else if (rules.eqKey && /^=/.test(rest))
444
+ push(w, "property");
445
+ else if (rules.colonKey && /^:/.test(rest))
446
+ push(w, "property");
447
+ else
448
+ push(w, "plain");
449
+ i += w.length;
450
+ continue;
451
+ }
452
+ push(line[i] ?? "", "plain");
453
+ i++;
454
+ }
455
+ return out;
456
+ }
457
+ /** Tokenizes code into styled segments, maintaining multi-line string/comment state. */
458
+ export function tokenizeCode(code, lang) {
459
+ const rules = rulesFor(lang);
460
+ const state = {};
461
+ return sanitizeCode(code)
462
+ .split("\n")
463
+ .map((line) => scanLine(line, rules, state));
464
+ }
465
+ const TOKEN_PAINT = {
466
+ keyword: (t) => style.bold(style.magenta(t)),
467
+ string: (t) => style.green(t),
468
+ comment: (t) => style.gray(style.italic(t)),
469
+ number: (t) => style.yellow(t),
470
+ function: (t) => style.cyan(t),
471
+ property: (t) => style.brightCyan(t),
472
+ plain: (t) => t,
473
+ };
474
+ /**
475
+ * Per-line highlighting for markdown code blocks (pi-tui `MarkdownTheme.highlightCode`).
476
+ * Returns one styled (or plain, under NO_COLOR) line per source line; never emits ANSI for
477
+ * bytes that came from the input.
478
+ */
479
+ export function highlightCode(code, lang) {
480
+ const lines = tokenizeCode(code, lang);
481
+ if (process.env.NO_COLOR !== undefined)
482
+ return lines.map((tokens) => tokens.map((t) => t.text).join(""));
483
+ return lines.map((tokens) => tokens.map((t) => (t.style === "plain" ? t.text : TOKEN_PAINT[t.style](t.text))).join(""));
484
+ }
485
+ /**
486
+ * Normalizes table separator rows so models' sloppy ASCII art still renders as aligned tables:
487
+ * em/en/box dashes become plain `-` runs (marked's table tokenizer only accepts `-`), while
488
+ * alignment colons are preserved. Anything that is not an obvious separator row, including
489
+ * fenced or indented code, is left untouched. A bare `---` (horizontal rule / setext) has no
490
+ * `|` and is never touched.
491
+ */
492
+ export function normalizeTableSeparators(markdown) {
493
+ const out = [];
494
+ let fence;
495
+ for (const line of markdown.split("\n")) {
496
+ const fenceMatch = /^ {0,3}(`{3,}|~{3,})/.exec(line);
497
+ if (fenceMatch) {
498
+ const marker = fenceMatch[1]?.[0] ?? "";
499
+ if (!fence)
500
+ fence = marker;
501
+ else if (fenceMatch[1]?.startsWith(fence))
502
+ fence = undefined;
503
+ out.push(line);
504
+ continue;
505
+ }
506
+ if (!fence && isTableSeparator(line)) {
507
+ out.push(line.replace(/(:)?[-—–─]+(:)?/g, (_m, left, right) => `${left ?? ""}---${right ?? ""}`));
508
+ continue;
509
+ }
510
+ out.push(line);
511
+ }
512
+ return out.join("\n");
513
+ }
514
+ function isTableSeparator(line) {
515
+ if (!line.includes("|") || /^ {4}/.test(line))
516
+ return false;
517
+ const cells = line.split("|");
518
+ let dashes = 0;
519
+ for (const cell of cells) {
520
+ const c = cell.trim();
521
+ if (c === "")
522
+ continue;
523
+ if (!/^:?[-—–─]+:?$/.test(c))
524
+ return false;
525
+ dashes++;
526
+ }
527
+ return dashes > 0;
528
+ }
529
+ /** Pre-parse hook for the Markdown component: normalizes table separators before marked parses. */
530
+ export const markdownTransform = (markdown, _availableWidth) => normalizeTableSeparators(markdown);
531
+ /** Subtle base tone for response body text; inline styles override it. */
532
+ export const markdownDefaultTextStyle = { color: (t) => style.gray(t) };
20
533
  export const markdownTheme = {
21
- heading: (t) => style.bold(style.cyan(t)),
534
+ heading: (t) => style.bold(style.brightCyan(t)),
22
535
  link: (t) => style.underline(style.blue(t)),
23
536
  linkUrl: (t) => style.dim(t),
24
537
  code: (t) => style.yellow(t),
@@ -32,6 +545,8 @@ export const markdownTheme = {
32
545
  italic: (t) => style.italic(t),
33
546
  strikethrough: (t) => style.strike(t),
34
547
  underline: (t) => style.underline(t),
548
+ highlightCode,
549
+ codeBlockIndent: " ",
35
550
  };
36
551
  export const selectListTheme = {
37
552
  selectedPrefix: (t) => style.cyan(t),
@@ -0,0 +1,7 @@
1
+ export interface ViewerState {
2
+ lastSeenVersion?: string;
3
+ }
4
+ export declare const viewerStateFile: (stateDir: string) => string;
5
+ export declare function readViewerState(stateDir: string): Promise<ViewerState>;
6
+ /** Merges `patch` into the stored state with an atomic replace; returns whether it was saved. */
7
+ export declare function writeViewerState(stateDir: string, patch: Partial<ViewerState>): Promise<boolean>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Per-viewer state of the terminal UI that is not configuration: the last version whose changelog
3
+ * the user has been told about (`lastSeenVersion`). Stored next to the session database as
4
+ * `tui-state.json`; every failure (read-only disk, corrupt file) is swallowed, because a missing
5
+ * notice must never break the terminal.
6
+ */
7
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
8
+ import { dirname, join } from "node:path";
9
+ export const viewerStateFile = (stateDir) => join(stateDir, "tui-state.json");
10
+ export async function readViewerState(stateDir) {
11
+ try {
12
+ const parsed = JSON.parse(await readFile(viewerStateFile(stateDir), "utf8"));
13
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
14
+ return {};
15
+ const version = parsed.lastSeenVersion;
16
+ return typeof version === "string" && version.length <= 100 ? { lastSeenVersion: version } : {};
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ }
22
+ /** Merges `patch` into the stored state with an atomic replace; returns whether it was saved. */
23
+ export async function writeViewerState(stateDir, patch) {
24
+ try {
25
+ const file = viewerStateFile(stateDir);
26
+ const next = { ...(await readViewerState(stateDir)), ...patch };
27
+ await mkdir(dirname(file), { recursive: true });
28
+ const temporary = `${file}.${process.pid}.tmp`;
29
+ await writeFile(temporary, `${JSON.stringify(next)}\n`, { mode: 0o600 });
30
+ await rename(temporary, file);
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
package/dist/version.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Reads the package version at runtime so --version, doctor, plugin metadata and MCP client
3
- * metadata stay in sync with the published package without a build-time constant to update.
4
- * The manifest is resolved relative to the module (`src/version.ts` → `../package.json` in the
5
- * source tree; `dist/version.js` → `../package.json` of the installed package). Standalone
6
- * binaries inject the version at build time via ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
2
+ * Reads the package version at runtime so --version, the TUI header, doctor, plugin metadata and
3
+ * MCP client metadata stay in sync with the published package without a build-time constant to
4
+ * update. The manifest is resolved by walking UP from the module directory until a package.json
5
+ * with a string `version` is found (bounded, any depth: module, then up to 8 parents), so deeply
6
+ * nested call sites like `src/tui/app.ts` and compiled `dist/*` resolve the same manifest as
7
+ * `src/main.ts`. Standalone binaries inject the version at build time via
8
+ * ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
7
9
  */
8
10
  export declare function loadVersion(fromHere: string): string;
package/dist/version.js CHANGED
@@ -8,19 +8,55 @@ import { fileURLToPath } from "node:url";
8
8
  */
9
9
  const FALLBACK = "dev";
10
10
  /**
11
- * Reads the package version at runtime so --version, doctor, plugin metadata and MCP client
12
- * metadata stay in sync with the published package without a build-time constant to update.
13
- * The manifest is resolved relative to the module (`src/version.ts` → `../package.json` in the
14
- * source tree; `dist/version.js` → `../package.json` of the installed package). Standalone
15
- * binaries inject the version at build time via ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
11
+ * How many parent directories the loader walks up before giving up. Bounded so a missing
12
+ * manifest can never make the lookup climb an unbounded path.
13
+ */
14
+ const MAX_PARENT_HOPS = 8;
15
+ /** Manifests whose `name` identifies the CLI package; preferred when several are hit. */
16
+ const CLI_MANIFEST_NAMES = new Set(["@alisio/alisio-code", "alisio"]);
17
+ /**
18
+ * Walks up from `dir` looking for a package manifest. Returns the version of the nearest
19
+ * manifest that declares a string `version`, preferring a manifest whose `name` identifies the
20
+ * CLI package (`@alisio/alisio-code`/`alisio`) when several are hit — e.g. running from source
21
+ * inside the monorepo, where the repo-root manifest is also a candidate. `undefined` when no
22
+ * manifest with a version is reached within the bound.
23
+ */
24
+ function findManifestVersion(dir) {
25
+ let nearest;
26
+ let current = dir;
27
+ for (let hop = 0; hop <= MAX_PARENT_HOPS; hop++) {
28
+ try {
29
+ const parsed = JSON.parse(readFileSync(join(current, "package.json"), "utf8"));
30
+ if (typeof parsed.version === "string" && parsed.version) {
31
+ if (typeof parsed.name === "string" && CLI_MANIFEST_NAMES.has(parsed.name))
32
+ return parsed.version;
33
+ nearest ??= parsed.version;
34
+ }
35
+ }
36
+ catch {
37
+ // No manifest here; keep walking up.
38
+ }
39
+ const parent = dirname(current);
40
+ if (parent === current)
41
+ break; // Filesystem root.
42
+ current = parent;
43
+ }
44
+ return nearest;
45
+ }
46
+ /**
47
+ * Reads the package version at runtime so --version, the TUI header, doctor, plugin metadata and
48
+ * MCP client metadata stay in sync with the published package without a build-time constant to
49
+ * update. The manifest is resolved by walking UP from the module directory until a package.json
50
+ * with a string `version` is found (bounded, any depth: module, then up to 8 parents), so deeply
51
+ * nested call sites like `src/tui/app.ts` and compiled `dist/*` resolve the same manifest as
52
+ * `src/main.ts`. Standalone binaries inject the version at build time via
53
+ * ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
16
54
  */
17
55
  export function loadVersion(fromHere) {
18
56
  if (process.env.ALISIO_PACKAGE_VERSION)
19
57
  return process.env.ALISIO_PACKAGE_VERSION;
20
58
  try {
21
- const manifest = join(dirname(fileURLToPath(fromHere)), "..", "package.json");
22
- const parsed = JSON.parse(readFileSync(manifest, "utf8"));
23
- return typeof parsed.version === "string" && parsed.version ? parsed.version : FALLBACK;
59
+ return findManifestVersion(dirname(fileURLToPath(fromHere))) ?? FALLBACK;
24
60
  }
25
61
  catch {
26
62
  return FALLBACK;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/alisio-code",
3
- "version": "0.1.0-alpha.9",
3
+ "version": "0.1.0",
4
4
  "description": "Alisio: an extensible, provider-agnostic coding-agent harness for your terminal. TUI, OpenAI-compatible providers, permissioned local tools, context compaction, persistent memory and a typed plugin SDK.",
5
5
  "author": "Gustavo Gutiérrez",
6
6
  "license": "MIT",
@@ -45,14 +45,12 @@
45
45
  "alisio": "./dist/main.js"
46
46
  },
47
47
  "dependencies": {
48
- "@alisio/core": "0.1.0-alpha.7",
49
- "@alisio/plugin-deepseek": "0.1.0-alpha.7",
50
- "@alisio/plugin-memory": "0.1.0-alpha.6",
51
- "@alisio/plugin-openai-compatible": "0.1.0-alpha.7",
52
- "@alisio/plugin-opencode": "0.1.0-alpha.6",
53
- "@alisio/plugin-opencode-go": "0.1.0-alpha.6",
54
- "@alisio/plugin-subagents": "0.1.0-alpha.6",
55
- "@alisio/sdk": "0.1.0-alpha.5",
48
+ "@alisio/core": "0.1.0",
49
+ "@alisio/plugin-memory": "0.1.0",
50
+ "@alisio/plugin-openai-compatible": "0.1.0",
51
+ "@alisio/plugin-subagents": "0.1.0",
52
+ "@alisio/sdk": "0.1.0",
53
+ "@alisio/server": "0.1.0",
56
54
  "@earendil-works/pi-tui": "0.87.1",
57
55
  "commander": "15.0.0"
58
56
  },