@akanjs/cli 3.1.0-rc.4 → 3.1.0-rc.6

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 (89) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-ymte1tv8.js → CodeAgent-rkjyxgec.js} +2 -2
  3. package/{CodeTui-4rhdm1qw.js → CodeTui-h7pyj6xa.js} +1 -1
  4. package/TunnelShare-661cxz5e.js +14 -0
  5. package/{agent.command-r5x1mch5.js → agent.command-3qxf4xcj.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-3dkr7rdz.js → application.command-m1kcd7jf.js} +12 -11
  8. package/{applicationBuildRunner-h0j2swx1.js → applicationBuildRunner-dj0xkrep.js} +16 -9
  9. package/buildBatch.proc.js +7 -7
  10. package/{cloud.command-4kyxfw0y.js → cloud.command-3jzym8q7.js} +17 -16
  11. package/cloud.script-r4nskbc6.js +24 -0
  12. package/{code.command-sjvzt8fc.js → code.command-0ad6gw2a.js} +6 -6
  13. package/{context.command-j9frz0dz.js → context.command-0qx447mb.js} +17 -16
  14. package/context.runner-1vsjztjg.js +25 -0
  15. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  16. package/{guideline.command-1epw6hey.js → guideline.command-cv5bmrx2.js} +2 -2
  17. package/guidelines/conventions/conventions.instruction.md +450 -1084
  18. package/guidelines/cssRule/cssRule.instruction.md +16 -1
  19. package/guidelines/fieldRule/fieldRule.instruction.md +57 -0
  20. package/guidelines/framework/framework.instruction.md +4 -7
  21. package/guidelines/mcpRule/mcpRule.instruction.md +11 -8
  22. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  23. package/guidelines/modelStore/modelStore.instruction.md +9 -0
  24. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  25. package/guidelines/queryRule/queryRule.instruction.md +35 -8
  26. package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
  27. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  28. package/guidelines/transportRule/transportRule.instruction.md +76 -8
  29. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  30. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +49 -4
  31. package/incrementalBuilder.proc.js +4 -4
  32. package/{index-jw6st2rd.js → index-192ha4xh.js} +356 -307
  33. package/{index-1hkr7mrb.js → index-1p98exm2.js} +2 -2
  34. package/{index-8byzcvxp.js → index-1xj2t7cb.js} +7 -7
  35. package/{index-er3hekv9.js → index-20bxxyvn.js} +80 -80
  36. package/{index-xe3x2kw5.js → index-35hc95h5.js} +1 -1
  37. package/{index-p5nrc5he.js → index-4mhy6qme.js} +4 -4
  38. package/{index-0qc5nd61.js → index-5edzv8fn.js} +5 -5
  39. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  40. package/index-62md4dwy.js +260 -0
  41. package/{index-z3v7abrk.js → index-665zp9rs.js} +38 -15
  42. package/{index-1t2xw0p0.js → index-6xn2n442.js} +4 -4
  43. package/{index-jg3fj8h0.js → index-86jt2a0e.js} +1 -1
  44. package/{index-k33me5sp.js → index-8fp3b47h.js} +419 -418
  45. package/{index-shastb7m.js → index-9z426msb.js} +17 -12
  46. package/{index-gdybjw3x.js → index-awbdfcf2.js} +4 -4
  47. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  48. package/{index-jppthsff.js → index-cpb3nvma.js} +57 -58
  49. package/{index-b60ccq2s.js → index-d47ypebv.js} +17 -15
  50. package/{index-gc05vqd2.js → index-d72hfmdm.js} +13 -13
  51. package/{index-akha197v.js → index-fmzcs8v6.js} +4 -4
  52. package/{index-28ftaz3d.js → index-fyq1z6kt.js} +29 -27
  53. package/{index-vwmv64ad.js → index-g1mbppa9.js} +238 -236
  54. package/{index-hedt951c.js → index-h47cf1gg.js} +7 -7
  55. package/{index-hfqs9jvc.js → index-j4mkhz93.js} +3 -3
  56. package/{index-08a5ga6n.js → index-jw1zm273.js} +28 -30
  57. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  58. package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
  59. package/{index-2wyn17fa.js → index-pydbnfez.js} +2 -2
  60. package/{index-bjh4wbtk.js → index-qwa5k1j8.js} +1 -1
  61. package/{index-81a1ywp6.js → index-tamvyvj3.js} +3 -3
  62. package/{index-dez4t5sd.js → index-trpasv9q.js} +260 -259
  63. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  64. package/{index-2k0t8p3p.js → index-va06nr10.js} +1086 -1075
  65. package/{index-9c3cd767.js → index-vk0pa0mn.js} +20 -21
  66. package/{index-qyaqzh8e.js → index-w44cy4vs.js} +2216 -2216
  67. package/index.js +22 -22
  68. package/{library.command-n58dgakb.js → library.command-erp2f56n.js} +3 -3
  69. package/{localRegistry.command-f2768sww.js → localRegistry.command-xxpqgxjw.js} +15 -14
  70. package/{module.command-xzjg5k7n.js → module.command-gs2r615e.js} +5 -5
  71. package/{package.command-24sh1j1q.js → package.command-7343f74r.js} +4 -4
  72. package/package.json +2 -2
  73. package/{page.command-fqnm88na.js → page.command-4h4a8pnh.js} +3 -3
  74. package/{pipeline.command-vn6taz75.js → pipeline.command-a2nrk26m.js} +3 -3
  75. package/{primitive.command-4j1g1a0d.js → primitive.command-w6t0asqz.js} +6 -6
  76. package/{projectTypecheck-3y7ayty0.js → projectTypecheck-f534enqj.js} +1 -1
  77. package/{quality.command-5hqmcdwt.js → quality.command-77x7vbbx.js} +4 -4
  78. package/{repair.command-rw5tk460.js → repair.command-y6sstev7.js} +5 -5
  79. package/{scalar.command-0jpap45m.js → scalar.command-20b8cy0k.js} +4 -4
  80. package/{subspace.command-y7n9sghk.js → subspace.command-92xkc0s0.js} +11 -10
  81. package/templates/lib/cnst.ts +0 -1
  82. package/templates/workspaceRoot/.gitignore.template +24 -53
  83. package/{tunnel.command-5gjwh1f8.js → tunnel.command-2kw8vt9g.js} +8 -7
  84. package/{workflow.command-3t22f7eb.js → workflow.command-2djwmpqf.js} +10 -10
  85. package/{workspace.command-cq5xg2jq.js → workspace.command-5se6spqb.js} +28 -27
  86. package/TunnelShare-p8ymkww8.js +0 -13
  87. package/cloud.script-jc3hc5d9.js +0 -23
  88. package/context.runner-v0def5my.js +0 -24
  89. package/index-n68jx79w.js +0 -120
