@akanjs/cli 3.0.0-beta.3 → 3.0.0-beta.30

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 (194) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-xnfyyz8h.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-h66xb1fh.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-cwh9ezsm.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-c4v7kjff.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/application.command-yzsswrz6.js +241 -0
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-qkt6spd5.js} +121 -77
  12. package/buildBatch.proc.js +120 -32
  13. package/cloud.command-v8h688jc.js +95 -0
  14. package/cloud.script-yct1sk4k.js +20 -0
  15. package/code.command-jff3tw7y.js +154 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/context.command-y5sevk3c.js +48 -0
  19. package/context.runner-90hsmacr.js +21 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
  21. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  22. package/fileSys-wzzrntkw.js +9 -0
  23. package/guideline.command-11cndccb.js +42 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +209 -33
  25. package/guidelines/conventions/conventions.instruction.md +143 -41
  26. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +21 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +268 -6
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  34. package/incrementalBuilder.proc.js +564 -109
  35. package/{index-fz5q3gd7.js → index-0ncc0mbk.js} +10 -17
  36. package/index-167c77aq.js +69 -0
  37. package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
  38. package/index-2wc41k5w.js +49 -0
  39. package/{index-j9yhg404.js → index-35mmm9ca.js} +4 -7
  40. package/{index-a64esmtb.js → index-376gpdds.js} +523 -1022
  41. package/index-3hx1z96c.js +87 -0
  42. package/{index-v0qh106m.js → index-3rh7thcx.js} +961 -892
  43. package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
  44. package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
  45. package/index-5rp0h6na.js +1119 -0
  46. package/index-6x9p8dpb.js +4300 -0
  47. package/index-7md2g9vw.js +6 -0
  48. package/index-8tv7356t.js +700 -0
  49. package/index-94s080qm.js +266 -0
  50. package/index-9dp031by.js +12 -0
  51. package/{index-ws5e0j51.js → index-a4s954kq.js} +43 -26
  52. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  53. package/{index-ne2qy0nk.js → index-bxmejydw.js} +12 -12
  54. package/index-c6eggbee.js +123 -0
  55. package/index-d2242ksy.js +99 -0
  56. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  57. package/{index-259dsnz3.js → index-gxamdf18.js} +5 -8
  58. package/{index-4edzf1gr.js → index-hf6f4xnb.js} +77 -150
  59. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  60. package/{index-gyd220p2.js → index-hmdq5pqm.js} +554 -577
  61. package/{index-600zhnve.js → index-hqd0h1pb.js} +75 -203
  62. package/index-kb16nzyt.js +997 -0
  63. package/index-mk3b9rk1.js +2729 -0
  64. package/{index-2tv3fdx4.js → index-n3jk2501.js} +1205 -1458
  65. package/index-p6pbz9n5.js +25 -0
  66. package/{index-zry5nz81.js → index-qcejqgts.js} +51 -30
  67. package/{index-7737gxcp.js → index-rtwhw4aq.js} +107 -241
  68. package/{index-pnqbkf5p.js → index-se643zvp.js} +6 -2
  69. package/index-tsb0f8ya.js +53 -0
  70. package/{index-ktb5njgy.js → index-tzc1gtrg.js} +49 -99
  71. package/index-wa5jbxj1.js +760 -0
  72. package/index-wfwgcnyc.js +187 -0
  73. package/{index-mak3cd28.js → index-xb7qhxa8.js} +137 -255
  74. package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
  75. package/index.js +23 -22
  76. package/{library.command-nbsky672.js → library.command-n9kp7yms.js} +4 -5
  77. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-jx6kxt2x.js} +23 -42
  78. package/{module.command-29vqzrh8.js → module.command-q2xjf6cx.js} +8 -11
  79. package/{package.command-axv0abmn.js → package.command-q5a3nsw5.js} +6 -7
  80. package/package.json +15 -7
  81. package/{page.command-vs9s3zrh.js → page.command-ppenrxjn.js} +4 -5
  82. package/{primitive.command-cbxmqbtc.js → primitive.command-dvgg8nem.js} +9 -22
  83. package/prompter-76k56b37.js +8 -0
  84. package/{quality.command-gmbs7nws.js → quality.command-pz7kg3pc.js} +205 -377
  85. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  86. package/{repair.command-bqsjptsj.js → repair.command-a6py9xvz.js} +11 -35
  87. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  88. package/{scalar.command-6k1vhe9s.js → scalar.command-7f3xfpen.js} +7 -10
  89. package/skills/akan-code/SKILL.md +130 -0
  90. package/skills/akan-debug/SKILL.md +42 -0
  91. package/skills/akan-deps/SKILL.md +50 -0
  92. package/skills/akan-err/SKILL.md +39 -0
  93. package/skills/akan-module/SKILL.md +45 -0
  94. package/skills/akan-scaffold/SKILL.md +76 -0
  95. package/skills/akan-scalar/SKILL.md +52 -0
  96. package/skills/akan-service/SKILL.md +34 -0
  97. package/skills/akan-store/SKILL.md +67 -0
  98. package/skills/akan-ui/SKILL.md +63 -0
  99. package/skills/akan-validate/SKILL.md +44 -0
  100. package/{subspace.command-fwjfcts7.js → subspace.command-z7nqs508.js} +138 -67
  101. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  102. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  103. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  104. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  105. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  106. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  107. package/templates/appSample/lib/task/task.signal.ts +1 -1
  108. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  109. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  110. package/templates/client.ts +7 -30
  111. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  112. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  113. package/templates/crudPages/new/page.tsx +2 -1
  114. package/templates/crudPages/page.tsx +2 -1
  115. package/templates/crudSinglePage/page.tsx +2 -1
  116. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  117. package/templates/lib/__lib/lib.document.ts +0 -2
  118. package/templates/lib/sig.ts +8 -30
  119. package/templates/localDev/docker-compose.yaml.template +3 -14
  120. package/templates/module/__model__.signal.ts +12 -4
  121. package/templates/pkgRoot/tsconfig.json.template +2 -7
  122. package/templates/server.ts +1 -3
  123. package/templates/service/__model__.abstract.md.template +8 -0
  124. package/templates/workspaceRoot/.gitignore.template +0 -1
  125. package/templates/workspaceRoot/package.json.template +1 -3
  126. package/tunnel.command-zy4wbcgt.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-f142t17s.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-gpk1149x.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/application.command-pj2m7584.js +0 -204
  133. package/applicationReleasePackager-4yasxwv9.js +0 -244
  134. package/capacitorApp-36rbr858.js +0 -12
  135. package/cloud.command-jshwdq22.js +0 -93
  136. package/context.command-ez8a0zyz.js +0 -80
  137. package/devTuiView-c08ccbat.js +0 -9
  138. package/getDirname-dr3jz1xg.js +0 -7
  139. package/guideline.command-xmqwx8zm.js +0 -359
  140. package/guidelines/agentRule/agentRule.generate.json +0 -31
  141. package/guidelines/componentRule/componentRule.generate.json +0 -31
  142. package/guidelines/conventions/conventions.generate.json +0 -25
  143. package/guidelines/cssRule/cssRule.generate.json +0 -31
  144. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  145. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  146. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  147. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  148. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  149. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  150. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  151. package/guidelines/framework/framework.generate.json +0 -32
  152. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  153. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  154. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  155. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  156. package/guidelines/modelService/modelService.generate.json +0 -31
  157. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  158. package/guidelines/modelStore/modelStore.generate.json +0 -31
  159. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  160. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  161. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  162. package/guidelines/modelView/modelView.generate.json +0 -31
  163. package/guidelines/modelZone/modelZone.generate.json +0 -31
  164. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  165. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  166. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  167. package/guidelines/queryRule/queryRule.generate.json +0 -31
  168. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  169. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  170. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  171. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  172. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  173. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  174. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  175. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  176. package/guidelines/transportRule/transportRule.generate.json +0 -31
  177. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  178. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  179. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  180. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  181. package/index-2hq52jq3.js +0 -465
  182. package/index-47pj3aee.js +0 -76
  183. package/index-6wwzs1bc.js +0 -7
  184. package/index-8pm30m73.js +0 -841
  185. package/index-kn1h7p1x.js +0 -560
  186. package/index-meh558jy.js +0 -65
  187. package/index-rwg2aa4f.js +0 -229
  188. package/index-w8kmx1b6.js +0 -2176
  189. package/index-wt7qrtd4.js +0 -1516
  190. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  191. package/templates/module/__model__.abstract.md +0 -28
  192. package/templates/service/__model__.abstract.md +0 -28
  193. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
  194. package/workflow.command-nwpcdc8p.js +0 -107
