@akanjs/cli 3.0.0-beta.14 → 3.0.0-beta.15

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 (80) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-d3wvbq8q.js +9 -0
  3. package/CodeAgentRpcHost-qvwfv0n9.js +7 -0
  4. package/CodeTui-6c9a2wtk.js +1103 -0
  5. package/README.md +3 -0
  6. package/{agent.command-bhwsc2aj.js → agent.command-9cg8m87z.js} +6 -6
  7. package/akanCode.js +100 -0
  8. package/{application.command-436y543n.js → application.command-x75men32.js} +6 -5
  9. package/{applicationBuildRunner-4f1wrba0.js → applicationBuildRunner-d3702f1w.js} +2 -2
  10. package/{applicationReleasePackager-jkjmy5ee.js → applicationReleasePackager-2kdyj3te.js} +1 -1
  11. package/buildBatch.proc.js +2 -2
  12. package/{capacitorApp-hqvsrec4.js → capacitorApp-qk975zyz.js} +2 -2
  13. package/{cloud.command-3xn0xpz8.js → cloud.command-a09wggfq.js} +9 -8
  14. package/cloud.script-qd3hpybf.js +20 -0
  15. package/code.command-ca7rpmez.js +147 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/{context.command-hxh96cc3.js → context.command-9kbm0527.js} +14 -13
  19. package/context.runner-wtqpw7mh.js +22 -0
  20. package/{devTui-4ted58a6.js → devTui-v19fbnpk.js} +114 -8
  21. package/{devTuiView-1dj2yq7t.js → devTuiView-pa63jrjq.js} +1 -1
  22. package/{guideline.command-cyw8c0m0.js → guideline.command-qvrh5pnx.js} +6 -6
  23. package/guidelines/agentRule/agentRule.instruction.md +22 -2
  24. package/guidelines/conventions/conventions.instruction.md +3 -1
  25. package/incrementalBuilder.proc.js +1 -1
  26. package/{index-tt6kzwj5.js → index-0qvq9aeb.js} +3 -3
  27. package/{index-jwjaf9fy.js → index-1p146w4s.js} +44 -44
  28. package/{index-7f4dsf9v.js → index-1sggq0m8.js} +1 -1
  29. package/{index-15r9a10e.js → index-3dmebxnk.js} +2 -2
  30. package/{index-vrwdx0zx.js → index-3hr7cpy1.js} +1 -1
  31. package/{index-31hmq4ys.js → index-5hy11qaq.js} +8 -8
  32. package/{index-8pkvrcv1.js → index-658xafm1.js} +3 -3
  33. package/index-7md2g9vw.js +6 -0
  34. package/{index-ys7dvdb3.js → index-84b080zh.js} +1 -1
  35. package/index-8k16atqy.js +82 -0
  36. package/{index-8tcj5wk8.js → index-91brpnzb.js} +5 -5
  37. package/{index-em4swemt.js → index-bchkysja.js} +3 -3
  38. package/{index-fad0hhty.js → index-bzjnp9ap.js} +22 -80
  39. package/{index-sg1bgdhe.js → index-chp5gqmm.js} +27 -105
  40. package/{index-r73ha1rf.js → index-d35a8vc0.js} +4 -4
  41. package/{index-1nwxbr2y.js → index-ea1smknd.js} +2 -2
  42. package/index-egt4vfgb.js +69 -0
  43. package/index-erzcytap.js +2434 -0
  44. package/{index-sh88vtye.js → index-f4gpcp98.js} +1 -1
  45. package/{index-b3ae16mg.js → index-gca9y01h.js} +1 -1
  46. package/index-sxg87a8f.js +727 -0
  47. package/{index-h7yw2z9n.js → index-twq9p2sp.js} +8 -8
  48. package/{index-9fbk3dbh.js → index-v156f836.js} +4 -4
  49. package/{index-1mrmve4s.js → index-v1y3cx0k.js} +2 -2
  50. package/{index-t8gvwcrx.js → index-va2je2qm.js} +2 -2
  51. package/index-wfwknnje.js +124 -0
  52. package/index-x7g1qzpt.js +121 -0
  53. package/index.js +21 -20
  54. package/{library.command-1f9h2ex6.js → library.command-n89re6cx.js} +3 -3
  55. package/{localRegistry.command-4cbgz63s.js → localRegistry.command-1mmnegph.js} +8 -7
  56. package/{module.command-ew0484va.js → module.command-4m82zgfp.js} +5 -5
  57. package/{package.command-y2rh4np7.js → package.command-9dv0p6zq.js} +3 -3
  58. package/package.json +9 -2
  59. package/{page.command-e5h29fyt.js → page.command-q86b0m0z.js} +3 -3
  60. package/{primitive.command-50ecfzg4.js → primitive.command-kaneasq6.js} +6 -6
  61. package/prompter-9mn3kp36.js +8 -0
  62. package/{quality.command-a2jytczs.js → quality.command-sgapx315.js} +2 -2
  63. package/{repair.command-c2amvt74.js → repair.command-efc5gdbq.js} +5 -5
  64. package/{scalar.command-bptz6aj6.js → scalar.command-e2rpkhqy.js} +4 -4
  65. package/skills/akan-debug/SKILL.md +42 -0
  66. package/skills/akan-deps/SKILL.md +50 -0
  67. package/skills/akan-err/SKILL.md +39 -0
  68. package/skills/akan-module/SKILL.md +45 -0
  69. package/skills/akan-scaffold/SKILL.md +76 -0
  70. package/skills/akan-scalar/SKILL.md +52 -0
  71. package/skills/akan-service/SKILL.md +34 -0
  72. package/skills/akan-store/SKILL.md +63 -0
  73. package/skills/akan-ui/SKILL.md +63 -0
  74. package/skills/akan-validate/SKILL.md +44 -0
  75. package/{subspace.command-kmemx1vm.js → subspace.command-005rz1x6.js} +4 -4
  76. package/{tunnel.command-67q8vsg5.js → tunnel.command-66j9estx.js} +2 -2
  77. package/{workflow.command-2ze5y2xp.js → workflow.command-z1xcm4c7.js} +9 -9
  78. package/{workspace.command-2n385jkt.js → workspace.command-4ahrhp3k.js} +22 -20
  79. package/cloud.script-0a76c2g8.js +0 -19
  80. /package/{index-5nyznra1.js → index-p1j0cyvd.js} +0 -0