@@ -4,7 +4,7 @@ import {
4
4
  akanCodePaths2,
5
5
  McpServerConfig,
6
6
  McpSignIn
7
- } from "./index-er3hekv9.js";
7
+ } from "./index-20bxxyvn.js";
8
8
  import {
9
9
  stripAnsi
10
10
  } from "./index-7md2g9vw.js";
@@ -15,136 +15,433 @@ import {
15
15
  akanCodeModelWarnings2
16
16
  } from "./index-c6eggbee.js";
17
17
 
18
- // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgent.ts
19
- import {
20
- createAgentSession,
21
- DefaultResourceLoader
22
- } from "@earendil-works/pi-coding-agent";
23
- import {
24
- codeAgentClip as codeAgentClip5,
25
- codeAgentLabelChars as codeAgentLabelChars2,
26
- codeAgentRenderAnswer,
27
- codeAgentSessionName as codeAgentSessionName2
28
- } from "akanjs/common";
18
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
19
+ import { existsSync as existsSync4, mkdirSync, mkdtempSync } from "fs";
20
+ import { tmpdir } from "os";
21
+ import path4 from "path";
22
+ import { ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
29
23
 
30
- // pkgs/@akanjs/devkit/codeAgent/feedback/DevLogFeedback.ts
31
- import { existsSync, statSync } from "fs";
24
+ // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
25
+ import { existsSync, readFileSync } from "fs";
32
26
  import path from "path";
33
27
 
34
- // pkgs/@akanjs/devkit/codeAgent/tools/AkanEditScope.ts
35
- var empty = { paths: [], apps: [], libs: [], needsSync: false, touchesTsx: false };
36
-
37
- class AkanEditScope2 {
38
- static async since(cwd, baseline) {
39
- const current = await AkanEditScope2.baseline(cwd);
40
- const changed = [...current].filter((line) => !baseline.has(line));
41
- if (!changed.length)
42
- return empty;
43
- const entries = changed.map((line) => ({ code: line.slice(0, 2), file: line.slice(3).split(" -> ").at(-1) ?? "" }));
44
- const paths = entries.map((entry) => entry.file).filter(Boolean);
45
- return {
46
- paths,
47
- apps: AkanEditScope2.#targets(paths, "apps"),
48
- libs: AkanEditScope2.#targets(paths, "libs"),
49
- needsSync: entries.some((entry) => /[ARD?]/.test(entry.code) && /^(apps|libs)\//.test(entry.file)),
50
- touchesTsx: paths.some((file) => file.endsWith(".tsx"))
51
- };
28
+ class AkanEnvKeys2 {
29
+ static async apply(runtime, workspaceRoot) {
30
+ const applied = [];
31
+ for (const [name, value] of AkanEnvKeys2.#entries(workspaceRoot)) {
32
+ const provider = AkanEnvKeys2.#providerOf(name);
33
+ if (!provider || !value)
34
+ continue;
35
+ await runtime.setRuntimeApiKey(provider, value);
36
+ applied.push(provider);
37
+ }
38
+ return applied;
52
39
  }
53
- static async baseline(cwd) {
54
- const proc = Bun.spawn(["git", "status", "--porcelain=v1", "--untracked-files=all"], {
55
- cwd,
56
- stdout: "pipe",
57
- stderr: "ignore"
40
+ static #entries(workspaceRoot) {
41
+ const fromProcess = Object.entries(process.env).filter(([, value]) => typeof value === "string");
42
+ const file = path.join(workspaceRoot, ".env");
43
+ if (!existsSync(file))
44
+ return fromProcess;
45
+ const fromFile = readFileSync(file, "utf8").split(`
46
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
47
+ const index = line.indexOf("=");
48
+ return [
49
+ line.slice(0, index).trim(),
50
+ line.slice(index + 1).trim().replace(/^["']|["']$/g, "")
51
+ ];
58
52
  });
59
- const text = await new Response(proc.stdout).text();
60
- await proc.exited;
61
- return new Set(text.split(`
62
- `).filter((line) => line.trim().length > 3));
53
+ return [...fromFile, ...fromProcess];
63
54
  }
64
- static #targets(paths, root) {
65
- const names = paths.filter((file) => file.startsWith(`${root}/`)).map((file) => file.split("/")[1] ?? "").filter(Boolean);
66
- return [...new Set(names)];
55
+ static #providerOf(name) {
56
+ const match = /^([A-Z0-9_]+)_API_KEY$/.exec(name);
57
+ return match?.[1]?.toLowerCase().replace(/_/g, "-");
67
58
  }
68
59
  }
69
60
 
70
- // pkgs/@akanjs/devkit/codeAgent/feedback/DevLogFeedback.ts
71
- var errorPattern = /\bERROR\b|\bFATAL\b|\[stderr\]|error:|Error:|\u2716|Unhandled|Cannot find|is not a function/;
72
- var noisePattern = /DEBUG|\bWARN\b|MCP catalogue|rate limit/;
61
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanContextFiles.ts
62
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
63
+ import path2 from "path";
73
64
 
74
- class DevLogFeedback2 {
75
- key = "dev-server";
76
- #options;
77
- #marks = new Map;
78
- #baseline = new Set;
79
- constructor(options) {
80
- this.#options = { graceMs: 1500, maxLines: 30, ...options };
65
+ class AkanContextFiles {
66
+ static #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
67
+ static extend(base, options) {
68
+ const seen = new Set(base.map((file) => file.path));
69
+ const contents = new Set(base.map((file) => file.content.trim()));
70
+ const added = [];
71
+ for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
72
+ if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim())
73
+ continue;
74
+ seen.add(file.path);
75
+ contents.add(file.content.trim());
76
+ added.push(file);
77
+ }
78
+ return [...base, ...added];
81
79
  }
82
- async begin() {
83
- for (const app of this.#options.apps)
84
- this.#marks.set(app, DevLogFeedback2.#sizeOf(this.#pathOf(app)));
85
- this.#baseline = await AkanEditScope2.baseline(this.#options.cwd);
80
+ static #claude(base) {
81
+ const files = [];
82
+ for (const file of base) {
83
+ if (!/^AGENTS\.(md|MD)$/.test(path2.basename(file.path)))
84
+ continue;
85
+ for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
86
+ const candidate = path2.join(path2.dirname(file.path), name);
87
+ const content = AkanContextFiles.#read(candidate);
88
+ if (content !== undefined)
89
+ files.push({ path: candidate, content });
90
+ }
91
+ }
92
+ return files;
86
93
  }
87
- async observe() {
88
- const running = this.#options.apps.filter((app) => existsSync(this.#pathOf(app)));
89
- if (!running.length)
90
- return;
91
- if (!(await AkanEditScope2.since(this.#options.cwd, this.#baseline)).paths.length)
94
+ static #cursor(options) {
95
+ const files = [];
96
+ const roots = [...new Set([options.workspaceRoot, options.cwd])];
97
+ for (const root of roots) {
98
+ const legacy = AkanContextFiles.#read(path2.join(root, ".cursorrules"));
99
+ if (legacy !== undefined)
100
+ files.push({ path: path2.join(root, ".cursorrules"), content: legacy });
101
+ for (const file of AkanContextFiles.#mdcFiles(path2.join(root, ".cursor", "rules"))) {
102
+ const raw = AkanContextFiles.#read(file);
103
+ if (raw === undefined)
104
+ continue;
105
+ const body = AkanContextFiles.#applied(raw);
106
+ if (body !== undefined)
107
+ files.push({ path: file, content: body });
108
+ }
109
+ }
110
+ return files;
111
+ }
112
+ static #applied(raw) {
113
+ const match = AkanContextFiles.#frontmatter.exec(raw);
114
+ if (!match)
115
+ return raw;
116
+ if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? ""))
92
117
  return;
93
- await Bun.sleep(this.#options.graceMs);
94
- const reports = [];
95
- for (const app of running) {
96
- const lines = await this.#linesSince(app);
97
- if (lines.length)
98
- reports.push(`**${app}** dev server:
99
- \`\`\`
100
- ${lines.join(`
101
- `)}
102
- \`\`\``);
118
+ return raw.slice(match[0].length);
119
+ }
120
+ static #mdcFiles(dir) {
121
+ if (!existsSync2(dir))
122
+ return [];
123
+ try {
124
+ return readdirSync(dir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".mdc")).map((entry) => path2.join(entry.parentPath ?? dir, entry.name)).sort();
125
+ } catch {
126
+ return [];
103
127
  }
104
- if (!reports.length)
128
+ }
129
+ static #read(file) {
130
+ try {
131
+ if (!existsSync2(file) || !statSync(file).isFile())
132
+ return;
133
+ return readFileSync2(file, "utf8").replace(/^\uFEFF/, "");
134
+ } catch {
105
135
  return;
106
- return [
107
- "The dev server logged errors after your edits:",
108
- ...reports,
109
- `Full log: ${running.map((app) => path.relative(this.#options.workspaceRoot, this.#pathOf(app))).join(", ")}`
110
- ].join(`
136
+ }
137
+ }
138
+ }
111
139
 
140
+ // pkgs/@akanjs/devkit/codeAgent/agent/akanSystemPrompt.ts
141
+ var akanSystemPrompt2 = (profile) => {
142
+ const lines = [
143
+ "You are the akan coding agent, running inside an Akan.js workspace.",
144
+ "",
145
+ "- 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.",
146
+ "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
147
+ "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
148
+ "- 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.",
149
+ "- 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`."
150
+ ];
151
+ if (profile.tools.builtin.includes("write"))
152
+ lines.push("- After editing, call `akan_verify`. It runs sync, lint, typecheck and the SSR check over what the working tree changed. A turn that edits and does not verify is not finished.");
153
+ else
154
+ lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
155
+ if (profile.tools.subagent)
156
+ 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.');
157
+ if (profile.ui.canPrompt)
158
+ 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.");
159
+ if (profile.approval !== "never")
160
+ 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.`);
161
+ 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.");
162
+ return lines.join(`
112
163
  `);
164
+ };
165
+
166
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
167
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
168
+ import path3 from "path";
169
+
170
+ // pkgs/@akanjs/devkit/codeAgent/agent/jsonLines.ts
171
+ import { closeSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2 } from "fs";
172
+ var readHead = (file, maxBytes) => {
173
+ const fd = openSync(file, "r");
174
+ try {
175
+ const head = Buffer.alloc(maxBytes);
176
+ return head.subarray(0, readSync(fd, head, 0, maxBytes, 0)).toString("utf8");
177
+ } finally {
178
+ closeSync(fd);
113
179
  }
114
- async#linesSince(app) {
115
- const file = this.#pathOf(app);
116
- const from = this.#marks.get(app) ?? 0;
117
- const size = DevLogFeedback2.#sizeOf(file);
118
- if (size <= from)
119
- return [];
120
- const text = await Bun.file(file).slice(from, size).text();
121
- this.#marks.set(app, size);
180
+ };
181
+ var readJsonLines = (file, maxBytes) => {
182
+ try {
183
+ const text = maxBytes !== undefined && statSync2(file).size > maxBytes ? readHead(file, maxBytes) : readFileSync3(file, "utf8");
122
184
  return text.split(`
123
- `).filter((line) => errorPattern.test(line) && !noisePattern.test(line)).slice(-this.#options.maxLines);
185
+ `).filter((line) => !!line.trim());
186
+ } catch {
187
+ return [];
124
188
  }
125
- #pathOf(app) {
126
- return path.join(this.#options.workspaceRoot, "local", "apps", app, "runtime", "dev.log");
189
+ };
190
+ var parseJsonLine = (line) => {
191
+ try {
192
+ return JSON.parse(line);
193
+ } catch {
194
+ return;
127
195
  }
128
- static async previewUrl(workspaceRoot, app) {
129
- const file = path.join(workspaceRoot, "local", "apps", app, "runtime", "dev.log");
130
- if (!existsSync(file))
196
+ };
197
+
198
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
199
+ class CodeSessionIndex2 {
200
+ static readLimit = 512 * 1024;
201
+ static list(dir, limit = 30) {
202
+ if (!existsSync3(dir))
203
+ return [];
204
+ const files = readdirSync2(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path3.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
205
+ return files.map((entry) => CodeSessionIndex2.#read(entry.file, entry.updatedAt)).filter((entry) => !!entry);
206
+ }
207
+ static fileOf(dir, id) {
208
+ if (!existsSync3(dir))
131
209
  return;
132
- const matches = [...(await Bun.file(file).text()).matchAll(/ready \(pid=\d+\) \u2014 (https?:\/\/\S+)/g)];
133
- return matches.at(-1)?.[1];
210
+ const match = readdirSync2(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
211
+ return match ? path3.join(dir, match) : undefined;
134
212
  }
135
- static #sizeOf(file) {
136
- return existsSync(file) ? statSync(file).size : 0;
213
+ static #mtime(file) {
214
+ try {
215
+ return statSync3(file).mtimeMs;
216
+ } catch {
217
+ return 0;
218
+ }
137
219
  }
138
- }
139
-
140
- // pkgs/@akanjs/devkit/codeAgent/feedback/PreviewChrome.ts
141
- import { existsSync as existsSync2 } from "fs";
142
- import { homedir } from "os";
143
- import path2 from "path";
144
-
145
- class PreviewChrome2 {
146
- static pathEnvKey = "BUN_CHROME_PATH";
147
- static backendEnvKey = "AKAN_CODE_WEBVIEW_BACKEND";
220
+ static #read(file, updatedAt) {
221
+ const lines = readJsonLines(file, CodeSessionIndex2.readLimit);
222
+ if (!lines.length)
223
+ return;
224
+ const entry = { id: "", file, name: undefined, opening: "", updatedAt, turns: 0 };
225
+ for (const line of lines) {
226
+ const record = parseJsonLine(line);
227
+ if (!record)
228
+ continue;
229
+ if (record.type === "session" && typeof record.id === "string")
230
+ entry.id = record.id;
231
+ if (record.type === "session_info" && typeof record.name === "string")
232
+ entry.name = record.name;
233
+ if (record.type !== "message")
234
+ continue;
235
+ const message = record.message;
236
+ if (message?.role !== "user")
237
+ continue;
238
+ entry.turns += 1;
239
+ if (!entry.opening)
240
+ entry.opening = CodeSessionIndex2.#text(message.content);
241
+ }
242
+ return entry.id ? entry : undefined;
243
+ }
244
+ static #text(content) {
245
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part === "object" && part && ("text" in part) ? String(part.text) : "").join(" ") : "";
246
+ return text.replace(/\s+/g, " ").trim();
247
+ }
248
+ }
249
+
250
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
251
+ class AkanCodeServices2 {
252
+ static settings() {
253
+ return SettingsManager.inMemory({
254
+ enableInstallTelemetry: false,
255
+ enableAnalytics: false,
256
+ quietStartup: true,
257
+ compaction: { reserveTokens: 32768 }
258
+ });
259
+ }
260
+ static async runtime(workspaceRoot, proxy = null) {
261
+ mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
262
+ const runtime = await ModelRuntime.create({
263
+ authPath: proxy ? path4.join(mkdtempSync(path4.join(tmpdir(), "akan-code-proxy-")), "auth.json") : akanCodePaths2.authFile(),
264
+ modelsPath: akanCodePaths2.modelsFile()
265
+ });
266
+ if (proxy)
267
+ await proxy.apply(runtime);
268
+ else
269
+ await AkanEnvKeys2.apply(runtime, workspaceRoot);
270
+ return runtime;
271
+ }
272
+ static sessions(workspaceRoot, cwd, profile, resume) {
273
+ if (profile.session.store === "memory")
274
+ return SessionManager.inMemory(cwd);
275
+ const dir = akanCodePaths2.sessionsDir(workspaceRoot);
276
+ mkdirSync(dir, { recursive: true });
277
+ const manager = SessionManager.create(cwd, dir);
278
+ if (!resume)
279
+ return manager;
280
+ const file = CodeSessionIndex2.fileOf(dir, resume);
281
+ if (!file)
282
+ throw new Error(`No stored session ${resume} in ${dir}`);
283
+ manager.setSessionFile(file);
284
+ return manager;
285
+ }
286
+ static #skillPaths(profile, workspaceRoot) {
287
+ if (!profile.context.skills)
288
+ return [];
289
+ const dirs = [
290
+ akanCodePaths2.builtinSkillsDir(),
291
+ akanCodePaths2.globalSkillsDir(),
292
+ akanCodePaths2.skillsDir(workspaceRoot)
293
+ ];
294
+ return dirs.filter((dir) => !!dir && existsSync4(dir));
295
+ }
296
+ static resourceOptions(options) {
297
+ const present = (dir) => existsSync4(dir) ? [dir] : [];
298
+ return {
299
+ noExtensions: true,
300
+ noSkills: true,
301
+ noPromptTemplates: true,
302
+ noThemes: true,
303
+ noContextFiles: !options.profile.context.projectFiles,
304
+ agentsFilesOverride: ({ agentsFiles }) => ({
305
+ agentsFiles: options.profile.context.projectFiles ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd }) : agentsFiles
306
+ }),
307
+ appendSystemPrompt: [akanSystemPrompt2(options.profile)],
308
+ additionalExtensionPaths: present(akanCodePaths2.extensionsDir(options.workspaceRoot)),
309
+ additionalSkillPaths: AkanCodeServices2.#skillPaths(options.profile, options.workspaceRoot),
310
+ extensionFactories: options.extensions
311
+ };
312
+ }
313
+ }
314
+
315
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgent.ts
316
+ import {
317
+ createAgentSession,
318
+ DefaultResourceLoader
319
+ } from "@earendil-works/pi-coding-agent";
320
+ import {
321
+ codeAgentClip as codeAgentClip5,
322
+ codeAgentLabelChars as codeAgentLabelChars2,
323
+ codeAgentRenderAnswer,
324
+ codeAgentSessionName as codeAgentSessionName2
325
+ } from "akanjs/common";
326
+
327
+ // pkgs/@akanjs/devkit/codeAgent/feedback/DevLogFeedback.ts
328
+ import { existsSync as existsSync5, statSync as statSync4 } from "fs";
329
+ import path5 from "path";
330
+
331
+ // pkgs/@akanjs/devkit/codeAgent/tools/AkanEditScope.ts
332
+ var empty = { paths: [], apps: [], libs: [], needsSync: false, touchesTsx: false };
333
+
334
+ class AkanEditScope2 {
335
+ static async since(cwd, baseline) {
336
+ const current = await AkanEditScope2.baseline(cwd);
337
+ const changed = [...current].filter((line) => !baseline.has(line));
338
+ if (!changed.length)
339
+ return empty;
340
+ const entries = changed.map((line) => ({ code: line.slice(0, 2), file: line.slice(3).split(" -> ").at(-1) ?? "" }));
341
+ const paths = entries.map((entry) => entry.file).filter(Boolean);
342
+ return {
343
+ paths,
344
+ apps: AkanEditScope2.#targets(paths, "apps"),
345
+ libs: AkanEditScope2.#targets(paths, "libs"),
346
+ needsSync: entries.some((entry) => /[ARD?]/.test(entry.code) && /^(apps|libs)\//.test(entry.file)),
347
+ touchesTsx: paths.some((file) => file.endsWith(".tsx"))
348
+ };
349
+ }
350
+ static async baseline(cwd) {
351
+ const proc = Bun.spawn(["git", "status", "--porcelain=v1", "--untracked-files=all"], {
352
+ cwd,
353
+ stdout: "pipe",
354
+ stderr: "ignore"
355
+ });
356
+ const text = await new Response(proc.stdout).text();
357
+ await proc.exited;
358
+ return new Set(text.split(`
359
+ `).filter((line) => line.trim().length > 3));
360
+ }
361
+ static #targets(paths, root) {
362
+ const names = paths.filter((file) => file.startsWith(`${root}/`)).map((file) => file.split("/")[1] ?? "").filter(Boolean);
363
+ return [...new Set(names)];
364
+ }
365
+ }
366
+
367
+ // pkgs/@akanjs/devkit/codeAgent/feedback/DevLogFeedback.ts
368
+ var errorPattern = /\bERROR\b|\bFATAL\b|\[stderr\]|error:|Error:|\u2716|Unhandled|Cannot find|is not a function/;
369
+ var noisePattern = /DEBUG|\bWARN\b|MCP catalogue|rate limit/;
370
+
371
+ class DevLogFeedback2 {
372
+ key = "dev-server";
373
+ #options;
374
+ #marks = new Map;
375
+ #baseline = new Set;
376
+ constructor(options) {
377
+ this.#options = { graceMs: 1500, maxLines: 30, ...options };
378
+ }
379
+ async begin() {
380
+ for (const app of this.#options.apps)
381
+ this.#marks.set(app, DevLogFeedback2.#sizeOf(this.#pathOf(app)));
382
+ this.#baseline = await AkanEditScope2.baseline(this.#options.cwd);
383
+ }
384
+ async observe() {
385
+ const running = this.#options.apps.filter((app) => existsSync5(this.#pathOf(app)));
386
+ if (!running.length)
387
+ return;
388
+ if (!(await AkanEditScope2.since(this.#options.cwd, this.#baseline)).paths.length)
389
+ return;
390
+ await Bun.sleep(this.#options.graceMs);
391
+ const reports = [];
392
+ for (const app of running) {
393
+ const lines = await this.#linesSince(app);
394
+ if (lines.length)
395
+ reports.push(`**${app}** dev server:
396
+ \`\`\`
397
+ ${lines.join(`
398
+ `)}
399
+ \`\`\``);
400
+ }
401
+ if (!reports.length)
402
+ return;
403
+ return [
404
+ "The dev server logged errors after your edits:",
405
+ ...reports,
406
+ `Full log: ${running.map((app) => path5.relative(this.#options.workspaceRoot, this.#pathOf(app))).join(", ")}`
407
+ ].join(`
408
+
409
+ `);
410
+ }
411
+ async#linesSince(app) {
412
+ const file = this.#pathOf(app);
413
+ const from = this.#marks.get(app) ?? 0;
414
+ const size = DevLogFeedback2.#sizeOf(file);
415
+ if (size <= from)
416
+ return [];
417
+ const text = await Bun.file(file).slice(from, size).text();
418
+ this.#marks.set(app, size);
419
+ return text.split(`
420
+ `).filter((line) => errorPattern.test(line) && !noisePattern.test(line)).slice(-this.#options.maxLines);
421
+ }
422
+ #pathOf(app) {
423
+ return path5.join(this.#options.workspaceRoot, "local", "apps", app, "runtime", "dev.log");
424
+ }
425
+ static async previewUrl(workspaceRoot, app) {
426
+ const file = path5.join(workspaceRoot, "local", "apps", app, "runtime", "dev.log");
427
+ if (!existsSync5(file))
428
+ return;
429
+ const matches = [...(await Bun.file(file).text()).matchAll(/ready \(pid=\d+\) \u2014 (https?:\/\/\S+)/g)];
430
+ return matches.at(-1)?.[1];
431
+ }
432
+ static #sizeOf(file) {
433
+ return existsSync5(file) ? statSync4(file).size : 0;
434
+ }
435
+ }
436
+
437
+ // pkgs/@akanjs/devkit/codeAgent/feedback/PreviewChrome.ts
438
+ import { existsSync as existsSync6 } from "fs";
439
+ import { homedir } from "os";
440
+ import path6 from "path";
441
+
442
+ class PreviewChrome2 {
443
+ static pathEnvKey = "BUN_CHROME_PATH";
444
+ static backendEnvKey = "AKAN_CODE_WEBVIEW_BACKEND";
148
445
  static executables = [
149
446
  "chromium",
150
447
  "chromium-browser",
@@ -178,10 +475,10 @@ class PreviewChrome2 {
178
475
  }
179
476
  if (process.platform !== "darwin")
180
477
  return;
181
- for (const root of ["/Applications", path2.join(homedir(), "Applications")])
478
+ for (const root of ["/Applications", path6.join(homedir(), "Applications")])
182
479
  for (const bundle of PreviewChrome2.appBundles) {
183
- const candidate = path2.join(root, bundle);
184
- if (existsSync2(candidate))
480
+ const candidate = path6.join(root, bundle);
481
+ if (existsSync6(candidate))
185
482
  return candidate;
186
483
  }
187
484
  return;
@@ -388,8 +685,8 @@ class TurnFeedback2 {
388
685
  }
389
686
 
390
687
  // pkgs/@akanjs/devkit/codeAgent/tools/AkanCli.ts
391
- import { existsSync as existsSync3 } from "fs";
392
- import path3 from "path";
688
+ import { existsSync as existsSync7 } from "fs";
689
+ import path7 from "path";
393
690
  class AkanCli2 {
394
691
  #cwd;
395
692
  #entry;
@@ -416,10 +713,10 @@ class AkanCli2 {
416
713
  }
417
714
  static #resolveEntry() {
418
715
  const candidates = [
419
- path3.join(import.meta.dir, "index.js"),
420
- path3.join(import.meta.dir, "..", "..", "..", "cli", "index.ts")
716
+ path7.join(import.meta.dir, "index.js"),
717
+ path7.join(import.meta.dir, "..", "..", "..", "cli", "index.ts")
421
718
  ];
422
- return candidates.find((candidate) => existsSync3(candidate)) ?? Bun.main;
719
+ return candidates.find((candidate) => existsSync7(candidate)) ?? Bun.main;
423
720
  }
424
721
  }
425
722
 
@@ -511,7 +808,7 @@ class AkanToolPack2 {
511
808
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
512
809
  }
513
810
  async names() {
514
- const { ContextRunner } = await import("./context.runner-v0def5my.js");
811
+ const { ContextRunner } = await import("./context.runner-1vsjztjg.js");
515
812
  return [
516
813
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
517
814
  AkanToolPack2.verifyToolName
@@ -519,7 +816,7 @@ class AkanToolPack2 {
519
816
  }
520
817
  static verifyToolName = "akan_verify";
521
818
  async#register(pi) {
522
- const { ContextRunner } = await import("./context.runner-v0def5my.js");
819
+ const { ContextRunner } = await import("./context.runner-1vsjztjg.js");
523
820
  const runner = new ContextRunner;
524
821
  const guidelineNames = await AkanToolPack2.#guidelineNames();
525
822
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -1027,7 +1324,7 @@ class McpToolPack2 {
1027
1324
  }
1028
1325
 
1029
1326
  // pkgs/@akanjs/devkit/codeAgent/tools/SessionToolPack.ts
1030
- import { SessionManager } from "@earendil-works/pi-coding-agent";
1327
+ import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
1031
1328
  import { codeAgentClip as codeAgentClip2, codeAgentOutputChars as codeAgentOutputChars2 } from "akanjs/common";
1032
1329
  import { Type as Type5 } from "typebox";
1033
1330
  var snippetChars = 240;
@@ -1093,7 +1390,7 @@ class SessionToolPack2 {
1093
1390
  return codeAgentClip2(session.allMessagesText, codeAgentOutputChars2 * 2);
1094
1391
  }
1095
1392
  async#list() {
1096
- return await SessionManager.list(this.#options.cwd, akanCodePaths2.sessionsDir(this.#options.workspaceRoot));
1393
+ return await SessionManager2.list(this.#options.cwd, akanCodePaths2.sessionsDir(this.#options.workspaceRoot));
1097
1394
  }
1098
1395
  }
1099
1396
 
@@ -1220,7 +1517,7 @@ ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1220
1517
  return;
1221
1518
  }
1222
1519
  static #readable(html) {
1223
- return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/[ \t]+/g, " ").replace(/\n{3,}/g, `
1520
+ return html.replace(/<script[\s\S]*?<\/script[^<>]*>/gi, " ").replace(/<style[\s\S]*?<\/style[^<>]*>/gi, " ").replace(/<[^<>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&amp;/g, "&").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, `
1224
1521
 
1225
1522
  `).trim();
1226
1523
  }
@@ -1314,303 +1611,6 @@ class AkanCodePlugins2 {
1314
1611
  }
1315
1612
  }
1316
1613
 
1317
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1318
- import { existsSync as existsSync7, mkdirSync, mkdtempSync } from "fs";
1319
- import { tmpdir } from "os";
1320
- import path7 from "path";
1321
- import { ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
1322
-
1323
- // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
1324
- import { existsSync as existsSync4, readFileSync } from "fs";
1325
- import path4 from "path";
1326
-
1327
- class AkanEnvKeys2 {
1328
- static async apply(runtime, workspaceRoot) {
1329
- const applied = [];
1330
- for (const [name, value] of AkanEnvKeys2.#entries(workspaceRoot)) {
1331
- const provider = AkanEnvKeys2.#providerOf(name);
1332
- if (!provider || !value)
1333
- continue;
1334
- await runtime.setRuntimeApiKey(provider, value);
1335
- applied.push(provider);
1336
- }
1337
- return applied;
1338
- }
1339
- static #entries(workspaceRoot) {
1340
- const fromProcess = Object.entries(process.env).filter(([, value]) => typeof value === "string");
1341
- const file = path4.join(workspaceRoot, ".env");
1342
- if (!existsSync4(file))
1343
- return fromProcess;
1344
- const fromFile = readFileSync(file, "utf8").split(`
1345
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
1346
- const index = line.indexOf("=");
1347
- return [
1348
- line.slice(0, index).trim(),
1349
- line.slice(index + 1).trim().replace(/^["']|["']$/g, "")
1350
- ];
1351
- });
1352
- return [...fromFile, ...fromProcess];
1353
- }
1354
- static #providerOf(name) {
1355
- const match = /^([A-Z0-9_]+)_API_KEY$/.exec(name);
1356
- return match?.[1]?.toLowerCase().replace(/_/g, "-");
1357
- }
1358
- }
1359
-
1360
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanContextFiles.ts
1361
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1362
- import path5 from "path";
1363
-
1364
- class AkanContextFiles {
1365
- static #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1366
- static extend(base, options) {
1367
- const seen = new Set(base.map((file) => file.path));
1368
- const contents = new Set(base.map((file) => file.content.trim()));
1369
- const added = [];
1370
- for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
1371
- if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim())
1372
- continue;
1373
- seen.add(file.path);
1374
- contents.add(file.content.trim());
1375
- added.push(file);
1376
- }
1377
- return [...base, ...added];
1378
- }
1379
- static #claude(base) {
1380
- const files = [];
1381
- for (const file of base) {
1382
- if (!/^AGENTS\.(md|MD)$/.test(path5.basename(file.path)))
1383
- continue;
1384
- for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
1385
- const candidate = path5.join(path5.dirname(file.path), name);
1386
- const content = AkanContextFiles.#read(candidate);
1387
- if (content !== undefined)
1388
- files.push({ path: candidate, content });
1389
- }
1390
- }
1391
- return files;
1392
- }
1393
- static #cursor(options) {
1394
- const files = [];
1395
- const roots = [...new Set([options.workspaceRoot, options.cwd])];
1396
- for (const root of roots) {
1397
- const legacy = AkanContextFiles.#read(path5.join(root, ".cursorrules"));
1398
- if (legacy !== undefined)
1399
- files.push({ path: path5.join(root, ".cursorrules"), content: legacy });
1400
- for (const file of AkanContextFiles.#mdcFiles(path5.join(root, ".cursor", "rules"))) {
1401
- const raw = AkanContextFiles.#read(file);
1402
- if (raw === undefined)
1403
- continue;
1404
- const body = AkanContextFiles.#applied(raw);
1405
- if (body !== undefined)
1406
- files.push({ path: file, content: body });
1407
- }
1408
- }
1409
- return files;
1410
- }
1411
- static #applied(raw) {
1412
- const match = AkanContextFiles.#frontmatter.exec(raw);
1413
- if (!match)
1414
- return raw;
1415
- if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? ""))
1416
- return;
1417
- return raw.slice(match[0].length);
1418
- }
1419
- static #mdcFiles(dir) {
1420
- if (!existsSync5(dir))
1421
- return [];
1422
- try {
1423
- return readdirSync(dir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".mdc")).map((entry) => path5.join(entry.parentPath ?? dir, entry.name)).sort();
1424
- } catch {
1425
- return [];
1426
- }
1427
- }
1428
- static #read(file) {
1429
- try {
1430
- if (!existsSync5(file) || !statSync2(file).isFile())
1431
- return;
1432
- return readFileSync2(file, "utf8").replace(/^\uFEFF/, "");
1433
- } catch {
1434
- return;
1435
- }
1436
- }
1437
- }
1438
-
1439
- // pkgs/@akanjs/devkit/codeAgent/agent/akanSystemPrompt.ts
1440
- var akanSystemPrompt2 = (profile) => {
1441
- const lines = [
1442
- "You are the akan coding agent, running inside an Akan.js workspace.",
1443
- "",
1444
- "- 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.",
1445
- "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
1446
- "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
1447
- "- 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.",
1448
- "- 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`."
1449
- ];
1450
- if (profile.tools.builtin.includes("write"))
1451
- lines.push("- After editing, call `akan_verify`. It runs sync, lint, typecheck and the SSR check over what the working tree changed. A turn that edits and does not verify is not finished.");
1452
- else
1453
- lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
1454
- if (profile.tools.subagent)
1455
- 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.');
1456
- if (profile.ui.canPrompt)
1457
- 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.");
1458
- if (profile.approval !== "never")
1459
- 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.`);
1460
- 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.");
1461
- return lines.join(`
1462
- `);
1463
- };
1464
-
1465
- // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
1466
- import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
1467
- import path6 from "path";
1468
-
1469
- // pkgs/@akanjs/devkit/codeAgent/agent/jsonLines.ts
1470
- import { closeSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync3 } from "fs";
1471
- var readHead = (file, maxBytes) => {
1472
- const fd = openSync(file, "r");
1473
- try {
1474
- const head = Buffer.alloc(maxBytes);
1475
- return head.subarray(0, readSync(fd, head, 0, maxBytes, 0)).toString("utf8");
1476
- } finally {
1477
- closeSync(fd);
1478
- }
1479
- };
1480
- var readJsonLines = (file, maxBytes) => {
1481
- try {
1482
- const text = maxBytes !== undefined && statSync3(file).size > maxBytes ? readHead(file, maxBytes) : readFileSync3(file, "utf8");
1483
- return text.split(`
1484
- `).filter((line) => !!line.trim());
1485
- } catch {
1486
- return [];
1487
- }
1488
- };
1489
- var parseJsonLine = (line) => {
1490
- try {
1491
- return JSON.parse(line);
1492
- } catch {
1493
- return;
1494
- }
1495
- };
1496
-
1497
- // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
1498
- class CodeSessionIndex2 {
1499
- static readLimit = 512 * 1024;
1500
- static list(dir, limit = 30) {
1501
- if (!existsSync6(dir))
1502
- return [];
1503
- const files = readdirSync2(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path6.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
1504
- return files.map((entry) => CodeSessionIndex2.#read(entry.file, entry.updatedAt)).filter((entry) => !!entry);
1505
- }
1506
- static fileOf(dir, id) {
1507
- if (!existsSync6(dir))
1508
- return;
1509
- const match = readdirSync2(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
1510
- return match ? path6.join(dir, match) : undefined;
1511
- }
1512
- static #mtime(file) {
1513
- try {
1514
- return statSync4(file).mtimeMs;
1515
- } catch {
1516
- return 0;
1517
- }
1518
- }
1519
- static #read(file, updatedAt) {
1520
- const lines = readJsonLines(file, CodeSessionIndex2.readLimit);
1521
- if (!lines.length)
1522
- return;
1523
- const entry = { id: "", file, name: undefined, opening: "", updatedAt, turns: 0 };
1524
- for (const line of lines) {
1525
- const record = parseJsonLine(line);
1526
- if (!record)
1527
- continue;
1528
- if (record.type === "session" && typeof record.id === "string")
1529
- entry.id = record.id;
1530
- if (record.type === "session_info" && typeof record.name === "string")
1531
- entry.name = record.name;
1532
- if (record.type !== "message")
1533
- continue;
1534
- const message = record.message;
1535
- if (message?.role !== "user")
1536
- continue;
1537
- entry.turns += 1;
1538
- if (!entry.opening)
1539
- entry.opening = CodeSessionIndex2.#text(message.content);
1540
- }
1541
- return entry.id ? entry : undefined;
1542
- }
1543
- static #text(content) {
1544
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part === "object" && part && ("text" in part) ? String(part.text) : "").join(" ") : "";
1545
- return text.replace(/\s+/g, " ").trim();
1546
- }
1547
- }
1548
-
1549
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1550
- class AkanCodeServices2 {
1551
- static settings() {
1552
- return SettingsManager.inMemory({
1553
- enableInstallTelemetry: false,
1554
- enableAnalytics: false,
1555
- quietStartup: true,
1556
- compaction: { reserveTokens: 32768 }
1557
- });
1558
- }
1559
- static async runtime(workspaceRoot, proxy = null) {
1560
- mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
1561
- const runtime = await ModelRuntime.create({
1562
- authPath: proxy ? path7.join(mkdtempSync(path7.join(tmpdir(), "akan-code-proxy-")), "auth.json") : akanCodePaths2.authFile(),
1563
- modelsPath: akanCodePaths2.modelsFile()
1564
- });
1565
- if (proxy)
1566
- await proxy.apply(runtime);
1567
- else
1568
- await AkanEnvKeys2.apply(runtime, workspaceRoot);
1569
- return runtime;
1570
- }
1571
- static sessions(workspaceRoot, cwd, profile, resume) {
1572
- if (profile.session.store === "memory")
1573
- return SessionManager2.inMemory(cwd);
1574
- const dir = akanCodePaths2.sessionsDir(workspaceRoot);
1575
- mkdirSync(dir, { recursive: true });
1576
- const manager = SessionManager2.create(cwd, dir);
1577
- if (!resume)
1578
- return manager;
1579
- const file = CodeSessionIndex2.fileOf(dir, resume);
1580
- if (!file)
1581
- throw new Error(`No stored session ${resume} in ${dir}`);
1582
- manager.setSessionFile(file);
1583
- return manager;
1584
- }
1585
- static #skillPaths(profile, workspaceRoot) {
1586
- if (!profile.context.skills)
1587
- return [];
1588
- const dirs = [
1589
- akanCodePaths2.builtinSkillsDir(),
1590
- akanCodePaths2.globalSkillsDir(),
1591
- akanCodePaths2.skillsDir(workspaceRoot)
1592
- ];
1593
- return dirs.filter((dir) => !!dir && existsSync7(dir));
1594
- }
1595
- static resourceOptions(options) {
1596
- const present = (dir) => existsSync7(dir) ? [dir] : [];
1597
- return {
1598
- noExtensions: true,
1599
- noSkills: true,
1600
- noPromptTemplates: true,
1601
- noThemes: true,
1602
- noContextFiles: !options.profile.context.projectFiles,
1603
- agentsFilesOverride: ({ agentsFiles }) => ({
1604
- agentsFiles: options.profile.context.projectFiles ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd }) : agentsFiles
1605
- }),
1606
- appendSystemPrompt: [akanSystemPrompt2(options.profile)],
1607
- additionalExtensionPaths: present(akanCodePaths2.extensionsDir(options.workspaceRoot)),
1608
- additionalSkillPaths: AkanCodeServices2.#skillPaths(options.profile, options.workspaceRoot),
1609
- extensionFactories: options.extensions
1610
- };
1611
- }
1612
- }
1613
-
1614
1614
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentAsks.ts
1615
1615
  class CodeAgentAsks2 {
1616
1616
  #question;
@@ -1964,6 +1964,7 @@ class CodeAgentGate2 {
1964
1964
 
1965
1965
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentProxy.ts
1966
1966
  import { readFile } from "fs/promises";
1967
+ import { trimTrailingSlashes } from "akanjs/common";
1967
1968
  class CodeAgentProxy {
1968
1969
  static placeholderKey = "akan-code-proxy";
1969
1970
  baseUrl;
@@ -1971,7 +1972,7 @@ class CodeAgentProxy {
1971
1972
  #tokenFile;
1972
1973
  #token;
1973
1974
  constructor({ baseUrl, providers, tokenFile, token }) {
1974
- this.baseUrl = baseUrl.replace(/\/+$/, "");
1975
+ this.baseUrl = trimTrailingSlashes(baseUrl);
1975
1976
  this.providers = providers;
1976
1977
  this.#tokenFile = tokenFile;
1977
1978
  this.#token = token;