@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
@@ -1,10 +1,10 @@
1
1
  // @bun
2
2
  import {
3
- openBrowser,
4
- plainTextOf,
5
- HOST_SOURCE,
6
- DevLogBuffer
7
- } from "./index-cnzm1wn5.js";
3
+ stripAnsi
4
+ } from "./index-7md2g9vw.js";
5
+ import {
6
+ openBrowser
7
+ } from "./index-chp5gqmm.js";
8
8
  import {
9
9
  writeClipboard
10
10
  } from "./index-p6pbz9n5.js";
@@ -156,6 +156,8 @@ var DevTuiApp = ({ actions }) => {
156
156
  actions.copyLines();
157
157
  else if (input === "Y")
158
158
  actions.copyPath();
159
+ else if (input === "s")
160
+ actions.copyShareUrl();
159
161
  else if (input === "o")
160
162
  actions.openSelected();
161
163
  else if (input === "r")
@@ -216,12 +218,95 @@ var DevTuiApp = ({ actions }) => {
216
218
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
217
219
  dimColor: true,
218
220
  wrap: "truncate",
219
- children: `Tab next \xB7 1-9 app \xB7 \u2191\u2193 scroll (shift page) \xB7 G follow${snapshot.following ? "" : " (paused)"} \xB7 / grep${snapshot.grep ? ` (${snapshot.grep})` : ""} \xB7 e errors${snapshot.errorsOnly ? " on" : ""} \xB7 c clear \xB7 y copy \xB7 Y path \xB7 o open \xB7 r restart \xB7 q quit`
221
+ children: `${snapshot.hasShare ? "s share \xB7 " : ""}Tab next \xB7 1-9 app \xB7 \u2191\u2193 scroll (shift page) \xB7 G follow${snapshot.following ? "" : " (paused)"} \xB7 / grep${snapshot.grep ? ` (${snapshot.grep})` : ""} \xB7 e errors${snapshot.errorsOnly ? " on" : ""} \xB7 c clear \xB7 y copy \xB7 Y path \xB7 o open \xB7 r restart \xB7 q quit`
220
222
  }, undefined, false, undefined, this)
221
223
  ]
222
224
  }, undefined, true, undefined, this);
223
225
  };
224
226
 