@@ -0,0 +1,727 @@
1
+ // @bun
2
+ // pkgs/@akanjs/cli/code/CodeTuiClipboard.ts
3
+ import { existsSync } from "fs";
4
+ import { tmpdir } from "os";
5
+ import path from "path";
6
+
7
+ class CodeTuiClipboard2 {
8
+ static imageTypes = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
9
+ static imagePathsIn(text) {
10
+ const pieces = text.split(`
11
+ `).flatMap((line) => line.split(/ (?=\/)/));
12
+ const images = [];
13
+ const rest = [];
14
+ for (const piece of pieces) {
15
+ const file = CodeTuiClipboard2.imagePathIn(piece);
16
+ if (file)
17
+ images.push(file);
18
+ else if (piece.trim())
19
+ rest.push(piece);
20
+ }
21
+ return { images, rest: rest.join(" ") };
22
+ }
23
+ static imagePathIn(text) {
24
+ const file = text.trim().replace(/^['"]|['"]$/g, "").replace(/\\ /g, " ");
25
+ if (!file || file.includes(`
26
+ `))
27
+ return;
28
+ const lower = file.toLowerCase();
29
+ if (!CodeTuiClipboard2.imageTypes.some((ext) => lower.endsWith(ext)))
30
+ return;
31
+ return existsSync(file) ? file : undefined;
32
+ }
33
+ static async image() {
34
+ const png = await CodeTuiClipboard2.#read();
35
+ if (!png?.length)
36
+ return;
37
+ const file = path.join(tmpdir(), `akan-code-${Date.now()}.png`);
38
+ await Bun.write(file, png);
39
+ return file;
40
+ }
41
+ static has() {
42
+ try {
43
+ return Bun.Image.hasClipboardImage();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ static async#read() {
49
+ try {
50
+ const image = Bun.Image.fromClipboard();
51
+ if (image)
52
+ return new Uint8Array(await image.png().toBuffer());
53
+ } catch {}
54
+ if (process.platform === "darwin")
55
+ return await CodeTuiClipboard2.#darwin();
56
+ for (const command of [
57
+ ["wl-paste", "--no-newline", "--type", "image/png"],
58
+ ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
59
+ ]) {
60
+ const bytes = await CodeTuiClipboard2.#bytes(command);
61
+ if (bytes?.length)
62
+ return bytes;
63
+ }
64
+ return;
65
+ }
66
+ static async#darwin() {
67
+ return CodeTuiClipboard2.fromAppleScript(await CodeTuiClipboard2.#text(["osascript", "-e", CodeTuiClipboard2.ask]));
68
+ }
69
+ static ask = "the clipboard as \xABclass PNGf\xBB";
70
+ static fromAppleScript(out) {
71
+ const hex = /\u00ABdata PNGf([0-9A-Fa-f]*)\u00BB/.exec(out ?? "")?.[1];
72
+ if (!hex || hex.length % 2)
73
+ return;
74
+ return Uint8Array.from((hex.match(/../g) ?? []).map((byte) => Number.parseInt(byte, 16)));
75
+ }
76
+ static async#bytes(command) {
77
+ try {
78
+ const proc = Bun.spawn(command, { stdout: "pipe", stderr: "ignore" });
79
+ const bytes = new Uint8Array(await new Response(proc.stdout).arrayBuffer());
80
+ return await proc.exited === 0 ? bytes : undefined;
81
+ } catch {
82
+ return;
83
+ }
84
+ }
85
+ static async#text(command) {
86
+ const bytes = await CodeTuiClipboard2.#bytes(command);
87
+ return bytes ? new TextDecoder().decode(bytes) : undefined;
88
+ }
89
+ }
90
+
91
+ // pkgs/@akanjs/cli/code/CodeTuiCommands.ts
92
+ class CodeTuiCommands2 {
93
+ static all = [
94
+ { name: "help", desc: "list these commands and the keys" },
95
+ { name: "tools", desc: "what this session can call" },
96
+ { name: "model", desc: "switch model", arg: "<provider>/<id>" },
97
+ { name: "effort", desc: "how hard the model thinks before it answers", arg: "<level>" },
98
+ { name: "thinking", desc: "show the model's reasoning, or fold it away again" },
99
+ { name: "compact", desc: "summarise the conversation to free the window", arg: "[notes]" },
100
+ { name: "abort", desc: "stop the running turn" },
101
+ { name: "clear", desc: "wipe the screen; the model keeps the conversation" },
102
+ { name: "name", desc: "name this session", arg: "<text>" },
103
+ { name: "fork", desc: "continue this conversation twice; the copy takes over here", arg: "[name]" },
104
+ { name: "agents", desc: "what a sub-agent may be, and what one may spend" },
105
+ { name: "peers", desc: "the other akan code sessions running here" },
106
+ { name: "msg", desc: "hand a message to another session", arg: "<peer> <text>" },
107
+ { name: "quit", desc: "leave the session" }
108
+ ];
109
+ static prefixOf(text, caret) {
110
+ if (!text.startsWith("/"))
111
+ return;
112
+ const head = text.slice(0, caret);
113
+ if (/\s/.test(head) || head.includes(`
114
+ `))
115
+ return;
116
+ return head.slice(1);
117
+ }
118
+ static matches(prefix) {
119
+ const needle = prefix.toLowerCase();
120
+ return CodeTuiCommands2.all.filter((command) => command.name.startsWith(needle));
121
+ }
122
+ static help() {
123
+ const usage = (command) => `/${command.name}${command.arg ? ` ${command.arg}` : ""}`;
124
+ const column = Math.max(...CodeTuiCommands2.all.map((command) => usage(command).length)) + 2;
125
+ const list = CodeTuiCommands2.all.map((command) => `${usage(command).padEnd(column)}${command.desc}`).join(`
126
+ `);
127
+ return [
128
+ list,
129
+ "",
130
+ "Keys: enter send \xB7 shift+enter newline \xB7 \u2191\u2193 move (history at the edges) \xB7 \u2190\u2192 move",
131
+ "esc interrupt \xB7 pgup/pgdn scroll \xB7 ^c quit",
132
+ "@ completes a file in this repo \xB7 \u2190 at the start of the prompt lists past sessions",
133
+ "",
134
+ "Images: cmd+v attaches what is on the clipboard \xB7 ^v does too, without the terminal in the way",
135
+ "",
136
+ 'shift+enter needs a terminal that reports it \u2014 in VS Code bind it to send "\\u001b\\r".'
137
+ ].join(`
138
+ `);
139
+ }
140
+ }
141
+
142
+ // pkgs/@akanjs/cli/code/CodeTuiLines.ts
143
+ import stringWidth from "string-width";
144
+
145
+ class CodeTuiLines2 {
146
+ static width(text) {
147
+ return stringWidth(text);
148
+ }
149
+ static rows(rows, width, keyBase) {
150
+ const lines = [];
151
+ rows.forEach((row, idx) => {
152
+ for (const line of CodeTuiLines2.#row(row, Math.max(8, width), `${keyBase}:${idx}`))
153
+ lines.push(line);
154
+ });
155
+ return lines;
156
+ }
157
+ static #row(row, width, key) {
158
+ const prefix = row.prefix;
159
+ const head = prefix ? CodeTuiLines2.width(prefix.text) : 0;
160
+ const hang = row.hang ?? " ".repeat(head);
161
+ const body = width - Math.max(head, CodeTuiLines2.width(hang));
162
+ const wrapped = CodeTuiLines2.#wrap(row.spans, Math.max(4, body));
163
+ if (!wrapped.length)
164
+ wrapped.push([]);
165
+ return wrapped.map((spans, at) => ({
166
+ key: `${key}:${at}`,
167
+ spans: [...at === 0 ? prefix ? [prefix] : [] : hang ? [{ text: hang }] : [], ...spans]
168
+ }));
169
+ }
170
+ static #wrap(spans, width) {
171
+ const lines = [];
172
+ let line = [];
173
+ let used = 0;
174
+ let soft = false;
175
+ const push = (fromWrap) => {
176
+ lines.push(line);
177
+ line = [];
178
+ used = 0;
179
+ soft = fromWrap;
180
+ };
181
+ for (const span of spans) {
182
+ const { text, ...style } = span;
183
+ for (const piece of text.split(`
184
+ `).flatMap((part, at) => at ? [null, part] : [part])) {
185
+ if (piece === null) {
186
+ push(false);
187
+ continue;
188
+ }
189
+ for (const word of CodeTuiLines2.#words(piece)) {
190
+ const size = CodeTuiLines2.width(word);
191
+ if (!word.trim() && !used && soft)
192
+ continue;
193
+ if (used && used + size > width)
194
+ push(true);
195
+ if (size > width) {
196
+ for (const chunk of CodeTuiLines2.#chunks(word, width)) {
197
+ if (used)
198
+ push(true);
199
+ line.push({ ...style, text: chunk });
200
+ used = CodeTuiLines2.width(chunk);
201
+ if (used >= width)
202
+ push(true);
203
+ }
204
+ continue;
205
+ }
206
+ line.push({ ...style, text: word });
207
+ used += size;
208
+ }
209
+ }
210
+ }
211
+ if (line.length)
212
+ lines.push(line);
213
+ return lines;
214
+ }
215
+ static #words(text) {
216
+ return text.match(/\S+\s*|\s+/g) ?? [];
217
+ }
218
+ static #chunks(word, width) {
219
+ const chunks = [];
220
+ let chunk = "";
221
+ for (const char of word) {
222
+ if (CodeTuiLines2.width(chunk + char) > width) {
223
+ chunks.push(chunk);
224
+ chunk = char;
225
+ } else
226
+ chunk += char;
227
+ }
228
+ if (chunk)
229
+ chunks.push(chunk);
230
+ return chunks;
231
+ }
232
+ static text(line) {
233
+ return line.spans.map((span) => span.text).join("");
234
+ }
235
+ }
236
+
237
+ // pkgs/@akanjs/cli/code/CodeTuiEditor.ts
238
+ class CodeTuiEditor2 {
239
+ #text = "";
240
+ #caret = 0;
241
+ get text() {
242
+ return this.#text;
243
+ }
244
+ get caret() {
245
+ return this.#caret;
246
+ }
247
+ get isEmpty() {
248
+ return !this.#text.length;
249
+ }
250
+ set(text, caret = text.length) {
251
+ this.#text = text;
252
+ this.#caret = Math.max(0, Math.min(caret, text.length));
253
+ }
254
+ clear() {
255
+ this.set("");
256
+ }
257
+ replace(from, to, text) {
258
+ this.set(this.#text.slice(0, from) + text + this.#text.slice(to), from + text.length);
259
+ }
260
+ token() {
261
+ const before = this.#text.slice(0, this.#caret);
262
+ const from = Math.max(before.lastIndexOf(" "), before.lastIndexOf(`
263
+ `)) + 1;
264
+ const after = /\s/.exec(this.#text.slice(this.#caret));
265
+ const to = after ? this.#caret + (after.index ?? 0) : this.#text.length;
266
+ return { from, to, text: this.#text.slice(from, to) };
267
+ }
268
+ insert(text) {
269
+ const clean = text.replace(/\r\n?/g, `
270
+ `);
271
+ this.set(this.#text.slice(0, this.#caret) + clean + this.#text.slice(this.#caret), this.#caret + clean.length);
272
+ }
273
+ newline() {
274
+ this.insert(`
275
+ `);
276
+ }
277
+ backspace() {
278
+ if (!this.#caret)
279
+ return;
280
+ const at = CodeTuiEditor2.#prev(this.#text, this.#caret);
281
+ this.set(this.#text.slice(0, at) + this.#text.slice(this.#caret), at);
282
+ }
283
+ deleteForward() {
284
+ if (this.#caret >= this.#text.length)
285
+ return;
286
+ const at = CodeTuiEditor2.#next(this.#text, this.#caret);
287
+ this.set(this.#text.slice(0, this.#caret) + this.#text.slice(at), this.#caret);
288
+ }
289
+ deleteWord() {
290
+ const before = this.#text.slice(0, this.#caret);
291
+ const at = before.replace(/\s*\S*$/, "").length;
292
+ this.set(before.slice(0, at) + this.#text.slice(this.#caret), at);
293
+ }
294
+ deleteToLineStart() {
295
+ const at = this.#text.lastIndexOf(`
296
+ `, Math.max(0, this.#caret - 1)) + 1;
297
+ this.set(this.#text.slice(0, at) + this.#text.slice(this.#caret), at);
298
+ }
299
+ left() {
300
+ this.#caret = CodeTuiEditor2.#prev(this.#text, this.#caret);
301
+ }
302
+ right() {
303
+ this.#caret = CodeTuiEditor2.#next(this.#text, this.#caret);
304
+ }
305
+ wordLeft() {
306
+ this.#caret = this.#text.slice(0, this.#caret).replace(/\s*\S*$/, "").length;
307
+ }
308
+ wordRight() {
309
+ const rest = /^\s*\S*/.exec(this.#text.slice(this.#caret))?.[0] ?? "";
310
+ this.#caret = Math.min(this.#text.length, this.#caret + rest.length);
311
+ }
312
+ move(direction, width) {
313
+ const { rows, row, col } = this.layout(width);
314
+ const target = rows[row + direction];
315
+ if (!target)
316
+ return false;
317
+ this.#caret = target.at + CodeTuiEditor2.#indexAt(target.text, col);
318
+ return true;
319
+ }
320
+ homeOfRow(width) {
321
+ const { rows, row } = this.layout(width);
322
+ this.#caret = rows[row]?.at ?? 0;
323
+ }
324
+ endOfRow(width) {
325
+ const { rows, row } = this.layout(width);
326
+ const current = rows[row];
327
+ this.#caret = current ? current.at + current.text.length : this.#text.length;
328
+ }
329
+ layout(width) {
330
+ const room = Math.max(4, width);
331
+ const rows = [];
332
+ let at = 0;
333
+ for (const line of this.#text.split(`
334
+ `)) {
335
+ for (const text of CodeTuiEditor2.#wrap(line, room)) {
336
+ rows.push({ text, at });
337
+ at += text.length;
338
+ }
339
+ at += 1;
340
+ }
341
+ if (!rows.length)
342
+ rows.push({ text: "", at: 0 });
343
+ let row = 0;
344
+ for (let index = 0;index < rows.length; index += 1)
345
+ if ((rows[index]?.at ?? 0) <= this.#caret)
346
+ row = index;
347
+ const current = rows[row] ?? { text: "", at: 0 };
348
+ return { rows, row, col: CodeTuiLines2.width(current.text.slice(0, Math.max(0, this.#caret - current.at))) };
349
+ }
350
+ static #indexAt(row, col) {
351
+ let at = 0;
352
+ for (const char of row) {
353
+ if (CodeTuiLines2.width(row.slice(0, at + char.length)) > col)
354
+ break;
355
+ at += char.length;
356
+ }
357
+ return at;
358
+ }
359
+ static #wrap(line, width) {
360
+ if (!line.length)
361
+ return [""];
362
+ const rows = [];
363
+ let row = "";
364
+ for (const char of line) {
365
+ if (row && CodeTuiLines2.width(row + char) > width) {
366
+ rows.push(row);
367
+ row = "";
368
+ }
369
+ row += char;
370
+ }
371
+ if (row)
372
+ rows.push(row);
373
+ return rows;
374
+ }
375
+ static #prev(text, at) {
376
+ if (at <= 0)
377
+ return 0;
378
+ return at - ([...text.slice(0, at)].at(-1)?.length ?? 1);
379
+ }
380
+ static #next(text, at) {
381
+ if (at >= text.length)
382
+ return text.length;
383
+ return at + ([...text.slice(at)][0]?.length ?? 1);
384
+ }
385
+ }
386
+
387
+ // pkgs/@akanjs/cli/code/CodeTuiFiles.ts
388
+ class CodeTuiFiles2 {
389
+ static limit = 8;
390
+ #root;
391
+ #all = [];
392
+ #loaded = false;
393
+ constructor(root) {
394
+ this.#root = root;
395
+ }
396
+ get ready() {
397
+ return this.#loaded;
398
+ }
399
+ async load() {
400
+ this.#all = await CodeTuiFiles2.#list(this.#root);
401
+ this.#loaded = true;
402
+ }
403
+ match(query, limit = CodeTuiFiles2.limit) {
404
+ const needle = query.toLowerCase();
405
+ if (!needle)
406
+ return this.#all.slice(0, limit);
407
+ const hits = this.#all.filter((file) => file.toLowerCase().includes(needle));
408
+ return hits.sort((left, right) => left.length - right.length).slice(0, limit);
409
+ }
410
+ static async#list(root) {
411
+ try {
412
+ const proc = Bun.spawn(["git", "ls-files", "--cached", "--others", "--exclude-standard"], {
413
+ cwd: root,
414
+ stdout: "pipe",
415
+ stderr: "ignore"
416
+ });
417
+ const text = await new Response(proc.stdout).text();
418
+ if (await proc.exited !== 0)
419
+ return [];
420
+ return text.split(`
421
+ `).filter((line) => !!line.trim());
422
+ } catch {
423
+ return [];
424
+ }
425
+ }
426
+ }
427
+
428
+ // pkgs/@akanjs/cli/code/CodeTuiMarkdown.ts
429
+ import { MarkdownBlocks, MarkdownSpans } from "akanjs/common";
430
+ class CodeTuiMarkdown2 {
431
+ static bullet = "\u2022";
432
+ static minColumn = 6;
433
+ static rows(source, width = 80) {
434
+ const rows = [];
435
+ const blocks = MarkdownBlocks.of(source);
436
+ blocks.forEach((block, at) => {
437
+ if (at)
438
+ rows.push({ spans: [] });
439
+ for (const row of CodeTuiMarkdown2.#block(block, width))
440
+ rows.push(row);
441
+ });
442
+ return rows;
443
+ }
444
+ static #block(block, width) {
445
+ switch (block.kind) {
446
+ case "heading":
447
+ return [
448
+ {
449
+ spans: CodeTuiMarkdown2.spans(block.text, { bold: true, ...block.level <= 2 ? { color: "cyan" } : {} })
450
+ }
451
+ ];
452
+ case "code":
453
+ return CodeTuiMarkdown2.#code(block.lang, block.text);
454
+ case "quote":
455
+ return block.text.split(`
456
+ `).map((line) => ({
457
+ prefix: { text: "\u258C ", dim: true },
458
+ spans: CodeTuiMarkdown2.spans(line, { dim: true })
459
+ }));
460
+ case "rule":
461
+ return [{ spans: [{ text: "\u2500".repeat(24), dim: true }] }];
462
+ case "list":
463
+ return block.items.map((item) => {
464
+ const indent = " ".repeat(item.depth);
465
+ const marker = item.ordered ? `${item.num}. ` : `${CodeTuiMarkdown2.bullet} `;
466
+ return {
467
+ prefix: { text: `${indent}${marker}`, dim: true },
468
+ spans: CodeTuiMarkdown2.spans(item.text)
469
+ };
470
+ });
471
+ case "table":
472
+ return CodeTuiMarkdown2.#table(block, width);
473
+ default:
474
+ return [{ spans: CodeTuiMarkdown2.spans(block.text) }];
475
+ }
476
+ }
477
+ static #code(lang, text) {
478
+ const label = lang?.trim();
479
+ const bar = { text: "\u2502 ", dim: true };
480
+ return [
481
+ ...label ? [{ spans: [{ text: label, dim: true, italic: true }] }] : [],
482
+ ...text.split(`
483
+ `).map((line) => ({ prefix: bar, hang: "\u2502 ", spans: [{ text: line, color: "green" }] }))
484
+ ];
485
+ }
486
+ static #table(block, width) {
487
+ const columns = block.head.length;
488
+ const grid = [block.head, ...block.rows].map((row) => Array.from({ length: columns }, (_, col) => MarkdownSpans.plain(row[col] ?? "")));
489
+ const widths = CodeTuiMarkdown2.#columns(grid, columns, width);
490
+ const rule = { spans: [{ text: widths.map((size) => "\u2500".repeat(size)).join(" "), dim: true }] };
491
+ const [head, ...body] = grid;
492
+ return [
493
+ ...CodeTuiMarkdown2.#gridRow(head ?? [], widths, block.aligns, { bold: true }),
494
+ rule,
495
+ ...body.flatMap((row) => CodeTuiMarkdown2.#gridRow(row, widths, block.aligns, {}))
496
+ ];
497
+ }
498
+ static #columns(grid, columns, width) {
499
+ const natural = Array.from({ length: columns }, (_, col) => Math.max(1, ...grid.map((row) => CodeTuiLines2.width(row[col] ?? ""))));
500
+ const gaps = 2 * Math.max(0, columns - 1);
501
+ const room = Math.max(columns * CodeTuiMarkdown2.minColumn, width - gaps);
502
+ const total = natural.reduce((sum, size) => sum + size, 0);
503
+ if (total <= width - gaps)
504
+ return natural;
505
+ const floors = natural.map((size, col) => Math.min(size, Math.max(CodeTuiMarkdown2.minColumn, CodeTuiMarkdown2.#longestWord(grid, col))));
506
+ const held = floors.reduce((sum, size) => sum + size, 0);
507
+ if (held > room)
508
+ return natural.map((size) => Math.max(1, Math.floor(size * room / total)));
509
+ const want = natural.map((size, col) => size - (floors[col] ?? 0));
510
+ const wanted = want.reduce((sum, size) => sum + size, 0) || 1;
511
+ const slack = room - held;
512
+ return floors.map((size, col) => size + Math.floor(slack * (want[col] ?? 0) / wanted));
513
+ }
514
+ static #longestWord(grid, col) {
515
+ return Math.max(1, ...grid.flatMap((row) => (row[col] ?? "").split(/\s+/).map((word) => CodeTuiLines2.width(word))));
516
+ }
517
+ static #gridRow(cells, widths, aligns, style) {
518
+ const wrapped = widths.map((size, col) => CodeTuiMarkdown2.#cell(cells[col] ?? "", size));
519
+ const height = Math.max(1, ...wrapped.map((lines) => lines.length));
520
+ return Array.from({ length: height }, (_, line) => ({
521
+ spans: widths.flatMap((size, col) => {
522
+ const text = wrapped[col]?.[line] ?? "";
523
+ const fill = " ".repeat(Math.max(0, size - CodeTuiLines2.width(text)));
524
+ const align = aligns[col];
525
+ const lead = align === "right" ? fill : align === "center" ? " ".repeat(Math.floor(fill.length / 2)) : "";
526
+ const tail = fill.slice(lead.length);
527
+ return [{ text: lead }, { ...style, text }, { text: `${tail}${col === widths.length - 1 ? "" : " "}` }];
528
+ })
529
+ }));
530
+ }
531
+ static #cell(text, width) {
532
+ const lines = [];
533
+ let line = "";
534
+ for (const word of text.split(/(\s+)/)) {
535
+ if (!word)
536
+ continue;
537
+ if (line && CodeTuiLines2.width(line + word) > width) {
538
+ lines.push(line.trimEnd());
539
+ line = word.trim() ? word : "";
540
+ continue;
541
+ }
542
+ line += word;
543
+ }
544
+ if (line.trim() || !lines.length)
545
+ lines.push(line.trimEnd());
546
+ return lines.flatMap((entry) => CodeTuiLines2.width(entry) <= width ? [entry] : CodeTuiMarkdown2.#hardSplit(entry, width));
547
+ }
548
+ static #hardSplit(text, width) {
549
+ const chunks = [];
550
+ let chunk = "";
551
+ for (const char of text) {
552
+ if (CodeTuiLines2.width(chunk + char) > width) {
553
+ chunks.push(chunk);
554
+ chunk = "";
555
+ }
556
+ chunk += char;
557
+ }
558
+ if (chunk)
559
+ chunks.push(chunk);
560
+ return chunks;
561
+ }
562
+ static spans(text, base = {}) {
563
+ return MarkdownSpans.of(text).flatMap((span) => {
564
+ if (span.kind === "link")
565
+ return [
566
+ { ...base, underline: true, text: span.text },
567
+ { ...base, dim: true, text: ` (${span.href})` }
568
+ ];
569
+ return [{ ...base, ...CodeTuiMarkdown2.#span(span), text: span.text }];
570
+ });
571
+ }
572
+ static #span(span) {
573
+ switch (span.kind) {
574
+ case "code":
575
+ return { color: "yellow" };
576
+ case "strong":
577
+ return { bold: true };
578
+ case "em":
579
+ return { italic: true };
580
+ case "del":
581
+ return { strikethrough: true };
582
+ default:
583
+ return {};
584
+ }
585
+ }
586
+ }
587
+
588
+ // pkgs/@akanjs/cli/code/CodeTuiParts.ts
589
+ import { codeAgentClip } from "akanjs/common";
590
+ class CodeTuiParts2 {
591
+ static thinkingLines = 6;
592
+ static outputLines = 4;
593
+ static lines(parts, width, options = {}) {
594
+ const lines = [];
595
+ parts.forEach((part, at) => {
596
+ const rows = CodeTuiParts2.rows(part, width, options);
597
+ for (const line of CodeTuiLines2.rows(rows, width, `${part.id}:${at}`))
598
+ lines.push(line);
599
+ });
600
+ return lines;
601
+ }
602
+ static rows(part, width = 80, options = {}) {
603
+ switch (part.kind) {
604
+ case "user":
605
+ return [
606
+ { prefix: { text: "\u203A ", color: "cyan", bold: true }, spans: [{ text: part.text, color: "cyan" }] },
607
+ ...part.images ? [
608
+ {
609
+ prefix: { text: " ", dim: true },
610
+ spans: [{ text: `\u29C9 ${part.images} image${part.images > 1 ? "s" : ""}`, dim: true }]
611
+ }
612
+ ] : []
613
+ ];
614
+ case "assistant":
615
+ return CodeTuiParts2.#assistant(part, width);
616
+ case "thinking":
617
+ return options.thinking ? CodeTuiParts2.#thinking(part.text) : CodeTuiParts2.#folded(part.text);
618
+ case "tool":
619
+ return CodeTuiParts2.#tool(part);
620
+ case "notice":
621
+ return part.text.split(`
622
+ `).map((line, at) => ({
623
+ prefix: { text: at === 0 && part.level !== "info" ? "! " : " ", ...CodeTuiParts2.#noticeStyle(part.level) },
624
+ spans: CodeTuiMarkdown2.spans(line, CodeTuiParts2.#noticeStyle(part.level))
625
+ }));
626
+ case "question":
627
+ return CodeTuiParts2.#question(part);
628
+ case "approval":
629
+ return [
630
+ { prefix: { text: "? ", color: "yellow" }, spans: [{ text: part.request.summary, color: "yellow" }] },
631
+ ...part.approved === undefined ? [] : [{ prefix: { text: " " }, spans: [{ text: part.approved ? "approved" : "denied", dim: true }] }]
632
+ ];
633
+ case "host":
634
+ return [{ prefix: { text: " ", dim: true }, spans: [{ text: `${part.hostKind} ${part.text}`, dim: true }] }];
635
+ default:
636
+ return [];
637
+ }
638
+ }
639
+ static #assistant(part, width) {
640
+ const rows = CodeTuiMarkdown2.rows(part.text, width);
641
+ if (!part.truncated)
642
+ return rows;
643
+ return [
644
+ ...rows,
645
+ { spans: [{ text: "\u2026 the answer stopped at the model's output limit \u2014 ask it to continue", color: "yellow" }] }
646
+ ];
647
+ }
648
+ static #folded(text) {
649
+ const count = text.split(`
650
+ `).filter((line) => line.trim()).length;
651
+ return [
652
+ {
653
+ prefix: { text: "\u273B ", dim: true },
654
+ spans: [{ text: `thinking \xB7 ${count} line${count === 1 ? "" : "s"} \xB7 /thinking to show`, dim: true }]
655
+ }
656
+ ];
657
+ }
658
+ static #thinking(text) {
659
+ const all = text.split(`
660
+ `).filter((line) => line.trim());
661
+ const shown = all.slice(-CodeTuiParts2.thinkingLines);
662
+ const hidden = all.length - shown.length;
663
+ return [
664
+ ...hidden ? [{ prefix: { text: "\xB7 ", dim: true }, spans: [{ text: `(${hidden} earlier lines)`, dim: true }] }] : [],
665
+ ...shown.map((line) => ({
666
+ prefix: { text: "\xB7 ", dim: true },
667
+ spans: [{ text: line, dim: true, italic: true }]
668
+ }))
669
+ ];
670
+ }
671
+ static #tool(part) {
672
+ const running = part.outcome === undefined;
673
+ const mark = running ? "\u25D0" : part.outcome === "ok" ? "\u23FA" : part.outcome === "blocked" ? "\u29B8" : "\u23FA";
674
+ const color = running ? "cyan" : part.outcome === "ok" ? "green" : part.outcome === "blocked" ? "yellow" : "red";
675
+ const head = {
676
+ prefix: { text: `${mark} `, color },
677
+ hang: " ",
678
+ spans: CodeTuiParts2.#title(part.tool.name, part.tool.title)
679
+ };
680
+ const detail = CodeTuiParts2.#detail(part);
681
+ if (!detail.length)
682
+ return [head];
683
+ return [
684
+ head,
685
+ ...detail.map((line, at) => ({
686
+ prefix: { text: at === 0 ? " \u23BF " : " ", dim: true },
687
+ hang: " ",
688
+ spans: [{ text: line, dim: true }]
689
+ }))
690
+ ];
691
+ }
692
+ static #title(name, title) {
693
+ if (!title.startsWith(name))
694
+ return [{ text: title, bold: true }];
695
+ return [
696
+ { text: name, bold: true },
697
+ { text: title.slice(name.length), dim: true }
698
+ ];
699
+ }
700
+ static #detail(part) {
701
+ if (part.outcome === undefined)
702
+ return part.progress ? [part.progress] : [];
703
+ if (part.outcome === "ok")
704
+ return [];
705
+ return (part.output ?? "").split(`
706
+ `).filter((line) => line.trim()).slice(0, CodeTuiParts2.outputLines).map((line) => codeAgentClip(line.trim(), 200));
707
+ }
708
+ static #question(part) {
709
+ return [
710
+ { prefix: { text: "? ", color: "yellow" }, spans: [{ text: part.question.prompt, color: "yellow" }] },
711
+ ...(part.question.options ?? []).map((option) => ({
712
+ prefix: { text: " \xB7 ", dim: true },
713
+ spans: [{ text: `${option.label}${option.recommended ? " (recommended)" : ""}`, dim: true }]
714
+ })),
715
+ ...part.rendered === undefined ? [] : [{ prefix: { text: " = ", color: "green" }, spans: [{ text: part.rendered, color: "green" }] }]
716
+ ];
717
+ }
718
+ static #noticeStyle(level) {
719
+ if (level === "error")
720
+ return { color: "red" };
721
+ if (level === "warning")
722
+ return { color: "yellow" };
723
+ return { dim: true };
724
+ }
725
+ }
726
+
727
+ export { CodeTuiClipboard2, CodeTuiCommands2, CodeTuiLines2, CodeTuiEditor2, CodeTuiFiles2, CodeTuiMarkdown2, CodeTuiParts2 };