@akanjs/cli 3.0.0-beta.17 → 3.0.0-beta.18

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 (133) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-gjqnc2mg.js → CodeAgent-0035exh3.js} +2 -2
  3. package/{CodeTui-nd9hqvrw.js → CodeTui-eweg1hep.js} +205 -34
  4. package/{agent.command-n664dtcv.js → agent.command-65zqfkg0.js} +6 -6
  5. package/akanCode.js +5 -3
  6. package/{application.command-t5542jte.js → application.command-2wcj5da3.js} +18 -11
  7. package/{applicationBuildRunner-x4gbfn7y.js → applicationBuildRunner-m0hhgwsx.js} +6 -5
  8. package/{applicationReleasePackager-z61ep017.js → applicationReleasePackager-vykfzgx3.js} +1 -1
  9. package/buildBatch.proc.js +2 -2
  10. package/{capacitorApp-shv3ajh5.js → capacitorApp-2daqhb5h.js} +2 -2
  11. package/{cloud.command-dk5tj39k.js → cloud.command-960bgbf4.js} +8 -8
  12. package/{cloud.script-ch3xrw3s.js → cloud.script-qhcnqt1n.js} +8 -8
  13. package/{code.command-88sebgnn.js → code.command-4feh3ef7.js} +6 -6
  14. package/{context.command-pqsm5jrk.js → context.command-kzehkx48.js} +14 -14
  15. package/context.runner-z0rrcz7b.js +22 -0
  16. package/{devTui-gy6mendg.js → devTui-ffjsarqa.js} +3 -3
  17. package/{devTuiView-y1548wy6.js → devTuiView-1q1mdgrv.js} +1 -1
  18. package/{guideline.command-rpc2emw8.js → guideline.command-3w2g20x4.js} +8 -13
  19. package/guidelines/conventions/conventions.instruction.md +37 -22
  20. package/guidelines/queryRule/queryRule.instruction.md +1 -1
  21. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  22. package/guidelines/transportRule/transportRule.instruction.md +10 -8
  23. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +4 -3
  24. package/incrementalBuilder.proc.js +1 -1
  25. package/{index-bzakk0c2.js → index-14zrtgfa.js} +5 -3
  26. package/{index-57p9p179.js → index-3hpxeys1.js} +6 -5
  27. package/{index-sn7dnzq4.js → index-4r42ext5.js} +1 -1
  28. package/{index-3mg4h788.js → index-6q589y9t.js} +2 -2
  29. package/index-9dp031by.js +12 -0
  30. package/{index-p4p9t45x.js → index-bwxpk2cj.js} +4 -18
  31. package/{index-g9zghs4h.js → index-cjw1sj1x.js} +12 -12
  32. package/{index-te0y1c0m.js → index-d9fjz06h.js} +12 -7
  33. package/{index-4n4821gp.js → index-dw943k93.js} +4 -4
  34. package/{index-4cvpzkq6.js → index-ef228gzf.js} +3 -3
  35. package/{index-3cymrzsb.js → index-erfc1x40.js} +17 -3
  36. package/{index-ns128c4q.js → index-h94faz96.js} +13 -20
  37. package/index-jd01kj5r.js +656 -0
  38. package/{index-2sq06vv0.js → index-jwexzh42.js} +200 -54
  39. package/{index-kvzjda3v.js → index-m41ptvat.js} +5 -5
  40. package/{index-qh7brn20.js → index-mt2154s2.js} +6 -2
  41. package/{index-xjc7gg65.js → index-pn8mhpps.js} +23 -33
  42. package/{index-vpx68e4x.js → index-q9c3c0hy.js} +2 -2
  43. package/{index-v17gj504.js → index-rpgxs245.js} +2 -2
  44. package/{index-twq9p2sp.js → index-s5rhm04y.js} +0 -6
  45. package/{index-p0tew3fq.js → index-sbfk9daf.js} +8 -3
  46. package/{index-n8kt2wh6.js → index-wcg582a8.js} +5 -5
  47. package/{index-6na0sd2b.js → index-ycy302an.js} +5 -2
  48. package/{index-qhtj9y6j.js → index-yw6g3bbv.js} +74 -21
  49. package/{index-2pm1j01n.js → index-zmj143x7.js} +4 -4
  50. package/{index-zn8qgeqx.js → index-zq0mr3hh.js} +1 -1
  51. package/index.js +21 -21
  52. package/{library.command-m8p2r1dh.js → library.command-fp0nhr62.js} +3 -3
  53. package/{localRegistry.command-yppdqrxv.js → localRegistry.command-8y0ecxb9.js} +7 -7
  54. package/{module.command-mfzvqt0n.js → module.command-kqkn48t2.js} +5 -5
  55. package/{package.command-cgp2p2v5.js → package.command-qverjh22.js} +4 -4
  56. package/package.json +2 -2
  57. package/{page.command-taenb02b.js → page.command-94bwhd0g.js} +3 -3
  58. package/{primitive.command-7kpncmkk.js → primitive.command-2ydvtah2.js} +6 -6
  59. package/{prompter-9mn3kp36.js → prompter-1f8d9pcc.js} +1 -1
  60. package/{quality.command-1ar94hrw.js → quality.command-fa0gth4p.js} +3 -4
  61. package/{repair.command-n08t6j8p.js → repair.command-4qng2hpm.js} +5 -5
  62. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  63. package/{scalar.command-mq8qfp4t.js → scalar.command-pb2y95sh.js} +4 -4
  64. package/skills/akan-code/SKILL.md +122 -0
  65. package/skills/akan-module/SKILL.md +2 -2
  66. package/skills/akan-store/SKILL.md +6 -2
  67. package/{subspace.command-jqv5n79w.js → subspace.command-vkxmwp74.js} +4 -4
  68. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  69. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  70. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  71. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  72. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  73. package/templates/appSample/lib/task/task.signal.test.ts +14 -0
  74. package/templates/appSample/lib/task/task.signal.ts +1 -1
  75. package/templates/appSample/srvkit/AuthGuard.ts +4 -1
  76. package/templates/appSample/srvkit/SessionInternalArg.ts +3 -1
  77. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  78. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  79. package/templates/crudPages/new/page.tsx +2 -1
  80. package/templates/crudPages/page.tsx +2 -1
  81. package/templates/crudSinglePage/page.tsx +2 -1
  82. package/templates/module/__model__.signal.ts +12 -4
  83. package/templates/pkgRoot/tsconfig.json.template +2 -7
  84. package/templates/service/__model__.abstract.md.template +8 -0
  85. package/{tunnel.command-ht2es78c.js → tunnel.command-02cvp9nf.js} +2 -2
  86. package/{workflow.command-tvz4nm9n.js → workflow.command-132txnrh.js} +9 -9
  87. package/{workspace.command-txv1yv5e.js → workspace.command-d5wkswek.js} +21 -21
  88. package/context.runner-bys6ykcp.js +0 -22
  89. package/guidelines/agentRule/agentRule.generate.json +0 -31
  90. package/guidelines/componentRule/componentRule.generate.json +0 -31
  91. package/guidelines/conventions/conventions.generate.json +0 -25
  92. package/guidelines/cssRule/cssRule.generate.json +0 -31
  93. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  94. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  95. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  96. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  97. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  98. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  99. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  100. package/guidelines/framework/framework.generate.json +0 -32
  101. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  102. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  103. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  104. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  105. package/guidelines/modelService/modelService.generate.json +0 -31
  106. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  107. package/guidelines/modelStore/modelStore.generate.json +0 -31
  108. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  109. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  110. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  111. package/guidelines/modelView/modelView.generate.json +0 -31
  112. package/guidelines/modelZone/modelZone.generate.json +0 -31
  113. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  114. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  115. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  116. package/guidelines/queryRule/queryRule.generate.json +0 -31
  117. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  118. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  119. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  120. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  121. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  122. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  123. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  124. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  125. package/guidelines/transportRule/transportRule.generate.json +0 -31
  126. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  127. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  128. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  129. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -34
  130. package/index-qt8dqgnf.js +0 -250
  131. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  132. package/templates/module/__model__.abstract.md +0 -28
  133. package/templates/service/__model__.abstract.md +0 -28