227
+ // pkgs/@akanjs/cli/application/devLogBuffer.ts
228
+ var plainTextOf = (lines, { withApp = false } = {}) => {
229
+ const width = withApp ? lines.reduce((max, line) => Math.max(max, line.app.length), 0) : 0;
230
+ return lines.map((line) => `${withApp ? `${line.app.padEnd(width)} \u2502 ` : ""}${stripAnsi(line.text)}`).join(`
231
+ `);
232
+ };
233
+ var HOST_SOURCE = "host";
234
+ var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
235
+ var sourceOf = (text) => {
236
+ const match = childPrefix.exec(text);
237
+ return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
238
+ };
239
+
240
+ class DevLogBuffer {
241
+ static defaultLimit = 5000;
242
+ #limit;
243
+ #lines = [];
244
+ #partial = new Map;
245
+ #sources = new Map;
246
+ #seq = 0;
247
+ constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
248
+ this.#limit = Math.max(1, limit);
249
+ }
250
+ get size() {
251
+ return this.#lines.length;
252
+ }
253
+ sourcesOf(app) {
254
+ return [...this.#sources.get(app) ?? []];
255
+ }
256
+ push(app, kind, chunk) {
257
+ const key = `${app}:${kind}`;
258
+ const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
259
+ `);
260
+ this.#partial.set(key, parts.pop() ?? "");
261
+ return this.#append(app, kind, parts);
262
+ }
263
+ flushPartials() {
264
+ const flushed = [];
265
+ for (const [key, remainder] of [...this.#partial]) {
266
+ this.#partial.set(key, "");
267
+ if (!remainder)
268
+ continue;
269
+ const split = key.lastIndexOf(":");
270
+ const app = key.slice(0, split);
271
+ const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
272
+ flushed.push(...this.#append(app, kind, [remainder]));
273
+ }
274
+ return flushed;
275
+ }
276
+ select({ app = null, source = null, grep, errorsOnly = false } = {}) {
277
+ const needle = grep?.trim().toLowerCase();
278
+ return this.#lines.filter((line) => {
279
+ if (app !== null && line.app !== app)
280
+ return false;
281
+ if (source !== null && line.source !== source)
282
+ return false;
283
+ if (errorsOnly && line.kind !== "stderr")
284
+ return false;
285
+ if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
286
+ return false;
287
+ return true;
288
+ });
289
+ }
290
+ clear(app = null) {
291
+ const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
292
+ this.#lines.length = 0;
293
+ this.#lines.push(...kept);
294
+ }
295
+ #append(app, kind, texts) {
296
+ const known = this.#sources.get(app) ?? new Set;
297
+ this.#sources.set(app, known);
298
+ const added = texts.map((text) => {
299
+ const source = sourceOf(text);
300
+ known.add(source);
301
+ return { seq: ++this.#seq, app, source, kind, text };
302
+ });
303
+ this.#lines.push(...added);
304
+ if (this.#lines.length > this.#limit)
305
+ this.#lines.splice(0, this.#lines.length - this.#limit);
306
+ return added;
307
+ }
308
+ }
309
+
225
310
  // pkgs/@akanjs/cli/application/devLogWindow.ts
226
311
  var windowOf = (lines, rows, anchor) => {
227
312
  const height = Math.max(1, Math.trunc(rows));
@@ -293,6 +378,7 @@ class DevTui {
293
378
  clear: this.#clear,
294
379
  copyLines: this.#copyLines,
295
380
  copyPath: this.#copyPath,
381
+ copyShareUrl: this.#copyShareUrl,
296
382
  openSelected: this.#openSelected,
297
383
  restartSelected: this.#restartSelected,
298
384
  quit: this.#quit
@@ -328,7 +414,7 @@ class DevTui {
328
414
  process.stdout.write(`${line.app} \u2502 ${line.text}
329
415
  `);
330
416
  for (const status of this.#statuses)
331
- process.stdout.write(`${status.name} ${status.url} \u2014 ${status.state}
417
+ process.stdout.write(`${status.name} ${status.url}${status.shareUrl ? ` \xB7 ${status.shareUrl}` : ""} \u2014 ${status.state}
332
418
  `);
333
419
  const log = this.#supervisor.sessionLog;
334
420
  for (const name of log.appNames)
@@ -388,7 +474,7 @@ class DevTui {
388
474
  this.#cachedSnapshot = {
389
475
  rows,
390
476
  selected: this.#indexOfTarget(rows),
391
- title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` : "all apps",
477
+ title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""}${status.shareUrl ? ` \xB7 \u25C8 ${status.shareUrl}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` : this.#mergedTitle(),
392
478
  lines: view.lines,
393
479
  above: view.above,
394
480
  below: view.below,
@@ -399,6 +485,7 @@ class DevTui {
399
485
  errorsOnly: this.#errorsOnly,
400
486
  editingGrep: this.#editingGrep,
401
487
  notice: this.#notice,
488
+ hasShare: this.#statuses.some((candidate) => !!candidate.shareUrl),
402
489
  readyCount: this.#statuses.filter((candidate) => candidate.state === "ready").length,
403
490
  appCount: this.#statuses.length,
404
491
  logRows,
@@ -500,6 +587,25 @@ class DevTui {
500
587
  const app = this.#target.app;
501
588
  return (app ? [app] : log.appNames).map((name) => log.relativePathOf(name));
502
589
  }
590
+ #copyShareUrl = () => {
591
+ const urls = this.#shareUrls();
592
+ if (urls.length === 0) {
593
+ this.#setNotice("no public share \u2014 start with --share");
594
+ return;
595
+ }
596
+ writeClipboard(urls.join(`
597
+ `)).then((copied) => {
598
+ this.#setNotice(copied ? `copied ${urls.join(" \xB7 ")}` : urls.join(" \xB7 "));
599
+ });
600
+ };
601
+ #shareUrls() {
602
+ const app = this.#target.app;
603
+ return this.#statuses.filter((status) => app === null || status.name === app).map((status) => status.shareUrl).filter((url) => !!url);
604
+ }
605
+ #mergedTitle() {
606
+ const urls = this.#shareUrls();
607
+ return urls.length === 0 ? "all apps" : `all apps \xB7 \u25C8 ${urls.join(" \xB7 ")}`;
608
+ }
503
609
  #setNotice(text) {
504
610
  this.#notice = text;
505
611
  if (this.#noticeTimer)
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // pkgs/@akanjs/cli/application/devTuiView.ts
3
3
  var createDevTuiView = async (supervisor) => {
4
- const { DevTui } = await import("./devTui-myg3pqeg.js");
4
+ const { DevTui } = await import("./devTui-v19fbnpk.js");
5
5
  return new DevTui(supervisor);
6
6
  };
7
7
  export {
@@ -0,0 +1,48 @@
1
+ // @bun
2
+ import {
3
+ script,
4
+ command
5
+ } from "./index-gca9y01h.js";
6
+ import"./index-q0y2jq9j.js";
7
+ import"./index-p1j0cyvd.js";
8
+ import"./index-6wwzs1bc.js";
9
+ import {
10
+ Prompter2
11
+ } from "./index-twq9p2sp.js";
12
+
13
+ // pkgs/@akanjs/cli/guideline/guideline.script.ts
14
+ import { Logger } from "akanjs/common";
15
+
16
+ class GuidelineScript extends script("guideline") {
17
+ async guideline(action, name = null, format = "markdown") {
18
+ if (action === "list") {
19
+ const guidelines = await Prompter2.listGuidelines();
20
+ Logger.rawLog(format === "json" ? JSON.stringify({ guidelines }, null, 2) : guidelines.join(`
21
+ `));
22
+ return;
23
+ }
24
+ if (action === "show") {
25
+ if (!name)
26
+ throw new Error("Guideline name is required. Example: akan guideline show framework");
27
+ const [instruction, guideJson] = await Promise.all([Prompter2.getInstruction(name), Prompter2.getGuideJson(name)]);
28
+ Logger.rawLog(format === "json" ? JSON.stringify({ name, instruction, guideJson }, null, 2) : instruction);
29
+ return;
30
+ }
31
+ throw new Error(`Unknown guideline action: ${action}. Use "list" or "show".`);
32
+ }
33
+ }
34
+
35
+ // pkgs/@akanjs/cli/guideline/guideline.command.ts
36
+ class GuidelineCommand extends command("guideline", [GuidelineScript], ({ public: target }) => ({
37
+ guideline: target({ desc: "List or show Akan AI guideline instructions" }).arg("action", String, { desc: "list or show" }).arg("name", String, { desc: "guideline name for show", nullable: true }).option("format", String, {
38
+ desc: "output format",
39
+ default: "markdown",
40
+ enum: ["markdown", "json"]
41
+ }).exec(async function(action, name, format) {
42
+ await this.guidelineScript.guideline(action, name, format);
43
+ })
44
+ })) {
45
+ }
46
+ export {
47
+ GuidelineCommand
48
+ };
@@ -46,7 +46,7 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
46
46
  already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
47
47
  controlled `open` with **no** `onOpenChange` draws no close button at all rather than an inert one. Then the
48
48
  slots: `AgentLauncher`, `AgentSteps`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
49
- `AgentQuestion`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
49
+ `AgentQuestion`, `AgentToolCard`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
50
50
  `akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
51
51
  one it is replacing. **`AgentSteps` is one agent turn** — the messages between the user message that opened it
52
52
  and the next one, with `isRunning` saying whether more are still coming — which is the grain a chat needs to
@@ -76,19 +76,30 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
76
76
  - **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
77
77
  — or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
78
78
  fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
79
- rather than one provider's, so they survive a swap. **DeepSeek is the built-in default** (`deepseek-v4-flash` at
80
- `https://api.deepseek.com`); with no `apiKey` the app still boots and the chat answers `llmUnavailable`. Swap
81
- providers the way middleware is applied: `option.applyAdaptor(LlmAdaptorRole, AnthropicLlm)`. An app may still
82
- write its own — an `adapt()` class in a `srvkit/` implementing `LlmAdaptor.chat(request, onDelta?)`, where
83
- ignoring `onDelta` still answers whole — but for vision it no longer has to.
84
- - **Three providers ship, and only two of them read a picture.** `DeepseekLlm` is the default and declares no
85
- `accepts`, which is a fact about its API rather than a gap. `OpenaiLlm` speaks the same chat-completions dialect
86
- — `OpenaiDialect`, shared between them so a protocol fix lands once — against `https://api.openai.com/v1` and
87
- declares `{ image: true }`, sending an image as a content part. `AnthropicLlm` is the Messages API and declares
88
- `{ image: true, document: true }`; it is its own file rather than a branch, because the system prompt is a field
89
- and not a message, tool calls and results are content blocks, the results ride in a *user* turn, roles must
90
- alternate — so two wire turns that map to one role are merged — and `max_tokens` is required. **Both new ones
91
- require `model`**: a default would age into a 404, and worse, it would decide the vision claim for the app.
79
+ rather than one provider's, so they survive a swap. **`OpenaiLlm` is the built-in default**, pointed at
80
+ `https://api.openai.com/v1`; with no `apiKey` or no `model` the app still boots and the chat answers
81
+ `llmUnavailable`. Swap providers the way middleware is applied:
82
+ `option.applyAdaptor(LlmAdaptorRole, AnthropicLlm)`. An app may still write its own — an `adapt()` class in a
83
+ `srvkit/` implementing `LlmAdaptor.chat(request, onDelta?)`, where ignoring `onDelta` still answers whole — and
84
+ whatever settings it needs beyond `LlmOption` travel through the same `setLlm`, read back with
85
+ `use<MyLlmOption>()` from an interface extending it.
86
+ - **Two adaptors ship, one per wire — a vendor is a host and a model name, not a protocol.** `OpenaiLlm` is the
87
+ chat-completions dialect (`OpenaiDialect`) pointed at whatever `host` names, so OpenAI, DeepSeek, Groq,
88
+ Together, OpenRouter, Ollama and a self-hosted vLLM are all one class and one `option.setLlm({ host, model })`.
89
+ `AnthropicLlm` is the Messages API and declares `{ image: true, document: true }`; it is its own file rather
90
+ than a branch, because the system prompt is a field and not a message, tool calls and results are content
91
+ blocks, the results ride in a *user* turn, roles must alternate — so two wire turns that map to one role are
92
+ merged — and `max_tokens` is required. **Both require `model`**: a default would age into a 404, and worse, it
93
+ would decide the vision claim for the app.
94
+ - **`OpenaiLlm` claims vision for its default host and for nothing else.** OpenAI's own endpoint takes image
95
+ parts, so that is what it answers with no `host` set. A `host` the app named is a gateway the class knows
96
+ nothing about, and it stays text-only until `option.setLlm({ accepts })` says otherwise — handing bytes to a
97
+ model that cannot decode them kills the whole turn on a 400, where text-only degrades them to a note the model
98
+ can repeat back.
99
+ - **A refusal is one key, `agent.error.llmRequestFailed`, and it names the host.** The provider's own sentence
100
+ rides in `reason` and the hostname in `provider`, so an adaptor an app wrote reports through the same
101
+ translated channel the shipped ones do rather than needing a key in the framework's dictionary it cannot
102
+ add.
92
103
  - **`accepts` is answered per model, through `option.setLlm({ accepts })`.** An adaptor answers for an API and one
93
104
  API serves models that differ, so the override rides beside the `model` it is a fact about. It is not a table
94
105
  the framework keeps: a table is a claim about models that ship after it, and getting this wrong is the worst
@@ -205,6 +216,15 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
205
216
  is called from components the composer cannot see; writing the token is the composer's half, and `refer` warns
206
217
  rather than staging silently when no chat is mounted on that session — the case where a card sits outside the
207
218
  `<Agent.Zone>` its chat is inside.
219
+ - **The composer draws a pointer as the name it points at, never as the token.** Where `reference` sources are
220
+ declared the input is a Lexical editor rather than a textarea, and each pointer is one atomic node — a backspace
221
+ takes the whole thing rather than a character of a label that would then name nothing. Nothing above it changes:
222
+ the node's text content *is* the `@[…](mention:…)` token, so the draft string the chat reads, the `@` query, the
223
+ chips, the wire and the server's framing all see what they always saw, and every offset the chat hands over is
224
+ an offset into that string. It is its own chunk behind the chat's, fetched only where a source was declared, and
225
+ `mentions={false}` keeps the plain textarea — for an app that overrides `AgentComposer`, or one that would
226
+ rather see the tokens it is sending. An override that draws its own textarea keeps working: it fills the
227
+ `inputRef` it is handed, and the chat reads the caret off that when no editor filled `handleRef`.
208
228
  - **`attach` and `voice` both carry functions, so a server layout cannot pass either.** A closure does not cross
209
229
  the RSC boundary — `non-scalar-props-restricted` says so on `page/**` — so an app that wants either mounts the
210
230
  chat from a small client component in the app's own `ui/` that calls the hook.
@@ -276,6 +296,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
276
296
  `data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate. Reach a store action
277
297
  from the body — `.exec((id) => st.do.removeX(id))` — which is how an agent gets CRUD; `st.do` on its own
278
298
  reaches nobody.
299
+ - **`.card()` is the other way the chain ends, for an answer that is the user's to give.** Where `.exec()` runs a
300
+ function, `.card(({ submit, cancel }, ...args) => <Form …/>)` parks the call in the chat and renders the app's own
301
+ component there; what `submit` is handed is what the model reads back, and `cancel` is the refusal it reads
302
+ instead. A name and a phone number, a date somebody has to look up, a signature — a model that fills those in
303
+ has answered its own question, and every other shape of "ask the user" is `askUser`. The declared arguments are
304
+ checked **before** the card is parked, so a bad one reaches the model as a refusal rather than throwing inside
305
+ the render; `confirm` is not read at all, because the card in front of the user is already the asking. The frame
306
+ draws its own way out even when the card does not, and the turn is never parked on something with no exit.
279
307
  - **The second argument is how the call behaves, never what it is.** `{ settle, confirm, guard }`:
280
308
  `settle: false` marks a read that returns what is already there and skips the wait; every other tool is waited
281
309
  out before its effect on the screen is reported, because a write may still be landing when `exec` resolves. A
@@ -674,7 +702,10 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
674
702
  is among them. The loop parks on the question exactly as it parks on an approval, a dismissal is the tool's
675
703
  error result rather than a silent empty answer, and the settled exchange renders as question-and-answer instead
676
704
  of a tool row. **Never re-implement it per screen** — a `st.tool("askAboutX")` that opens a modal is the same
677
- thing with a worse transcript — and a hook tool named `askUser` shadows it like any other built-in.
705
+ thing with a worse transcript — and a hook tool named `askUser` shadows it like any other built-in. The line
706
+ between it and a `.card()` tool is what the answer *is*: a sentence or a pick off a list is `askUser` and belongs
707
+ to the conversation, while a shape the model must not invent — fields, a form, a signature — is a card the screen
708
+ declares, because only the screen knows what filling it in looks like.
678
709
 
679
710
  ## What The Page Draws
680
711
  - **The page shows the agent's work, not just the transcript.** A chat panel is closed as often as it is open, and
@@ -714,9 +714,12 @@ what every developer must know even when not building one.
714
714
  every tool runs in the caller's own browser session, gated by guards and the approval card. Its guard is
715
715
  `AgentRelayAccess`, which refuses every call until the app names a guard of its own —
716
716
  `option.setAgentAccess(SignedIn)`. **The LLM is configured in `option.ts`, never through the environment**
717
- (`option.setLlm({ apiKey, model, host })`). Three providers ship — `DeepseekLlm` (the default, text only),
718
- `OpenaiLlm` and `AnthropicLlm` (both read images) — and swapping one in is
719
- `option.applyAdaptor(LlmAdaptorRole, AnthropicLlm)`.
717
+ (`option.setLlm({ apiKey, model, host })`). Two adaptors ship, one per wire: `OpenaiLlm` is the default and
718
+ speaks the chat-completions dialect to whatever `host` names — OpenAI, DeepSeek, Groq, OpenRouter, Ollama — and
719
+ `AnthropicLlm` speaks the Messages API and reads a PDF as well as a picture. Both require `model`. A provider
720
+ neither covers is an `adapt()` class in `srvkit/` implementing `LlmAdaptor`, applied with
721
+ `option.applyAdaptor(LlmAdaptorRole, TheClass)`; whatever settings it needs beyond `LlmOption` ride `setLlm`
722
+ too, read back with `use<MyLlmOption>()`.
720
723
  - **Declare the tool beside the control that already does it**, never as a separate surface:
721
724
  `st.tool("x").desc("…").arg("id", ID).exec(fn)` returns the callable to hand to `onClick` — one handler for the
722
725
  person and the agent. Publish it only where the screen already renders the control; a falsy name declares the
@@ -943,7 +946,9 @@ when two shapes disagree.
943
946
  selected one's log beside it. `Tab` walks every row and `1`-`9` jump to that app (the rail shows each
944
947
  app's digit), `↑↓` scroll (shift for a page, `G` back to following),
945
948
  `/` greps, `e` shows stderr only, `c` clears, `y` copies, `Y` copies the log path, `o` opens the browser,
946
- `r` restarts that app, `q` quits.
949
+ `r` restarts that app, `q` quits. Under `--share`, `s` copies the public URL — the selected app's, or every
950
+ app's from the merged row — and the header carries it ahead of the local one, because the line `--share`
951
+ printed before the view took the terminal is gone by its first repaint.
947
952
  **`--plain` prints prefixed interleaved lines instead**, and a pipe, a redirect or an unsized terminal
948
953
  downgrades to that on its own.
949
954
  - **A supervised session writes `local/apps/<app>/runtime/dev.log`, and that path is how a log gets handed over.**
@@ -6,7 +6,7 @@ Use this to orchestrate AI-assisted module creation. The model should emit small
6
6
  ## Ownership
7
7
  - Input description explains the business object, lifecycle, actions, queries, UI needs, and relations.
8
8
  - The first output pass should prioritize data shape and persistence over decorative UI.
9
- - Each returned code block must start with `// File: <relative path>` so `AiSession.writeTypescripts()` can parse it.
9
+ - Each returned code block must start with `// File: <relative path>` so the caller can tell which file it writes.
10
10
  - Generated files must target `lib/<model>/...` or `lib/__scalar/<scalar>/...` only unless explicitly requested.
11
11
 
12
12
  ## Current Akan Patterns
@@ -21,6 +21,7 @@
21
21
  "create a mutation",
22
22
  "internal triggers",
23
23
  "insights",
24
+ "migrating the LLM adaptor and the removed --ai commands",
24
25
  "auto-generated API reference"
25
26
  ],
26
27
  "rules": [
@@ -393,6 +393,68 @@ Rules the loader and `akan sync` enforce: a chain module has no other export; `p
393
393
  `_overrides.tsx` is untouched. MCP: `prompts/get` now runs the page's body under the caller's token and hands the
394
394
  model the description, what the page fetched, and the tools of those modules — see `mcpRule`.
395
395
 
396
+ ---
397
+
398
+ ### Recipe 8: Migrating the LLM Adaptor and the Removed `--ai` Commands
399
+
400
+ Two changes land together. The runtime LLM seam now names a wire rather than a vendor, and the CLI's AI editor is
401
+ gone. They touch different files and neither one is optional — the first breaks a build or, worse, degrades
402
+ silently; the second breaks a script that calls a command that no longer exists.
403
+
404
+ #### The adaptor, in `lib/option.ts` and `env/env.server.*.ts`
405
+
406
+ `DeepseekLlm` is deleted. `OpenaiLlm` is the default and speaks the chat-completions dialect to whatever `host`
407
+ names, so DeepSeek, Groq, Together, OpenRouter, Ollama and a self-hosted vLLM are the same class with a different
408
+ `host` and `model`. `AnthropicLlm` is unchanged.
409
+
410
+ ```ts
411
+ // before — env/env.server.local.ts
412
+ export const env: ModulesOptions = {
413
+ ...libEnv,
414
+ llm: { apiKey: process.env.DEEPSEEK_KEY },
415
+ };
416
+
417
+ // after — the default model and host are gone, so name both
418
+ export const env: ModulesOptions = {
419
+ ...libEnv,
420
+ llm: { apiKey: process.env.DEEPSEEK_KEY, model: "deepseek-v4-flash", host: "https://api.deepseek.com" },
421
+ };
422
+ ```
423
+
424
+ | Before | After | What happens if you skip it |
425
+ |---|---|---|
426
+ | `setLlm({ apiKey })`, relying on the DeepSeek default | `setLlm({ apiKey, model, host })` | The app boots, the log warns once, and the chat answers `llmUnavailable` to every turn |
427
+ | `applyAdaptor(LlmAdaptorRole, DeepseekLlm)` | `OpenaiLlm` + `host: "https://api.deepseek.com"` | Build error — the class is gone |
428
+ | `OpenaiLlm` pointed at a **named** `host`, reading images | add `accepts: { image: true }` to `setLlm` | **Silent**: images reach the model as a note saying they could not be read |
429
+ | An adaptor of your own throwing `agent.error.<vendor>RequestFailed` | `new Err("agent.error.llmRequestFailed", { provider, status, reason })` | The chat prints the raw key instead of a sentence |
430
+ | Provider settings in a second `option.use({ myLlmOption })` | put them in `setLlm` and read `use<MyLlmOption>()` | Nothing breaks; the second channel is no longer needed |
431
+
432
+ `OpenaiLlm` claims vision for its default host (`https://api.openai.com/v1`) and for nothing else, because a host
433
+ you named is a gateway it knows nothing about — and bytes handed to a model that cannot decode them kill the whole
434
+ turn on a 400, where text-only degrades them to a note. Declare `accepts` whenever you set `host`.
435
+
436
+ `LlmOption` is now a floor rather than the whole shape: `setLlm` carries whatever else it is handed to the role, so
437
+ an adaptor of your own declares `interface MyLlmOption extends LlmOption { region: string }`, reads it with
438
+ `use<MyLlmOption>()`, and stops needing a config channel beside `setLlm`.
439
+
440
+ #### The CLI, in scripts and CI
441
+
442
+ `AiSession` and every command that drove it are removed. There is no replacement command — the workspace's agent
443
+ surface (`akan context`, the MCP tools, `akan guideline show`) is what feeds a coding agent now.
444
+
445
+ | Removed | What to do instead |
446
+ |---|---|
447
+ | `akan create-module <name> --ai`, `akan create-scalar <name> --ai` | Drop the flag. The command scaffolds the same files it always did; write the constant and dictionary yourself or let a coding agent do it against `get_guideline modelConstant` / `modelDictionary` |
448
+ | `akan compact <app-or-lib>` | Edit the `*.abstract.md` by hand. `akan quality scan` still warns past 300 lines |
449
+ | `akan set-llm`, `akan reset-llm`, `akan ask` | Nothing. The runtime LLM is `option.setLlm(...)` and has never been this config |
450
+ | `akan generate-instruction`, `akan update-instruction`, `akan generate-document`, `akan reapply-instruction` | Nothing. `akan guideline list` / `show` still read the bundled guidelines |
451
+ | `import { AiSession } from "@akanjs/devkit/aiEditor"` | Nothing — the module is gone |
452
+
453
+ `~/.akan/config.json` keeps an `llm` entry if it had one. It is ignored; delete it, because it holds an API key.
454
+
455
+ Then: `bun run akan typecheck <app>`, `bun run akan lint <app>`, and one agent turn in the browser to confirm the
456
+ provider answers.
457
+
396
458
  ### Data Flow Summary
397
459
 
398
460
  For each business question, follow this chain:
@@ -3,7 +3,7 @@ import {
3
3
  CodegenLock,
4
4
  WorkspaceExecutor,
5
5
  AppExecutor
6
- } from "./index-5nyznra1.js";
6
+ } from "./index-p1j0cyvd.js";
7
7
  import"./index-q0y2jq9j.js";
8
8
  import"./index-6wwzs1bc.js";
9
9
  import {
@@ -3,20 +3,17 @@ import {
3
3
  getDefaultHostConfig,
4
4
  GlobalConfig,
5
5
  CloudApi
6
- } from "./index-649svkyt.js";
6
+ } from "./index-myj01w9r.js";
7
+ import {
8
+ runner
9
+ } from "./index-gca9y01h.js";
7
10
  import {
8
11
  WorkspaceExecutor,
9
12
  AppExecutor
10
- } from "./index-5nyznra1.js";
11
- import {
12
- AiSession
13
- } from "./index-5tw8t1ts.js";
14
- import {
15
- runner
16
- } from "./index-b3ae16mg.js";
13
+ } from "./index-p1j0cyvd.js";
17
14
  import {
18
15
  openBrowser
19
- } from "./index-cnzm1wn5.js";
16
+ } from "./index-chp5gqmm.js";
20
17
 