@@ -0,0 +1,1457 @@
1
+ // @bun
2
+ import {
3
+ SubagentPool2,
4
+ McpServerConfig,
5
+ McpTokenStore,
6
+ McpSignIn
7
+ } from "./index-8tv7356t.js";
8
+ import {
9
+ CodeTuiLines2,
10
+ CodeTuiAgents2,
11
+ CodeTuiClipboard2,
12
+ CodeTuiCommands2,
13
+ CodeTuiEditor2,
14
+ CodeTuiFiles2,
15
+ CodeTuiMarkdown2,
16
+ CodeTuiParts2
17
+ } from "./index-wa5jbxj1.js";
18
+ import {
19
+ openBrowser
20
+ } from "./index-9dp031by.js";
21
+
22
+ // pkgs/@akanjs/cli/code/CodeTui.tsx
23
+ import {
24
+ CodeTranscript,
25
+ codeAgentClip
26
+ } from "akanjs/common";
27
+ import { render } from "ink";
28
+
29
+ // pkgs/@akanjs/cli/ui/CodeTuiApp.tsx
30
+ import { Box, Text, useCursor, useInput } from "ink";
31
+ import { useEffect, useRef, useState } from "react";
32
+
33
+ // pkgs/@akanjs/cli/code/CodeTuiMouse.ts
34
+ class CodeTuiMouse {
35
+ static on = "\x1B[?1000h\x1B[?1006h";
36
+ static off = "\x1B[?1006l\x1B[?1000l";
37
+ static step = 3;
38
+ #pending = 0;
39
+ read(input) {
40
+ if (this.#pending) {
41
+ const button = (input.codePointAt(0) ?? 32) - 32;
42
+ const rest = input.slice(this.#pending);
43
+ this.#pending = 0;
44
+ return { rows: CodeTuiMouse.rowsOf(button), rest };
45
+ }
46
+ const sgr = /^\[<(\d+);\d+;\d+[Mm]$/.exec(input);
47
+ if (sgr)
48
+ return { rows: CodeTuiMouse.rowsOf(Number(sgr[1])), rest: "" };
49
+ if (input !== "[M")
50
+ return;
51
+ this.#pending = 3;
52
+ return { rows: 0, rest: "" };
53
+ }
54
+ static rowsOf(button) {
55
+ if (!(button & 64))
56
+ return 0;
57
+ const direction = button & 3;
58
+ if (direction === 0)
59
+ return -CodeTuiMouse.step;
60
+ if (direction === 1)
61
+ return CodeTuiMouse.step;
62
+ return 0;
63
+ }
64
+ }
65
+
66
+ // pkgs/@akanjs/cli/ui/CodeTuiApp.tsx
67
+ import { jsxDEV } from "react/jsx-dev-runtime";
68
+ var promptMarkWidth = 2;
69
+ var pasteOpen = "[200~";
70
+ var pasteClose = "[201~";
71
+ var continuation = /^[^\r\n]*\\(?:\r\n|[\r\n])$/;
72
+ var Styled = ({ span }) => /* @__PURE__ */ jsxDEV(Text, {
73
+ bold: span.bold,
74
+ color: span.color,
75
+ dimColor: span.dim,
76
+ italic: span.italic,
77
+ strikethrough: span.strikethrough,
78
+ underline: span.underline,
79
+ children: span.text
80
+ }, undefined, false, undefined, this);
81
+ var Line = ({ line }) => /* @__PURE__ */ jsxDEV(Text, {
82
+ wrap: "truncate",
83
+ children: line.spans.map((span, at) => /* @__PURE__ */ jsxDEV(Styled, {
84
+ span
85
+ }, `${line.key}:${at}`, false, undefined, this))
86
+ }, undefined, false, undefined, this);
87
+ var Transcript = ({ lines, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
88
+ flexDirection: "column",
89
+ width,
90
+ height,
91
+ paddingX: 1,
92
+ children: lines.map((line) => /* @__PURE__ */ jsxDEV(Line, {
93
+ line
94
+ }, line.key, false, undefined, this))
95
+ }, undefined, false, undefined, this);
96
+ var Overlay = ({
97
+ overlay,
98
+ width,
99
+ height
100
+ }) => /* @__PURE__ */ jsxDEV(Box, {
101
+ borderDimColor: true,
102
+ borderStyle: "round",
103
+ flexDirection: "column",
104
+ height,
105
+ paddingX: 1,
106
+ width,
107
+ children: [
108
+ /* @__PURE__ */ jsxDEV(Text, {
109
+ wrap: "truncate",
110
+ children: [
111
+ /* @__PURE__ */ jsxDEV(Text, {
112
+ bold: true,
113
+ color: "cyan",
114
+ children: overlay.title
115
+ }, undefined, false, undefined, this),
116
+ overlay.above || overlay.below ? /* @__PURE__ */ jsxDEV(Text, {
117
+ dimColor: true,
118
+ children: ` \u25B2${overlay.above} \u25BC${overlay.below}`
119
+ }, undefined, false, undefined, this) : null
120
+ ]
121
+ }, undefined, true, undefined, this),
122
+ overlay.lines.map((line) => /* @__PURE__ */ jsxDEV(Line, {
123
+ line
124
+ }, line.key, false, undefined, this))
125
+ ]
126
+ }, undefined, true, undefined, this);
127
+ var Rule = ({ spans }) => /* @__PURE__ */ jsxDEV(Text, {
128
+ wrap: "truncate",
129
+ children: spans.map((span, at) => /* @__PURE__ */ jsxDEV(Styled, {
130
+ span
131
+ }, `${at}:${span.text}`, false, undefined, this))
132
+ }, undefined, false, undefined, this);
133
+ var Menu = ({ options, selected, marker, checked }) => /* @__PURE__ */ jsxDEV(Box, {
134
+ flexDirection: "column",
135
+ children: options.map((option, idx) => /* @__PURE__ */ jsxDEV(Text, {
136
+ wrap: "truncate",
137
+ children: [
138
+ /* @__PURE__ */ jsxDEV(Text, {
139
+ color: idx === selected ? "cyan" : undefined,
140
+ children: idx === selected ? "\u276F " : " "
141
+ }, undefined, false, undefined, this),
142
+ checked ? /* @__PURE__ */ jsxDEV(Text, {
143
+ children: checked.includes(option.key) ? "[x] " : "[ ] "
144
+ }, undefined, false, undefined, this) : null,
145
+ /* @__PURE__ */ jsxDEV(Text, {
146
+ bold: idx === selected && marker,
147
+ children: option.label
148
+ }, undefined, false, undefined, this),
149
+ option.detail ? /* @__PURE__ */ jsxDEV(Text, {
150
+ dimColor: true,
151
+ children: ` ${option.detail}`
152
+ }, undefined, false, undefined, this) : null
153
+ ]
154
+ }, option.key, true, undefined, this))
155
+ }, undefined, false, undefined, this);
156
+ var CodeTuiApp = ({ actions }) => {
157
+ const [snapshot, setSnapshot] = useState(actions.snapshot);
158
+ const pasting = useRef(null);
159
+ const mouse = useRef(new CodeTuiMouse);
160
+ const { setCursorPosition } = useCursor();
161
+ useEffect(() => {
162
+ const unsubscribe = actions.subscribe(() => setSnapshot(actions.snapshot()));
163
+ setSnapshot(actions.snapshot());
164
+ return unsubscribe;
165
+ }, [actions]);
166
+ setCursorPosition(snapshot.cursor ?? undefined);
167
+ useInput((input, key) => {
168
+ const page = Math.max(1, snapshot.bodyHeight - 3);
169
+ if (input === pasteOpen) {
170
+ pasting.current = [];
171
+ return;
172
+ }
173
+ if (input === pasteClose) {
174
+ const text = pasting.current?.join("") ?? "";
175
+ pasting.current = null;
176
+ return actions.pasted(text);
177
+ }
178
+ if (pasting.current) {
179
+ pasting.current.push(key.return ? `
180
+ ` : key.tab ? "\t" : input);
181
+ return;
182
+ }
183
+ const wheel = mouse.current.read(input);
184
+ if (wheel) {
185
+ if (wheel.rows)
186
+ actions.scroll(wheel.rows);
187
+ if (wheel.rest)
188
+ actions.type(wheel.rest);
189
+ return;
190
+ }
191
+ if (key.ctrl && input === "c")
192
+ return actions.interrupt();
193
+ if (key.pageUp)
194
+ return actions.scroll(-page);
195
+ if (key.pageDown)
196
+ return actions.scroll(page);
197
+ if (key.escape)
198
+ return actions.cancel();
199
+ if (snapshot.overlay) {
200
+ if (key.upArrow)
201
+ return actions.scroll(-1);
202
+ if (key.downArrow)
203
+ return actions.scroll(1);
204
+ if (key.return)
205
+ return snapshot.overlay.selectable ? actions.submit() : actions.cancel();
206
+ return;
207
+ }
208
+ if (snapshot.mode === "confirm") {
209
+ if (input === "y" || input === "Y")
210
+ return actions.answerConfirm(true);
211
+ if (input === "n" || input === "N")
212
+ return actions.answerConfirm(false);
213
+ return;
214
+ }
215
+ if (snapshot.mode === "select") {
216
+ if (key.upArrow)
217
+ return actions.move(-1);
218
+ if (key.downArrow)
219
+ return actions.move(1);
220
+ if (input === " " && snapshot.multiSelect)
221
+ return actions.toggle();
222
+ if (key.return)
223
+ return actions.submit();
224
+ return;
225
+ }
226
+ if (key.tab)
227
+ return actions.complete();
228
+ if (key.upArrow)
229
+ return actions.vertical(-1);
230
+ if (key.downArrow)
231
+ return actions.vertical(1);
232
+ if (key.leftArrow)
233
+ return actions.edit(key.meta ? "wordLeft" : "left");
234
+ if (key.rightArrow)
235
+ return actions.edit(key.meta ? "wordRight" : "right");
236
+ if (key.return && (key.shift || key.meta))
237
+ return actions.newline();
238
+ if (key.ctrl && input === "j")
239
+ return actions.newline();
240
+ if (continuation.test(input)) {
241
+ actions.type(input.replace(/[\r\n]+$/, ""));
242
+ return actions.newline();
243
+ }
244
+ if (key.return)
245
+ return actions.submit();
246
+ if (key.backspace || key.delete)
247
+ return actions.edit(key.meta ? "deleteWord" : "backspace");
248
+ if (key.ctrl && input === "w")
249
+ return actions.edit("deleteWord");
250
+ if (key.ctrl && input === "u")
251
+ return actions.edit("deleteToStart");
252
+ if (key.ctrl && input === "a")
253
+ return actions.edit("home");
254
+ if (key.ctrl && input === "e")
255
+ return actions.edit("end");
256
+ if (key.ctrl && input === "d")
257
+ return actions.edit("delete");
258
+ if (key.ctrl && input === "v")
259
+ return actions.paste();
260
+ if (input && !key.ctrl && !key.meta)
261
+ return actions.type(input);
262
+ });
263
+ const paneWidth = snapshot.columns;
264
+ return /* @__PURE__ */ jsxDEV(Box, {
265
+ flexDirection: "column",
266
+ width: paneWidth,
267
+ height: snapshot.frameRows,
268
+ children: [
269
+ snapshot.overlay ? /* @__PURE__ */ jsxDEV(Overlay, {
270
+ height: snapshot.bodyHeight,
271
+ overlay: snapshot.overlay,
272
+ width: paneWidth
273
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Transcript, {
274
+ height: snapshot.bodyHeight,
275
+ lines: snapshot.lines,
276
+ width: paneWidth
277
+ }, undefined, false, undefined, this),
278
+ snapshot.menu.length ? /* @__PURE__ */ jsxDEV(Menu, {
279
+ marker: true,
280
+ options: snapshot.menu,
281
+ selected: snapshot.menuSelected
282
+ }, undefined, false, undefined, this) : null,
283
+ /* @__PURE__ */ jsxDEV(Rule, {
284
+ spans: snapshot.topRule
285
+ }, undefined, false, undefined, this),
286
+ snapshot.mode === "input" ? /* @__PURE__ */ jsxDEV(Box, {
287
+ flexDirection: "column",
288
+ children: [
289
+ snapshot.offered ? /* @__PURE__ */ jsxDEV(Text, {
290
+ dimColor: true,
291
+ wrap: "truncate",
292
+ children: " \u29C9 an image is on your clipboard \u2014 cmd+v or ^v attaches it"
293
+ }, undefined, false, undefined, this) : null,
294
+ snapshot.input.map((row, at) => /* @__PURE__ */ jsxDEV(Text, {
295
+ wrap: "truncate",
296
+ children: [
297
+ /* @__PURE__ */ jsxDEV(Text, {
298
+ color: snapshot.status ? "yellow" : "cyan",
299
+ children: at === 0 ? snapshot.status ? "\u25D0 " : "\u203A " : " "
300
+ }, undefined, false, undefined, this),
301
+ at === 0 && !row && snapshot.input.length === 1 ? /* @__PURE__ */ jsxDEV(Text, {
302
+ dimColor: true,
303
+ children: snapshot.placeholder
304
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
305
+ children: row
306
+ }, undefined, false, undefined, this)
307
+ ]
308
+ }, `in:${at}`, true, undefined, this))
309
+ ]
310
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Box, {
311
+ flexDirection: "column",
312
+ children: [
313
+ /* @__PURE__ */ jsxDEV(Text, {
314
+ color: "yellow",
315
+ wrap: "truncate",
316
+ children: snapshot.prompt
317
+ }, undefined, false, undefined, this),
318
+ snapshot.options.length ? /* @__PURE__ */ jsxDEV(Menu, {
319
+ checked: snapshot.multiSelect ? snapshot.checked : undefined,
320
+ marker: false,
321
+ options: snapshot.options,
322
+ selected: snapshot.selected
323
+ }, undefined, false, undefined, this) : null
324
+ ]
325
+ }, undefined, true, undefined, this),
326
+ snapshot.subagents.length ? /* @__PURE__ */ jsxDEV(Box, {
327
+ flexDirection: "column",
328
+ children: snapshot.subagents.map((line) => /* @__PURE__ */ jsxDEV(Line, {
329
+ line
330
+ }, line.key, false, undefined, this))
331
+ }, undefined, false, undefined, this) : null,
332
+ /* @__PURE__ */ jsxDEV(Rule, {
333
+ spans: snapshot.bottomRule
334
+ }, undefined, false, undefined, this),
335
+ snapshot.notice ? /* @__PURE__ */ jsxDEV(Text, {
336
+ color: "cyan",
337
+ wrap: "truncate",
338
+ children: snapshot.notice
339
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
340
+ dimColor: true,
341
+ wrap: "truncate",
342
+ children: snapshot.hint
343
+ }, undefined, false, undefined, this)
344
+ ]
345
+ }, undefined, true, undefined, this);
346
+ };
347
+
348
+ // pkgs/@akanjs/cli/code/CodeTuiMcp.ts
349
+ class CodeTuiMcp {
350
+ static namePattern = /^[a-zA-Z0-9_-]+$/;
351
+ static usage = [
352
+ "/mcp add <name> <command> [args\u2026] declare a server this session starts",
353
+ "/mcp add <name> <https://\u2026> declare one it reaches over http",
354
+ "/mcp add --local <name> \u2026 declare it for this repo only, not every repo",
355
+ "/mcp login <name> sign in through the browser (OAuth)",
356
+ "/mcp logout <name> forget the token, leaving the server declared",
357
+ "/mcp remove <name> undeclare it, from this repo's file first",
358
+ "/mcp remove --local <name> undeclare it from this repo's file only",
359
+ "/mcp reload reopen this session so the file takes effect"
360
+ ];
361
+ static #where(view) {
362
+ return view.files.map(({ scope, file }) => `${scope.padEnd(11)}${file}`);
363
+ }
364
+ static list(view) {
365
+ const lines = view.problem ? [view.problem, ""] : [];
366
+ const rows = CodeTuiMcp.#rows(view);
367
+ if (!rows.length)
368
+ return [
369
+ ...lines,
370
+ "No MCP server is declared.",
371
+ "",
372
+ "Declare one here, or paste an editor's `mcpServers` block into either file:",
373
+ ...CodeTuiMcp.#where(view),
374
+ "",
375
+ ...CodeTuiMcp.usage
376
+ ].join(`
377
+ `);
378
+ const width = (at) => Math.max(...rows.map((row) => (row[at] ?? "").length)) + 2;
379
+ const [name, scope, transport, target] = [width(0), width(1), width(2), width(3)];
380
+ return [
381
+ ...lines,
382
+ `${rows.length} server${rows.length === 1 ? "" : "s"}`,
383
+ "",
384
+ ...rows.map(([a, b, c, d, e]) => `${(a ?? "").padEnd(name)}${(b ?? "").padEnd(scope)}${(c ?? "").padEnd(transport)}${(d ?? "").padEnd(target)}${e ?? ""}`),
385
+ "",
386
+ ...CodeTuiMcp.#where(view),
387
+ "",
388
+ ...CodeTuiMcp.usage
389
+ ].join(`
390
+ `);
391
+ }
392
+ static #rows(view) {
393
+ const byName = new Map;
394
+ for (const entry of view.declared)
395
+ byName.set(entry.ref.name, { ref: entry.ref, disabled: entry.disabled, scope: entry.scope });
396
+ for (const live of view.status)
397
+ byName.set(live.name, { ...byName.get(live.name), live });
398
+ return [...byName].sort(([a], [b]) => a.localeCompare(b)).map(([name, entry]) => {
399
+ const transport = entry.live?.transport ?? entry.ref?.transport ?? "";
400
+ const target = entry.live?.target ?? CodeTuiMcp.targetOf(entry.ref);
401
+ return [name, entry.scope ?? "", transport, target, CodeTuiMcp.#stateOf(entry)];
402
+ });
403
+ }
404
+ static #stateOf(entry) {
405
+ if (entry.disabled)
406
+ return "disabled in the file";
407
+ if (!entry.live)
408
+ return "declared \xB7 /mcp reload to connect";
409
+ if (entry.live.auth === "required")
410
+ return `sign-in needed \xB7 /mcp login ${entry.live.name}`;
411
+ if (entry.live.error)
412
+ return `unreachable \u2014 ${entry.live.error}`;
413
+ const tools = `${entry.live.tools.length} tool${entry.live.tools.length === 1 ? "" : "s"}`;
414
+ if (!entry.ref)
415
+ return `${tools} \xB7 removed from the file`;
416
+ return entry.live.auth === "authorized" ? `${tools} \xB7 signed in` : tools;
417
+ }
418
+ static targetOf(ref) {
419
+ if (!ref)
420
+ return "";
421
+ return ref.url ?? [ref.command, ...ref.args ?? []].filter(Boolean).join(" ");
422
+ }
423
+ static tools(status) {
424
+ if (status.error)
425
+ return `${status.name} is unreachable \u2014 ${status.error}`;
426
+ if (status.auth === "required")
427
+ return `${status.name} needs signing in \u2014 /mcp login ${status.name}`;
428
+ if (!status.tools.length)
429
+ return `${status.name} connected and published no tools.`;
430
+ return [`${status.name} \xB7 ${status.target}`, "", status.tools.join(`
431
+ `)].join(`
432
+ `);
433
+ }
434
+ }
435
+
436
+ // pkgs/@akanjs/cli/code/CodeTuiModels.ts
437
+ class CodeTuiModels {
438
+ static envKeyOf(provider) {
439
+ return `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
440
+ }
441
+ static usage = [
442
+ "/model <provider>/<id> switch to it, e.g. /model deepseek/deepseek-v4-pro",
443
+ "/model providers every provider, and which ones have a key",
444
+ "/model <provider> what one provider offers"
445
+ ];
446
+ static list(catalogue) {
447
+ const ready = catalogue.filter((provider) => provider.authorized);
448
+ const locked = catalogue.length - ready.length;
449
+ if (!ready.length)
450
+ return [
451
+ "No provider has a key, so there is no model to switch to.",
452
+ "",
453
+ `Set <PROVIDER>_API_KEY in the workspace \`.env\` or your environment \u2014 ${CodeTuiModels.envKeyOf("deepseek")}, ${CodeTuiModels.envKeyOf("anthropic")}.`,
454
+ "",
455
+ ...CodeTuiModels.usage
456
+ ].join(`
457
+ `);
458
+ const rows = ready.flatMap((provider) => CodeTuiModels.#modelRows(provider));
459
+ return [
460
+ `${rows.length} model${rows.length === 1 ? "" : "s"} ready \xB7 ${ready.map((provider) => provider.id).join(", ")}`,
461
+ "",
462
+ ...CodeTuiModels.#table(rows),
463
+ "",
464
+ `${locked} more provider${locked === 1 ? "" : "s"} need a key \u2014 /model providers`,
465
+ "",
466
+ ...CodeTuiModels.usage
467
+ ].join(`
468
+ `);
469
+ }
470
+ static providers(catalogue) {
471
+ const rows = catalogue.map((provider) => [
472
+ provider.id,
473
+ provider.authorized ? "ready" : "no key",
474
+ `${provider.models.length} model${provider.models.length === 1 ? "" : "s"}`,
475
+ provider.name
476
+ ]);
477
+ return [
478
+ `${catalogue.length} providers`,
479
+ "",
480
+ ...CodeTuiModels.#table(rows),
481
+ "",
482
+ "A provider is reached by setting <PROVIDER>_API_KEY \u2014 in the workspace `.env` or your environment,",
483
+ "both of which are read at startup. /model <provider> lists what one offers.",
484
+ "",
485
+ ...CodeTuiModels.usage
486
+ ].join(`
487
+ `);
488
+ }
489
+ static ofProvider(catalogue, id) {
490
+ const provider = catalogue.find((entry) => entry.id === id);
491
+ if (!provider)
492
+ return;
493
+ const rows = CodeTuiModels.#modelRows(provider);
494
+ const head = provider.authorized ? `${provider.name} \xB7 ${rows.length} models \xB7 ready` : `${provider.name} \xB7 ${rows.length} models \xB7 set ${CodeTuiModels.envKeyOf(provider.id)} to use it`;
495
+ return [head, "", ...CodeTuiModels.#table(rows)].join(`
496
+ `);
497
+ }
498
+ static #modelRows(provider) {
499
+ return provider.models.map((model) => [
500
+ `${model.current ? "\u276F " : " "}${provider.id}/${model.id}`,
501
+ model.name,
502
+ model.contextWindow ? `${Math.round(model.contextWindow / 1000)}k ctx` : ""
503
+ ]);
504
+ }
505
+ static #table(rows) {
506
+ const width = (at) => Math.max(0, ...rows.map((row) => (row[at] ?? "").length)) + 2;
507
+ const columns = [width(0), width(1)];
508
+ return rows.map((row) => `${(row[0] ?? "").padEnd(columns[0] ?? 0)}${(row[1] ?? "").padEnd(columns[1] ?? 0)}${row[2] ?? ""}`.trimEnd());
509
+ }
510
+ }
511
+
512
+ // pkgs/@akanjs/cli/code/CodeTui.tsx
513
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
514
+
515
+ class CodeTui {
516
+ static frameMs = 34;
517
+ static clipboardMs = 2000;
518
+ static noticeMs = 4000;
519
+ static chromeRows = 1;
520
+ static promptRuleRows = 1;
521
+ static minBodyRows = 3;
522
+ static maxInputRows = 8;
523
+ static title = "Akan \u2014 write one line, deploy every stack";
524
+ static imageMark = /\[image #(\d+)] ?/g;
525
+ static freeTextKey = "__free_text__";
526
+ static pasteOn = "\x1B[?2004h";
527
+ static pasteOff = "\x1B[?2004l";
528
+ #agent;
529
+ #transcript = new CodeTranscript;
530
+ #editor = new CodeTuiEditor2;
531
+ #files;
532
+ #listeners = new Set;
533
+ #history = [];
534
+ #instance;
535
+ #stdout;
536
+ #historyAt = -1;
537
+ #anchor = null;
538
+ #selected = 0;
539
+ #typing = false;
540
+ #menuAt = 0;
541
+ #checked = [];
542
+ #overlay = null;
543
+ #overlayAt = 0;
544
+ #picker = null;
545
+ #images = [];
546
+ #switchTo;
547
+ #offered = false;
548
+ #clipboardTimer = null;
549
+ #notice = "";
550
+ #opening;
551
+ #thinking;
552
+ #noticeTimer = null;
553
+ #frameTimer = null;
554
+ #cached = null;
555
+ #unsubscribe;
556
+ #exiting = false;
557
+ constructor(agent, { thinking = false, notice, ...streams } = {}) {
558
+ this.#agent = agent;
559
+ this.#thinking = thinking;
560
+ this.#opening = notice ?? "";
561
+ this.#files = new CodeTuiFiles2(agent.profile.paths.root);
562
+ this.#stdout = streams.stdout ?? process.stdout;
563
+ this.#stdout.on("resize", this.#onResize);
564
+ this.#instance = render(/* @__PURE__ */ jsxDEV2(CodeTuiApp, {
565
+ actions: {
566
+ subscribe: this.#subscribe,
567
+ snapshot: this.#snapshot,
568
+ type: this.#type,
569
+ edit: this.#edit,
570
+ submit: this.#submit,
571
+ newline: this.#newline,
572
+ paste: this.#paste,
573
+ pasted: this.#pasted,
574
+ complete: this.#complete,
575
+ vertical: this.#vertical,
576
+ scroll: this.#scroll,
577
+ move: this.#move,
578
+ toggle: this.#toggle,
579
+ answerConfirm: this.#answerConfirm,
580
+ cancel: this.#cancel,
581
+ interrupt: this.#interrupt
582
+ }
583
+ }, undefined, false, undefined, this), {
584
+ patchConsole: true,
585
+ exitOnCtrlC: false,
586
+ kittyKeyboard: { mode: "auto" },
587
+ ...streams
588
+ });
589
+ }
590
+ async run(seed) {
591
+ this.#stdout.write(`${CodeTui.pasteOn}${CodeTuiMouse.on}`);
592
+ if (this.#opening)
593
+ this.#say(this.#opening);
594
+ this.#unsubscribe = this.#agent.on((event) => {
595
+ this.#transcript.apply(event);
596
+ if (event.type === "question")
597
+ this.#openQuestion(event.question);
598
+ this.#scheduleFrame();
599
+ });
600
+ this.#agent.announce();
601
+ this.#files.load().then(() => this.#renderNow());
602
+ this.#watchClipboard();
603
+ if (seed?.trim())
604
+ this.#send(seed.trim());
605
+ await this.#instance.waitUntilExit();
606
+ this.#close();
607
+ return this.#switchTo;
608
+ }
609
+ close() {
610
+ this.#quit();
611
+ }
612
+ #watchClipboard() {
613
+ if (!this.#agent.canSeeImages)
614
+ return;
615
+ this.#clipboardTimer = setInterval(() => {
616
+ const offered = CodeTuiClipboard2.has();
617
+ if (offered === this.#offered)
618
+ return;
619
+ this.#offered = offered;
620
+ this.#renderNow();
621
+ }, CodeTui.clipboardMs);
622
+ }
623
+ #close() {
624
+ if (this.#clipboardTimer)
625
+ clearInterval(this.#clipboardTimer);
626
+ this.#clipboardTimer = null;
627
+ if (this.#frameTimer)
628
+ clearTimeout(this.#frameTimer);
629
+ if (this.#noticeTimer)
630
+ clearTimeout(this.#noticeTimer);
631
+ this.#frameTimer = null;
632
+ this.#noticeTimer = null;
633
+ this.#stdout.off("resize", this.#onResize);
634
+ this.#unsubscribe?.();
635
+ this.#farewell();
636
+ }
637
+ #farewell() {
638
+ const info = this.#transcript.info;
639
+ if (this.#switchTo || !info?.sessionId || !this.#agent.sessions().length)
640
+ return;
641
+ if (!this.#transcript.parts.some((part) => part.kind === "user"))
642
+ return;
643
+ const name = info.name ? ` (${info.name})` : "";
644
+ this.#stdout.write(`Resume this session${name} with:
645
+ akan code --resume ${info.sessionId}
646
+ `);
647
+ }
648
+ #onResize = () => this.#renderNow();
649
+ #subscribe = (listener) => {
650
+ this.#listeners.add(listener);
651
+ return () => this.#listeners.delete(listener);
652
+ };
653
+ #scheduleFrame() {
654
+ if (this.#frameTimer)
655
+ return;
656
+ this.#frameTimer = setTimeout(() => {
657
+ this.#frameTimer = null;
658
+ this.#renderNow();
659
+ }, CodeTui.frameMs);
660
+ }
661
+ #renderNow() {
662
+ this.#cached = null;
663
+ for (const listener of this.#listeners)
664
+ listener();
665
+ }
666
+ static layout(frameRows, askRows, railRows = 0) {
667
+ const room = Math.max(1, frameRows - CodeTui.minBodyRows - CodeTui.chromeRows - railRows);
668
+ const ask = Math.max(1, Math.min(askRows, room));
669
+ return { ask, bodyHeight: Math.max(CodeTui.minBodyRows, frameRows - ask - CodeTui.chromeRows - railRows) };
670
+ }
671
+ static frameRowsOf(terminalRows) {
672
+ return Math.max(CodeTui.minBodyRows + CodeTui.chromeRows + 1, terminalRows - 1);
673
+ }
674
+ #snapshot = () => {
675
+ if (this.#cached)
676
+ return this.#cached;
677
+ const columns = Math.max(40, this.#stdout.columns || 80);
678
+ const frameRows = CodeTui.frameRowsOf(Math.max(10, this.#stdout.rows || 24));
679
+ const question = this.#transcript.question;
680
+ const approval = this.#transcript.approval;
681
+ const mode = this.#typing ? "input" : CodeTui.#modeOf(question, approval);
682
+ const subagents = this.#railRows(columns);
683
+ const ask = mode === "input" ? this.#inputBlock(columns, frameRows - subagents.length) : this.#askBlock(question, frameRows - subagents.length);
684
+ const { bodyHeight } = CodeTui.layout(frameRows, ask.rows, subagents.length);
685
+ const width = columns - 2;
686
+ const lines = this.#lines(width);
687
+ const end = this.#anchor === null ? lines.length : Math.min(lines.length, Math.max(bodyHeight, this.#anchor));
688
+ const start = Math.max(0, end - bodyHeight);
689
+ const above = start;
690
+ const below = lines.length - end;
691
+ this.#cached = {
692
+ topRule: CodeTui.rule(this.#anchor === null ? "" : `\u25B2${above} \u25BC${below} paused`, this.#transcript.info?.name ?? this.#transcript.info?.sessionId.slice(0, 8) ?? "", columns),
693
+ bottomRule: CodeTui.rule(this.#queueLabel(), this.#statusLabel(), columns),
694
+ lines: lines.slice(start, end),
695
+ above,
696
+ below,
697
+ following: this.#anchor === null,
698
+ overlay: this.#overlayBlock(width, bodyHeight),
699
+ status: this.#status(),
700
+ subagents,
701
+ mode,
702
+ placeholder: this.#placeholder(),
703
+ offered: this.#offered,
704
+ prompt: CodeTui.#promptOf(question, approval),
705
+ selected: ask.selected,
706
+ checked: this.#checked,
707
+ multiSelect: !!question?.multiSelect,
708
+ notice: this.#notice,
709
+ hint: this.#overlay ? "esc close \xB7 \u2191\u2193 scroll" : this.#hint(mode, !!question?.multiSelect),
710
+ columns,
711
+ frameRows,
712
+ bodyHeight,
713
+ cursor: ask.caret ? { x: promptMarkWidth + ask.caret.col, y: bodyHeight + ask.above + ask.caret.row } : null,
714
+ ...ask.snapshot
715
+ };
716
+ return this.#cached;
717
+ };
718
+ #lines(width) {
719
+ return [
720
+ ...CodeTuiLines2.rows(this.#banner(), width, "banner"),
721
+ ...CodeTuiParts2.lines(this.#transcript.parts, width, { thinking: this.#thinking })
722
+ ];
723
+ }
724
+ #railRows(columns) {
725
+ const agents = this.#transcript.subagents;
726
+ if (!agents.length)
727
+ return [];
728
+ const rows = CodeTuiAgents2.rows(agents, this.#transcript.info?.name ?? "main", columns);
729
+ const room = CodeTui.frameRowsOf(Math.max(10, this.#stdout.rows || 24)) - CodeTui.minBodyRows - CodeTui.chromeRows - 2 * CodeTui.promptRuleRows - 1;
730
+ return rows.length <= room ? rows : [];
731
+ }
732
+ #banner() {
733
+ const cwd = this.#transcript.info?.cwd;
734
+ return [
735
+ { prefix: { text: "\u273B ", color: "cyan" }, spans: [{ text: CodeTui.title, bold: true }] },
736
+ {
737
+ prefix: { text: " " },
738
+ spans: [{ text: cwd ? `${cwd} \xB7 /help for commands` : "/help for commands", dim: true }]
739
+ }
740
+ ];
741
+ }
742
+ static rule(left, right, width) {
743
+ const room = (label) => label ? 3 + CodeTuiLines2.width(label) : 1;
744
+ const tail = right ? codeAgentClip(right, Math.max(1, width - 4)) : "";
745
+ const head = width - room(left) - room(tail) >= 0 ? left : "";
746
+ const fill = Math.max(0, width - room(head) - room(tail));
747
+ return [
748
+ ...head ? [
749
+ { text: "\u2500 ", dim: true },
750
+ { text: head, color: "cyan" },
751
+ { text: " ", dim: true }
752
+ ] : [{ text: "\u2500", dim: true }],
753
+ { text: "\u2500".repeat(fill), dim: true },
754
+ ...tail ? [
755
+ { text: " ", dim: true },
756
+ { text: tail, dim: true },
757
+ { text: " \u2500", dim: true }
758
+ ] : [{ text: "\u2500", dim: true }]
759
+ ];
760
+ }
761
+ #overlayBlock(width, height) {
762
+ if (this.#picker)
763
+ return this.#pickerBlock(width, height);
764
+ const overlay = this.#overlay;
765
+ if (!overlay)
766
+ return null;
767
+ const room = Math.max(1, height - 3);
768
+ const body = Math.max(8, width - 4);
769
+ const all = CodeTui.#overlayRows(overlay.text, body);
770
+ const first = Math.max(0, Math.min(this.#overlayAt, all.length - room));
771
+ return {
772
+ title: overlay.title,
773
+ lines: all.slice(first, first + room),
774
+ above: first,
775
+ below: Math.max(0, all.length - first - room),
776
+ selectable: false
777
+ };
778
+ }
779
+ #pickerBlock(width, height) {
780
+ const picker = this.#picker;
781
+ if (!picker)
782
+ return null;
783
+ const room = Math.max(1, height - 3);
784
+ const body = Math.max(8, width - 4);
785
+ const first = Math.max(0, Math.min(picker.at - Math.floor(room / 2), picker.entries.length - room));
786
+ const rows = picker.entries.slice(first, first + room).map((entry, at) => CodeTui.#sessionRow(entry, first + at === picker.at));
787
+ return {
788
+ title: `sessions \xB7 ${picker.entries.length}`,
789
+ lines: rows.flatMap((row, at) => CodeTuiLines2.rows([row], body, `sessions:${first + at}`).slice(0, 1)),
790
+ above: first,
791
+ below: Math.max(0, picker.entries.length - first - room),
792
+ selectable: true
793
+ };
794
+ }
795
+ static #sessionRow(entry, selected) {
796
+ const label = entry.name ?? entry.id.slice(0, 8);
797
+ return {
798
+ prefix: { text: selected ? "\u276F " : " ", color: "cyan" },
799
+ spans: [
800
+ { text: label, bold: selected },
801
+ { text: ` ${CodeTui.#ago(entry.updatedAt)} \xB7 ${entry.turns} turn${entry.turns === 1 ? "" : "s"}`, dim: true },
802
+ ...entry.opening ? [{ text: ` ${entry.opening}`, dim: true }] : []
803
+ ]
804
+ };
805
+ }
806
+ static #ago(at) {
807
+ const minutes = Math.max(0, Math.round((Date.now() - at) / 60000));
808
+ if (minutes < 60)
809
+ return `${minutes}m ago`;
810
+ if (minutes < 60 * 24)
811
+ return `${Math.round(minutes / 60)}h ago`;
812
+ return `${Math.round(minutes / (60 * 24))}d ago`;
813
+ }
814
+ #inputBlock(columns, frameRows) {
815
+ const all = this.#menu()?.options ?? [];
816
+ const { rows, row, col } = this.#editor.layout(columns - promptMarkWidth - 1);
817
+ const offered = this.#offered ? 1 : 0;
818
+ const room = frameRows - CodeTui.minBodyRows - CodeTui.chromeRows - 2 * CodeTui.promptRuleRows - offered;
819
+ const menuRows = Math.max(0, Math.min(all.length, room - 1));
820
+ const menuFrom = Math.max(0, Math.min(this.#menuAt - Math.floor(menuRows / 2), all.length - menuRows));
821
+ const menu = all.slice(menuFrom, menuFrom + menuRows);
822
+ const above = menu.length + CodeTui.promptRuleRows + offered;
823
+ const chrome = above + CodeTui.promptRuleRows;
824
+ const budget = Math.max(1, Math.min(CodeTui.maxInputRows, room - menu.length));
825
+ const first = Math.max(0, Math.min(row - budget + 1, rows.length - budget));
826
+ const shown = rows.slice(first, first + budget);
827
+ return {
828
+ rows: chrome + shown.length,
829
+ above,
830
+ selected: 0,
831
+ caret: { row: row - first, col },
832
+ snapshot: {
833
+ input: shown.map((entry) => entry.text),
834
+ menu,
835
+ menuSelected: this.#menuAt - menuFrom,
836
+ options: []
837
+ }
838
+ };
839
+ }
840
+ #askBlock(question, frameRows) {
841
+ const all = CodeTui.#optionsOf(question);
842
+ const chrome = 1 + 2 * CodeTui.promptRuleRows;
843
+ const budget = Math.max(1, frameRows - CodeTui.minBodyRows - CodeTui.chromeRows - chrome);
844
+ const shown = Math.min(all.length, budget);
845
+ const first = Math.max(0, Math.min(this.#selected - Math.floor(shown / 2), all.length - shown));
846
+ return {
847
+ rows: chrome + shown,
848
+ above: CodeTui.promptRuleRows,
849
+ selected: this.#selected - first,
850
+ caret: undefined,
851
+ snapshot: {
852
+ input: [],
853
+ menu: [],
854
+ menuSelected: 0,
855
+ options: all.slice(first, first + shown)
856
+ }
857
+ };
858
+ }
859
+ #menu() {
860
+ const prefix = CodeTuiCommands2.prefixOf(this.#editor.text, this.#editor.caret);
861
+ if (prefix !== undefined)
862
+ return {
863
+ kind: "command",
864
+ from: 0,
865
+ to: this.#editor.text.length,
866
+ options: CodeTuiCommands2.matches(prefix).map((command) => ({
867
+ key: command.name,
868
+ label: `/${command.name}${command.arg ? ` ${command.arg}` : ""}`,
869
+ detail: command.desc
870
+ }))
871
+ };
872
+ const token = this.#editor.token();
873
+ if (!token.text.startsWith("@"))
874
+ return null;
875
+ const options = this.#files.match(token.text.slice(1)).map((file) => ({ key: file, label: `@${file}` }));
876
+ if (!options.length && this.#files.ready)
877
+ return null;
878
+ return { kind: "file", from: token.from, to: token.to, options };
879
+ }
880
+ static #overlayRows(text, width) {
881
+ return text.split(`
882
+ `).flatMap((line, at) => CodeTuiLines2.rows(line.trim() ? CodeTuiMarkdown2.rows(line, width) : [{ spans: [{ text: " " }] }], width, `overlay:${at}`));
883
+ }
884
+ #queueLabel() {
885
+ const queue = this.#transcript.queue;
886
+ const parts = [];
887
+ if (queue.steering || queue.followUp)
888
+ parts.push(`queued ${queue.steering + queue.followUp}`);
889
+ if (this.#transcript.compacting)
890
+ parts.push("compacting");
891
+ return parts.join(" \xB7 ");
892
+ }
893
+ #statusLabel() {
894
+ const info = this.#transcript.info;
895
+ if (!info)
896
+ return "starting\u2026";
897
+ const context = this.#transcript.context;
898
+ const window = info.contextTokens ? `${Math.round(info.contextTokens / 1000)}k` : "";
899
+ const used = context?.max ? `${Math.round(context.used / context.max * 100)}% of ${window}` : "";
900
+ return [info.model?.name ?? "no model", used || window && `${window} ctx`, info.profile, info.effort].filter((part) => !!part).join(" \xB7 ");
901
+ }
902
+ #status() {
903
+ if (this.#transcript.compacting)
904
+ return "compacting";
905
+ return this.#transcript.streaming ? "working" : "";
906
+ }
907
+ static #modeOf(question, approval) {
908
+ if (approval)
909
+ return "confirm";
910
+ if (!question)
911
+ return "input";
912
+ if (question.kind === "confirm")
913
+ return "confirm";
914
+ if (question.kind === "select" && question.options?.length)
915
+ return "select";
916
+ return "input";
917
+ }
918
+ static #optionsOf(question) {
919
+ const options = (question?.options ?? []).map((option) => ({
920
+ key: option.key,
921
+ label: option.recommended ? `${option.label} (recommended)` : option.label,
922
+ ...option.detail ? { detail: option.detail } : {}
923
+ }));
924
+ if (!options.length || !question?.freeText)
925
+ return options;
926
+ return [...options, { key: CodeTui.freeTextKey, label: "Something else\u2026", detail: "answer in your own words" }];
927
+ }
928
+ static #promptOf(question, approval) {
929
+ if (approval)
930
+ return `approve? ${approval.summary} [y/n]`;
931
+ return question ? `? ${question.prompt}` : "";
932
+ }
933
+ #hint(mode, multiSelect) {
934
+ if (this.#typing)
935
+ return "enter answer \xB7 esc back to the choices";
936
+ if (mode === "confirm")
937
+ return "y yes \xB7 n no \xB7 esc no";
938
+ if (mode === "select")
939
+ return `\u2191\u2193 choose${multiSelect ? " \xB7 space toggle" : ""} \xB7 enter answer \xB7 esc skip \xB7 pgup/pgdn scroll`;
940
+ return "enter send \xB7 shift+enter newline \xB7 \u2191\u2193 move \xB7 esc interrupt \xB7 pgup/pgdn scroll \xB7 ^c quit \xB7 /help";
941
+ }
942
+ #placeholder() {
943
+ if (this.#typing)
944
+ return "your answer";
945
+ if (this.#transcript.question)
946
+ return "type an answer, or esc to skip";
947
+ return this.#transcript.streaming ? "type to steer, esc to interrupt" : "ask for something, / for commands";
948
+ }
949
+ #type = (text) => {
950
+ const pasted = this.#agent.canSeeImages ? CodeTuiClipboard2.imagePathsIn(text) : undefined;
951
+ if (pasted?.images.length) {
952
+ for (const file of pasted.images)
953
+ this.#attach(file);
954
+ if (pasted.rest)
955
+ this.#editor.insert(pasted.rest);
956
+ } else
957
+ this.#editor.insert(text);
958
+ this.#menuAt = 0;
959
+ this.#historyAt = -1;
960
+ this.#renderNow();
961
+ };
962
+ #newline = () => {
963
+ this.#editor.newline();
964
+ this.#historyAt = -1;
965
+ this.#renderNow();
966
+ };
967
+ #edit = (action) => {
968
+ const width = this.#snapshot().columns - promptMarkWidth - 1;
969
+ const run = {
970
+ backspace: () => this.#editor.backspace(),
971
+ delete: () => this.#editor.deleteForward(),
972
+ left: () => this.#editor.caret ? this.#editor.left() : this.#openSessions(),
973
+ right: () => this.#editor.right(),
974
+ wordLeft: () => this.#editor.wordLeft(),
975
+ wordRight: () => this.#editor.wordRight(),
976
+ home: () => this.#editor.homeOfRow(width),
977
+ end: () => this.#editor.endOfRow(width),
978
+ deleteWord: () => this.#editor.deleteWord(),
979
+ deleteToStart: () => this.#editor.deleteToLineStart()
980
+ };
981
+ run[action]();
982
+ if (action.startsWith("delete") || action === "backspace")
983
+ this.#historyAt = -1;
984
+ this.#renderNow();
985
+ };
986
+ #vertical = (delta) => {
987
+ if (this.#picker)
988
+ return this.#move(delta);
989
+ if (this.#overlay)
990
+ return this.#scroll(delta);
991
+ const menu = this.#menu()?.options ?? [];
992
+ if (menu.length) {
993
+ this.#menuAt = (this.#menuAt + delta + menu.length) % menu.length;
994
+ return this.#renderNow();
995
+ }
996
+ if (this.#historyAt < 0 && this.#editor.move(delta, this.#snapshot().columns - promptMarkWidth - 1))
997
+ return this.#renderNow();
998
+ if (delta < 0)
999
+ return this.#historyPrev();
1000
+ return this.#historyNext();
1001
+ };
1002
+ #complete = () => {
1003
+ const menu = this.#menu();
1004
+ const chosen = menu?.options[this.#menuAt];
1005
+ if (!menu || !chosen)
1006
+ return;
1007
+ const command = CodeTuiCommands2.all.find((entry) => entry.name === chosen.key);
1008
+ const text = menu.kind === "command" ? `/${chosen.key}${command?.arg ? " " : ""}` : `@${chosen.key} `;
1009
+ this.#editor.replace(menu.from, menu.to, text);
1010
+ this.#menuAt = 0;
1011
+ this.#renderNow();
1012
+ };
1013
+ #historyPrev() {
1014
+ if (!this.#history.length)
1015
+ return;
1016
+ this.#historyAt = this.#historyAt < 0 ? this.#history.length - 1 : Math.max(0, this.#historyAt - 1);
1017
+ this.#editor.set(this.#history[this.#historyAt] ?? "");
1018
+ this.#renderNow();
1019
+ }
1020
+ #historyNext() {
1021
+ if (this.#historyAt < 0)
1022
+ return;
1023
+ this.#historyAt += 1;
1024
+ if (this.#historyAt >= this.#history.length) {
1025
+ this.#historyAt = -1;
1026
+ this.#editor.clear();
1027
+ } else
1028
+ this.#editor.set(this.#history[this.#historyAt] ?? "");
1029
+ this.#renderNow();
1030
+ }
1031
+ #scroll = (delta) => {
1032
+ if (this.#picker)
1033
+ return this.#move(Math.sign(delta));
1034
+ if (this.#overlay) {
1035
+ this.#overlayAt = Math.max(0, this.#overlayAt + Math.trunc(delta));
1036
+ return this.#renderNow();
1037
+ }
1038
+ const snapshot = this.#snapshot();
1039
+ const total = snapshot.above + snapshot.lines.length + snapshot.below;
1040
+ const rows = Math.max(1, snapshot.lines.length);
1041
+ if (total <= rows)
1042
+ return;
1043
+ const end = this.#anchor === null ? total : this.#anchor;
1044
+ const next = Math.min(total, Math.max(rows, end + Math.trunc(delta)));
1045
+ this.#anchor = next >= total ? null : next;
1046
+ this.#renderNow();
1047
+ };
1048
+ #move = (delta) => {
1049
+ const picker = this.#picker;
1050
+ if (picker) {
1051
+ picker.at = Math.max(0, Math.min(picker.entries.length - 1, picker.at + delta));
1052
+ return this.#renderNow();
1053
+ }
1054
+ const count = CodeTui.#optionsOf(this.#transcript.question).length;
1055
+ if (!count)
1056
+ return;
1057
+ this.#selected = (this.#selected + delta + count) % count;
1058
+ this.#renderNow();
1059
+ };
1060
+ #toggle = () => {
1061
+ const option = CodeTui.#optionsOf(this.#transcript.question)[this.#selected];
1062
+ if (!option)
1063
+ return;
1064
+ this.#checked = this.#checked.includes(option.key) ? this.#checked.filter((key) => key !== option.key) : [...this.#checked, option.key];
1065
+ this.#renderNow();
1066
+ };
1067
+ #answerConfirm = (value) => {
1068
+ const approval = this.#transcript.approval;
1069
+ if (approval)
1070
+ return void this.#agent.approve(approval.approvalId, value);
1071
+ const question = this.#transcript.question;
1072
+ if (question)
1073
+ this.#answer(question.questionId, { text: value ? "yes" : "no" });
1074
+ };
1075
+ #cancel = () => {
1076
+ if (this.#overlay || this.#picker)
1077
+ return this.#dismiss();
1078
+ if (this.#typing && this.#transcript.question?.options?.length) {
1079
+ this.#typing = false;
1080
+ this.#editor.clear();
1081
+ return this.#renderNow();
1082
+ }
1083
+ const approval = this.#transcript.approval;
1084
+ if (approval)
1085
+ return void this.#agent.approve(approval.approvalId, false);
1086
+ const question = this.#transcript.question;
1087
+ if (question)
1088
+ return void this.#answer(question.questionId, {});
1089
+ if (this.#transcript.streaming)
1090
+ return void this.#agent.abort();
1091
+ if (!this.#editor.isEmpty) {
1092
+ this.#editor.clear();
1093
+ this.#renderNow();
1094
+ }
1095
+ };
1096
+ #interrupt = () => {
1097
+ if (this.#transcript.streaming) {
1098
+ this.#agent.abort();
1099
+ this.#say("interrupted \u2014 ^c again to quit");
1100
+ return;
1101
+ }
1102
+ this.#quit();
1103
+ };
1104
+ #quit() {
1105
+ if (this.#exiting)
1106
+ return;
1107
+ this.#exiting = true;
1108
+ this.#stdout.write(`${CodeTuiMouse.off}${CodeTui.pasteOff}`);
1109
+ this.#instance.clear();
1110
+ this.#instance.unmount();
1111
+ }
1112
+ #submit = () => {
1113
+ if (this.#picker)
1114
+ return this.#resume(this.#picker.entries[this.#picker.at]?.id);
1115
+ const question = this.#transcript.question;
1116
+ if (question)
1117
+ return void this.#answerCurrent(question.questionId);
1118
+ const menu = this.#menu();
1119
+ if (menu?.kind === "file")
1120
+ return this.#complete();
1121
+ const chosen = menu?.options[this.#menuAt];
1122
+ const raw = chosen ? `/${chosen.key}` : this.#editor.text;
1123
+ const pending = this.#pending(raw);
1124
+ if (!pending.text && !pending.images.length)
1125
+ return;
1126
+ this.#editor.clear();
1127
+ this.#historyAt = -1;
1128
+ if (pending.text)
1129
+ this.#history.push(pending.text);
1130
+ if (pending.text.startsWith("/"))
1131
+ return this.#command(pending.text);
1132
+ this.#send(pending.text, pending.images);
1133
+ };
1134
+ #send(text, images = []) {
1135
+ this.#images = [];
1136
+ this.#transcript.echo(text, images.length);
1137
+ this.#renderNow();
1138
+ this.#agent.prompt(text, images).catch((error) => this.#fail(error));
1139
+ }
1140
+ #paste = () => {
1141
+ if (!this.#agent.canSeeImages)
1142
+ return this.#open("images", CodeTui.#imageHelp(this.#transcript.info?.model));
1143
+ CodeTuiClipboard2.image().then((file) => file ? this.#attach(file) : this.#say("nothing on the clipboard we can attach")).catch((error) => this.#fail(error));
1144
+ };
1145
+ #pasted = (text) => {
1146
+ if (text)
1147
+ return this.#type(text.replace(/\r\n?/g, `
1148
+ `));
1149
+ if (CodeTuiClipboard2.has())
1150
+ this.#paste();
1151
+ };
1152
+ #attach(file) {
1153
+ this.#images.push(file);
1154
+ this.#editor.insert(`[image #${this.#images.length}] `);
1155
+ this.#renderNow();
1156
+ }
1157
+ #pending(text) {
1158
+ const marks = [...text.matchAll(CodeTui.imageMark)].map((match) => Number(match[1]));
1159
+ const images = marks.map((mark) => this.#images[mark - 1]).filter((file) => !!file).map((path) => ({ path }));
1160
+ return { images, text: text.replace(CodeTui.imageMark, "").trim() };
1161
+ }
1162
+ static #imageHelp(model) {
1163
+ const provider = model?.provider ?? "<provider>";
1164
+ const id = model?.id ?? "<model>";
1165
+ return [
1166
+ `The model catalogue declares ${provider}/${id} text-only, so an image would go to a model that cannot read it.`,
1167
+ "",
1168
+ "If that is wrong, override the entry in ~/.akan/code/models.json:",
1169
+ "",
1170
+ `{"providers":{"${provider}":{"modelOverrides":{"${id}":{"input":["text","image"]}}}}}`
1171
+ ].join(`
1172
+ `);
1173
+ }
1174
+ #openSessions = () => {
1175
+ const entries = this.#agent.sessions();
1176
+ if (!entries.length)
1177
+ return this.#say("no stored sessions yet");
1178
+ const current = this.#transcript.info?.sessionId;
1179
+ this.#picker = {
1180
+ entries,
1181
+ at: Math.max(0, entries.findIndex((entry) => entry.id === current))
1182
+ };
1183
+ this.#renderNow();
1184
+ };
1185
+ #resume(id) {
1186
+ if (!id)
1187
+ return this.#dismiss();
1188
+ if (id === this.#transcript.info?.sessionId)
1189
+ return this.#dismiss();
1190
+ this.#switchTo = { id };
1191
+ this.#quit();
1192
+ }
1193
+ #fork(argument) {
1194
+ if (!this.#transcript.parts.some((part) => part.kind === "user"))
1195
+ return this.#open("fork", "Nothing has been said in this session yet, so a fork of it is a new session.");
1196
+ const from = this.#transcript.info?.sessionId ?? "";
1197
+ try {
1198
+ const forked = this.#agent.fork(argument.trim() || undefined);
1199
+ this.#switchTo = { id: forked.id, notice: `forked \u2014 the original is akan code --resume ${from}` };
1200
+ this.#quit();
1201
+ } catch (error) {
1202
+ this.#fail(error);
1203
+ }
1204
+ }
1205
+ #openQuestion(question) {
1206
+ this.#typing = false;
1207
+ this.#checked = [];
1208
+ this.#selected = Math.max(0, (question.options ?? []).findIndex((option) => option.recommended));
1209
+ }
1210
+ #answerCurrent(questionId) {
1211
+ const question = this.#transcript.question;
1212
+ const shown = CodeTui.#optionsOf(question);
1213
+ if (this.#typing || !shown.length) {
1214
+ const text = this.#editor.text.trim();
1215
+ this.#editor.clear();
1216
+ this.#typing = false;
1217
+ return void this.#answer(questionId, { text });
1218
+ }
1219
+ if (shown[this.#selected]?.key === CodeTui.freeTextKey) {
1220
+ this.#typing = true;
1221
+ this.#editor.clear();
1222
+ return this.#renderNow();
1223
+ }
1224
+ const keys = question?.multiSelect ? this.#checked.filter((key) => key !== CodeTui.freeTextKey) : [shown[this.#selected]?.key ?? ""];
1225
+ this.#editor.clear();
1226
+ this.#answer(questionId, { keys });
1227
+ }
1228
+ async#answer(questionId, answer) {
1229
+ this.#selected = 0;
1230
+ this.#checked = [];
1231
+ this.#renderNow();
1232
+ await this.#agent.answer(questionId, answer);
1233
+ }
1234
+ #command(line) {
1235
+ const [name, ...rest] = line.slice(1).split(" ");
1236
+ const argument = rest.join(" ").trim();
1237
+ switch (name) {
1238
+ case "help":
1239
+ return this.#open("help", CodeTuiCommands2.help());
1240
+ case "quit":
1241
+ case "exit":
1242
+ return this.#quit();
1243
+ case "abort":
1244
+ return void this.#agent.abort();
1245
+ case "clear":
1246
+ this.#anchor = null;
1247
+ this.#transcript.clear();
1248
+ return this.#say("screen cleared \xB7 the model still remembers \xB7 /compact shortens what it carries");
1249
+ case "compact":
1250
+ return void this.#agent.compact(argument || undefined).catch((error) => this.#fail(error));
1251
+ case "thinking":
1252
+ this.#thinking = !this.#thinking;
1253
+ return this.#say(this.#thinking ? "showing the model's reasoning" : "reasoning folded back to one line");
1254
+ case "tools": {
1255
+ const tools = this.#transcript.info?.tools ?? [];
1256
+ return this.#open("tools", `${tools.length} tools
1257
+
1258
+ ${tools.join(" \xB7 ")}`);
1259
+ }
1260
+ case "model":
1261
+ return this.#setModel(argument);
1262
+ case "effort":
1263
+ return this.#setEffort(argument);
1264
+ case "peers":
1265
+ return this.#open("peers", CodeTui.#peerList(this.#agent.peers()));
1266
+ case "msg":
1267
+ return this.#message(argument);
1268
+ case "fork":
1269
+ return this.#fork(argument);
1270
+ case "agents":
1271
+ return this.#open("agents", this.#agentList());
1272
+ case "mcp":
1273
+ return this.#mcp(argument);
1274
+ case "name":
1275
+ if (!argument)
1276
+ return this.#open("name", "Usage: /name <what this session is about>");
1277
+ this.#agent.setName(argument);
1278
+ return this.#say(`session named ${argument}`);
1279
+ default:
1280
+ return this.#open("unknown command", `There is no /${name}.
1281
+
1282
+ ${CodeTuiCommands2.help()}`);
1283
+ }
1284
+ }
1285
+ #agentList() {
1286
+ const budget = this.#agent.profile.tools.subagent;
1287
+ if (!budget)
1288
+ return "Sub-agents are off in this profile.";
1289
+ const column = Math.max(...Object.keys(SubagentPool2.kinds).map((kind) => kind.length)) + 2;
1290
+ const kinds = Object.entries(SubagentPool2.kinds).map(([kind, spec]) => `${kind.padEnd(column)}${spec.desc}`);
1291
+ return [
1292
+ ...kinds,
1293
+ "",
1294
+ `At most ${budget.maxConcurrent} at once, ${budget.maxDepth} deep, ${budget.budget} tokens for the whole tree.`,
1295
+ "",
1296
+ "Ask for one in a sentence \u2014 the model calls the tool. Its reading never enters this conversation."
1297
+ ].join(`
1298
+ `);
1299
+ }
1300
+ #mcp(argument) {
1301
+ const words = argument.split(" ").filter(Boolean);
1302
+ const local = words.includes("--local");
1303
+ const [verb, name, ...rest] = words.filter((word) => word !== "--local");
1304
+ const root = this.#agent.workspaceRoot;
1305
+ if (!verb)
1306
+ return this.#open("mcp", CodeTuiMcp.list(this.#mcpView()));
1307
+ if (verb === "reload")
1308
+ return this.#reload("reconnected the MCP servers");
1309
+ if (verb === "login" || verb === "logout")
1310
+ return this.#mcpAuth(verb, name);
1311
+ if (verb !== "add" && verb !== "remove") {
1312
+ const status = this.#agent.mcpServers().find((entry) => entry.name === verb);
1313
+ if (status)
1314
+ return this.#open(`mcp \xB7 ${verb}`, CodeTuiMcp.tools(status));
1315
+ return this.#open("mcp", [`There is no /mcp ${verb}.`, "", ...CodeTuiMcp.usage].join(`
1316
+ `));
1317
+ }
1318
+ if (!name)
1319
+ return this.#open("mcp", [`Usage:`, "", ...CodeTuiMcp.usage].join(`
1320
+ `));
1321
+ if (!CodeTuiMcp.namePattern.test(name))
1322
+ return this.#open("mcp", `"${name}" cannot name a server: letters, digits, dash and underscore only.`);
1323
+ try {
1324
+ if (verb === "remove") {
1325
+ const scope = McpServerConfig.remove(root, name, local ? "workspace" : undefined);
1326
+ const remaining = McpServerConfig.declared(root).find((entry) => entry.ref.name === name);
1327
+ if (!scope)
1328
+ return this.#open("mcp", remaining ? `"${name}" is not declared in this repo's file; the ${remaining.scope} file declares it \u2014 /mcp remove ${name} removes it there.` : `No server named "${name}" is declared.`);
1329
+ if (!remaining)
1330
+ return this.#reload(`removed ${name} from the ${scope} file`);
1331
+ return this.#reload(`removed ${name} from the ${scope} file \u2014 the ${remaining.scope} file still declares it, so it still applies; /mcp remove ${name} again to remove it there`);
1332
+ }
1333
+ const scope = local ? "workspace" : "global";
1334
+ const ref = McpServerConfig.add(root, name, rest, scope);
1335
+ const where = scope === "global" ? "every repo" : "this repo";
1336
+ return this.#reload(`added ${name} \xB7 ${McpServerConfig.targetOf(ref)} \xB7 ${where}`);
1337
+ } catch (error) {
1338
+ this.#fail(error);
1339
+ }
1340
+ }
1341
+ #mcpAuth(verb, name) {
1342
+ const root = this.#agent.workspaceRoot;
1343
+ if (!name)
1344
+ return this.#open("mcp", [`Usage: /mcp ${verb} <name>`, "", ...CodeTuiMcp.usage].join(`
1345
+ `));
1346
+ if (verb === "logout") {
1347
+ if (!McpTokenStore.clear(name))
1348
+ return this.#open("mcp", `"${name}" is not signed in.`);
1349
+ return this.#reload(`signed out of ${name}`);
1350
+ }
1351
+ const ref = McpServerConfig.read(root).find((entry) => entry.name === name);
1352
+ if (!ref)
1353
+ return this.#open("mcp", `No server named "${name}" is declared.`);
1354
+ this.#say(`signing in to ${name} \u2014 finish in the browser`);
1355
+ McpSignIn.run(ref, { open: (url) => void openBrowser(url), onNotice: (message) => this.#say(message) }).then(() => this.#reload(`signed in to ${name}`)).catch((error) => this.#fail(error));
1356
+ }
1357
+ #mcpView() {
1358
+ const root = this.#agent.workspaceRoot;
1359
+ const problem = McpServerConfig.problem(root);
1360
+ return {
1361
+ status: this.#agent.mcpServers(),
1362
+ declared: McpServerConfig.declared(root),
1363
+ files: McpServerConfig.files(root),
1364
+ ...problem ? { problem } : {}
1365
+ };
1366
+ }
1367
+ #reload(notice) {
1368
+ const id = this.#transcript.info?.sessionId;
1369
+ if (!id || !this.#agent.sessions().some((entry) => entry.id === id))
1370
+ return this.#open("mcp", "This session is not on disk yet, so there is nothing to reopen. Say something first, or start a new `akan code`.");
1371
+ this.#switchTo = { id, notice };
1372
+ this.#quit();
1373
+ }
1374
+ static #peerList(peers) {
1375
+ if (!peers.length)
1376
+ return "No other akan code session is running in this workspace.";
1377
+ const column = Math.max(...peers.map((peer) => peer.name.length)) + 2;
1378
+ const rows = peers.map((peer) => `${peer.name.padEnd(column)}${peer.id.slice(0, 8)} ${peer.cwd}`);
1379
+ return [...rows, "", "/msg <peer> <text> hands one of them a message."].join(`
1380
+ `);
1381
+ }
1382
+ #message(argument) {
1383
+ const [target, ...rest] = argument.split(" ");
1384
+ const text = rest.join(" ").trim();
1385
+ if (!target || !text)
1386
+ return this.#open("msg", `Usage: /msg <peer> <text>
1387
+
1388
+ ${CodeTui.#peerList(this.#agent.peers())}`);
1389
+ try {
1390
+ const peer = this.#agent.send(target, text);
1391
+ this.#say(`sent to ${peer.name}`);
1392
+ } catch (error) {
1393
+ this.#fail(error);
1394
+ }
1395
+ }
1396
+ #setEffort(argument) {
1397
+ const levels = this.#agent.efforts();
1398
+ if (!levels.length)
1399
+ return this.#open("effort", "This model does no reasoning, so it has no effort to set.");
1400
+ const wanted = argument.trim().toLowerCase();
1401
+ if (!levels.includes(wanted))
1402
+ return this.#open("effort", `Usage: /effort <level>
1403
+
1404
+ now: ${this.#transcript.info?.effort ?? "unknown"}
1405
+ ${levels.join(" \xB7 ")}`);
1406
+ try {
1407
+ this.#agent.setEffort(wanted);
1408
+ this.#say(`effort ${this.#transcript.info?.effort ?? wanted}`);
1409
+ } catch (error) {
1410
+ this.#fail(error);
1411
+ }
1412
+ }
1413
+ #setModel(argument) {
1414
+ const wanted = argument.trim();
1415
+ const [provider, ...rest] = wanted.split("/");
1416
+ if (provider && rest.length)
1417
+ return void this.#agent.setModel({ provider, id: rest.join("/") }).catch((error) => this.#fail(error));
1418
+ const catalogue = this.#agent.catalogue();
1419
+ if (wanted === "providers")
1420
+ return this.#open("model \xB7 providers", CodeTuiModels.providers(catalogue));
1421
+ if (!provider)
1422
+ return this.#open("model", CodeTuiModels.list(catalogue));
1423
+ const listing = CodeTuiModels.ofProvider(catalogue, provider);
1424
+ if (listing)
1425
+ return this.#open(`model \xB7 ${provider}`, listing);
1426
+ return this.#open("model", [`There is no provider called "${provider}".`, "", ...CodeTuiModels.usage].join(`
1427
+ `));
1428
+ }
1429
+ #open(title, text) {
1430
+ this.#overlay = { title, text };
1431
+ this.#overlayAt = 0;
1432
+ this.#renderNow();
1433
+ }
1434
+ #dismiss() {
1435
+ this.#overlay = null;
1436
+ this.#overlayAt = 0;
1437
+ this.#picker = null;
1438
+ this.#renderNow();
1439
+ }
1440
+ #fail(error) {
1441
+ this.#transcript.note("error", String(error));
1442
+ this.#renderNow();
1443
+ }
1444
+ #say(text) {
1445
+ this.#notice = text;
1446
+ if (this.#noticeTimer)
1447
+ clearTimeout(this.#noticeTimer);
1448
+ this.#noticeTimer = setTimeout(() => {
1449
+ this.#notice = "";
1450
+ this.#renderNow();
1451
+ }, CodeTui.noticeMs);
1452
+ this.#renderNow();
1453
+ }
1454
+ }
1455
+ export {
1456
+ CodeTui
1457
+ };