@akanjs/cli 3.0.0-beta.13 → 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 (92) 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/{TunnelShare-2kwqb1j6.js → TunnelShare-r5tc2qk5.js} +2 -2
  7. package/{agent.command-7357vxd8.js → agent.command-9cg8m87z.js} +6 -6
  8. package/akanCode.js +100 -0
  9. package/{application.command-8s8ns95d.js → application.command-x75men32.js} +6 -15
  10. package/{applicationBuildRunner-4f1wrba0.js → applicationBuildRunner-d3702f1w.js} +2 -2
  11. package/{applicationReleasePackager-jkjmy5ee.js → applicationReleasePackager-2kdyj3te.js} +1 -1
  12. package/buildBatch.proc.js +2 -2
  13. package/{capacitorApp-hqvsrec4.js → capacitorApp-qk975zyz.js} +2 -2
  14. package/{cloud.command-p5dnhft9.js → cloud.command-a09wggfq.js} +10 -22
  15. package/cloud.script-qd3hpybf.js +20 -0
  16. package/code.command-ca7rpmez.js +147 -0
  17. package/commandManifest.json +1 -1
  18. package/consoleToStderr-h5bryj94.js +7 -0
  19. package/{context.command-wsjt98c1.js → context.command-9kbm0527.js} +14 -15
  20. package/context.runner-wtqpw7mh.js +22 -0
  21. package/{devTui-myg3pqeg.js → devTui-v19fbnpk.js} +114 -8
  22. package/{devTuiView-gz2fbf4v.js → devTuiView-pa63jrjq.js} +1 -1
  23. package/guideline.command-qvrh5pnx.js +48 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +46 -15
  25. package/guidelines/conventions/conventions.instruction.md +9 -4
  26. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +1 -1
  27. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +1 -0
  28. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +62 -0
  29. package/incrementalBuilder.proc.js +1 -1
  30. package/{index-bz10w9t2.js → index-0qvq9aeb.js} +6 -16
  31. package/{index-jwjaf9fy.js → index-1p146w4s.js} +44 -44
  32. package/{index-7f4dsf9v.js → index-1sggq0m8.js} +1 -1
  33. package/{index-15r9a10e.js → index-3dmebxnk.js} +2 -2
  34. package/{index-vrwdx0zx.js → index-3hr7cpy1.js} +1 -1
  35. package/{index-s3fvrydw.js → index-5hy11qaq.js} +9 -9
  36. package/{index-8pkvrcv1.js → index-658xafm1.js} +3 -3
  37. package/index-7md2g9vw.js +6 -0
  38. package/{index-ys7dvdb3.js → index-84b080zh.js} +1 -1
  39. package/index-8k16atqy.js +82 -0
  40. package/{index-t3c045et.js → index-91brpnzb.js} +8 -21
  41. package/{index-em4swemt.js → index-bchkysja.js} +3 -3
  42. package/{index-j7rwsd23.js → index-bzjnp9ap.js} +22 -80
  43. package/{index-cnzm1wn5.js → index-chp5gqmm.js} +88 -169
  44. package/{index-h3g1152j.js → index-d35a8vc0.js} +4 -4
  45. package/{index-1nwxbr2y.js → index-ea1smknd.js} +2 -2
  46. package/index-egt4vfgb.js +69 -0
  47. package/index-erzcytap.js +2434 -0
  48. package/{index-sh88vtye.js → index-f4gpcp98.js} +1 -1
  49. package/{index-b3ae16mg.js → index-gca9y01h.js} +1 -1
  50. package/{index-wmb05v4q.js → index-gm76f0qb.js} +1 -1
  51. package/{index-649svkyt.js → index-myj01w9r.js} +8 -15
  52. package/index-sxg87a8f.js +727 -0
  53. package/{index-h7yw2z9n.js → index-twq9p2sp.js} +8 -8
  54. package/index-v156f836.js +234 -0
  55. package/index-v1y3cx0k.js +61 -0
  56. package/{index-t8gvwcrx.js → index-va2je2qm.js} +2 -2
  57. package/index-wfwknnje.js +124 -0
  58. package/index-x7g1qzpt.js +121 -0
  59. package/index.js +21 -20
  60. package/{library.command-1f9h2ex6.js → library.command-n89re6cx.js} +3 -3
  61. package/{localRegistry.command-w6w35j4r.js → localRegistry.command-1mmnegph.js} +9 -9
  62. package/{module.command-8zkq0q9q.js → module.command-4m82zgfp.js} +7 -10
  63. package/{package.command-y2rh4np7.js → package.command-9dv0p6zq.js} +3 -3
  64. package/package.json +9 -5
  65. package/{page.command-e5h29fyt.js → page.command-q86b0m0z.js} +3 -3
  66. package/{primitive.command-qm7tybpn.js → primitive.command-kaneasq6.js} +6 -9
  67. package/prompter-9mn3kp36.js +8 -0
  68. package/{quality.command-5evdd263.js → quality.command-sgapx315.js} +65 -5
  69. package/{repair.command-c2amvt74.js → repair.command-efc5gdbq.js} +5 -5
  70. package/{scalar.command-a1ewkmab.js → scalar.command-e2rpkhqy.js} +6 -9
  71. package/skills/akan-debug/SKILL.md +42 -0
  72. package/skills/akan-deps/SKILL.md +50 -0
  73. package/skills/akan-err/SKILL.md +39 -0
  74. package/skills/akan-module/SKILL.md +45 -0
  75. package/skills/akan-scaffold/SKILL.md +76 -0
  76. package/skills/akan-scalar/SKILL.md +52 -0
  77. package/skills/akan-service/SKILL.md +34 -0
  78. package/skills/akan-store/SKILL.md +63 -0
  79. package/skills/akan-ui/SKILL.md +63 -0
  80. package/skills/akan-validate/SKILL.md +44 -0
  81. package/{subspace.command-3w5x13a1.js → subspace.command-005rz1x6.js} +5 -5
  82. package/{tunnel.command-09n8h4s1.js → tunnel.command-66j9estx.js} +4 -4
  83. package/{workflow.command-y3fct1rq.js → workflow.command-z1xcm4c7.js} +9 -12
  84. package/{workspace.command-p6td4qad.js → workspace.command-4ahrhp3k.js} +23 -22
  85. package/abstractCompactor-g04vqcff.js +0 -102
  86. package/cloud.script-gxpbhras.js +0 -20
  87. package/guideline.command-snqzfyt0.js +0 -360
  88. package/index-5tw8t1ts.js +0 -338
  89. package/index-j1tajctp.js +0 -229
  90. package/index-kpnp2e10.js +0 -841
  91. package/index-meh558jy.js +0 -65
  92. /package/{index-5nyznra1.js → index-p1j0cyvd.js} +0 -0
