@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.20

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 (187) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-awv258wb.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-1nqh35ez.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-scf4e4pd.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-1s9ag7sb.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/{application.command-pj2m7584.js → application.command-49hwy6va.js} +51 -78
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-ybyp97pv.js} +63 -64
  12. package/{applicationReleasePackager-4yasxwv9.js → applicationReleasePackager-esnrphw8.js} +40 -60
  13. package/buildBatch.proc.js +19 -24
  14. package/{capacitorApp-36rbr858.js → capacitorApp-1synz4en.js} +3 -4
  15. package/cloud.command-d7rqtzt9.js +94 -0
  16. package/cloud.script-bkbpcr96.js +19 -0
  17. package/code.command-nn48xzej.js +154 -0
  18. package/commandManifest.json +1 -1
  19. package/consoleToStderr-h5bryj94.js +7 -0
  20. package/context.command-s3mcmvpa.js +48 -0
  21. package/context.runner-k99nb5pk.js +21 -0
  22. package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
  23. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  24. package/fileSys-5ngz93ga.js +9 -0
  25. package/guideline.command-sct4qnt5.js +42 -0
  26. package/guidelines/agentRule/agentRule.instruction.md +206 -25
  27. package/guidelines/conventions/conventions.instruction.md +79 -24
  28. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  29. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  30. package/guidelines/queryRule/queryRule.instruction.md +17 -7
  31. package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
  32. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  33. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  34. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  35. package/incrementalBuilder.proc.js +31 -48
  36. package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
  37. package/index-13ffe9wp.js +69 -0
  38. package/index-22d57v4c.js +266 -0
  39. package/{index-fz5q3gd7.js → index-31yr441d.js} +10 -17
  40. package/index-3hx1z96c.js +87 -0
  41. package/index-3sq321ak.js +53 -0
  42. package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
  43. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  44. package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
  45. package/{index-259dsnz3.js → index-5pc1qrbv.js} +5 -8
  46. package/index-7md2g9vw.js +6 -0
  47. package/index-8mna0947.js +187 -0
  48. package/{index-zry5nz81.js → index-8sf7wmdr.js} +51 -30
  49. package/{index-2tv3fdx4.js → index-9d9bgcx4.js} +342 -765
  50. package/index-9dp031by.js +12 -0
  51. package/{index-ne2qy0nk.js → index-ahemdr5n.js} +12 -12
  52. package/{index-ws5e0j51.js → index-as7xmyz8.js} +40 -24
  53. package/index-axzjaqve.js +1118 -0
  54. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  55. package/index-c6eggbee.js +123 -0
  56. package/index-d2242ksy.js +99 -0
  57. package/{index-a64esmtb.js → index-ddxvdd1p.js} +169 -305
  58. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  59. package/{index-pnqbkf5p.js → index-dzf3r49a.js} +6 -2
  60. package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
  61. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  62. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  63. package/{index-wt7qrtd4.js → index-mxav6j3c.js} +93 -232
  64. package/{index-gyd220p2.js → index-myhc3c1j.js} +554 -577
  65. package/{index-600zhnve.js → index-nv5976x3.js} +56 -197
  66. package/index-p6pbz9n5.js +25 -0
  67. package/{index-4edzf1gr.js → index-przybvy5.js} +77 -150
  68. package/index-smme4r47.js +49 -0
  69. package/{index-mak3cd28.js → index-w2zdcw3k.js} +137 -255
  70. package/index-wa5jbxj1.js +760 -0
  71. package/{index-v0qh106m.js → index-ww4z397j.js} +527 -723
  72. package/{index-j9yhg404.js → index-xqjwd384.js} +4 -7
  73. package/{index-7737gxcp.js → index-xqpzbab5.js} +107 -241
  74. package/index-xsjh6nzh.js +2729 -0
  75. package/{index-ktb5njgy.js → index-ygn77a5w.js} +47 -99
  76. package/index-ztp3e73t.js +700 -0
  77. package/index.js +23 -22
  78. package/{library.command-nbsky672.js → library.command-1gx04jgn.js} +4 -5
  79. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-zndxj1q6.js} +22 -42
  80. package/{module.command-29vqzrh8.js → module.command-3waddk95.js} +8 -11
  81. package/{package.command-axv0abmn.js → package.command-zrc7br1y.js} +6 -7
  82. package/package.json +14 -5
  83. package/{page.command-vs9s3zrh.js → page.command-vpjgardh.js} +4 -5
  84. package/{primitive.command-cbxmqbtc.js → primitive.command-sz84tk4t.js} +9 -22
  85. package/prompter-ptaa3nk0.js +8 -0
  86. package/{quality.command-gmbs7nws.js → quality.command-g869hafs.js} +203 -376
  87. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  88. package/{repair.command-bqsjptsj.js → repair.command-chsnjvvv.js} +11 -35
  89. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  90. package/{scalar.command-6k1vhe9s.js → scalar.command-2v21newx.js} +7 -10
  91. package/skills/akan-code/SKILL.md +130 -0
  92. package/skills/akan-debug/SKILL.md +42 -0
  93. package/skills/akan-deps/SKILL.md +50 -0
  94. package/skills/akan-err/SKILL.md +39 -0
  95. package/skills/akan-module/SKILL.md +45 -0
  96. package/skills/akan-scaffold/SKILL.md +76 -0
  97. package/skills/akan-scalar/SKILL.md +52 -0
  98. package/skills/akan-service/SKILL.md +34 -0
  99. package/skills/akan-store/SKILL.md +67 -0
  100. package/skills/akan-ui/SKILL.md +63 -0
  101. package/skills/akan-validate/SKILL.md +44 -0
  102. package/{subspace.command-fwjfcts7.js → subspace.command-ckcbwfp1.js} +138 -67
  103. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  104. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  105. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  106. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  107. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  108. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  109. package/templates/appSample/lib/task/task.signal.ts +1 -1
  110. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  111. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  112. package/templates/client.ts +7 -30
  113. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  114. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  115. package/templates/crudPages/new/page.tsx +2 -1
  116. package/templates/crudPages/page.tsx +2 -1
  117. package/templates/crudSinglePage/page.tsx +2 -1
  118. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  119. package/templates/lib/__lib/lib.document.ts +0 -2
  120. package/templates/lib/sig.ts +8 -30
  121. package/templates/localDev/docker-compose.yaml.template +3 -14
  122. package/templates/module/__model__.signal.ts +12 -4
  123. package/templates/pkgRoot/tsconfig.json.template +2 -7
  124. package/templates/server.ts +0 -2
  125. package/templates/service/__model__.abstract.md.template +8 -0
  126. package/tunnel.command-kd2yr4fx.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-44252cqv.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-fqcztt5p.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/cloud.command-jshwdq22.js +0 -93
  133. package/context.command-ez8a0zyz.js +0 -80
  134. package/devTuiView-c08ccbat.js +0 -9
  135. package/getDirname-dr3jz1xg.js +0 -7
  136. package/guideline.command-xmqwx8zm.js +0 -359
  137. package/guidelines/agentRule/agentRule.generate.json +0 -31
  138. package/guidelines/componentRule/componentRule.generate.json +0 -31
  139. package/guidelines/conventions/conventions.generate.json +0 -25
  140. package/guidelines/cssRule/cssRule.generate.json +0 -31
  141. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  142. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  143. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  144. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  145. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  146. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  147. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  148. package/guidelines/framework/framework.generate.json +0 -32
  149. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  150. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  151. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  152. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  153. package/guidelines/modelService/modelService.generate.json +0 -31
  154. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  155. package/guidelines/modelStore/modelStore.generate.json +0 -31
  156. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  157. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  158. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  159. package/guidelines/modelView/modelView.generate.json +0 -31
  160. package/guidelines/modelZone/modelZone.generate.json +0 -31
  161. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  162. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  163. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  164. package/guidelines/queryRule/queryRule.generate.json +0 -31
  165. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  166. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  167. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  168. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  169. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  170. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  171. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  172. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  173. package/guidelines/transportRule/transportRule.generate.json +0 -31
  174. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  175. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  176. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  177. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  178. package/index-2hq52jq3.js +0 -465
  179. package/index-6wwzs1bc.js +0 -7
  180. package/index-8pm30m73.js +0 -841
  181. package/index-kn1h7p1x.js +0 -560
  182. package/index-meh558jy.js +0 -65
  183. package/index-rwg2aa4f.js +0 -229
  184. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  185. package/templates/module/__model__.abstract.md +0 -28
  186. package/templates/service/__model__.abstract.md +0 -28
  187. package/workflow.command-nwpcdc8p.js +0 -107