@@ -2,8 +2,9 @@
2
2
  import {
3
3
  SubagentPool2,
4
4
  akanCodePaths2,
5
- McpServerConfig
6
- } from "./index-qt8dqgnf.js";
5
+ McpServerConfig,
6
+ McpSignIn
7
+ } from "./index-jd01kj5r.js";
7
8
  import {
8
9
  stripAnsi
9
10
  } from "./index-7md2g9vw.js";
@@ -513,7 +514,7 @@ class AkanToolPack2 {
513
514
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
514
515
  }
515
516
  async names() {
516
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
517
+ const { ContextRunner } = await import("./context.runner-z0rrcz7b.js");
517
518
  return [
518
519
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
519
520
  AkanToolPack2.verifyToolName
@@ -521,7 +522,7 @@ class AkanToolPack2 {
521
522
  }
522
523
  static verifyToolName = "akan_verify";
523
524
  async#register(pi) {
524
- const { ContextRunner } = await import("./context.runner-bys6ykcp.js");
525
+ const { ContextRunner } = await import("./context.runner-z0rrcz7b.js");
525
526
  const runner = new ContextRunner;
526
527
  const guidelineNames = await AkanToolPack2.#guidelineNames();
527
528
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -563,7 +564,7 @@ class AkanToolPack2 {
563
564
  }
564
565
  static async#guidelineNames() {
565
566
  try {
566
- const { Prompter } = await import("./prompter-9mn3kp36.js");
567
+ const { Prompter } = await import("./prompter-1f8d9pcc.js");
567
568
  return await Prompter.listGuidelines();
568
569
  } catch {
569
570
  return [];
@@ -576,9 +577,78 @@ class AkanToolPack2 {
576
577
  }
577
578
  }
578
579
 
579
- // pkgs/@akanjs/devkit/codeAgent/tools/MailToolPack.ts
580
+ // pkgs/@akanjs/devkit/codeAgent/tools/AskToolPack.ts
580
581
  import { Type as Type2 } from "typebox";
581
582
 
583
+ class AskToolPack {
584
+ static toolName = "ask_user";
585
+ static maxQuestions = 4;
586
+ #options;
587
+ constructor(options) {
588
+ this.#options = options;
589
+ }
590
+ names() {
591
+ return this.#options.profile.ui.canPrompt ? [AskToolPack.toolName] : [];
592
+ }
593
+ extension() {
594
+ if (!this.#options.profile.ui.canPrompt)
595
+ return;
596
+ return { name: "akan-ask", factory: (pi) => this.#register(pi) };
597
+ }
598
+ #register(pi) {
599
+ pi.registerTool({
600
+ name: AskToolPack.toolName,
601
+ label: "Ask",
602
+ description: "Ask the user to decide something you cannot decide from the code or the request. Use it when two readings of the task would lead to materially different work, not for choices with an obvious default \u2014 and never to ask permission for work you were already asked to do. Offer concrete options whenever you can name them; mark one recommended when you have a view.",
603
+ promptSnippet: "ask_user: put a decision the user owns back to them, with options",
604
+ parameters: Type2.Object({
605
+ questions: Type2.Array(Type2.Object({
606
+ question: Type2.String({ description: "The whole question, as one sentence ending in a question mark." }),
607
+ header: Type2.Optional(Type2.String({ description: "Two or three words naming what is being chosen, e.g. 'Auth method'." })),
608
+ options: Type2.Optional(Type2.Array(Type2.Object({
609
+ label: Type2.String({ description: "The choice, in one to five words." }),
610
+ detail: Type2.Optional(Type2.String({ description: "What picking it means, or the trade-off it carries." })),
611
+ recommended: Type2.Optional(Type2.Boolean({ description: "Mark at most one. It is offered first and labelled as such." }))
612
+ }), { description: "Two to four distinct choices. Leave it out to ask for an answer in prose." })),
613
+ multiSelect: Type2.Optional(Type2.Boolean({ description: "Several options may be chosen together, rather than exactly one." })),
614
+ freeText: Type2.Optional(Type2.Boolean({
615
+ description: "Offer a further choice that lets the user type an answer of their own. Default true."
616
+ }))
617
+ }), { description: `One to ${AskToolPack.maxQuestions} questions, asked in order.` })
618
+ }),
619
+ execute: async (_id, params) => {
620
+ const questions = (params.questions ?? []).slice(0, AskToolPack.maxQuestions);
621
+ if (!questions.length)
622
+ return { content: [{ type: "text", text: "No question was asked." }], details: undefined, isError: true };
623
+ const answers = [];
624
+ for (const entry of questions) {
625
+ const options = (entry.options ?? []).map((option, at) => ({
626
+ key: `${at}`,
627
+ label: option.label,
628
+ ...option.detail ? { detail: option.detail } : {},
629
+ ...option.recommended ? { recommended: true } : {}
630
+ }));
631
+ const answer = await this.#options.ask({
632
+ prompt: entry.header ? `${entry.header} \u2014 ${entry.question}` : entry.question,
633
+ kind: options.length ? "select" : "text",
634
+ ...options.length ? { options } : {},
635
+ ...entry.multiSelect ? { multiSelect: true } : {},
636
+ freeText: entry.freeText ?? true
637
+ });
638
+ answers.push(`${entry.question}
639
+ \u2192 ${answer ?? "(skipped \u2014 decide this yourself and say what you chose)"}`);
640
+ }
641
+ return { content: [{ type: "text", text: answers.join(`
642
+
643
+ `) }], details: undefined };
644
+ }
645
+ });
646
+ }
647
+ }
648
+
649
+ // pkgs/@akanjs/devkit/codeAgent/tools/MailToolPack.ts
650
+ import { Type as Type3 } from "typebox";
651
+
582
652
  class MailToolPack2 {
583
653
  static toolNames = ["list_peers", "send_peer_message"];
584
654
  #options;
@@ -599,7 +669,7 @@ class MailToolPack2 {
599
669
  label: "List peer sessions",
600
670
  description: "List the other akan code sessions running in this workspace right now, with their name and working directory. Call it before send_peer_message so you name a session that is actually there.",
601
671
  promptSnippet: "list_peers: the other akan code sessions running here",
602
- parameters: Type2.Object({}),
672
+ parameters: Type3.Object({}),
603
673
  execute: async () => ({
604
674
  content: [{ type: "text", text: MailToolPack2.#render(this.#options.mailbox()?.peers() ?? []) }],
605
675
  details: undefined
@@ -610,9 +680,9 @@ class MailToolPack2 {
610
680
  label: "Message a peer session",
611
681
  description: "Hand a message to another akan code session in this workspace. It arrives there as a prompt, so it costs that session a turn \u2014 use it to hand over a piece of work or to warn it about a change you made, not to chat. Say what you want done and what you already did.",
612
682
  promptSnippet: "send_peer_message: hand work to another akan code session",
613
- parameters: Type2.Object({
614
- to: Type2.String({ description: "The peer's name, or the front of its id, from list_peers." }),
615
- message: Type2.String({ description: "What that session should know or do." })
683
+ parameters: Type3.Object({
684
+ to: Type3.String({ description: "The peer's name, or the front of its id, from list_peers." }),
685
+ message: Type3.String({ description: "What that session should know or do." })
616
686
  }),
617
687
  execute: async (_id, params) => {
618
688
  const mailbox = this.#options.mailbox();
@@ -645,20 +715,32 @@ import {
645
715
  codeAgentClip,
646
716
  codeAgentOutputChars
647
717
  } from "akanjs/common";
648
- import { Type as Type3 } from "typebox";
718
+ import { Type as Type4 } from "typebox";
649
719
 
650
720
  // pkgs/@akanjs/devkit/codeAgent/tools/McpClient.ts
651
721
  var protocolVersion = "2025-06-18";
652
722
 
723
+ class McpUnauthorized2 extends Error {
724
+ challenge;
725
+ constructor(name, challenge) {
726
+ super(`MCP server "${name}" requires authentication`);
727
+ this.name = "McpUnauthorized";
728
+ this.challenge = challenge;
729
+ }
730
+ }
731
+
653
732
  class McpClient2 {
733
+ static maxToolPages = 50;
654
734
  ref;
655
735
  #proc;
656
736
  #nextId = 0;
657
737
  #buffer = "";
658
738
  #pending = new Map;
659
739
  #sessionId;
660
- constructor(ref) {
740
+ #token;
741
+ constructor(ref, token) {
661
742
  this.ref = ref;
743
+ this.#token = token;
662
744
  }
663
745
  async connect() {
664
746
  if (this.ref.transport === "stdio")
@@ -674,10 +756,19 @@ class McpClient2 {
674
756
  return this;
675
757
  }
676
758
  async listTools() {
677
- const response = await this.#call("tools/list", {});
678
- if (response.error)
679
- throw new Error(response.error.message);
680
- return response.result?.tools ?? [];
759
+ const tools = [];
760
+ let cursor;
761
+ for (let page = 0;page < McpClient2.maxToolPages; page += 1) {
762
+ const response = await this.#call("tools/list", cursor ? { cursor } : {});
763
+ if (response.error)
764
+ throw new Error(response.error.message);
765
+ tools.push(...response.result?.tools ?? []);
766
+ const next = response.result?.nextCursor;
767
+ if (!next || next === cursor)
768
+ return tools;
769
+ cursor = next;
770
+ }
771
+ return tools;
681
772
  }
682
773
  async callTool(name, args) {
683
774
  const response = await this.#call("tools/call", { name, arguments: args });
@@ -761,11 +852,16 @@ class McpClient2 {
761
852
  headers: {
762
853
  "content-type": "application/json",
763
854
  accept: "application/json, text/event-stream",
855
+ "mcp-protocol-version": protocolVersion,
856
+ ...this.ref.headers,
857
+ ...this.#token ? { authorization: `Bearer ${this.#token}` } : {},
764
858
  ...this.#sessionId ? { "mcp-session-id": this.#sessionId } : {}
765
859
  },
766
860
  body: JSON.stringify(body),
767
861
  signal: AbortSignal.timeout(30000)
768
862
  });
863
+ if (response.status === 401)
864
+ throw new McpUnauthorized2(this.ref.name, response.headers.get("www-authenticate") ?? "");
769
865
  this.#sessionId = response.headers.get("mcp-session-id") ?? this.#sessionId;
770
866
  const text = await response.text();
771
867
  if (!text.trim())
@@ -797,12 +893,22 @@ class McpToolPack2 {
797
893
  const pack = new McpToolPack2;
798
894
  for (const ref of refs) {
799
895
  const base = { name: ref.name, transport: ref.transport, target: McpServerConfig.targetOf(ref) };
896
+ const token = ref.transport === "http" ? await McpSignIn.token(ref, options.onNotice) : undefined;
800
897
  try {
801
- const client = await new McpClient2(ref).connect();
898
+ const client = await new McpClient2(ref, token).connect();
802
899
  const tools = await client.listTools();
803
900
  pack.#clients.push({ client, tools });
804
- pack.#status.push({ ...base, tools: tools.map((tool) => McpToolPack2.#nameOf(ref.name, tool.name)) });
901
+ pack.#status.push({
902
+ ...base,
903
+ tools: tools.map((tool) => McpToolPack2.#nameOf(ref.name, tool.name)),
904
+ auth: token ? "authorized" : "none"
905
+ });
805
906
  } catch (error) {
907
+ if (error instanceof McpUnauthorized2) {
908
+ options.onNotice?.(`MCP server "${ref.name}" needs signing in \u2014 /mcp login ${ref.name}`);
909
+ pack.#status.push({ ...base, tools: [], auth: "required" });
910
+ continue;
911
+ }
806
912
  options.onNotice?.(`MCP server "${ref.name}" is unavailable: ${String(error)}`);
807
913
  pack.#status.push({ ...base, tools: [], error: String(error) });
808
914
  }
@@ -832,7 +938,7 @@ class McpToolPack2 {
832
938
  label: `${client.ref.name}: ${tool.name}`,
833
939
  description,
834
940
  promptSnippet: `${name}: ${description.split(".")[0] ?? description}`,
835
- parameters: Type3.Unsafe(tool.inputSchema),
941
+ parameters: Type4.Unsafe(tool.inputSchema),
836
942
  execute: async (_id, params) => {
837
943
  const result = await client.callTool(tool.name, params ?? {});
838
944
  return {
@@ -858,7 +964,7 @@ class McpToolPack2 {
858
964
  // pkgs/@akanjs/devkit/codeAgent/tools/SessionToolPack.ts
859
965
  import { SessionManager } from "@earendil-works/pi-coding-agent";
860
966
  import { codeAgentClip as codeAgentClip2, codeAgentOutputChars as codeAgentOutputChars2 } from "akanjs/common";
861
- import { Type as Type4 } from "typebox";
967
+ import { Type as Type5 } from "typebox";
862
968
  var snippetChars = 240;
863
969
 
864
970
  class SessionToolPack2 {
@@ -881,9 +987,9 @@ class SessionToolPack2 {
881
987
  label: "Search sessions",
882
988
  description: "Search earlier coding-agent conversations in this workspace for a phrase, and get back the matching sessions with a snippet. Use it when the user refers to something decided before, or when you suspect this problem has been solved here already.",
883
989
  promptSnippet: "session_search: find earlier conversations in this workspace by phrase",
884
- parameters: Type4.Object({
885
- query: Type4.String({ description: "A phrase to look for, matched case-insensitively." }),
886
- limit: Type4.Optional(Type4.Number({ description: "How many sessions to return, default 5." }))
990
+ parameters: Type5.Object({
991
+ query: Type5.String({ description: "A phrase to look for, matched case-insensitively." }),
992
+ limit: Type5.Optional(Type5.Number({ description: "How many sessions to return, default 5." }))
887
993
  }),
888
994
  execute: async (_id, params) => {
889
995
  const matches = await this.#search(params.query, params.limit ?? 5);
@@ -895,7 +1001,7 @@ class SessionToolPack2 {
895
1001
  label: "Read session",
896
1002
  description: "Read one earlier conversation, found with session_search, as plain text.",
897
1003
  promptSnippet: "session_read: read one earlier conversation by id",
898
- parameters: Type4.Object({ sessionId: Type4.String() }),
1004
+ parameters: Type5.Object({ sessionId: Type5.String() }),
899
1005
  execute: async (_id, params) => {
900
1006
  const text = await this.#read(params.sessionId);
901
1007
  return { content: [{ type: "text", text }], details: undefined, isError: text.startsWith("No session") };
@@ -928,7 +1034,7 @@ class SessionToolPack2 {
928
1034
 
929
1035
  // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
930
1036
  import { codeAgentClip as codeAgentClip3, codeAgentOutputChars as codeAgentOutputChars3 } from "akanjs/common";
931
- import { Type as Type5 } from "typebox";
1037
+ import { Type as Type6 } from "typebox";
932
1038
 
933
1039
  class WebToolPack2 {
934
1040
  #options;
@@ -993,7 +1099,7 @@ class WebToolPack2 {
993
1099
  label: "Fetch",
994
1100
  description: "Fetch a URL and return its readable text. HTML is stripped to text; the result is truncated.",
995
1101
  promptSnippet: "web_fetch: fetch a URL and return its readable text",
996
- parameters: Type5.Object({ url: Type5.String({ description: "Absolute http(s) URL." }) }),
1102
+ parameters: Type6.Object({ url: Type6.String({ description: "Absolute http(s) URL." }) }),
997
1103
  execute: async (_id, params) => {
998
1104
  const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
999
1105
  if (refusal)
@@ -1018,9 +1124,9 @@ ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1018
1124
  label: "Search",
1019
1125
  description: `Search the web (${provider.name}) and return the top results as title, url and snippet.`,
1020
1126
  promptSnippet: "web_search: search the web and return titles, urls and snippets",
1021
- parameters: Type5.Object({
1022
- query: Type5.String(),
1023
- count: Type5.Optional(Type5.Number({ description: "How many results, default 5." }))
1127
+ parameters: Type6.Object({
1128
+ query: Type6.String(),
1129
+ count: Type6.Optional(Type6.Number({ description: "How many results, default 5." }))
1024
1130
  }),
1025
1131
  execute: async (_id, params) => {
1026
1132
  const key = process.env[provider.envKey] ?? "";
@@ -1076,10 +1182,19 @@ class AkanCodePlugins2 {
1076
1182
  const sessionExtension = sessions.extension();
1077
1183
  const mail = new MailToolPack2({ profile: options.profile, mailbox: options.mailbox });
1078
1184
  const mailExtension = mail.extension();
1185
+ const onAsk = options.onAsk;
1186
+ const ask = onAsk ? new AskToolPack({ profile: options.profile, ask: onAsk }) : undefined;
1187
+ const askExtension = ask?.extension();
1079
1188
  if (!options.profile.tools.akan)
1080
1189
  return {
1081
- extensions: [mcp?.extension(), webExtension, sessionExtension, mailExtension].filter((entry) => !!entry),
1082
- toolNames: [...mcp?.toolNames ?? [], ...web.names(), ...sessions.names(), ...mail.names()],
1190
+ extensions: [mcp?.extension(), webExtension, sessionExtension, mailExtension, askExtension].filter((entry) => !!entry),
1191
+ toolNames: [
1192
+ ...mcp?.toolNames ?? [],
1193
+ ...web.names(),
1194
+ ...sessions.names(),
1195
+ ...mail.names(),
1196
+ ...ask?.names() ?? []
1197
+ ],
1083
1198
  mcp: mcpStatus,
1084
1199
  dispose
1085
1200
  };
@@ -1114,6 +1229,8 @@ class AkanCodePlugins2 {
1114
1229
  extensions.push(sessionExtension);
1115
1230
  if (mailExtension)
1116
1231
  extensions.push(mailExtension);
1232
+ if (askExtension)
1233
+ extensions.push(askExtension);
1117
1234
  return {
1118
1235
  extensions,
1119
1236
  toolNames: [
@@ -1122,7 +1239,8 @@ class AkanCodePlugins2 {
1122
1239
  ...mcp?.toolNames ?? [],
1123
1240
  ...web.names(),
1124
1241
  ...sessions.names(),
1125
- ...mail.names()
1242
+ ...mail.names(),
1243
+ ...ask?.names() ?? []
1126
1244
  ],
1127
1245
  mcp: mcpStatus,
1128
1246
  dispose
@@ -1281,6 +1399,7 @@ var akanSystemPrompt2 = (profile) => {
1281
1399
  "- Prefer an akan workflow to a direct source edit. `list_workflows` / `explain_workflow` / `plan_workflow`, then `apply_workflow({ planPath })`. Editing source by hand is the fallback, not the default.",
1282
1400
  "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
1283
1401
  "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
1402
+ "- A question about `akan code` itself \u2014 its slash commands, MCP servers, sessions, models, keys, or where its files live \u2014 is answered by the `akan-code` skill. Read it rather than describing the tool from memory.",
1284
1403
  "- Never hand-edit a generated file (`lib/cnst.ts`, `db.ts`, `st.ts`, any `*/index.ts` barrel). Fix the owning source and run `repair_generated` or `akan sync`."
1285
1404
  ];
1286
1405
  if (profile.tools.builtin.includes("write"))
@@ -1289,6 +1408,8 @@ var akanSystemPrompt2 = (profile) => {
1289
1408
  lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
1290
1409
  if (profile.tools.subagent)
1291
1410
  lines.push('- Hand a self-contained search or read to `task` \u2014 `type: "explore"` reads and reports, `type: "code"` makes the change. Only its answer comes back, so give it everything it needs and ask for exactly what you want returned. Do it yourself when the reading is short or when you need the files in front of you.');
1411
+ if (profile.ui.canPrompt)
1412
+ lines.push("- Ask with `ask_user` when two readings of the request would lead to materially different work, and offer the options you can name. Do not ask for a choice that has an obvious default, and do not ask permission for work you were already asked to do \u2014 make the call, say the assumption, and carry on.");
1292
1413
  if (profile.approval !== "never")
1293
1414
  lines.push(`- Some tool calls need the user's approval (policy: ${profile.approval}). A refusal is an answer; do not retry it a different way.`);
1294
1415
  lines.push("- Secrets are refused at the tool boundary. If you need a value from `.env`, ask the user for it rather than looking for another way to read the file.", "", "## How to answer", "", "- Answer in as few words as carry the answer. A short question gets one sentence with no heading.", "- Do not open with what you are about to do, and do not close with a summary of what you did. The tool calls are already on the user's screen.", "- Skip headings, bold labels and bullet lists unless the answer is genuinely a list. Prose is shorter.", "- State a file as `path:line`. Do not paste a file back that the user can open.", "- Say what you could not do, and why, in one line. Do not offer alternatives nobody asked for.");
@@ -1403,8 +1524,12 @@ class AkanCodeServices2 {
1403
1524
  static #skillPaths(profile, workspaceRoot) {
1404
1525
  if (!profile.context.skills)
1405
1526
  return [];
1406
- const workspace = akanCodePaths2.skillsDir(workspaceRoot);
1407
- return [akanCodePaths2.builtinSkillsDir(), existsSync7(workspace) ? workspace : undefined].filter((dir) => !!dir);
1527
+ const dirs = [
1528
+ akanCodePaths2.builtinSkillsDir(),
1529
+ akanCodePaths2.globalSkillsDir(),
1530
+ akanCodePaths2.skillsDir(workspaceRoot)
1531
+ ];
1532
+ return dirs.filter((dir) => !!dir && existsSync7(dir));
1408
1533
  }
1409
1534
  static resourceOptions(options) {
1410
1535
  const present = (dir) => existsSync7(dir) ? [dir] : [];
@@ -2041,7 +2166,8 @@ class CodeAgent2 {
2041
2166
  currentSessionId: () => agent.sessionId,
2042
2167
  mailbox: () => agent.#mailbox,
2043
2168
  onNotice: (message) => agent.#emit({ type: "notice", level: "warning", message }),
2044
- onAgents: (agents) => agent.#emit({ type: "subagent", agents })
2169
+ onAgents: (agents) => agent.#emit({ type: "subagent", agents }),
2170
+ onAsk: (question) => agent.ask(question)
2045
2171
  });
2046
2172
  const resourceLoader = new DefaultResourceLoader({
2047
2173
  cwd,
@@ -2160,6 +2286,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2160
2286
  if (text)
2161
2287
  this.#emit({ type: "message", turnId, role: message.role, text });
2162
2288
  }
2289
+ this.#emitContextNow();
2163
2290
  }
2164
2291
  static #textOf(content) {
2165
2292
  if (typeof content === "string")
@@ -2249,6 +2376,23 @@ Continue the task.`);
2249
2376
  session.setThinkingLevel(effort);
2250
2377
  this.#emit({ type: "session", info: this.info });
2251
2378
  }
2379
+ catalogue() {
2380
+ const runtime = this.#session?.modelRuntime;
2381
+ if (!runtime)
2382
+ return [];
2383
+ const current = this.#session?.model;
2384
+ return runtime.getProviders().map((provider) => ({
2385
+ id: provider.id,
2386
+ name: provider.name ?? provider.id,
2387
+ authorized: runtime.hasConfiguredAuth(provider.id),
2388
+ models: (runtime.getModels(provider.id) ?? []).map((model) => ({
2389
+ id: model.id,
2390
+ name: model.name,
2391
+ ...model.contextWindow ? { contextWindow: model.contextWindow } : {},
2392
+ current: model.provider === current?.provider && model.id === current?.id
2393
+ }))
2394
+ })).sort((a, b) => a.id.localeCompare(b.id));
2395
+ }
2252
2396
  async setModel(ref) {
2253
2397
  const session = this.#require();
2254
2398
  const model = await akanCodeModel2(session.modelRuntime, ref);
@@ -2301,6 +2445,9 @@ Continue the task.`);
2301
2445
  #emitContextUsage(eventType) {
2302
2446
  if (eventType !== "agent_end" && eventType !== "compaction_end")
2303
2447
  return;
2448
+ this.#emitContextNow();
2449
+ }
2450
+ #emitContextNow() {
2304
2451
  const usage = this.#session?.getContextUsage();
2305
2452
  if (usage?.tokens == null)
2306
2453
  return;
@@ -2376,29 +2523,28 @@ Continue the task.`);
2376
2523
  }
2377
2524
  return await this.#asks.openApproval(approvalId);
2378
2525
  }
2526
+ async ask(spec) {
2527
+ if (!this.#profile.ui.canPrompt)
2528
+ return;
2529
+ const question = { questionId: this.#asks.nextId("q"), ...spec };
2530
+ this.#emit({ type: "question", question });
2531
+ if (this.#profile.interaction.question === "suspend") {
2532
+ this.#mapper.noteOutcome("awaiting");
2533
+ return;
2534
+ }
2535
+ return await this.#asks.openQuestion(question) || undefined;
2536
+ }
2379
2537
  #ui() {
2380
2538
  return new CodeAgentUi2({
2381
- ask: async (prompt, kind, choices) => {
2382
- if (!this.#profile.ui.canPrompt)
2383
- return;
2384
- const questionId = this.#asks.nextId("q");
2385
- const question = {
2386
- questionId,
2387
- prompt,
2388
- kind,
2389
- ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2390
- freeText: kind === "text"
2391
- };
2392
- this.#emit({ type: "question", question });
2393
- if (this.#profile.interaction.question === "suspend") {
2394
- this.#mapper.noteOutcome("awaiting");
2395
- return;
2396
- }
2397
- return await this.#asks.openQuestion(question) || undefined;
2398
- },
2539
+ ask: async (prompt, kind, choices) => await this.ask({
2540
+ prompt,
2541
+ kind,
2542
+ ...choices ? { options: choices.map((label) => ({ key: label, label })) } : {},
2543
+ freeText: kind === "text"
2544
+ }),
2399
2545
  notify: (level, message) => this.#emit({ type: "notice", level, message })
2400
2546
  });
2401
2547
  }
2402
2548
  }
2403
2549
 
2404
- export { AkanEditScope2, DevLogFeedback2, PreviewChrome2, PreviewView2, PreviewFeedback2, TurnFeedback2, AkanCli2, AkanVerifier2, VerifyFeedback2, AkanToolPack2, MailToolPack2, McpClient2, McpToolPack2, SessionToolPack2, WebToolPack2, AkanCodePlugins2, AkanEnvKeys2, akanSystemPrompt2, CodeSessionIndex2, AkanCodeServices2, CodeAgentAsks2, CodeAgentEventMapper2, CodeAgentGate2, CodeAgentUi2, CodeMailbox2, CodeSessionFork2, CodeAgent2 };
2550
+ export { AkanEditScope2, DevLogFeedback2, PreviewChrome2, PreviewView2, PreviewFeedback2, TurnFeedback2, AkanCli2, AkanVerifier2, VerifyFeedback2, AkanToolPack2, MailToolPack2, McpUnauthorized2, McpClient2, McpToolPack2, SessionToolPack2, WebToolPack2, AkanCodePlugins2, AkanEnvKeys2, akanSystemPrompt2, CodeSessionIndex2, AkanCodeServices2, CodeAgentAsks2, CodeAgentEventMapper2, CodeAgentGate2, CodeAgentUi2, CodeMailbox2, CodeSessionFork2, CodeAgent2 };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6na0sd2b.js";
5
+ } from "./index-ycy302an.js";
6
6
  import {
7
7
  AGENT_BLOCK_START,
8
8
  AGENT_BLOCK_END,
@@ -13,17 +13,17 @@ import {
13
13
  CLAUDE_COMMENT_RULE,
14
14
  AppExecutor,
15
15
  LibExecutor
16
- } from "./index-p4p9t45x.js";
16
+ } from "./index-bwxpk2cj.js";
17
17
  import {
18
18
  collectRecipeSources2,
19
19
  scanRecipes2
20
20
  } from "./index-by5gpp8x.js";
21
21
  import {
22
22
  AkanContextAnalyzer
23
- } from "./index-v17gj504.js";
23
+ } from "./index-rpgxs245.js";
24
24
  import {
25
25
  Prompter2
26
- } from "./index-twq9p2sp.js";
26
+ } from "./index-s5rhm04y.js";
27
27
 
28
28
  // pkgs/@akanjs/cli/agent/agent.script.ts
29
29
  import { Logger as Logger2 } from "akanjs/common";
@@ -148,7 +148,7 @@ If generated output is stale or broken, update the owning source file and run \`
148
148
  ${recipeIndex}## MCP Workflow Policy
149
149
 
150
150
  - **Prefer an Akan workflow to a direct source edit.** A direct edit is denied when an allowlisted workflow or repair tool can make the change.
151
- - Inspect with \`akan mcp --mode plan\` (\`list_workflows\`, \`explain_workflow\`, \`plan_workflow\`); apply with \`akan mcp --mode apply\`, which allowlists \`apply_workflow\`, \`run_validation\`, and the repair tools.
151
+ - Inspect with \`akan mcp --mode plan\` (\`list_workflows\`, \`explain_workflow\`, \`plan_workflow\`); apply with \`akan mcp --mode apply\`, which allowlists \`apply_workflow\`, \`run_validation\`, and three of the five repair tools \u2014 \`repair_generated\`, \`repair_imports\` and \`repair_module_shape\`. \`akan repair format\` and \`akan repair dictionary\` are CLI-only.
152
152
  - If \`plan_workflow\` returns \`planPath\` or \`next.tool=apply_workflow\`, call \`apply_workflow({ planPath })\` before editing source. Then run \`run_validation\` with \`validationTarget\` when present, otherwise \`applyReportPath\`.
153
153
  - Split a compound request into workflows and apply each \`planPath\` in order, such as \`create-module\` followed by \`add-field\`.
154
154
  - When no workflow exists, or apply reports unsupported/no-op/failed diagnostics needing manual action, keep edits scoped to owning source files and never patch generated files directly.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6na0sd2b.js";
5
+ } from "./index-ycy302an.js";
6
6
 
7
7
  // pkgs/@akanjs/cli/page/page.runner.ts
8
8
  class PageRunner extends runner("page") {
@@ -10,7 +10,11 @@ class PageRunner extends runner("page") {
10
10
  await app.applyTemplate({
11
11
  basePath: basePath ?? `page/(${app.name})/(public)/${module.name}`,
12
12
  template: single ? "crudSinglePage" : "crudPages",
13
- dict: { model: module.name, appName: module.sys.name }
13
+ dict: {
14
+ model: module.name,
15
+ appName: module.sys.name,
16
+ clientPath: `@${module.sys.type}s/${module.sys.name}/client`
17
+ }
14
18
  });
15
19
  }
16
20
  }
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6na0sd2b.js";
5
+ } from "./index-ycy302an.js";
6
6
  import {
7
7
  ModuleExecutor
8
- } from "./index-p4p9t45x.js";
8
+ } from "./index-bwxpk2cj.js";
9
9
  import {
10
10
  sourceFile,
11
11
  moduleSourcePaths,
@@ -13,10 +13,10 @@ import {
13
13
  createPassedPrimitiveReport,
14
14
  bilingualLabelForField,
15
15
  bilingualDescriptionForField
16
- } from "./index-qhtj9y6j.js";
16
+ } from "./index-yw6g3bbv.js";
17
17
  import {
18
18
  PageScript
19
- } from "./index-qh7brn20.js";
19
+ } from "./index-mt2154s2.js";
20
20
  import {
21
21
  pluralizeName
22
22
  } from "./index-nz8prx9g.js";
@@ -31,37 +31,17 @@ var purposeByModule = {
31
31
  project: "Project represents a project workspace or business initiative managed by the app.",
32
32
  task: "Task represents work items that move through the app workflow."
33
33
  };
34
- var moduleAbstractContent = (moduleName) => {
34
+ var moduleAbstractContent = (moduleName, sharedGuards) => {
35
35
  const title = capitalize(moduleName);
36
- const label = bilingualLabelForField(moduleName);
37
- const purpose = purposeByModule[moduleName] ?? `${title} represents ${label.en.toLowerCase()} records managed by the app.`;
38
- return `# ${title} Module Abstract
39
-
40
- ## Purpose
41
-
36
+ const noun = bilingualLabelForField(moduleName).en.toLowerCase();
37
+ const purpose = purposeByModule[moduleName] ?? `${title} represents ${noun} records managed by the app.`;
38
+ const writeRule = sharedGuards ? `Anyone may read a ${noun}; only an admin creates, updates or removes one.` : `Anyone may read a ${noun}; nobody creates, updates or removes one until the slice names a guard.`;
39
+ return `# ${moduleName} Abstract
42
40
  ${purpose}
43
41
 
44
- ## Domain Rules
45
-
46
- - Keep ${label.en.toLowerCase()} data consistent with user-facing dictionary labels.
47
-
48
- ## Data Meaning
49
-
50
- ${label.en} (${label.ko}) is the primary business concept for this module.
51
-
52
- ## Workflows
53
-
54
- No lifecycle workflow yet.
55
-
56
- ## Agent Notes
57
-
58
- - Read this abstract before changing the module.
59
- - Update this file when business invariants, workflows, or public behavior change.
60
- - Do not update this file for formatting-only, import-only, or style-only changes.
61
-
62
- ## Related Modules
63
-
64
- - None yet.
42
+ ## Rules
43
+ - ${writeRule}
44
+ - Removal is soft: a removed ${noun} keeps its row with \`removedAt\` set.
65
45
  `;
66
46
  };
67
47
  var localModuleFilename = (moduleName, pathKey) => moduleSourcePaths(moduleName)[pathKey].replace(`lib/${moduleName}/`, "");
@@ -92,6 +72,14 @@ class ModuleRunner extends runner("module") {
92
72
  async removeModule(module) {
93
73
  await module.sys.removeDir(`lib/${module.name}`);
94
74
  }
75
+ async#hasSharedGuards(module) {
76
+ const { sys } = module;
77
+ if (sys.type === "lib" && sys.name === "shared")
78
+ return true;
79
+ if (!await sys.exists("lib/srv.ts"))
80
+ return false;
81
+ return (await sys.readFile("lib/srv.ts")).includes('"@libs/shared/lib/srv"');
82
+ }
95
83
  async createComponentTemplate(module, type) {
96
84
  await module.sys.applyTemplate({
97
85
  basePath: `./lib/${module.name}`,
@@ -122,9 +110,11 @@ class ModuleRunner extends runner("module") {
122
110
  zone: localModuleFilename(module.name, "zone"),
123
111
  util: localModuleFilename(module.name, "util")
124
112
  };
113
+ const sharedGuards = await this.#hasSharedGuards(module);
125
114
  await module.applyTemplate({
126
115
  basePath: `.`,
127
116
  template: "module",
117
+ options: { sharedGuards },
128
118
  dict: {
129
119
  model: module.name,
130
120
  models: names,
@@ -136,7 +126,7 @@ class ModuleRunner extends runner("module") {
136
126
  modelDescKo: modelDescription.ko
137
127
  }
138
128
  });
139
- await module.writeFile(filenames.abstract, moduleAbstractContent(module.name));
129
+ await module.writeFile(filenames.abstract, moduleAbstractContent(module.name, sharedGuards));
140
130
  const [
141
131
  abstractContent,
142
132
  constantContent,
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6na0sd2b.js";
5
+ } from "./index-ycy302an.js";
6
6
  import {
7
7
  generatedFilesForSync,
8
8
  createPassedPrimitiveReport,
9
9
  scalarChangedFiles
10
- } from "./index-qhtj9y6j.js";
10
+ } from "./index-yw6g3bbv.js";
11
11
  import {
12
12
  pluralizeName
13
13
  } from "./index-nz8prx9g.js";