21
18
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
22
19
  import path from "path";
@@ -239,13 +236,6 @@ ${chalk.green("\u27A4")} Authentication Required`));
239
236
  `));
240
237
  }
241
238
  }
242
- async setLlm() {
243
- await AiSession.init({ useExisting: false });
244
- }
245
- resetLlm() {
246
- AiSession.setLlmConfig(null);
247
- Logger.rawLog(chalk.green("\u2611\uFE0F LLM model config is cleared. Please run `akan set-llm` to set a new LLM model."));
248
- }
249
239
  async getAkanPkgs(workspace) {
250
240
  const pkgs = await workspace.getPkgs();
251
241
  return pkgs.filter((pkg) => pkg === "akanjs" || pkg === "create-akan-workspace" || pkg.startsWith("@akanjs/"));
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  AppExecutor,
4
4
  LibExecutor
5
- } from "./index-5nyznra1.js";
5
+ } from "./index-p1j0cyvd.js";
6
6
 
7
7
  // pkgs/@akanjs/devkit/workflow/utils.ts
8
8
  var jsonText = (value, { trailingNewline = true } = {}) => `${JSON.stringify(value, null, 2)}${trailingNewline ? `
@@ -21,49 +21,6 @@ var uniqueBy = (values, key) => {
21
21
  };