@@ -0,0 +1,700 @@
1
+ // @bun
2
+ import {
3
+ ConfigLock
4
+ } from "./index-3hx1z96c.js";
5
+
6
+ // pkgs/@akanjs/devkit/codeAgent/agent/SubagentPool.ts
7
+ import { codeAgentReadOnlyBuiltins } from "akanjs/common";
8
+ import { Type } from "typebox";
9
+ var maxResultChars = 8000;
10
+ var tickMs = 1000;
11
+
12
+ class SubagentPool2 {
13
+ static kinds = {
14
+ explore: {
15
+ readOnly: true,
16
+ desc: "reads, searches and reports back. It cannot write a file or run a command."
17
+ },
18
+ code: {
19
+ readOnly: false,
20
+ desc: "makes the change itself, with the same tools as this session."
21
+ }
22
+ };
23
+ static defaultKind = "explore";
24
+ #options;
25
+ #children = new Map;
26
+ #spend;
27
+ #ticker = null;
28
+ constructor(options) {
29
+ this.#options = options;
30
+ this.#spend = options.spend ?? { tokens: 0, running: 0 };
31
+ }
32
+ static extensionFor(options) {
33
+ if (!options.parent.tools.subagent)
34
+ return;
35
+ if (options.depth >= options.parent.tools.subagent.maxDepth)
36
+ return;
37
+ const pool = new SubagentPool2(options);
38
+ return { name: "akan-subagent", factory: (pi) => pool.#register(pi) };
39
+ }
40
+ static toolName = "task";
41
+ #register(pi) {
42
+ pi.registerTool({
43
+ name: SubagentPool2.toolName,
44
+ label: "Task",
45
+ description: "Run a focused sub-agent on one self-contained question and get back its answer. Use it for searching and reading that would otherwise fill this conversation \u2014 the sub-agent's own reading does not come back, only its answer. Give it everything it needs: it cannot see this conversation.",
46
+ promptSnippet: "task: run a sub-agent on one self-contained question and get back only its answer",
47
+ parameters: Type.Object({
48
+ description: Type.String({ description: "Three to five words naming the task, for the activity log." }),
49
+ prompt: Type.String({
50
+ description: "The complete instruction. The sub-agent sees nothing of this conversation."
51
+ }),
52
+ type: Type.Optional(Type.Union(Object.entries(SubagentPool2.kinds).map(([kind, spec]) => Type.Literal(kind, { description: spec.desc })), { description: `What the sub-agent may do. Defaults to ${SubagentPool2.defaultKind}.` }))
53
+ }),
54
+ execute: async (id, params, signal) => {
55
+ const kind = params.type ?? SubagentPool2.defaultKind;
56
+ const refusal = this.#refuse();
57
+ if (refusal)
58
+ return { content: [{ type: "text", text: refusal }], details: undefined, isError: true };
59
+ this.#spend.running += 1;
60
+ this.#children.set(id, { kind, description: params.description, startedAt: Date.now() });
61
+ this.#report();
62
+ this.#options.onNotice?.(`${kind} sub-agent \xB7 ${params.description}`);
63
+ try {
64
+ const text = await this.#run(id, kind, params.prompt, signal);
65
+ return { content: [{ type: "text", text }], details: undefined };
66
+ } finally {
67
+ this.#spend.running -= 1;
68
+ this.#children.delete(id);
69
+ this.#report();
70
+ }
71
+ }
72
+ });
73
+ }
74
+ #refuse() {
75
+ const budget = this.#options.parent.tools.subagent;
76
+ if (!budget)
77
+ return "Sub-agents are disabled by this profile.";
78
+ if (this.#spend.running >= budget.maxConcurrent)
79
+ return `Too many sub-agents are already running (limit ${budget.maxConcurrent} at once, counted across the whole task tree). ${this.#children.size ? "Wait for one of yours to finish, or do" : "Do"} this part yourself.`;
80
+ if (this.#spend.tokens >= budget.budget)
81
+ return `The sub-agent token budget (${budget.budget}) is spent. Do the rest of this work yourself.`;
82
+ return;
83
+ }
84
+ static childOf(parent, kind) {
85
+ const budget = parent.tools.subagent;
86
+ return {
87
+ ...parent,
88
+ name: `${parent.name}:${kind}`,
89
+ tools: {
90
+ ...parent.tools,
91
+ builtin: SubagentPool2.kinds[kind].readOnly ? parent.tools.builtin.filter((tool) => codeAgentReadOnlyBuiltins.includes(tool)) : parent.tools.builtin,
92
+ subagent: budget ? { ...budget, maxDepth: budget.maxDepth } : false
93
+ },
94
+ session: { store: "memory", crossSession: false },
95
+ ui: { canPrompt: false },
96
+ context: { projectFiles: false, skills: parent.context.skills }
97
+ };
98
+ }
99
+ #report() {
100
+ const onAgents = this.#options.onAgents;
101
+ if (!onAgents)
102
+ return;
103
+ onAgents([...this.#children].map(([id, child]) => ({
104
+ id,
105
+ kind: child.kind,
106
+ description: child.description,
107
+ startedAt: child.startedAt,
108
+ tokens: child.agent?.tokensUsed ?? 0
109
+ })));
110
+ if (this.#children.size && !this.#ticker) {
111
+ this.#ticker = setInterval(() => this.#report(), tickMs);
112
+ this.#ticker.unref?.();
113
+ } else if (!this.#children.size && this.#ticker) {
114
+ clearInterval(this.#ticker);
115
+ this.#ticker = null;
116
+ }
117
+ }
118
+ async#run(id, kind, prompt, signal) {
119
+ const { CodeAgent } = await import("./CodeAgent-awv258wb.js");
120
+ const budget = this.#options.parent.tools.subagent;
121
+ const child = SubagentPool2.childOf(this.#options.parent, kind);
122
+ const agent = await CodeAgent.create({
123
+ workspace: this.#options.workspace,
124
+ cwd: this.#options.cwd,
125
+ profile: child,
126
+ apps: [],
127
+ depth: this.#options.depth + 1,
128
+ subagentSpend: this.#spend
129
+ });
130
+ const running = this.#children.get(id);
131
+ if (running)
132
+ running.agent = agent;
133
+ let stopped = false;
134
+ const abort = () => {
135
+ stopped = true;
136
+ agent.abort();
137
+ };
138
+ signal?.addEventListener("abort", abort, { once: true });
139
+ if (signal?.aborted)
140
+ stopped = true;
141
+ let finished = false;
142
+ try {
143
+ let answer = "";
144
+ agent.on((event) => {
145
+ if (event.type === "message" && event.role === "assistant")
146
+ answer = event.text;
147
+ if (event.type === "turn_start" && stopped)
148
+ agent.abort();
149
+ });
150
+ if (!stopped) {
151
+ await agent.prompt(prompt);
152
+ await agent.waitForIdle();
153
+ }
154
+ finished = true;
155
+ return answer.length > maxResultChars ? `${answer.slice(0, maxResultChars)}
156
+ \u2026(truncated)` : answer || "(no answer)";
157
+ } finally {
158
+ signal?.removeEventListener("abort", abort);
159
+ const tokens = agent.tokensUsed;
160
+ this.#spend.tokens += tokens;
161
+ const outcome = stopped ? "stopped" : finished ? "finished" : "failed";
162
+ const ceiling = budget ? budget.budget : 0;
163
+ this.#options.onNotice?.(`${kind} sub-agent ${outcome}, ${tokens} tokens (${this.#spend.tokens} of ${ceiling} spent)`);
164
+ agent.dispose();
165
+ }
166
+ }
167
+ }
168
+
169
+ // pkgs/@akanjs/devkit/codeAgent/agent/akanCodePaths.ts
170
+ import { existsSync } from "fs";
171
+ import { homedir } from "os";
172
+ import path from "path";
173
+ var builtinSkillsDir = () => {
174
+ const candidates = [path.join(import.meta.dir, "skills"), path.join(import.meta.dir, "..", "skills")];
175
+ return candidates.find((candidate) => existsSync(candidate));
176
+ };
177
+ var globalDir = () => process.env.AKAN_CODE_HOME ?? path.join(homedir(), ".akan", "code");
178
+ var akanCodePaths2 = {
179
+ workspaceDir: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code"),
180
+ sessionsDir: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code", "sessions"),
181
+ extensionsDir: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code", "extensions"),
182
+ skillsDir: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code", "skills"),
183
+ mailDir: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code", "mail"),
184
+ builtinSkillsDir,
185
+ mcpFile: (workspaceRoot) => path.join(workspaceRoot, ".akan", "code", "mcp.json"),
186
+ globalMcpFile: () => path.join(globalDir(), "mcp.json"),
187
+ globalSkillsDir: () => path.join(globalDir(), "skills"),
188
+ globalDir,
189
+ authFile: () => path.join(globalDir(), "auth.json"),
190
+ mcpAuthFile: () => path.join(globalDir(), "mcpAuth.json"),
191
+ modelsFile: () => path.join(globalDir(), "models.json")
192
+ };
193
+
194
+ // pkgs/@akanjs/devkit/codeAgent/tools/McpServerConfig.ts
195
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
196
+ import path2 from "path";
197
+ class McpServerConfig {
198
+ static file(workspaceRoot, scope = "workspace") {
199
+ return scope === "global" ? akanCodePaths2.globalMcpFile() : akanCodePaths2.mcpFile(workspaceRoot);
200
+ }
201
+ static files(workspaceRoot) {
202
+ return [
203
+ { scope: "global", file: McpServerConfig.file(workspaceRoot, "global") },
204
+ { scope: "workspace", file: McpServerConfig.file(workspaceRoot, "workspace") }
205
+ ];
206
+ }
207
+ static read(workspaceRoot) {
208
+ return McpServerConfig.declared(workspaceRoot).filter((entry) => !entry.disabled).map((entry) => entry.ref);
209
+ }
210
+ static declared(workspaceRoot) {
211
+ const byName = new Map;
212
+ for (const { scope } of McpServerConfig.files(workspaceRoot))
213
+ for (const [name, entry] of Object.entries(McpServerConfig.#parse(workspaceRoot, scope).map))
214
+ byName.set(name, { ref: McpServerConfig.#refOf(name, entry), disabled: !!entry.disabled, scope });
215
+ return [...byName.values()];
216
+ }
217
+ static add(workspaceRoot, name, target, scope = "global") {
218
+ const [head, ...rest] = target;
219
+ if (!head)
220
+ throw new Error("Give the server a command to run, or a url to reach.");
221
+ const entry = /^https?:\/\//.test(head) ? { url: head } : { command: head, args: rest };
222
+ const { file, key, map } = McpServerConfig.#parse(workspaceRoot, scope);
223
+ const next = { ...map, [name]: entry };
224
+ mkdirSync(path2.dirname(file), { recursive: true });
225
+ writeFileSync(file, `${JSON.stringify({ [key]: next }, null, 2)}
226
+ `);
227
+ return McpServerConfig.#refOf(name, entry);
228
+ }
229
+ static remove(workspaceRoot, name, only) {
230
+ const candidates = McpServerConfig.files(workspaceRoot).filter(({ scope }) => !only || scope === only);
231
+ for (const { scope } of candidates.reverse()) {
232
+ const { file, key, map } = McpServerConfig.#parse(workspaceRoot, scope);
233
+ if (!(name in map))
234
+ continue;
235
+ const next = { ...map };
236
+ delete next[name];
237
+ writeFileSync(file, `${JSON.stringify({ [key]: next }, null, 2)}
238
+ `);
239
+ return scope;
240
+ }
241
+ return false;
242
+ }
243
+ static #refOf(name, entry) {
244
+ return {
245
+ name,
246
+ transport: entry.url ? "http" : "stdio",
247
+ ...entry.command ? { command: entry.command } : {},
248
+ ...entry.args ? { args: entry.args } : {},
249
+ ...entry.url ? { url: entry.url } : {},
250
+ ...entry.env ? { env: entry.env } : {},
251
+ ...entry.headers ? { headers: entry.headers } : {},
252
+ ...entry.oauth ? { oauth: entry.oauth } : {}
253
+ };
254
+ }
255
+ static targetOf(ref) {
256
+ return ref.url ?? [ref.command, ...ref.args ?? []].filter(Boolean).join(" ");
257
+ }
258
+ static problem(workspaceRoot) {
259
+ for (const { file } of McpServerConfig.files(workspaceRoot)) {
260
+ if (!existsSync2(file))
261
+ continue;
262
+ try {
263
+ JSON.parse(readFileSync(file, "utf8"));
264
+ } catch (error) {
265
+ return `${file} is not valid JSON \u2014 ${String(error)}`;
266
+ }
267
+ }
268
+ return;
269
+ }
270
+ static #parse(workspaceRoot, scope) {
271
+ const file = McpServerConfig.file(workspaceRoot, scope);
272
+ const empty = { file, key: "mcpServers", map: {} };
273
+ if (!existsSync2(file))
274
+ return empty;
275
+ try {
276
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
277
+ if (parsed.servers && !parsed.mcpServers)
278
+ return { file, key: "servers", map: parsed.servers };
279
+ return { ...empty, map: parsed.mcpServers ?? {} };
280
+ } catch {
281
+ return empty;
282
+ }
283
+ }
284
+ }
285
+
286
+ // pkgs/@akanjs/devkit/codeAgent/tools/McpTokenStore.ts
287
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
288
+ import path3 from "path";
289
+ class McpTokenStore {
290
+ static file() {
291
+ return akanCodePaths2.mcpAuthFile();
292
+ }
293
+ static lockFile() {
294
+ return `${McpTokenStore.file()}.lock`;
295
+ }
296
+ static read(name) {
297
+ return McpTokenStore.#all()[name];
298
+ }
299
+ static names() {
300
+ return Object.keys(McpTokenStore.#all());
301
+ }
302
+ static write(name, auth) {
303
+ McpTokenStore.#save({ ...McpTokenStore.#all(), [name]: auth });
304
+ }
305
+ static clear(name) {
306
+ const all = McpTokenStore.#all();
307
+ if (!(name in all))
308
+ return false;
309
+ delete all[name];
310
+ McpTokenStore.#save(all);
311
+ return true;
312
+ }
313
+ static isExpired(auth) {
314
+ return auth.expiresAt !== undefined && auth.expiresAt <= Date.now() + McpTokenStore.skewMs;
315
+ }
316
+ static skewMs = 60000;
317
+ static #all() {
318
+ const file = McpTokenStore.file();
319
+ if (!existsSync3(file))
320
+ return {};
321
+ try {
322
+ const parsed = JSON.parse(readFileSync2(file, "utf8"));
323
+ return parsed.servers ?? {};
324
+ } catch {
325
+ return {};
326
+ }
327
+ }
328
+ static #save(servers) {
329
+ const file = McpTokenStore.file();
330
+ mkdirSync2(path3.dirname(file), { recursive: true, mode: 448 });
331
+ const temp = `${file}.${process.pid}.tmp`;
332
+ writeFileSync2(temp, `${JSON.stringify({ servers }, null, 2)}
333
+ `, { mode: 384 });
334
+ renameSync(temp, file);
335
+ chmodSync(file, 384);
336
+ }
337
+ }
338
+
339
+ // pkgs/@akanjs/devkit/codeAgent/tools/McpOAuth.ts
340
+ import { createHash, randomBytes } from "crypto";
341
+
342
+ class McpOAuth {
343
+ static resourceWellKnown = "/.well-known/oauth-protected-resource";
344
+ static serverWellKnown = "/.well-known/oauth-authorization-server";
345
+ static openidWellKnown = "/.well-known/openid-configuration";
346
+ static ports = [33418, 33419, 33420, 33421, 33422];
347
+ static callbackPath = "/callback";
348
+ static timeoutMs = 300000;
349
+ static #json(response) {
350
+ return response.json();
351
+ }
352
+ static resourceMetadataUrls(serverUrl, challenge) {
353
+ const named = challenge ? /resource_metadata="([^"]+)"/.exec(challenge)?.[1] : undefined;
354
+ if (named)
355
+ return [named];
356
+ const url = new URL(serverUrl);
357
+ const withPath = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
358
+ return [
359
+ new URL(`${McpOAuth.resourceWellKnown}${withPath}`, url.origin).href,
360
+ new URL(McpOAuth.resourceWellKnown, url.origin).href
361
+ ];
362
+ }
363
+ static serverMetadataUrls(issuer) {
364
+ const url = new URL(issuer);
365
+ const withPath = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
366
+ return [
367
+ new URL(`${McpOAuth.serverWellKnown}${withPath}`, url.origin).href,
368
+ new URL(`${withPath}${McpOAuth.openidWellKnown}`, url.origin).href,
369
+ new URL(McpOAuth.serverWellKnown, url.origin).href
370
+ ];
371
+ }
372
+ static async discover(serverUrl, challenge) {
373
+ const resourceDoc = await McpOAuth.#first(McpOAuth.resourceMetadataUrls(serverUrl, challenge));
374
+ const resource = typeof resourceDoc?.resource === "string" ? resourceDoc.resource : serverUrl;
375
+ const resourceScopes = McpOAuth.#strings(resourceDoc?.scopes_supported);
376
+ const issuers = McpOAuth.#strings(resourceDoc?.authorization_servers);
377
+ const candidates = issuers.length ? issuers : [new URL(serverUrl).origin];
378
+ for (const issuer of candidates) {
379
+ const doc = await McpOAuth.#first(McpOAuth.serverMetadataUrls(issuer));
380
+ if (!doc)
381
+ continue;
382
+ const methods = McpOAuth.#strings(doc.code_challenge_methods_supported);
383
+ if (methods.length && !methods.includes("S256"))
384
+ throw new Error(`${issuer} does not offer PKCE S256, which is the only method this client will use.`);
385
+ const authorizationEndpoint = typeof doc.authorization_endpoint === "string" ? doc.authorization_endpoint : "";
386
+ const tokenEndpoint = typeof doc.token_endpoint === "string" ? doc.token_endpoint : "";
387
+ if (!authorizationEndpoint || !tokenEndpoint)
388
+ continue;
389
+ return {
390
+ issuer: typeof doc.issuer === "string" ? doc.issuer : issuer,
391
+ authorizationEndpoint,
392
+ tokenEndpoint,
393
+ ...typeof doc.registration_endpoint === "string" ? { registrationEndpoint: doc.registration_endpoint } : {},
394
+ ...McpOAuth.#strings(doc.scopes_supported).length ? { scopesSupported: McpOAuth.#strings(doc.scopes_supported) } : {},
395
+ resource,
396
+ ...resourceScopes.length ? { resourceScopes } : {}
397
+ };
398
+ }
399
+ throw new Error(`No OAuth metadata was found for ${serverUrl}. Declare "oauth" for it in mcp.json.`);
400
+ }
401
+ static async register(server, redirectUri, name) {
402
+ if (!server.registrationEndpoint)
403
+ throw new Error(`${server.issuer} registers no clients on demand. Put its "oauth": { "clientId": "\u2026" } in mcp.json.`);
404
+ const response = await fetch(server.registrationEndpoint, {
405
+ method: "POST",
406
+ headers: { "content-type": "application/json" },
407
+ body: JSON.stringify({
408
+ client_name: `akan code (${name})`,
409
+ redirect_uris: [redirectUri],
410
+ grant_types: ["authorization_code", "refresh_token"],
411
+ response_types: ["code"],
412
+ token_endpoint_auth_method: "none",
413
+ application_type: "native"
414
+ }),
415
+ signal: AbortSignal.timeout(30000)
416
+ });
417
+ const body = await McpOAuth.#json(response);
418
+ if (!response.ok || !body.client_id)
419
+ throw new Error(`${server.issuer} refused the client registration: ${body.error ?? response.status}`);
420
+ return { clientId: body.client_id, ...body.client_secret ? { clientSecret: body.client_secret } : {} };
421
+ }
422
+ static async authorize(options) {
423
+ const verifier = randomBytes(32).toString("base64url");
424
+ const state = randomBytes(16).toString("base64url");
425
+ const scope = options.scope ?? options.server.resourceScopes?.join(" ") ?? options.server.scopesSupported?.join(" ");
426
+ const authorize = new URL(options.server.authorizationEndpoint);
427
+ for (const [key, value] of Object.entries({
428
+ response_type: "code",
429
+ client_id: options.clientId,
430
+ redirect_uri: options.redirectUri,
431
+ code_challenge: createHash("sha256").update(verifier).digest("base64url"),
432
+ code_challenge_method: "S256",
433
+ state,
434
+ resource: options.server.resource,
435
+ ...scope ? { scope } : {}
436
+ }))
437
+ authorize.searchParams.set(key, value);
438
+ options.open(authorize.href);
439
+ const params = await options.wait();
440
+ const error = params.get("error");
441
+ if (error)
442
+ throw new Error(`${error}: ${params.get("error_description") ?? "the authorization was refused"}`);
443
+ if (params.get("state") !== state)
444
+ throw new Error("The authorization response carried the wrong state.");
445
+ const iss = params.get("iss");
446
+ if (iss && iss !== options.server.issuer)
447
+ throw new Error(`The authorization response came from ${iss}, not ${options.server.issuer}.`);
448
+ const code = params.get("code");
449
+ if (!code)
450
+ throw new Error("The authorization response carried no code.");
451
+ return await McpOAuth.#token(options.server, {
452
+ grant_type: "authorization_code",
453
+ code,
454
+ redirect_uri: options.redirectUri,
455
+ client_id: options.clientId,
456
+ code_verifier: verifier,
457
+ resource: options.server.resource,
458
+ ...options.clientSecret ? { client_secret: options.clientSecret } : {}
459
+ });
460
+ }
461
+ static async refresh(server, auth) {
462
+ if (!auth.refreshToken)
463
+ throw new Error("There is no refresh token for this server.");
464
+ return await McpOAuth.#token(server, {
465
+ grant_type: "refresh_token",
466
+ refresh_token: auth.refreshToken,
467
+ client_id: auth.clientId,
468
+ resource: server.resource,
469
+ ...auth.clientSecret ? { client_secret: auth.clientSecret } : {},
470
+ ...auth.scope ? { scope: auth.scope } : {}
471
+ });
472
+ }
473
+ static scopeOf(ref, server) {
474
+ return ref.oauth?.scope ?? server.resourceScopes?.join(" ") ?? server.scopesSupported?.join(" ");
475
+ }
476
+ static async#token(server, form) {
477
+ const response = await fetch(server.tokenEndpoint, {
478
+ method: "POST",
479
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
480
+ body: new URLSearchParams(form).toString(),
481
+ signal: AbortSignal.timeout(30000)
482
+ });
483
+ const body = await McpOAuth.#json(response);
484
+ if (!response.ok || !body.access_token)
485
+ throw new Error(`${server.tokenEndpoint} refused the token request: ${body.error ?? response.status}`);
486
+ return {
487
+ accessToken: body.access_token,
488
+ ...body.refresh_token ? { refreshToken: body.refresh_token } : {},
489
+ ...body.expires_in ? { expiresAt: Date.now() + body.expires_in * 1000 } : {},
490
+ ...body.scope ? { scope: body.scope } : {}
491
+ };
492
+ }
493
+ static async#first(urls) {
494
+ for (const url of urls) {
495
+ try {
496
+ const response = await fetch(url, {
497
+ headers: { accept: "application/json", "mcp-protocol-version": "2025-06-18" },
498
+ signal: AbortSignal.timeout(15000)
499
+ });
500
+ if (!response.ok)
501
+ continue;
502
+ return await McpOAuth.#json(response);
503
+ } catch {}
504
+ }
505
+ return;
506
+ }
507
+ static #strings(value) {
508
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
509
+ }
510
+ }
511
+
512
+ // pkgs/@akanjs/devkit/codeAgent/tools/McpOAuthLoopback.ts
513
+ class McpOAuthLoopback {
514
+ #server;
515
+ #port;
516
+ #resolve;
517
+ #answer;
518
+ constructor(port) {
519
+ this.#port = port;
520
+ this.#answer = new Promise((resolve) => {
521
+ this.#resolve = resolve;
522
+ });
523
+ this.#server = Bun.serve({
524
+ port,
525
+ hostname: "127.0.0.1",
526
+ fetch: (request) => this.#handle(request)
527
+ });
528
+ }
529
+ static bind(preferred) {
530
+ const ports = preferred ? [preferred, ...McpOAuth.ports.filter((port) => port !== preferred)] : McpOAuth.ports;
531
+ const failures = [];
532
+ for (const port of ports) {
533
+ try {
534
+ return new McpOAuthLoopback(port);
535
+ } catch (error) {
536
+ failures.push(`${port}: ${String(error)}`);
537
+ }
538
+ }
539
+ throw new Error(`No loopback port was free for the sign-in \u2014 ${failures.join(", ")}`);
540
+ }
541
+ get redirectUri() {
542
+ return `http://127.0.0.1:${this.#port}${McpOAuth.callbackPath}`;
543
+ }
544
+ async wait() {
545
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("The sign-in was not completed in time.")), McpOAuth.timeoutMs).unref?.());
546
+ return await Promise.race([this.#answer, timeout]);
547
+ }
548
+ close() {
549
+ this.#server.stop(true);
550
+ }
551
+ #handle(request) {
552
+ const url = new URL(request.url);
553
+ if (url.pathname !== McpOAuth.callbackPath)
554
+ return new Response("Not found", { status: 404 });
555
+ this.#resolve?.(url.searchParams);
556
+ const failed = url.searchParams.get("error");
557
+ return new Response(`<!doctype html><meta charset="utf-8"><title>akan code</title><body style="font:16px system-ui;padding:3rem">
558
+ <h1>${failed ? "Sign-in failed" : "Signed in"}</h1>
559
+ <p>${failed ? `The authorization server said: ${Bun.escapeHTML(failed)}` : "You can close this tab and go back to the terminal."}</p>`, { status: failed ? 400 : 200, headers: { "content-type": "text/html; charset=utf-8" } });
560
+ }
561
+ }
562
+
563
+ // pkgs/@akanjs/devkit/codeAgent/tools/McpSignIn.ts
564
+ class McpSignIn {
565
+ static #refreshing = new Map;
566
+ static async token(ref, onNotice) {
567
+ const stored = McpTokenStore.read(ref.name);
568
+ if (!stored)
569
+ return;
570
+ if (!McpTokenStore.isExpired(stored))
571
+ return stored.accessToken;
572
+ return await McpSignIn.#refreshOnce(ref, stored, onNotice);
573
+ }
574
+ static async retryToken(ref, refused, onNotice) {
575
+ const stored = McpTokenStore.read(ref.name);
576
+ if (!stored)
577
+ return;
578
+ if (stored.accessToken !== refused && !McpTokenStore.isExpired(stored))
579
+ return stored.accessToken;
580
+ return await McpSignIn.#refreshOnce(ref, stored, onNotice);
581
+ }
582
+ static async#refreshOnce(ref, stored, onNotice) {
583
+ const key = `${ref.name}
584
+ ${stored.refreshToken}`;
585
+ let pending = McpSignIn.#refreshing.get(key);
586
+ if (!pending) {
587
+ pending = McpSignIn.#refreshed(ref, stored, onNotice).finally(() => McpSignIn.#refreshing.delete(key));
588
+ McpSignIn.#refreshing.set(key, pending);
589
+ }
590
+ return await pending;
591
+ }
592
+ static async#refreshed(ref, stored, onNotice) {
593
+ if (!stored.refreshToken)
594
+ return;
595
+ try {
596
+ return await ConfigLock.run(McpTokenStore.lockFile(), async () => {
597
+ const current = McpTokenStore.read(ref.name);
598
+ if (!current)
599
+ return;
600
+ if (current.accessToken !== stored.accessToken && !McpTokenStore.isExpired(current))
601
+ return current.accessToken;
602
+ const server = {
603
+ issuer: current.issuer,
604
+ authorizationEndpoint: "",
605
+ tokenEndpoint: current.tokenEndpoint,
606
+ resource: current.resource
607
+ };
608
+ const tokens = await McpOAuth.refresh(server, current);
609
+ const { expiresAt: _spent, ...grant } = current;
610
+ McpTokenStore.write(ref.name, { ...grant, ...tokens });
611
+ return tokens.accessToken;
612
+ });
613
+ } catch (error) {
614
+ onNotice?.(`MCP server "${ref.name}" needs signing in again \u2014 ${String(error)}`);
615
+ return;
616
+ }
617
+ }
618
+ static async run(ref, options) {
619
+ if (!ref.url)
620
+ throw new Error(`"${ref.name}" is a stdio server: give it its credential through "env".`);
621
+ const challenge = await McpSignIn.challenge(ref);
622
+ const server = await McpOAuth.discover(ref.url, challenge);
623
+ const stored = McpTokenStore.read(ref.name);
624
+ const reusable = stored && stored.issuer === server.issuer && !ref.oauth?.clientId ? stored : undefined;
625
+ const loopback = McpOAuthLoopback.bind(McpSignIn.#portOf(reusable?.redirectUri));
626
+ try {
627
+ const redirectUri = loopback.redirectUri;
628
+ const client = await McpSignIn.#client(ref, server, redirectUri, reusable);
629
+ options.onNotice?.(`Opening a browser to sign in to "${ref.name}"\u2026`);
630
+ const scope = McpOAuth.scopeOf(ref, server);
631
+ const tokens = await McpOAuth.authorize({
632
+ server,
633
+ clientId: client.clientId,
634
+ ...client.clientSecret ? { clientSecret: client.clientSecret } : {},
635
+ redirectUri,
636
+ ...scope ? { scope } : {},
637
+ open: options.open,
638
+ wait: () => loopback.wait()
639
+ });
640
+ const auth = {
641
+ issuer: server.issuer,
642
+ tokenEndpoint: server.tokenEndpoint,
643
+ resource: server.resource,
644
+ clientId: client.clientId,
645
+ ...client.clientSecret ? { clientSecret: client.clientSecret } : {},
646
+ redirectUri,
647
+ ...tokens
648
+ };
649
+ McpTokenStore.write(ref.name, auth);
650
+ return auth;
651
+ } finally {
652
+ loopback.close();
653
+ }
654
+ }
655
+ static async challenge(ref) {
656
+ if (!ref.url)
657
+ return null;
658
+ try {
659
+ const response = await fetch(ref.url, {
660
+ method: "POST",
661
+ headers: {
662
+ "content-type": "application/json",
663
+ accept: "application/json, text/event-stream",
664
+ ...ref.headers
665
+ },
666
+ body: JSON.stringify({
667
+ jsonrpc: "2.0",
668
+ id: 0,
669
+ method: "initialize",
670
+ params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "akan-code", version: "1" } }
671
+ }),
672
+ signal: AbortSignal.timeout(15000)
673
+ });
674
+ return response.status === 401 ? response.headers.get("www-authenticate") ?? "" : null;
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+ static async#client(ref, server, redirectUri, reusable) {
680
+ if (ref.oauth?.clientId)
681
+ return {
682
+ clientId: ref.oauth.clientId,
683
+ ...ref.oauth.clientSecret ? { clientSecret: ref.oauth.clientSecret } : {}
684
+ };
685
+ if (reusable && reusable.redirectUri === redirectUri)
686
+ return {
687
+ clientId: reusable.clientId,
688
+ ...reusable.clientSecret ? { clientSecret: reusable.clientSecret } : {}
689
+ };
690
+ return await McpOAuth.register(server, redirectUri, ref.name);
691
+ }
692
+ static #portOf(redirectUri) {
693
+ if (!redirectUri)
694
+ return;
695
+ const port = Number(new URL(redirectUri).port);
696
+ return Number.isFinite(port) && port > 0 ? port : undefined;
697
+ }
698
+ }
699
+
700
+ export { SubagentPool2, akanCodePaths2, McpServerConfig, McpTokenStore, McpSignIn };