@akanjs/devkit 2.3.11-rc.6 → 2.3.11-rc.7

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.
package/akanContext.ts CHANGED
@@ -119,6 +119,10 @@ export type JsonRpcRequest = {
119
119
  export type McpFraming = "content-length" | "newline";
120
120
  export type AkanMcpMode = "readonly" | "plan" | "apply";
121
121
 
122
+ // Coding-agent tools that can host the Akan MCP server. Cursor and Claude Code both read a JSON
123
+ // `mcpServers` map; Codex reads a TOML `[mcp_servers.<name>]` table.
124
+ export type AkanMcpInstallTarget = "cursor" | "claude" | "codex";
125
+
122
126
  export type CursorMcpConfig = {
123
127
  mcpServers?: Record<string, unknown>;
124
128
  };
@@ -133,17 +137,79 @@ export const resourceList = [
133
137
  ];
134
138
 
135
139
  export const cursorMcpConfigPath = ".cursor/mcp.json";
140
+ // Claude Code reads project-scoped MCP servers from `.mcp.json` at the workspace root.
141
+ export const claudeMcpConfigPath = ".mcp.json";
142
+ // Codex reads project-scoped config (trusted projects) from `.codex/config.toml`.
143
+ export const codexMcpConfigPath = ".codex/config.toml";
144
+
145
+ export const akanMcpInstallTargets: AkanMcpInstallTarget[] = ["cursor", "claude", "codex"];
146
+
147
+ export const akanMcpInstallConfigPaths: Record<AkanMcpInstallTarget, string> = {
148
+ cursor: cursorMcpConfigPath,
149
+ claude: claudeMcpConfigPath,
150
+ codex: codexMcpConfigPath,
151
+ };
136
152
 
153
+ // `akan mcp` resolves the workspace from process.cwd(), so every launcher must run it from the
154
+ // workspace root. Cursor expands its own ${workspaceFolder} variable. Claude Code does not guarantee
155
+ // the server's cwd but sets CLAUDE_PROJECT_DIR in its environment, so we cd into that at runtime.
156
+ // Codex inherits its own launch cwd (it also discovers .codex/config.toml from cwd), so it runs the
157
+ // command directly and must be started from the workspace root.
137
158
  const cursorWorkspaceFolder = "$" + "{workspaceFolder}";
159
+ const claudeProjectDir = "$CLAUDE_PROJECT_DIR";
160
+
161
+ const akanMcpCommand = (mode: AkanMcpMode, { cd }: { cd?: string } = {}) =>
162
+ cd ? `cd "${cd}" && akan mcp --mode ${mode}` : `akan mcp --mode ${mode}`;
138
163
 
139
164
  export const createAkanCursorMcpServer = (mode: AkanMcpMode = "readonly") => ({
140
165
  type: "stdio",
141
166
  command: "bash",
142
- args: ["-lc", `cd "${cursorWorkspaceFolder}" && akan mcp --mode ${mode}`],
167
+ args: ["-lc", akanMcpCommand(mode, { cd: cursorWorkspaceFolder })],
168
+ });
169
+
170
+ export const createAkanClaudeMcpServer = (mode: AkanMcpMode = "readonly") => ({
171
+ type: "stdio",
172
+ command: "bash",
173
+ args: ["-lc", akanMcpCommand(mode, { cd: claudeProjectDir })],
143
174
  });
144
175
 
176
+ // JSON-config targets (Cursor, Claude Code) share the same `mcpServers` entry shape.
177
+ export const createAkanMcpServer = (target: "cursor" | "claude", mode: AkanMcpMode = "readonly") =>
178
+ target === "cursor" ? createAkanCursorMcpServer(mode) : createAkanClaudeMcpServer(mode);
179
+
145
180
  export const akanCursorMcpServer = createAkanCursorMcpServer();
146
181
 
182
+ // Codex config is TOML and we have no TOML serializer, so we build the `[mcp_servers.akan]` table as text.
183
+ export const codexMcpServerTableHeader = "[mcp_servers.akan]";
184
+ export const createAkanCodexMcpServerBlock = (mode: AkanMcpMode = "readonly") =>
185
+ `${codexMcpServerTableHeader}\ncommand = "bash"\nargs = ["-lc", "${akanMcpCommand(mode)}"]\n`;
186
+
187
+ // A TOML table runs from its header until the next top-level `[header]` or EOF. We upsert only the
188
+ // akan table and preserve everything else in the file, mirroring the JSON merge behavior.
189
+ const codexAkanTablePattern = /^\[mcp_servers\.akan\][^\n]*\n(?:(?!\[)[^\n]*(?:\n|$))*/m;
190
+
191
+ export const upsertCodexMcpServerBlock = (
192
+ existing: string,
193
+ block: string,
194
+ { force = false }: { force?: boolean } = {},
195
+ ) => {
196
+ const nextBlock = block.endsWith("\n") ? block : `${block}\n`;
197
+ const match = existing.match(codexAkanTablePattern);
198
+ if (!match) {
199
+ if (!existing.trim()) return nextBlock;
200
+ return `${existing.replace(/\s*$/, "")}\n\n${nextBlock}`;
201
+ }
202
+ if (match[0].trim() === nextBlock.trim()) return existing;
203
+ if (!force)
204
+ throw new Error(`${codexMcpConfigPath} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
205
+ const start = match.index ?? 0;
206
+ const before = existing.slice(0, start);
207
+ const after = existing.slice(start + match[0].length);
208
+ // The matched table absorbed its trailing blank line, so re-insert one before any following table.
209
+ const separator = after && !after.startsWith("\n") ? "\n" : "";
210
+ return `${before}${nextBlock}${separator}${after}`;
211
+ };
212
+
147
213
  export const renderDoctorText = (result: AkanDoctorResult) => {
148
214
  const lines = [`Akan doctor status: ${result.status}`];
149
215
  if (result.diagnostics.length === 0) {
@@ -189,7 +255,6 @@ const generatedFiles = [
189
255
  "*/lib/cnst.ts",
190
256
  "*/lib/db.ts",
191
257
  "*/lib/dict.ts",
192
- "*/lib/option.ts",
193
258
  "*/lib/sig.ts",
194
259
  "*/lib/srv.ts",
195
260
  "*/lib/st.ts",
package/executors.ts CHANGED
@@ -1392,6 +1392,12 @@ export class AppExecutor extends SysExecutor {
1392
1392
  ...routeEnv,
1393
1393
  ...(devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}),
1394
1394
  });
1395
+ // `start` spawns subprocesses that carry `env`, but `build` runs its phases in this same process and
1396
+ // reads `process.env` directly. Publish the resolved env here so SSR/CSR bundling (fed by getPublicEnv,
1397
+ // which filters to AKAN_PUBLIC_*) sees AKAN_PUBLIC_APP_NAME — otherwise SSR throws
1398
+ // "environment variable AKAN_PUBLIC_APP_NAME is required". Only AKAN_PUBLIC_* is baked into bundles, so
1399
+ // this does not leak non-public env.
1400
+ if (type === "build") Object.assign(process.env, env);
1395
1401
  return { env };
1396
1402
  }
1397
1403
  #publicEnv: Record<string, string> | null = null;
@@ -2,8 +2,10 @@ engine biome(1.0)
2
2
  language js(typescript, jsx)
3
3
 
4
4
  // Every model already gets auto-generated CRUD endpoints in its slice fetch
5
- // contract (see pkgs/akanjs/fetch/fetchType/sliceFetch.type.ts):
6
- // <refName>, light<Model>, create<Model>, update<Model>, remove<Model>.
5
+ // contract (see pkgs/akanjs/fetch/fetchType/sliceFetch.type.ts and
6
+ // pkgs/akanjs/fetch/client/fetchClient.ts):
7
+ // <refName>, light<Model>, create<Model>, update<Model>, remove<Model>,
8
+ // view<Model>, edit<Model>, merge<Model>.
7
9
  // Re-declaring one of them inside the `endpoint(...)` block of a
8
10
  // `<model>.signal.ts` file shadows the framework contract and can pass
9
11
  // sync/typecheck/build while failing at runtime, so flag it here.
@@ -15,12 +17,12 @@ language js(typescript, jsx)
15
17
  $filename <: r".*/([a-z][a-zA-Z0-9]*)\.signal\.ts"($model),
16
18
  or {
17
19
  $key <: $model,
18
- $key <: r"(?:light|create|update|remove)([A-Z][a-zA-Z0-9]*)"($keyCap) where {
20
+ $key <: r"(?:light|create|update|remove|view|edit|merge)([A-Z][a-zA-Z0-9]*)"($keyCap) where {
19
21
  $keyCap <: $Cap
20
22
  }
21
23
  },
22
24
  register_diagnostic(
23
25
  span = $key,
24
- message = "This endpoint name collides with an auto-generated CRUD endpoint (<model>, light/create/update/remove<Model>). Rename it or move the logic into the service; do not re-declare predefined endpoints."
26
+ message = "This endpoint name collides with an auto-generated CRUD endpoint (<model>, light/create/update/remove/view/edit/merge<Model>). Rename it or move the logic into the service; do not re-declare predefined endpoints."
25
27
  )
26
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.3.11-rc.6",
3
+ "version": "2.3.11-rc.7",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@tailwindcss/node": "^4.3.0",
34
34
  "@trapezedev/project": "^7.1.4",
35
- "akanjs": "2.3.11-rc.6",
35
+ "akanjs": "2.3.11-rc.7",
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
38
  "daisyui": "5.5.23",
@@ -196,7 +196,6 @@ export const generatedFilePathsForTarget = (targetRoot: string, reason = "Genera
196
196
  [
197
197
  { path: `${targetRoot}/lib/cnst.ts`, action: "sync", reason },
198
198
  { path: `${targetRoot}/lib/dict.ts`, action: "sync", reason },
199
- { path: `${targetRoot}/lib/option.ts`, action: "sync", reason },
200
199
  { path: `${targetRoot}/lib/index.ts`, action: "sync", reason },
201
200
  ] satisfies PrimitiveGeneratedFile[];
202
201