22
22
  var compactDiagnostics = (diagnostics) => diagnostics.filter((diagnostic) => !!diagnostic);
23
23
 
24
- // pkgs/@akanjs/devkit/workflow/primitive.ts
25
- var createPrimitiveWriteReport = ({
26
- command,
27
- status,
28
- changedFiles = [],
29
- generatedFiles = [],
30
- validationCommands = [],
31
- diagnostics = [],
32
- nextActions = []
33
- }) => ({
34
- schemaVersion: 1,
35
- command,
36
- status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
37
- changedFiles,
38
- generatedFiles,
39
- validationCommands,
40
- diagnostics,
41
- nextActions
42
- });
43
- var renderPrimitiveWriteReport = (report) => [
44
- `# Primitive Write: ${report.command}`,
45
- "",
46
- `- Status: ${report.status}`,
47
- "",
48
- "## Changed Files",
49
- ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
50
- "",
51
- "## Generated Files",
52
- ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
53
- "",
54
- "## Validation Commands",
55
- ...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
56
- "",
57
- "## Diagnostics",
58
- ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
59
- "",
60
- "## Next Actions",
61
- ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
62
- ""
63
- ].join(`
64
- `);
65
- var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
66
-
67
24
  // pkgs/@akanjs/devkit/workflow/artifacts.ts