@@ -0,0 +1,1103 @@
1
+ // @bun
2
+ import {
3
+ SubagentPool2
4
+ } from "./index-x7g1qzpt.js";
5
+ import {
6
+ CodeTuiClipboard2,
7
+ CodeTuiCommands2,
8
+ CodeTuiLines2,
9
+ CodeTuiEditor2,
10
+ CodeTuiFiles2,
11
+ CodeTuiMarkdown2,
12
+ CodeTuiParts2
13
+ } from "./index-sxg87a8f.js";
14
+
15
+ // pkgs/@akanjs/cli/code/CodeTui.tsx
16
+ import {
17
+ CodeTranscript,
18
+ codeAgentClip
19
+ } from "akanjs/common";
20
+ import { render } from "ink";
21
+
22
+ // pkgs/@akanjs/cli/ui/CodeTuiApp.tsx
23
+ import { Box, Text, useCursor, useInput } from "ink";
24
+ import { useEffect, useRef, useState } from "react";
25
+ import { jsxDEV } from "react/jsx-dev-runtime";
26
+ var promptMarkWidth = 2;
27
+ var pasteOpen = "[200~";
28
+ var pasteClose = "[201~";
29
+ var Styled = ({ span }) => /* @__PURE__ */ jsxDEV(Text, {
30
+ bold: span.bold,
31
+ color: span.color,
32
+ dimColor: span.dim,
33
+ italic: span.italic,
34
+ strikethrough: span.strikethrough,
35
+ underline: span.underline,
36
+ children: span.text
37
+ }, undefined, false, undefined, this);
38
+ var Line = ({ line }) => /* @__PURE__ */ jsxDEV(Text, {
39
+ wrap: "truncate",
40
+ children: line.spans.map((span, at) => /* @__PURE__ */ jsxDEV(Styled, {
41
+ span
42
+ }, `${line.key}:${at}`, false, undefined, this))
43
+ }, undefined, false, undefined, this);
44
+ var Transcript = ({ lines, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
45
+ flexDirection: "column",
46
+ width,
47
+ height,
48
+ paddingX: 1,
49
+ children: lines.map((line) => /* @__PURE__ */ jsxDEV(Line, {
50
+ line
51
+ }, line.key, false, undefined, this))
52
+ }, undefined, false, undefined, this);
53
+ var Overlay = ({
54
+ overlay,
55
+ width,
56
+ height
57
+ }) => /* @__PURE__ */ jsxDEV(Box, {
58
+ borderDimColor: true,
59
+ borderStyle: "round",
60
+ flexDirection: "column",
61
+ height,
62
+ paddingX: 1,
63
+ width,
64
+ children: [
65
+ /* @__PURE__ */ jsxDEV(Text, {
66
+ wrap: "truncate",
67
+ children: [
68
+ /* @__PURE__ */ jsxDEV(Text, {
69
+ bold: true,
70
+ color: "cyan",
71
+ children: overlay.title
72
+ }, undefined, false, undefined, this),
73
+ overlay.above || overlay.below ? /* @__PURE__ */ jsxDEV(Text, {
74
+ dimColor: true,
75
+ children: ` \u25B2${overlay.above} \u25BC${overlay.below}`
76
+ }, undefined, false, undefined, this) : null
77
+ ]
78
+ }, undefined, true, undefined, this),
79
+ overlay.lines.map((line) => /* @__PURE__ */ jsxDEV(Line, {
80
+ line
81
+ }, line.key, false, undefined, this))
82
+ ]
83
+ }, undefined, true, undefined, this);
84
+ var Rule = ({ spans }) => /* @__PURE__ */ jsxDEV(Text, {
85
+ wrap: "truncate",
86
+ children: spans.map((span, at) => /* @__PURE__ */ jsxDEV(Styled, {
87
+ span
88
+ }, `${at}:${span.text}`, false, undefined, this))
89
+ }, undefined, false, undefined, this);
90
+ var Menu = ({ options, selected, marker, checked }) => /* @__PURE__ */ jsxDEV(Box, {
91
+ flexDirection: "column",
92
+ children: options.map((option, idx) => /* @__PURE__ */ jsxDEV(Text, {
93
+ wrap: "truncate",
94
+ children: [
95
+ /* @__PURE__ */ jsxDEV(Text, {
96
+ color: idx === selected ? "cyan" : undefined,
97
+ children: idx === selected ? "\u276F " : " "
98
+ }, undefined, false, undefined, this),
99
+ checked ? /* @__PURE__ */ jsxDEV(Text, {
100
+ children: checked.includes(option.key) ? "[x] " : "[ ] "
101
+ }, undefined, false, undefined, this) : null,
102
+ /* @__PURE__ */ jsxDEV(Text, {
103
+ bold: idx === selected && marker,
104
+ children: option.label
105
+ }, undefined, false, undefined, this),
106
+ option.detail ? /* @__PURE__ */ jsxDEV(Text, {
107
+ dimColor: true,
108
+ children: ` ${option.detail}`
109
+ }, undefined, false, undefined, this) : null
110
+ ]
111
+ }, option.key, true, undefined, this))
112
+ }, undefined, false, undefined, this);
113
+ var CodeTuiApp = ({ actions }) => {
114
+ const [snapshot, setSnapshot] = useState(actions.snapshot);
115
+ const pasting = useRef(null);
116
+ const { setCursorPosition } = useCursor();
117
+ useEffect(() => {
118
+ const unsubscribe = actions.subscribe(() => setSnapshot(actions.snapshot()));
119
+ setSnapshot(actions.snapshot());
120
+ return unsubscribe;
121
+ }, [actions]);
122
+ setCursorPosition(snapshot.cursor ?? undefined);
123
+ useInput((input, key) => {
124
+ const page = Math.max(1, snapshot.bodyHeight - 3);
125
+ if (input === pasteOpen) {
126
+ pasting.current = [];
127
+ return;
128
+ }
129
+ if (input === pasteClose) {
130
+ const text = pasting.current?.join("") ?? "";
131
+ pasting.current = null;
132
+ return actions.pasted(text);
133
+ }
134
+ if (pasting.current) {
135
+ pasting.current.push(key.return ? `
136
+ ` : key.tab ? "\t" : input);
137
+ return;
138
+ }
139
+ if (key.ctrl && input === "c")
140
+ return actions.interrupt();
141
+ if (key.pageUp)
142
+ return actions.scroll(-page);
143
+ if (key.pageDown)
144
+ return actions.scroll(page);
145
+ if (key.escape)
146
+ return actions.cancel();
147
+ if (snapshot.overlay) {
148
+ if (key.upArrow)
149
+ return actions.scroll(-1);
150
+ if (key.downArrow)
151
+ return actions.scroll(1);
152
+ if (key.return)
153
+ return snapshot.overlay.selectable ? actions.submit() : actions.cancel();
154
+ return;
155
+ }
156
+ if (snapshot.mode === "confirm") {
157
+ if (input === "y" || input === "Y")
158
+ return actions.answerConfirm(true);
159
+ if (input === "n" || input === "N")
160
+ return actions.answerConfirm(false);
161
+ return;
162
+ }
163
+ if (snapshot.mode === "select") {
164
+ if (key.upArrow)
165
+ return actions.move(-1);
166
+ if (key.downArrow)
167
+ return actions.move(1);
168
+ if (input === " " && snapshot.multiSelect)
169
+ return actions.toggle();
170
+ if (key.return)
171
+ return actions.submit();
172
+ return;
173
+ }
174
+ if (key.tab)
175
+ return actions.complete();
176
+ if (key.upArrow)
177
+ return actions.vertical(-1);
178
+ if (key.downArrow)
179
+ return actions.vertical(1);
180
+ if (key.leftArrow)
181
+ return actions.edit(key.meta ? "wordLeft" : "left");
182
+ if (key.rightArrow)
183
+ return actions.edit(key.meta ? "wordRight" : "right");
184
+ if (key.return && (key.shift || key.meta))
185
+ return actions.newline();
186
+ if (key.ctrl && input === "j")
187
+ return actions.newline();
188
+ if (key.return)
189
+ return actions.submit();
190
+ if (key.backspace || key.delete)
191
+ return actions.edit(key.meta ? "deleteWord" : "backspace");
192
+ if (key.ctrl && input === "w")
193
+ return actions.edit("deleteWord");
194
+ if (key.ctrl && input === "u")
195
+ return actions.edit("deleteToStart");
196
+ if (key.ctrl && input === "a")
197
+ return actions.edit("home");
198
+ if (key.ctrl && input === "e")
199
+ return actions.edit("end");
200
+ if (key.ctrl && input === "d")
201
+ return actions.edit("delete");
202
+ if (key.ctrl && input === "v")
203
+ return actions.paste();
204
+ if (input && !key.ctrl && !key.meta)
205
+ return actions.type(input);
206
+ });
207
+ const paneWidth = snapshot.columns;
208
+ return /* @__PURE__ */ jsxDEV(Box, {
209
+ flexDirection: "column",
210
+ width: paneWidth,
211
+ height: snapshot.frameRows,
212
+ children: [
213
+ snapshot.overlay ? /* @__PURE__ */ jsxDEV(Overlay, {
214
+ height: snapshot.bodyHeight,
215
+ overlay: snapshot.overlay,
216
+ width: paneWidth
217
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Transcript, {
218
+ height: snapshot.bodyHeight,
219
+ lines: snapshot.lines,
220
+ width: paneWidth
221
+ }, undefined, false, undefined, this),
222
+ snapshot.menu.length ? /* @__PURE__ */ jsxDEV(Menu, {
223
+ marker: true,
224
+ options: snapshot.menu,
225
+ selected: snapshot.menuSelected
226
+ }, undefined, false, undefined, this) : null,
227
+ /* @__PURE__ */ jsxDEV(Rule, {
228
+ spans: snapshot.topRule
229
+ }, undefined, false, undefined, this),
230
+ snapshot.mode === "input" ? /* @__PURE__ */ jsxDEV(Box, {
231
+ flexDirection: "column",
232
+ children: [
233
+ snapshot.offered ? /* @__PURE__ */ jsxDEV(Text, {
234
+ dimColor: true,
235
+ wrap: "truncate",
236
+ children: " \u29C9 an image is on your clipboard \u2014 cmd+v or ^v attaches it"
237
+ }, undefined, false, undefined, this) : null,
238
+ snapshot.input.map((row, at) => /* @__PURE__ */ jsxDEV(Text, {
239
+ wrap: "truncate",
240
+ children: [
241
+ /* @__PURE__ */ jsxDEV(Text, {
242
+ color: snapshot.status ? "yellow" : "cyan",
243
+ children: at === 0 ? snapshot.status ? "\u25D0 " : "\u203A " : " "
244
+ }, undefined, false, undefined, this),
245
+ at === 0 && !row && snapshot.input.length === 1 ? /* @__PURE__ */ jsxDEV(Text, {
246
+ dimColor: true,
247
+ children: snapshot.placeholder
248
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
249
+ children: row
250
+ }, undefined, false, undefined, this)
251
+ ]
252
+ }, `in:${at}`, true, undefined, this))
253
+ ]
254
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Box, {
255
+ flexDirection: "column",
256
+ children: [
257
+ /* @__PURE__ */ jsxDEV(Text, {
258
+ color: "yellow",
259
+ wrap: "truncate",
260
+ children: snapshot.prompt
261
+ }, undefined, false, undefined, this),
262
+ snapshot.options.length ? /* @__PURE__ */ jsxDEV(Menu, {
263
+ checked: snapshot.multiSelect ? snapshot.checked : undefined,
264
+ marker: false,
265
+ options: snapshot.options,
266
+ selected: snapshot.selected
267
+ }, undefined, false, undefined, this) : null
268
+ ]
269
+ }, undefined, true, undefined, this),
270
+ /* @__PURE__ */ jsxDEV(Rule, {
271
+ spans: snapshot.bottomRule
272
+ }, undefined, false, undefined, this),
273
+ snapshot.notice ? /* @__PURE__ */ jsxDEV(Text, {
274
+ color: "cyan",
275
+ wrap: "truncate",
276
+ children: snapshot.notice
277
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
278
+ dimColor: true,
279
+ wrap: "truncate",
280
+ children: snapshot.hint
281
+ }, undefined, false, undefined, this)
282
+ ]
283
+ }, undefined, true, undefined, this);
284
+ };
285
+
286
+ // pkgs/@akanjs/cli/code/CodeTui.tsx
287
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
288
+
289
+ class CodeTui {
290
+ static frameMs = 34;
291
+ static clipboardMs = 2000;
292
+ static noticeMs = 4000;
293
+ static chromeRows = 1;
294
+ static promptRuleRows = 1;
295
+ static minBodyRows = 3;
296
+ static maxInputRows = 8;
297
+ static title = "Akan \u2014 write one line, deploy every stack";
298
+ static imageMark = /\[image #(\d+)] ?/g;
299
+ static pasteOn = "\x1B[?2004h";
300
+ static pasteOff = "\x1B[?2004l";
301
+ #agent;
302
+ #transcript = new CodeTranscript;
303
+ #editor = new CodeTuiEditor2;
304
+ #files;
305
+ #listeners = new Set;
306
+ #history = [];
307
+ #instance;
308
+ #stdout;
309
+ #historyAt = -1;
310
+ #anchor = null;
311
+ #selected = 0;
312
+ #menuAt = 0;
313
+ #checked = [];
314
+ #overlay = null;
315
+ #overlayAt = 0;
316
+ #picker = null;
317
+ #images = [];
318
+ #switchTo;
319
+ #offered = false;
320
+ #clipboardTimer = null;
321
+ #notice = "";
322
+ #opening;
323
+ #thinking;
324
+ #noticeTimer = null;
325
+ #frameTimer = null;
326
+ #cached = null;
327
+ #unsubscribe;
328
+ #exiting = false;
329
+ constructor(agent, { thinking = false, notice, ...streams } = {}) {
330
+ this.#agent = agent;
331
+ this.#thinking = thinking;
332
+ this.#opening = notice ?? "";
333
+ this.#files = new CodeTuiFiles2(agent.profile.paths.root);
334
+ this.#stdout = streams.stdout ?? process.stdout;
335
+ this.#stdout.on("resize", this.#onResize);
336
+ this.#instance = render(/* @__PURE__ */ jsxDEV2(CodeTuiApp, {
337
+ actions: {
338
+ subscribe: this.#subscribe,
339
+ snapshot: this.#snapshot,
340
+ type: this.#type,
341
+ edit: this.#edit,
342
+ submit: this.#submit,
343
+ newline: this.#newline,
344
+ paste: this.#paste,
345
+ pasted: this.#pasted,
346
+ complete: this.#complete,
347
+ vertical: this.#vertical,
348
+ scroll: this.#scroll,
349
+ move: this.#move,
350
+ toggle: this.#toggle,
351
+ answerConfirm: this.#answerConfirm,
352
+ cancel: this.#cancel,
353
+ interrupt: this.#interrupt
354
+ }
355
+ }, undefined, false, undefined, this), {
356
+ patchConsole: true,
357
+ exitOnCtrlC: false,
358
+ kittyKeyboard: { mode: "auto" },
359
+ ...streams
360
+ });
361
+ }
362
+ async run(seed) {
363
+ this.#stdout.write(CodeTui.pasteOn);
364
+ if (this.#opening)
365
+ this.#say(this.#opening);
366
+ this.#unsubscribe = this.#agent.on((event) => {
367
+ this.#transcript.apply(event);
368
+ this.#scheduleFrame();
369
+ });
370
+ this.#agent.announce();
371
+ this.#files.load().then(() => this.#renderNow());
372
+ this.#watchClipboard();
373
+ if (seed?.trim())
374
+ this.#send(seed.trim());
375
+ await this.#instance.waitUntilExit();
376
+ this.#close();
377
+ return this.#switchTo;
378
+ }
379
+ close() {
380
+ this.#quit();
381
+ }
382
+ #watchClipboard() {
383
+ if (!this.#agent.canSeeImages)
384
+ return;
385
+ this.#clipboardTimer = setInterval(() => {
386
+ const offered = CodeTuiClipboard2.has();
387
+ if (offered === this.#offered)
388
+ return;
389
+ this.#offered = offered;
390
+ this.#renderNow();
391
+ }, CodeTui.clipboardMs);
392
+ }
393
+ #close() {
394
+ if (this.#clipboardTimer)
395
+ clearInterval(this.#clipboardTimer);
396
+ this.#clipboardTimer = null;
397
+ if (this.#frameTimer)
398
+ clearTimeout(this.#frameTimer);
399
+ if (this.#noticeTimer)
400
+ clearTimeout(this.#noticeTimer);
401
+ this.#frameTimer = null;
402
+ this.#noticeTimer = null;
403
+ this.#stdout.off("resize", this.#onResize);
404
+ this.#unsubscribe?.();
405
+ this.#farewell();
406
+ }
407
+ #farewell() {
408
+ const info = this.#transcript.info;
409
+ if (this.#switchTo || !info?.sessionId || !this.#agent.sessions().length)
410
+ return;
411
+ if (!this.#transcript.parts.some((part) => part.kind === "user"))
412
+ return;
413
+ const name = info.name ? ` (${info.name})` : "";
414
+ this.#stdout.write(`Resume this session${name} with:
415
+ akan code --resume ${info.sessionId}
416
+ `);
417
+ }
418
+ #onResize = () => this.#renderNow();
419
+ #subscribe = (listener) => {
420
+ this.#listeners.add(listener);
421
+ return () => this.#listeners.delete(listener);
422
+ };
423
+ #scheduleFrame() {
424
+ if (this.#frameTimer)
425
+ return;
426
+ this.#frameTimer = setTimeout(() => {
427
+ this.#frameTimer = null;
428
+ this.#renderNow();
429
+ }, CodeTui.frameMs);
430
+ }
431
+ #renderNow() {
432
+ this.#cached = null;
433
+ for (const listener of this.#listeners)
434
+ listener();
435
+ }
436
+ static layout(frameRows, askRows) {
437
+ const room = Math.max(1, frameRows - CodeTui.minBodyRows - CodeTui.chromeRows);
438
+ const ask = Math.max(1, Math.min(askRows, room));
439
+ return { ask, bodyHeight: Math.max(CodeTui.minBodyRows, frameRows - ask - CodeTui.chromeRows) };
440
+ }
441
+ static frameRowsOf(terminalRows) {
442
+ return Math.max(CodeTui.minBodyRows + CodeTui.chromeRows + 1, terminalRows - 1);
443
+ }
444
+ #snapshot = () => {
445
+ if (this.#cached)
446
+ return this.#cached;
447
+ const columns = Math.max(40, this.#stdout.columns || 80);
448
+ const frameRows = CodeTui.frameRowsOf(Math.max(10, this.#stdout.rows || 24));
449
+ const question = this.#transcript.question;
450
+ const approval = this.#transcript.approval;
451
+ const mode = CodeTui.#modeOf(question, approval);
452
+ const ask = mode === "input" ? this.#inputBlock(columns, frameRows) : this.#askBlock(question, frameRows);
453
+ const { bodyHeight } = CodeTui.layout(frameRows, ask.rows);
454
+ const width = columns - 2;
455
+ const lines = this.#lines(width);
456
+ const end = this.#anchor === null ? lines.length : Math.min(lines.length, Math.max(bodyHeight, this.#anchor));
457
+ const start = Math.max(0, end - bodyHeight);
458
+ const above = start;
459
+ const below = lines.length - end;
460
+ this.#cached = {
461
+ topRule: CodeTui.rule(this.#anchor === null ? "" : `\u25B2${above} \u25BC${below} paused`, this.#transcript.info?.name ?? this.#transcript.info?.sessionId.slice(0, 8) ?? "", columns),
462
+ bottomRule: CodeTui.rule(this.#queueLabel(), this.#statusLabel(), columns),
463
+ lines: lines.slice(start, end),
464
+ above,
465
+ below,
466
+ following: this.#anchor === null,
467
+ overlay: this.#overlayBlock(width, bodyHeight),
468
+ status: this.#status(),
469
+ mode,
470
+ placeholder: this.#transcript.streaming ? "type to steer, esc to interrupt" : "ask for something, / for commands",
471
+ offered: this.#offered,
472
+ prompt: CodeTui.#promptOf(question, approval),
473
+ selected: ask.selected,
474
+ checked: this.#checked,
475
+ multiSelect: !!question?.multiSelect,
476
+ notice: this.#notice,
477
+ hint: this.#overlay ? "esc close \xB7 \u2191\u2193 scroll" : CodeTui.#hintOf(mode, !!question?.multiSelect),
478
+ columns,
479
+ frameRows,
480
+ bodyHeight,
481
+ cursor: ask.caret ? { x: promptMarkWidth + ask.caret.col, y: bodyHeight + ask.above + ask.caret.row } : null,
482
+ ...ask.snapshot
483
+ };
484
+ return this.#cached;
485
+ };
486
+ #lines(width) {
487
+ return [
488
+ ...CodeTuiLines2.rows(this.#banner(), width, "banner"),
489
+ ...CodeTuiParts2.lines(this.#transcript.parts, width, { thinking: this.#thinking })
490
+ ];
491
+ }
492
+ #banner() {
493
+ const cwd = this.#transcript.info?.cwd;
494
+ return [
495
+ { prefix: { text: "\u273B ", color: "cyan" }, spans: [{ text: CodeTui.title, bold: true }] },
496
+ {
497
+ prefix: { text: " " },
498
+ spans: [{ text: cwd ? `${cwd} \xB7 /help for commands` : "/help for commands", dim: true }]
499
+ }
500
+ ];
501
+ }
502
+ static rule(left, right, width) {
503
+ const room = (label) => label ? 3 + CodeTuiLines2.width(label) : 1;
504
+ const tail = right ? codeAgentClip(right, Math.max(1, width - 4)) : "";
505
+ const head = width - room(left) - room(tail) >= 0 ? left : "";
506
+ const fill = Math.max(0, width - room(head) - room(tail));
507
+ return [
508
+ ...head ? [
509
+ { text: "\u2500 ", dim: true },
510
+ { text: head, color: "cyan" },
511
+ { text: " ", dim: true }
512
+ ] : [{ text: "\u2500", dim: true }],
513
+ { text: "\u2500".repeat(fill), dim: true },
514
+ ...tail ? [
515
+ { text: " ", dim: true },
516
+ { text: tail, dim: true },
517
+ { text: " \u2500", dim: true }
518
+ ] : [{ text: "\u2500", dim: true }]
519
+ ];
520
+ }
521
+ #overlayBlock(width, height) {
522
+ if (this.#picker)
523
+ return this.#pickerBlock(width, height);
524
+ const overlay = this.#overlay;
525
+ if (!overlay)
526
+ return null;
527
+ const room = Math.max(1, height - 3);
528
+ const body = Math.max(8, width - 4);
529
+ const all = CodeTui.#overlayRows(overlay.text, body);
530
+ const first = Math.max(0, Math.min(this.#overlayAt, all.length - room));
531
+ return {
532
+ title: overlay.title,
533
+ lines: all.slice(first, first + room),
534
+ above: first,
535
+ below: Math.max(0, all.length - first - room),
536
+ selectable: false
537
+ };
538
+ }
539
+ #pickerBlock(width, height) {
540
+ const picker = this.#picker;
541
+ if (!picker)
542
+ return null;
543
+ const room = Math.max(1, height - 3);
544
+ const body = Math.max(8, width - 4);
545
+ const first = Math.max(0, Math.min(picker.at - Math.floor(room / 2), picker.entries.length - room));
546
+ const rows = picker.entries.slice(first, first + room).map((entry, at) => CodeTui.#sessionRow(entry, first + at === picker.at));
547
+ return {
548
+ title: `sessions \xB7 ${picker.entries.length}`,
549
+ lines: rows.flatMap((row, at) => CodeTuiLines2.rows([row], body, `sessions:${first + at}`).slice(0, 1)),
550
+ above: first,
551
+ below: Math.max(0, picker.entries.length - first - room),
552
+ selectable: true
553
+ };
554
+ }
555
+ static #sessionRow(entry, selected) {
556
+ const label = entry.name ?? entry.id.slice(0, 8);
557
+ return {
558
+ prefix: { text: selected ? "\u276F " : " ", color: "cyan" },
559
+ spans: [
560
+ { text: label, bold: selected },
561
+ { text: ` ${CodeTui.#ago(entry.updatedAt)} \xB7 ${entry.turns} turn${entry.turns === 1 ? "" : "s"}`, dim: true },
562
+ ...entry.opening ? [{ text: ` ${entry.opening}`, dim: true }] : []
563
+ ]
564
+ };
565
+ }
566
+ static #ago(at) {
567
+ const minutes = Math.max(0, Math.round((Date.now() - at) / 60000));
568
+ if (minutes < 60)
569
+ return `${minutes}m ago`;
570
+ if (minutes < 60 * 24)
571
+ return `${Math.round(minutes / 60)}h ago`;
572
+ return `${Math.round(minutes / (60 * 24))}d ago`;
573
+ }
574
+ #inputBlock(columns, frameRows) {
575
+ const all = this.#menu()?.options ?? [];
576
+ const { rows, row, col } = this.#editor.layout(columns - promptMarkWidth - 1);
577
+ const offered = this.#offered ? 1 : 0;
578
+ const room = frameRows - CodeTui.minBodyRows - CodeTui.chromeRows - 2 * CodeTui.promptRuleRows - offered;
579
+ const menuRows = Math.max(0, Math.min(all.length, room - 1));
580
+ const menuFrom = Math.max(0, Math.min(this.#menuAt - Math.floor(menuRows / 2), all.length - menuRows));
581
+ const menu = all.slice(menuFrom, menuFrom + menuRows);
582
+ const above = menu.length + CodeTui.promptRuleRows + offered;
583
+ const chrome = above + CodeTui.promptRuleRows;
584
+ const budget = Math.max(1, Math.min(CodeTui.maxInputRows, room - menu.length));
585
+ const first = Math.max(0, Math.min(row - budget + 1, rows.length - budget));
586
+ const shown = rows.slice(first, first + budget);
587
+ return {
588
+ rows: chrome + shown.length,
589
+ above,
590
+ selected: 0,
591
+ caret: { row: row - first, col },
592
+ snapshot: {
593
+ input: shown.map((entry) => entry.text),
594
+ menu,
595
+ menuSelected: this.#menuAt - menuFrom,
596
+ options: []
597
+ }
598
+ };
599
+ }
600
+ #askBlock(question, frameRows) {
601
+ const all = CodeTui.#optionsOf(question);
602
+ const chrome = 1 + 2 * CodeTui.promptRuleRows;
603
+ const budget = Math.max(1, frameRows - CodeTui.minBodyRows - CodeTui.chromeRows - chrome);
604
+ const shown = Math.min(all.length, budget);
605
+ const first = Math.max(0, Math.min(this.#selected - Math.floor(shown / 2), all.length - shown));
606
+ return {
607
+ rows: chrome + shown,
608
+ above: CodeTui.promptRuleRows,
609
+ selected: this.#selected - first,
610
+ caret: undefined,
611
+ snapshot: {
612
+ input: [],
613
+ menu: [],
614
+ menuSelected: 0,
615
+ options: all.slice(first, first + shown)
616
+ }
617
+ };
618
+ }
619
+ #menu() {
620
+ const prefix = CodeTuiCommands2.prefixOf(this.#editor.text, this.#editor.caret);
621
+ if (prefix !== undefined)
622
+ return {
623
+ kind: "command",
624
+ from: 0,
625
+ to: this.#editor.text.length,
626
+ options: CodeTuiCommands2.matches(prefix).map((command) => ({
627
+ key: command.name,
628
+ label: `/${command.name}${command.arg ? ` ${command.arg}` : ""}`,
629
+ detail: command.desc
630
+ }))
631
+ };
632
+ const token = this.#editor.token();
633
+ if (!token.text.startsWith("@"))
634
+ return null;
635
+ const options = this.#files.match(token.text.slice(1)).map((file) => ({ key: file, label: `@${file}` }));
636
+ if (!options.length && this.#files.ready)
637
+ return null;
638
+ return { kind: "file", from: token.from, to: token.to, options };
639
+ }
640
+ static #overlayRows(text, width) {
641
+ return text.split(`
642
+ `).flatMap((line, at) => CodeTuiLines2.rows(line.trim() ? CodeTuiMarkdown2.rows(line, width) : [{ spans: [{ text: " " }] }], width, `overlay:${at}`));
643
+ }
644
+ #queueLabel() {
645
+ const queue = this.#transcript.queue;
646
+ const parts = [];
647
+ if (queue.steering || queue.followUp)
648
+ parts.push(`queued ${queue.steering + queue.followUp}`);
649
+ if (this.#transcript.compacting)
650
+ parts.push("compacting");
651
+ return parts.join(" \xB7 ");
652
+ }
653
+ #statusLabel() {
654
+ const info = this.#transcript.info;
655
+ if (!info)
656
+ return "starting\u2026";
657
+ const context = this.#transcript.context;
658
+ const window = info.contextTokens ? `${Math.round(info.contextTokens / 1000)}k` : "";
659
+ const used = context?.max ? `${Math.round(context.used / context.max * 100)}% of ${window}` : "";
660
+ return [info.model?.name ?? "no model", used || window && `${window} ctx`, info.profile, info.effort].filter((part) => !!part).join(" \xB7 ");
661
+ }
662
+ #status() {
663
+ if (this.#transcript.compacting)
664
+ return "compacting";
665
+ return this.#transcript.streaming ? "working" : "";
666
+ }
667
+ static #modeOf(question, approval) {
668
+ if (approval)
669
+ return "confirm";
670
+ if (!question)
671
+ return "input";
672
+ if (question.kind === "confirm")
673
+ return "confirm";
674
+ if (question.kind === "select" && question.options?.length)
675
+ return "select";
676
+ return "input";
677
+ }
678
+ static #optionsOf(question) {
679
+ return (question?.options ?? []).map((option) => ({
680
+ key: option.key,
681
+ label: option.label,
682
+ ...option.detail ? { detail: option.detail } : {}
683
+ }));
684
+ }
685
+ static #promptOf(question, approval) {
686
+ if (approval)
687
+ return `approve? ${approval.summary} [y/n]`;
688
+ return question ? `? ${question.prompt}` : "";
689
+ }
690
+ static #hintOf(mode, multiSelect) {
691
+ if (mode === "confirm")
692
+ return "y yes \xB7 n no \xB7 esc no";
693
+ if (mode === "select")
694
+ return `\u2191\u2193 choose${multiSelect ? " \xB7 space toggle" : ""} \xB7 enter answer \xB7 esc skip \xB7 pgup/pgdn scroll`;
695
+ return "enter send \xB7 shift+enter newline \xB7 \u2191\u2193 move \xB7 esc interrupt \xB7 pgup/pgdn scroll \xB7 ^c quit \xB7 /help";
696
+ }
697
+ #type = (text) => {
698
+ const pasted = this.#agent.canSeeImages ? CodeTuiClipboard2.imagePathsIn(text) : undefined;
699
+ if (pasted?.images.length) {
700
+ for (const file of pasted.images)
701
+ this.#attach(file);
702
+ if (pasted.rest)
703
+ this.#editor.insert(pasted.rest);
704
+ } else
705
+ this.#editor.insert(text);
706
+ this.#menuAt = 0;
707
+ this.#historyAt = -1;
708
+ this.#renderNow();
709
+ };
710
+ #newline = () => {
711
+ this.#editor.newline();
712
+ this.#historyAt = -1;
713
+ this.#renderNow();
714
+ };
715
+ #edit = (action) => {
716
+ const width = this.#snapshot().columns - promptMarkWidth - 1;
717
+ const run = {
718
+ backspace: () => this.#editor.backspace(),
719
+ delete: () => this.#editor.deleteForward(),
720
+ left: () => this.#editor.caret ? this.#editor.left() : this.#openSessions(),
721
+ right: () => this.#editor.right(),
722
+ wordLeft: () => this.#editor.wordLeft(),
723
+ wordRight: () => this.#editor.wordRight(),
724
+ home: () => this.#editor.homeOfRow(width),
725
+ end: () => this.#editor.endOfRow(width),
726
+ deleteWord: () => this.#editor.deleteWord(),
727
+ deleteToStart: () => this.#editor.deleteToLineStart()
728
+ };
729
+ run[action]();
730
+ if (action.startsWith("delete") || action === "backspace")
731
+ this.#historyAt = -1;
732
+ this.#renderNow();
733
+ };
734
+ #vertical = (delta) => {
735
+ if (this.#picker)
736
+ return this.#move(delta);
737
+ if (this.#overlay)
738
+ return this.#scroll(delta);
739
+ const menu = this.#menu()?.options ?? [];
740
+ if (menu.length) {
741
+ this.#menuAt = (this.#menuAt + delta + menu.length) % menu.length;
742
+ return this.#renderNow();
743
+ }
744
+ if (this.#historyAt < 0 && this.#editor.move(delta, this.#snapshot().columns - promptMarkWidth - 1))
745
+ return this.#renderNow();
746
+ if (delta < 0)
747
+ return this.#historyPrev();
748
+ return this.#historyNext();
749
+ };
750
+ #complete = () => {
751
+ const menu = this.#menu();
752
+ const chosen = menu?.options[this.#menuAt];
753
+ if (!menu || !chosen)
754
+ return;
755
+ const command = CodeTuiCommands2.all.find((entry) => entry.name === chosen.key);
756
+ const text = menu.kind === "command" ? `/${chosen.key}${command?.arg ? " " : ""}` : `@${chosen.key} `;
757
+ this.#editor.replace(menu.from, menu.to, text);
758
+ this.#menuAt = 0;
759
+ this.#renderNow();
760
+ };
761
+ #historyPrev() {
762
+ if (!this.#history.length)
763
+ return;
764
+ this.#historyAt = this.#historyAt < 0 ? this.#history.length - 1 : Math.max(0, this.#historyAt - 1);
765
+ this.#editor.set(this.#history[this.#historyAt] ?? "");
766
+ this.#renderNow();
767
+ }
768
+ #historyNext() {
769
+ if (this.#historyAt < 0)
770
+ return;
771
+ this.#historyAt += 1;
772
+ if (this.#historyAt >= this.#history.length) {
773
+ this.#historyAt = -1;
774
+ this.#editor.clear();
775
+ } else
776
+ this.#editor.set(this.#history[this.#historyAt] ?? "");
777
+ this.#renderNow();
778
+ }
779
+ #scroll = (delta) => {
780
+ if (this.#picker)
781
+ return this.#move(Math.sign(delta));
782
+ if (this.#overlay) {
783
+ this.#overlayAt = Math.max(0, this.#overlayAt + Math.trunc(delta));
784
+ return this.#renderNow();
785
+ }
786
+ const snapshot = this.#snapshot();
787
+ const total = snapshot.above + snapshot.lines.length + snapshot.below;
788
+ const rows = Math.max(1, snapshot.lines.length);
789
+ if (total <= rows)
790
+ return;
791
+ const end = this.#anchor === null ? total : this.#anchor;
792
+ const next = Math.min(total, Math.max(rows, end + Math.trunc(delta)));
793
+ this.#anchor = next >= total ? null : next;
794
+ this.#renderNow();
795
+ };
796
+ #move = (delta) => {
797
+ const picker = this.#picker;
798
+ if (picker) {
799
+ picker.at = Math.max(0, Math.min(picker.entries.length - 1, picker.at + delta));
800
+ return this.#renderNow();
801
+ }
802
+ const count = this.#transcript.question?.options?.length ?? 0;
803
+ if (!count)
804
+ return;
805
+ this.#selected = (this.#selected + delta + count) % count;
806
+ this.#renderNow();
807
+ };
808
+ #toggle = () => {
809
+ const option = this.#transcript.question?.options?.[this.#selected];
810
+ if (!option)
811
+ return;
812
+ this.#checked = this.#checked.includes(option.key) ? this.#checked.filter((key) => key !== option.key) : [...this.#checked, option.key];
813
+ this.#renderNow();
814
+ };
815
+ #answerConfirm = (value) => {
816
+ const approval = this.#transcript.approval;
817
+ if (approval)
818
+ return void this.#agent.approve(approval.approvalId, value);
819
+ const question = this.#transcript.question;
820
+ if (question)
821
+ this.#answer(question.questionId, { text: value ? "yes" : "no" });
822
+ };
823
+ #cancel = () => {
824
+ if (this.#overlay || this.#picker)
825
+ return this.#dismiss();
826
+ const approval = this.#transcript.approval;
827
+ if (approval)
828
+ return void this.#agent.approve(approval.approvalId, false);
829
+ const question = this.#transcript.question;
830
+ if (question)
831
+ return void this.#answer(question.questionId, {});
832
+ if (this.#transcript.streaming)
833
+ return void this.#agent.abort();
834
+ if (!this.#editor.isEmpty) {
835
+ this.#editor.clear();
836
+ this.#renderNow();
837
+ }
838
+ };
839
+ #interrupt = () => {
840
+ if (this.#transcript.streaming) {
841
+ this.#agent.abort();
842
+ this.#say("interrupted \u2014 ^c again to quit");
843
+ return;
844
+ }
845
+ this.#quit();
846
+ };
847
+ #quit() {
848
+ if (this.#exiting)
849
+ return;
850
+ this.#exiting = true;
851
+ this.#stdout.write(CodeTui.pasteOff);
852
+ this.#instance.clear();
853
+ this.#instance.unmount();
854
+ }
855
+ #submit = () => {
856
+ if (this.#picker)
857
+ return this.#resume(this.#picker.entries[this.#picker.at]?.id);
858
+ const question = this.#transcript.question;
859
+ if (question)
860
+ return void this.#answerCurrent(question.questionId);
861
+ const menu = this.#menu();
862
+ if (menu?.kind === "file")
863
+ return this.#complete();
864
+ const chosen = menu?.options[this.#menuAt];
865
+ const raw = chosen ? `/${chosen.key}` : this.#editor.text;
866
+ const pending = this.#pending(raw);
867
+ if (!pending.text && !pending.images.length)
868
+ return;
869
+ this.#editor.clear();
870
+ this.#historyAt = -1;
871
+ if (pending.text)
872
+ this.#history.push(pending.text);
873
+ if (pending.text.startsWith("/"))
874
+ return this.#command(pending.text);
875
+ this.#send(pending.text, pending.images);
876
+ };
877
+ #send(text, images = []) {
878
+ this.#images = [];
879
+ this.#transcript.echo(text, images.length);
880
+ this.#renderNow();
881
+ this.#agent.prompt(text, images).catch((error) => this.#fail(error));
882
+ }
883
+ #paste = () => {
884
+ if (!this.#agent.canSeeImages)
885
+ return this.#open("images", CodeTui.#imageHelp(this.#transcript.info?.model));
886
+ CodeTuiClipboard2.image().then((file) => file ? this.#attach(file) : this.#say("nothing on the clipboard we can attach")).catch((error) => this.#fail(error));
887
+ };
888
+ #pasted = (text) => {
889
+ if (text)
890
+ return this.#type(text.replace(/\r\n?/g, `
891
+ `));
892
+ if (CodeTuiClipboard2.has())
893
+ this.#paste();
894
+ };
895
+ #attach(file) {
896
+ this.#images.push(file);
897
+ this.#editor.insert(`[image #${this.#images.length}] `);
898
+ this.#renderNow();
899
+ }
900
+ #pending(text) {
901
+ const marks = [...text.matchAll(CodeTui.imageMark)].map((match) => Number(match[1]));
902
+ const images = marks.map((mark) => this.#images[mark - 1]).filter((file) => !!file).map((path) => ({ path }));
903
+ return { images, text: text.replace(CodeTui.imageMark, "").trim() };
904
+ }
905
+ static #imageHelp(model) {
906
+ const provider = model?.provider ?? "<provider>";
907
+ const id = model?.id ?? "<model>";
908
+ return [
909
+ `The model catalogue declares ${provider}/${id} text-only, so an image would go to a model that cannot read it.`,
910
+ "",
911
+ "If that is wrong, override the entry in ~/.akan/code/models.json:",
912
+ "",
913
+ `{"providers":{"${provider}":{"modelOverrides":{"${id}":{"input":["text","image"]}}}}}`
914
+ ].join(`
915
+ `);
916
+ }
917
+ #openSessions = () => {
918
+ const entries = this.#agent.sessions();
919
+ if (!entries.length)
920
+ return this.#say("no stored sessions yet");
921
+ const current = this.#transcript.info?.sessionId;
922
+ this.#picker = {
923
+ entries,
924
+ at: Math.max(0, entries.findIndex((entry) => entry.id === current))
925
+ };
926
+ this.#renderNow();
927
+ };
928
+ #resume(id) {
929
+ if (!id)
930
+ return this.#dismiss();
931
+ if (id === this.#transcript.info?.sessionId)
932
+ return this.#dismiss();
933
+ this.#switchTo = { id };
934
+ this.#quit();
935
+ }
936
+ #fork(argument) {
937
+ if (!this.#transcript.parts.some((part) => part.kind === "user"))
938
+ return this.#open("fork", "Nothing has been said in this session yet, so a fork of it is a new session.");
939
+ const from = this.#transcript.info?.sessionId ?? "";
940
+ try {
941
+ const forked = this.#agent.fork(argument.trim() || undefined);
942
+ this.#switchTo = { id: forked.id, notice: `forked \u2014 the original is akan code --resume ${from}` };
943
+ this.#quit();
944
+ } catch (error) {
945
+ this.#fail(error);
946
+ }
947
+ }
948
+ #answerCurrent(questionId) {
949
+ const options = this.#transcript.question?.options ?? [];
950
+ const answer = options.length ? { keys: this.#transcript.question?.multiSelect ? this.#checked : [options[this.#selected]?.key ?? ""] } : { text: this.#editor.text.trim() };
951
+ this.#editor.clear();
952
+ this.#answer(questionId, answer);
953
+ }
954
+ async#answer(questionId, answer) {
955
+ this.#selected = 0;
956
+ this.#checked = [];
957
+ this.#renderNow();
958
+ await this.#agent.answer(questionId, answer);
959
+ }
960
+ #command(line) {
961
+ const [name, ...rest] = line.slice(1).split(" ");
962
+ const argument = rest.join(" ").trim();
963
+ switch (name) {
964
+ case "help":
965
+ return this.#open("help", CodeTuiCommands2.help());
966
+ case "quit":
967
+ case "exit":
968
+ return this.#quit();
969
+ case "abort":
970
+ return void this.#agent.abort();
971
+ case "clear":
972
+ this.#anchor = null;
973
+ this.#transcript.clear();
974
+ return this.#say("screen cleared \xB7 the model still remembers \xB7 /compact shortens what it carries");
975
+ case "compact":
976
+ return void this.#agent.compact(argument || undefined).catch((error) => this.#fail(error));
977
+ case "thinking":
978
+ this.#thinking = !this.#thinking;
979
+ return this.#say(this.#thinking ? "showing the model's reasoning" : "reasoning folded back to one line");
980
+ case "tools": {
981
+ const tools = this.#transcript.info?.tools ?? [];
982
+ return this.#open("tools", `${tools.length} tools
983
+
984
+ ${tools.join(" \xB7 ")}`);
985
+ }
986
+ case "model":
987
+ return this.#setModel(argument);
988
+ case "effort":
989
+ return this.#setEffort(argument);
990
+ case "peers":
991
+ return this.#open("peers", CodeTui.#peerList(this.#agent.peers()));
992
+ case "msg":
993
+ return this.#message(argument);
994
+ case "fork":
995
+ return this.#fork(argument);
996
+ case "agents":
997
+ return this.#open("agents", this.#agentList());
998
+ case "name":
999
+ if (!argument)
1000
+ return this.#open("name", "Usage: /name <what this session is about>");
1001
+ this.#agent.setName(argument);
1002
+ return this.#say(`session named ${argument}`);
1003
+ default:
1004
+ return this.#open("unknown command", `There is no /${name}.
1005
+
1006
+ ${CodeTuiCommands2.help()}`);
1007
+ }
1008
+ }
1009
+ #agentList() {
1010
+ const budget = this.#agent.profile.tools.subagent;
1011
+ if (!budget)
1012
+ return "Sub-agents are off in this profile.";
1013
+ const column = Math.max(...Object.keys(SubagentPool2.kinds).map((kind) => kind.length)) + 2;
1014
+ const kinds = Object.entries(SubagentPool2.kinds).map(([kind, spec]) => `${kind.padEnd(column)}${spec.desc}`);
1015
+ return [
1016
+ ...kinds,
1017
+ "",
1018
+ `At most ${budget.maxConcurrent} at once, ${budget.maxDepth} deep, ${budget.budget} tokens for the whole tree.`,
1019
+ "",
1020
+ "Ask for one in a sentence \u2014 the model calls the tool. Its reading never enters this conversation."
1021
+ ].join(`
1022
+ `);
1023
+ }
1024
+ static #peerList(peers) {
1025
+ if (!peers.length)
1026
+ return "No other akan code session is running in this workspace.";
1027
+ const column = Math.max(...peers.map((peer) => peer.name.length)) + 2;
1028
+ const rows = peers.map((peer) => `${peer.name.padEnd(column)}${peer.id.slice(0, 8)} ${peer.cwd}`);
1029
+ return [...rows, "", "/msg <peer> <text> hands one of them a message."].join(`
1030
+ `);
1031
+ }
1032
+ #message(argument) {
1033
+ const [target, ...rest] = argument.split(" ");
1034
+ const text = rest.join(" ").trim();
1035
+ if (!target || !text)
1036
+ return this.#open("msg", `Usage: /msg <peer> <text>
1037
+
1038
+ ${CodeTui.#peerList(this.#agent.peers())}`);
1039
+ try {
1040
+ const peer = this.#agent.send(target, text);
1041
+ this.#say(`sent to ${peer.name}`);
1042
+ } catch (error) {
1043
+ this.#fail(error);
1044
+ }
1045
+ }
1046
+ #setEffort(argument) {
1047
+ const levels = this.#agent.efforts();
1048
+ if (!levels.length)
1049
+ return this.#open("effort", "This model does no reasoning, so it has no effort to set.");
1050
+ const wanted = argument.trim().toLowerCase();
1051
+ if (!levels.includes(wanted))
1052
+ return this.#open("effort", `Usage: /effort <level>
1053
+
1054
+ now: ${this.#transcript.info?.effort ?? "unknown"}
1055
+ ${levels.join(" \xB7 ")}`);
1056
+ try {
1057
+ this.#agent.setEffort(wanted);
1058
+ this.#say(`effort ${this.#transcript.info?.effort ?? wanted}`);
1059
+ } catch (error) {
1060
+ this.#fail(error);
1061
+ }
1062
+ }
1063
+ #setModel(argument) {
1064
+ const [provider, ...rest] = argument.split("/");
1065
+ if (!provider || !rest.length)
1066
+ return this.#open("model", `Usage: /model <provider>/<id>
1067
+
1068
+ e.g. /model deepseek/deepseek-v4-pro`);
1069
+ this.#agent.setModel({ provider, id: rest.join("/") }).catch((error) => this.#fail(error));
1070
+ }
1071
+ #open(title, text) {
1072
+ this.#overlay = { title, text };
1073
+ this.#overlayAt = 0;
1074
+ this.#renderNow();
1075
+ }
1076
+ #dismiss() {
1077
+ this.#overlay = null;
1078
+ this.#overlayAt = 0;
1079
+ this.#picker = null;
1080
+ this.#renderNow();
1081
+ }
1082
+ #fail(error) {
1083
+ this.#transcript.note("error", String(error));
1084
+ this.#renderNow();
1085
+ }
1086
+ #note(text) {
1087
+ this.#transcript.note("info", text);
1088
+ this.#renderNow();
1089
+ }
1090
+ #say(text) {
1091
+ this.#notice = text;
1092
+ if (this.#noticeTimer)
1093
+ clearTimeout(this.#noticeTimer);
1094
+ this.#noticeTimer = setTimeout(() => {
1095
+ this.#notice = "";
1096
+ this.#renderNow();
1097
+ }, CodeTui.noticeMs);
1098
+ this.#renderNow();
1099
+ }
1100
+ }
1101
+ export {
1102
+ CodeTui
1103
+ };