68
25
  var sourceChangeBlocked = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error" && (diagnostic.failureScope === "source-change" || !diagnostic.failureScope || diagnostic.failureScope === "unknown"));
69
26
  var workflowPlanApproval = {
@@ -415,6 +372,49 @@ var createRepairReport = ({
415
372
  generatedFiles,
416
373
  ...syncedAt ? { syncedAt } : {}
417
374
  });
375
+
376
+ // pkgs/@akanjs/devkit/workflow/primitive.ts
377
+ var createPrimitiveWriteReport = ({
378
+ command,
379
+ status,
380
+ changedFiles = [],
381
+ generatedFiles = [],
382
+ validationCommands = [],
383
+ diagnostics = [],
384
+ nextActions = []
385
+ }) => ({
386
+ schemaVersion: 1,
387
+ command,
388
+ status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
389
+ changedFiles,
390
+ generatedFiles,
391
+ validationCommands,
392
+ diagnostics,
393
+ nextActions
394
+ });
395
+ var renderPrimitiveWriteReport = (report) => [
396
+ `# Primitive Write: ${report.command}`,
397
+ "",
398
+ `- Status: ${report.status}`,
399
+ "",
400
+ "## Changed Files",
401
+ ...report.changedFiles.length ? report.changedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
402
+ "",
403
+ "## Generated Files",
404
+ ...report.generatedFiles.length ? report.generatedFiles.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`) : ["- none"],
405
+ "",
406
+ "## Validation Commands",
407
+ ...report.validationCommands.length ? report.validationCommands.map((validation) => `- \`${validation.command}\`: ${validation.reason}`) : ["- none"],
408
+ "",
409
+ "## Diagnostics",
410
+ ...report.diagnostics.length ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`) : ["- none"],
411
+ "",
412
+ "## Next Actions",
413
+ ...report.nextActions.length ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`) : ["- none"],
414
+ ""
415
+ ].join(`
416
+ `);
417
+ var renderPrimitiveReport = (report, format = "markdown") => format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
418
418
  // pkgs/@akanjs/devkit/workflow/executor.ts
419
419
  import ts3 from "typescript";
420
420
 
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  CommandExecutionError
4
- } from "./index-5nyznra1.js";
4
+ } from "./index-p1j0cyvd.js";
5
5
  import {
6
6
  resolveMobilePath,
7
7
  targetHtmlFilename
@@ -7,7 +7,7 @@ import {
7
7
  isScannedRootEntry,
8
8
  AppExecutor,
9
9
  LibExecutor
10
- } from "./index-5nyznra1.js";
10
+ } from "./index-p1j0cyvd.js";
11
11
  import {
12
12
  FileSys
13
13
  } from "./index-q0y2jq9j.js";
@@ -19,7 +19,7 @@ import {
19
19
  import {
20
20
  workflowRunArtifactPath,
21
21
  workflowSyncDir
22
- } from "./index-jwjaf9fy.js";
22
+ } from "./index-1p146w4s.js";
23
23
 
24
24
  // pkgs/@akanjs/devkit/akanContext.ts
25
25
  import { readdir } from "fs/promises";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  AsyncDefaultExportDetector,
4
4
  resolveSsrPageEntriesForApp
5
- } from "./index-5nyznra1.js";
5
+ } from "./index-p1j0cyvd.js";
6
6
  import {
7
7
  BarrelAnalyzer,
8
8
  createBarrelImportsPlugin,