@akanjs/devkit 3.0.0-beta.9 → 3.0.0

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 (341) hide show
  1. package/DEV_RUNTIME_KNOBS.md +44 -26
  2. package/README.ko.md +21 -7
  3. package/README.md +17 -8
  4. package/abstractDoc.test.ts +6 -15
  5. package/abstractDoc.ts +6 -8
  6. package/agentsIndex.test.ts +4 -7
  7. package/agentsIndex.ts +6 -22
  8. package/akanApp/BackendImportGraph.test.ts +8 -22
  9. package/akanApp/BackendImportGraph.ts +1 -20
  10. package/akanApp/akanApp.host.test.ts +56 -3
  11. package/akanApp/akanApp.host.ts +218 -278
  12. package/akanApp/devBootLatch.test.ts +39 -0
  13. package/akanApp/devBootLatch.ts +39 -0
  14. package/akanApp/devHostPolicy.test.ts +81 -35
  15. package/akanApp/devHostPolicy.ts +41 -102
  16. package/akanConfig/akanBin.test.ts +62 -0
  17. package/akanConfig/akanBin.ts +78 -0
  18. package/akanConfig/akanConfig.test.ts +439 -141
  19. package/akanConfig/akanConfig.ts +359 -275
  20. package/akanConfig/index.ts +1 -0
  21. package/akanConfig/types.ts +22 -8
  22. package/akanContext.ts +110 -160
  23. package/akanMcpContract.ts +28 -73
  24. package/appSelectionMemory.ts +1 -4
  25. package/applicationBuildReporter.ts +37 -5
  26. package/applicationBuildRunner.test.ts +41 -5
  27. package/applicationBuildRunner.ts +71 -63
  28. package/applicationTestPreload.ts +3 -12
  29. package/artifact/implicitRootLayout.test.ts +32 -30
  30. package/artifact/implicitRootLayout.ts +58 -106
  31. package/artifact/routeSeedIndex.test.ts +7 -6
  32. package/artifact/routeSeedIndex.ts +11 -44
  33. package/biome.base.json +35 -2
  34. package/biomeBase.ts +2 -5
  35. package/cloud/cloudApi.test.ts +202 -0
  36. package/cloud/cloudApi.ts +77 -39
  37. package/cloud/configLock.test.ts +64 -0
  38. package/cloud/configLock.ts +96 -0
  39. package/cloud/constants.ts +26 -5
  40. package/cloud/globalConfig.ts +71 -32
  41. package/codeAgent/agent/AkanCodeServices.ts +97 -0
  42. package/codeAgent/agent/AkanContextFiles.ts +94 -0
  43. package/codeAgent/agent/CodeAgent.ts +551 -0
  44. package/codeAgent/agent/CodeAgentAsks.ts +66 -0
  45. package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
  46. package/codeAgent/agent/CodeAgentGate.ts +93 -0
  47. package/codeAgent/agent/CodeAgentProxy.ts +82 -0
  48. package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
  49. package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
  50. package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
  51. package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
  52. package/codeAgent/agent/CodeAgentUi.ts +47 -0
  53. package/codeAgent/agent/CodeMailbox.ts +148 -0
  54. package/codeAgent/agent/CodeSessionFork.ts +56 -0
  55. package/codeAgent/agent/CodeSessionIndex.ts +78 -0
  56. package/codeAgent/agent/SubagentPool.ts +214 -0
  57. package/codeAgent/agent/akanCodeModel.ts +47 -0
  58. package/codeAgent/agent/akanCodePaths.ts +31 -0
  59. package/codeAgent/agent/akanContextFiles.test.ts +114 -0
  60. package/codeAgent/agent/akanSystemPrompt.ts +46 -0
  61. package/codeAgent/agent/codeAgent.test.ts +178 -0
  62. package/codeAgent/agent/codeAgentContract.test.ts +523 -0
  63. package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
  64. package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
  65. package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
  66. package/codeAgent/agent/codeMailbox.test.ts +76 -0
  67. package/codeAgent/agent/codeSessionFork.test.ts +58 -0
  68. package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
  69. package/codeAgent/agent/consoleToStderr.ts +8 -0
  70. package/codeAgent/agent/jsonLines.ts +29 -0
  71. package/codeAgent/agent/stringArg.ts +5 -0
  72. package/codeAgent/feedback/DevLogFeedback.ts +82 -0
  73. package/codeAgent/feedback/PreviewChrome.ts +82 -0
  74. package/codeAgent/feedback/PreviewFeedback.ts +46 -0
  75. package/codeAgent/feedback/PreviewView.ts +95 -0
  76. package/codeAgent/feedback/TurnFeedback.ts +98 -0
  77. package/codeAgent/feedback/VerifyFeedback.ts +40 -0
  78. package/codeAgent/feedback/previewChrome.test.ts +73 -0
  79. package/codeAgent/index.ts +34 -0
  80. package/codeAgent/skills/akan-code/SKILL.md +130 -0
  81. package/codeAgent/skills/akan-debug/SKILL.md +42 -0
  82. package/codeAgent/skills/akan-deps/SKILL.md +50 -0
  83. package/codeAgent/skills/akan-err/SKILL.md +39 -0
  84. package/codeAgent/skills/akan-module/SKILL.md +45 -0
  85. package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
  86. package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
  87. package/codeAgent/skills/akan-service/SKILL.md +34 -0
  88. package/codeAgent/skills/akan-store/SKILL.md +67 -0
  89. package/codeAgent/skills/akan-ui/SKILL.md +63 -0
  90. package/codeAgent/skills/akan-validate/SKILL.md +44 -0
  91. package/codeAgent/tools/AkanCli.ts +50 -0
  92. package/codeAgent/tools/AkanCodePlugins.ts +136 -0
  93. package/codeAgent/tools/AkanEditScope.ts +48 -0
  94. package/codeAgent/tools/AkanEnvKeys.ts +50 -0
  95. package/codeAgent/tools/AkanToolPack.ts +92 -0
  96. package/codeAgent/tools/AkanVerifier.ts +48 -0
  97. package/codeAgent/tools/AskToolPack.ts +99 -0
  98. package/codeAgent/tools/MailToolPack.ts +76 -0
  99. package/codeAgent/tools/McpClient.ts +187 -0
  100. package/codeAgent/tools/McpOAuth.ts +233 -0
  101. package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
  102. package/codeAgent/tools/McpServerConfig.ts +129 -0
  103. package/codeAgent/tools/McpSignIn.ts +158 -0
  104. package/codeAgent/tools/McpTokenStore.ts +81 -0
  105. package/codeAgent/tools/McpToolPack.ts +199 -0
  106. package/codeAgent/tools/SessionToolPack.ts +91 -0
  107. package/codeAgent/tools/WebToolPack.ts +147 -0
  108. package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
  109. package/codeAgent/tools/askToolPack.test.ts +116 -0
  110. package/codeAgent/tools/mcpOAuth.test.ts +612 -0
  111. package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
  112. package/codegenLock.test.ts +4 -13
  113. package/codegenLock.ts +3 -17
  114. package/commandDecorators/argMeta.ts +1 -5
  115. package/commandDecorators/camelToKebabCase.ts +1 -0
  116. package/commandDecorators/command.ts +46 -102
  117. package/commandDecorators/commandBuilder.ts +30 -61
  118. package/commandDecorators/commandDecorators.test.ts +2 -10
  119. package/commandDecorators/dependencyBuilder.ts +10 -1
  120. package/commandDecorators/helpFormatter.ts +68 -145
  121. package/commandDecorators/index.ts +0 -2
  122. package/commandDecorators/targetMeta.ts +9 -7
  123. package/createTunnel.ts +7 -14
  124. package/csrE2e/csrE2eHarness.fixture.ts +505 -0
  125. package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
  126. package/csrE2e/csrPageStack.e2e.test.ts +252 -0
  127. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  128. package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
  129. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  130. package/csrE2e/registryVendor.e2e.test.ts +221 -0
  131. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  132. package/dependencyScanner.test.ts +4 -16
  133. package/dependencyScanner.ts +31 -80
  134. package/devkitUtils.test.ts +48 -20
  135. package/executors.linkFallback.test.ts +103 -0
  136. package/executors.test.ts +176 -106
  137. package/executors.ts +212 -498
  138. package/fileEditor.ts +9 -37
  139. package/{getDirname.test.ts → fileSys.test.ts} +5 -3
  140. package/fileSys.ts +22 -9
  141. package/formSetterScanner.ts +3 -16
  142. package/frontendBuild/allRoutesBuilder.ts +7 -21
  143. package/frontendBuild/autoImportSync.test.ts +27 -38
  144. package/frontendBuild/autoImportSync.ts +31 -65
  145. package/frontendBuild/buildRouteClient.test.ts +123 -37
  146. package/frontendBuild/bundleDefine.test.ts +12 -0
  147. package/frontendBuild/bundleDefine.ts +16 -0
  148. package/frontendBuild/clientBuildTypes.ts +5 -36
  149. package/frontendBuild/clientEntriesBundler.ts +9 -40
  150. package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
  151. package/frontendBuild/clientEntryDiscovery.ts +122 -110
  152. package/frontendBuild/csrArtifactBuilder.ts +9 -31
  153. package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
  154. package/frontendBuild/csrDevArtifactWriter.ts +307 -0
  155. package/frontendBuild/csrDevBundler.test.ts +66 -0
  156. package/frontendBuild/csrDevBundler.ts +95 -0
  157. package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
  158. package/frontendBuild/csrDevFactoryCheck.ts +39 -0
  159. package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
  160. package/frontendBuild/csrDevModuleCompiler.ts +422 -0
  161. package/frontendBuild/csrDevPatcher.test.ts +572 -0
  162. package/frontendBuild/csrDevPatcher.ts +548 -0
  163. package/frontendBuild/csrDevPaths.ts +145 -0
  164. package/frontendBuild/csrDevResolver.test.ts +239 -0
  165. package/frontendBuild/csrDevResolver.ts +363 -0
  166. package/frontendBuild/csrDevTypes.ts +61 -0
  167. package/frontendBuild/csrEntryFiles.ts +24 -0
  168. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  169. package/frontendBuild/cssCandidateCache.ts +6 -41
  170. package/frontendBuild/cssCompiler.ts +31 -82
  171. package/frontendBuild/cssImportResolver.ts +6 -19
  172. package/frontendBuild/devChangePlanner.ts +7 -10
  173. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  174. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  175. package/frontendBuild/devRegistryBundler.ts +192 -0
  176. package/frontendBuild/fontOptimizer.test.ts +6 -14
  177. package/frontendBuild/fontOptimizer.ts +15 -72
  178. package/frontendBuild/fontPruner.test.ts +5 -10
  179. package/frontendBuild/fontPruner.ts +5 -20
  180. package/frontendBuild/frontendBuild.test.ts +57 -70
  181. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  182. package/frontendBuild/hmrWatcher.test.ts +59 -32
  183. package/frontendBuild/hmrWatcher.ts +31 -74
  184. package/frontendBuild/index.ts +4 -0
  185. package/frontendBuild/pagesBundleBuilder.ts +28 -30
  186. package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
  187. package/frontendBuild/precompressArtifacts.ts +5 -18
  188. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  189. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  190. package/frontendBuild/routeClientBuilder.ts +100 -62
  191. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  192. package/frontendBuild/serverGraphFile.test.ts +77 -0
  193. package/frontendBuild/serverGraphFile.ts +93 -0
  194. package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
  195. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  196. package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
  197. package/frontendBuild/ssrDevBundler.ts +85 -0
  198. package/frontendBuild/styleContract.ts +1 -8
  199. package/frontendBuild/themeValidator.ts +4 -26
  200. package/frontendBuild/watchRootResolver.test.ts +23 -42
  201. package/frontendBuild/watchRootResolver.ts +5 -27
  202. package/getRelatedCnsts.ts +12 -92
  203. package/incrementalBuilder/buildBatch.proc.ts +147 -63
  204. package/incrementalBuilder/buildBatchProtocol.ts +13 -38
  205. package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
  206. package/incrementalBuilder/buildBatchRunner.ts +26 -38
  207. package/incrementalBuilder/builderChannel.test.ts +24 -49
  208. package/incrementalBuilder/builderChannel.ts +6 -37
  209. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  210. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  211. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  212. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  213. package/incrementalBuilder/devWatchBatch.test.ts +21 -24
  214. package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
  215. package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
  216. package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
  217. package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
  218. package/index.ts +5 -19
  219. package/integration/devResourceProbe.ts +50 -56
  220. package/integration/devStability.integration.test.ts +105 -199
  221. package/integration/devStabilityHarness.test.ts +13 -28
  222. package/integration/devStabilityHarness.ts +65 -330
  223. package/integration/ssrMemoryProbe.ts +41 -167
  224. package/libSource.test.ts +4 -15
  225. package/libSource.ts +19 -31
  226. package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
  227. package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
  228. package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
  229. package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
  230. package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
  231. package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
  232. package/lint/gritRules.test.ts +8 -24
  233. package/lint/no-deep-internal-import.grit +2 -2
  234. package/lint/no-document-cookie.grit +29 -0
  235. package/lint/no-redeclare-predefined-endpoint.grit +2 -1
  236. package/lint/no-web-only-api-outside-webkit.grit +44 -0
  237. package/lint/no-web-storage.grit +30 -0
  238. package/linter.test.ts +1 -2
  239. package/linter.ts +26 -116
  240. package/mobile/desktopBin.test.ts +182 -0
  241. package/mobile/desktopBin.ts +144 -0
  242. package/mobile/desktopServerStage.test.ts +252 -0
  243. package/mobile/desktopServerStage.ts +178 -0
  244. package/mobile/index.ts +9 -0
  245. package/mobile/mobileTarget.test.ts +57 -8
  246. package/mobile/mobileTarget.ts +45 -26
  247. package/mobile/nativeApi.test.ts +30 -0
  248. package/mobile/nativeApi.ts +40 -0
  249. package/mobile/nativeApp.test.ts +184 -0
  250. package/mobile/nativeApp.ts +454 -0
  251. package/mobile/nativeAppLine.test.ts +69 -0
  252. package/mobile/nativeAppLine.ts +71 -0
  253. package/mobile/nativeConfig.test.ts +438 -0
  254. package/mobile/nativeConfig.ts +294 -0
  255. package/mobile/nativePluginFolders.test.ts +81 -0
  256. package/mobile/nativePluginFolders.ts +60 -0
  257. package/mobile/nativeWebDir.test.ts +80 -0
  258. package/mobile/nativeWebDir.ts +66 -0
  259. package/mobile/placeholderAppId.test.ts +13 -0
  260. package/mobile/placeholderAppId.ts +16 -0
  261. package/mobile/usageDescriptions.test.ts +27 -0
  262. package/mobile/usageDescriptions.ts +24 -0
  263. package/package.json +11 -11
  264. package/packageExports.test.ts +3 -18
  265. package/packageExportsMap.ts +3 -19
  266. package/piDependencyPins.test.ts +46 -0
  267. package/platformTest/BunTestReport.test.ts +60 -0
  268. package/platformTest/BunTestReport.ts +65 -0
  269. package/platformTest/IdleWatchedProcess.test.ts +56 -0
  270. package/platformTest/IdleWatchedProcess.ts +73 -0
  271. package/platformTest/LinuxDockerTarget.ts +105 -0
  272. package/platformTest/PlatformTestRun.test.ts +64 -0
  273. package/platformTest/PlatformTestRun.ts +141 -0
  274. package/platformTest/PlatformTestTarget.ts +142 -0
  275. package/platformTest/TestSnapshot.test.ts +72 -0
  276. package/platformTest/TestSnapshot.ts +103 -0
  277. package/platformTest/WindowsSshTarget.ts +151 -0
  278. package/prompter.ts +3 -13
  279. package/qualityScanner.test.ts +9 -26
  280. package/qualityScanner.ts +200 -286
  281. package/recipeScanner.test.ts +7 -12
  282. package/recipeScanner.ts +9 -38
  283. package/repoIdentity.ts +2 -9
  284. package/routeSourceValidator.test.ts +0 -1
  285. package/routeSourceValidator.ts +7 -42
  286. package/scanInfo.ts +91 -225
  287. package/selectModel.ts +1 -2
  288. package/semver.ts +3 -5
  289. package/slicePlanner.test.ts +6 -24
  290. package/slicePlanner.ts +11 -32
  291. package/spinner.test.ts +3 -8
  292. package/spinner.ts +4 -16
  293. package/sshTunnel.ts +1 -6
  294. package/ssrScanner.test.ts +1 -6
  295. package/ssrScanner.ts +103 -113
  296. package/stripAnsi.ts +3 -0
  297. package/subspace.test.ts +9 -25
  298. package/subspace.ts +48 -115
  299. package/subspaceConfig.ts +3 -11
  300. package/testHelpers.ts +194 -0
  301. package/transforms/asyncDefaultExportDetector.ts +3 -11
  302. package/transforms/barrelAnalyzer.ts +43 -135
  303. package/transforms/barrelImportsPlugin.ts +38 -175
  304. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  305. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  306. package/transforms/moduleSyntax.ts +32 -0
  307. package/transforms/rscUseClientTransform.ts +42 -20
  308. package/transforms/serverEnvPlugin.test.ts +103 -0
  309. package/transforms/serverEnvPlugin.ts +41 -0
  310. package/transforms/transforms.test.ts +166 -44
  311. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  312. package/transforms/useClientBundlePlugin.ts +6 -23
  313. package/typeChecker.ts +7 -63
  314. package/types.ts +2 -0
  315. package/uploadRelease.ts +10 -25
  316. package/workflow/artifacts.ts +18 -0
  317. package/workflow/executor.test.ts +1 -12
  318. package/workflow/executor.ts +101 -110
  319. package/workflow/index.ts +0 -1
  320. package/workflow/moduleIndex.test.ts +4 -15
  321. package/workflow/moduleIndex.ts +9 -49
  322. package/workflow/plan.ts +18 -25
  323. package/workflow/render.ts +80 -96
  324. package/workflow/rolloutGate.test.ts +1 -1
  325. package/workflow/source.ts +52 -69
  326. package/workflow/sourceAst.ts +50 -0
  327. package/workspaceLayout.test.ts +18 -3
  328. package/workspaceLayout.ts +22 -23
  329. package/abstractCompactor.ts +0 -112
  330. package/aiEditor.test.ts +0 -60
  331. package/aiEditor.ts +0 -387
  332. package/applicationReleasePackager.ts +0 -209
  333. package/capacitorApp.test.ts +0 -372
  334. package/capacitorApp.ts +0 -1648
  335. package/commandDecorators/commandMeta.ts +0 -7
  336. package/commandDecorators/types.ts +0 -10
  337. package/getDirname.ts +0 -4
  338. package/guideline.ts +0 -19
  339. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  340. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  341. package/workflow/primitive.ts +0 -59
@@ -1,6 +1,7 @@
1
- import { chmod, mkdir } from "node:fs/promises";
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
2
  import dayjs from "dayjs";
3
3
  import { FileSys } from "../fileSys";
4
+ import { ConfigLock } from "./configLock";
4
5
  import {
5
6
  type AccessToken,
6
7
  type AccessTokenDto,
@@ -12,6 +13,7 @@ import {
12
13
  type HostConfig,
13
14
  type HostConfigDto,
14
15
  type RemoteEnvServerConfig,
16
+ type TestTargetsConfig,
15
17
  } from "./constants";
16
18
 
17
19
  export class GlobalConfig {
@@ -27,58 +29,95 @@ export class GlobalConfig {
27
29
  ...akanConfig,
28
30
  cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
29
31
  remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers,
32
+ testTargets: akanConfig.testTargets ?? defaultAkanGlobalConfig.testTargets,
30
33
  };
31
34
  }
32
- /**
33
- * This file holds the cloud jwt, a refresh token that does not expire, and the LLM api key, so it is
34
- * written owner-only — the same `0600` the runtime gives its control socket. `Bun.write` takes no mode
35
- * and lands on `0666 & ~umask` (0644 on a default shell), so the mode is applied after the write; an
36
- * existing world-readable file is tightened by the next write rather than left as it was found.
37
- */
35
+ // Holds the cloud jwt and refresh token, so owner-only 0600: written to a 0600 sibling and renamed over the file,
36
+ // which also replaces a world-readable one and never shows another CLI process a half-written file.
38
37
  static async #setAkanGlobalConfig(akanConfig: AkanGlobalConfig) {
39
38
  await mkdir(basePath, { recursive: true, mode: 0o700 });
40
- await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
41
- await chmod(configPath, 0o600);
39
+ const temp = `${configPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
40
+ try {
41
+ await writeFile(temp, JSON.stringify(akanConfig, null, 2), { mode: 0o600 });
42
+ // Every CLI reads this file outside the lock, which on Windows makes the rename wait for the reader.
43
+ await FileSys.replace(temp, configPath);
44
+ } catch (error) {
45
+ await rm(temp, { force: true });
46
+ throw error;
47
+ }
48
+ }
49
+ static readonly #lockPath = `${configPath}.lock`;
50
+ // Every write re-reads under the lock: one built on an earlier read could restore a rotated-away refresh token.
51
+ static async #update(mutate: (akanConfig: AkanGlobalConfig) => AkanGlobalConfig) {
52
+ await ConfigLock.run(GlobalConfig.#lockPath, async () => {
53
+ await GlobalConfig.#setAkanGlobalConfig(mutate(await GlobalConfig.#getAkanGlobalConfig()));
54
+ });
42
55
  }
43
56
  static async getHostConfig(host = GlobalConfig.akanCloudHost): Promise<HostConfig> {
44
57
  const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
45
58
  return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
46
59
  }
47
60
  static async setHostConfig(config: HostConfig = getDefaultHostConfig()) {
48
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
49
- akanConfig.cloudHost[config.host] = GlobalConfig.toHostConfigDto(config);
50
- await GlobalConfig.#setAkanGlobalConfig(akanConfig);
61
+ await GlobalConfig.#update((akanConfig) => GlobalConfig.#withHost(akanConfig, config));
51
62
  }
52
- static async getLlmConfig(): Promise<AkanGlobalConfig["llm"]> {
53
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
54
- return akanConfig.llm ?? null;
63
+ static #withHost(akanConfig: AkanGlobalConfig, config: HostConfig): AkanGlobalConfig {
64
+ return {
65
+ ...akanConfig,
66
+ cloudHost: { ...akanConfig.cloudHost, [config.host]: GlobalConfig.toHostConfigDto(config) },
67
+ };
55
68
  }
56
- static async setLlmConfig(llmConfig: AkanGlobalConfig["llm"]) {
57
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
58
- await GlobalConfig.#setAkanGlobalConfig({ ...akanConfig, llm: llmConfig });
69
+ // The cloud rotates the refresh token and revokes every session of the account when one is presented twice, so it
70
+ // leaves the disk before it is sent: a refresh killed in flight leaves nothing to replay, only a sign-in to redo.
71
+ static async refreshHostAuth(host: string, refresh: (refreshToken: string) => Promise<AccessToken>) {
72
+ try {
73
+ return await ConfigLock.run(GlobalConfig.#lockPath, async () => {
74
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
75
+ const current = GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
76
+ const accessToken = current.auth?.accessToken;
77
+ const refreshToken = accessToken?.refreshToken;
78
+ if (!accessToken || !refreshToken || !GlobalConfig.needRefreshToken(accessToken)) return current;
79
+ const claimed: HostConfig = {
80
+ ...current,
81
+ auth: { ...current.auth, accessToken: { ...accessToken, refreshToken: null } },
82
+ };
83
+ await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(akanConfig, claimed));
84
+ const next = await refresh(refreshToken).catch(() => null);
85
+ if (!next?.jwt) return claimed;
86
+ const refreshed: HostConfig = { ...current, auth: { ...current.auth, accessToken: next } };
87
+ await GlobalConfig.#setAkanGlobalConfig(
88
+ GlobalConfig.#withHost(await GlobalConfig.#getAkanGlobalConfig(), refreshed),
89
+ );
90
+ return refreshed;
91
+ });
92
+ } catch {
93
+ // No lock, or a write that failed: what is stored is used as it is, and nothing is refreshed outside the lock.
94
+ return await GlobalConfig.getHostConfig(host);
95
+ }
59
96
  }
60
97
  static async getRemoteEnvServers(): Promise<AkanGlobalConfig["remoteEnvServers"]> {
61
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
62
- return akanConfig.remoteEnvServers;
98
+ return (await GlobalConfig.#getAkanGlobalConfig()).remoteEnvServers;
63
99
  }
64
100
  static async setRemoteEnvServer(name: string, config: RemoteEnvServerConfig) {
65
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
66
- await GlobalConfig.#setAkanGlobalConfig({
101
+ await GlobalConfig.#update((akanConfig) => ({
67
102
  ...akanConfig,
68
- remoteEnvServers: {
69
- ...akanConfig.remoteEnvServers,
70
- [name]: config,
71
- },
72
- });
103
+ remoteEnvServers: { ...akanConfig.remoteEnvServers, [name]: config },
104
+ }));
73
105
  }
74
106
  static async removeRemoteEnvServer(name: string) {
75
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
76
- const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
77
- await GlobalConfig.#setAkanGlobalConfig({
78
- ...akanConfig,
79
- remoteEnvServers,
107
+ await GlobalConfig.#update((akanConfig) => {
108
+ const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
109
+ return { ...akanConfig, remoteEnvServers };
80
110
  });
81
111
  }
112
+ static async getTestTargets(): Promise<TestTargetsConfig> {
113
+ return (await GlobalConfig.#getAkanGlobalConfig()).testTargets;
114
+ }
115
+ static async setTestTargets(testTargets: TestTargetsConfig) {
116
+ await GlobalConfig.#update((akanConfig) => ({
117
+ ...akanConfig,
118
+ testTargets: { ...akanConfig.testTargets, ...testTargets },
119
+ }));
120
+ }
82
121
  static needRefreshToken(accessToken: AccessToken): boolean {
83
122
  return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
84
123
  }
@@ -0,0 +1,97 @@
1
+ import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { type InlineExtension, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
5
+ import type { CodeAgentProfile } from "akanjs/common";
6
+ import { AkanEnvKeys } from "../tools/AkanEnvKeys";
7
+ import { type AkanContextFile, AkanContextFiles } from "./AkanContextFiles";
8
+ import { akanCodePaths } from "./akanCodePaths";
9
+ import { akanSystemPrompt } from "./akanSystemPrompt";
10
+ import type { CodeAgentProxy } from "./CodeAgentProxy";
11
+ import { CodeSessionIndex } from "./CodeSessionIndex";
12
+
13
+ export interface AkanCodeServicesOptions {
14
+ workspaceRoot: string;
15
+ cwd: string;
16
+ profile: CodeAgentProfile;
17
+ extensions: InlineExtension[];
18
+ sessionId?: string;
19
+ }
20
+
21
+ // All four engine services are injected explicitly: any one left out makes the engine fall back to `~/.pi/`.
22
+ export class AkanCodeServices {
23
+ static settings() {
24
+ // In memory on purpose: file settings would be a second configuration surface beside the profile.
25
+ // `enableInstallTelemetry` defaults to true and adds pi-branded headers to OpenRouter/NVIDIA/Cloudflare calls.
26
+ return SettingsManager.inMemory({
27
+ enableInstallTelemetry: false,
28
+ enableAnalytics: false,
29
+ quietStartup: true,
30
+ // Twice the engine's 16,384: at 16k a reasoning model stops mid-thought at the compaction threshold,
31
+ // and the recovery re-sends a near-full window to retry the turn.
32
+ compaction: { reserveTokens: 32_768 },
33
+ });
34
+ }
35
+
36
+ // The engine locks and rewrites `authPath` itself, so two `akan code` refreshing a token at once cannot clobber it.
37
+ static async runtime(workspaceRoot: string, proxy: CodeAgentProxy | null = null) {
38
+ mkdirSync(akanCodePaths.globalDir(), { recursive: true, mode: 0o700 });
39
+ const runtime = await ModelRuntime.create({
40
+ //* Behind a proxy no real key may reach a request, so credentials live in a throwaway file.
41
+ authPath: proxy
42
+ ? path.join(mkdtempSync(path.join(tmpdir(), "akan-code-proxy-")), "auth.json")
43
+ : akanCodePaths.authFile(),
44
+ modelsPath: akanCodePaths.modelsFile(),
45
+ });
46
+ if (proxy) await proxy.apply(runtime);
47
+ else await AkanEnvKeys.apply(runtime, workspaceRoot);
48
+ return runtime;
49
+ }
50
+
51
+ static sessions(workspaceRoot: string, cwd: string, profile: CodeAgentProfile, resume?: string) {
52
+ //* `remote` has no store of its own yet, so it keeps a file session: a pod worker's suspended asks need it.
53
+ if (profile.session.store === "memory") return SessionManager.inMemory(cwd);
54
+ const dir = akanCodePaths.sessionsDir(workspaceRoot);
55
+ mkdirSync(dir, { recursive: true });
56
+ const manager = SessionManager.create(cwd, dir);
57
+ if (!resume) return manager;
58
+ const file = CodeSessionIndex.fileOf(dir, resume);
59
+ // A mistyped id that quietly opened a new session would look like a session that lost its history.
60
+ if (!file) throw new Error(`No stored session ${resume} in ${dir}`);
61
+ manager.setSessionFile(file);
62
+ return manager;
63
+ }
64
+
65
+ // Skills are text, never code that runs, so unlike extensions they may come from the home directory too.
66
+ static #skillPaths(profile: CodeAgentProfile, workspaceRoot: string) {
67
+ if (!profile.context.skills) return [];
68
+ const dirs = [
69
+ akanCodePaths.builtinSkillsDir(),
70
+ akanCodePaths.globalSkillsDir(),
71
+ akanCodePaths.skillsDir(workspaceRoot),
72
+ ];
73
+ return dirs.filter((dir): dir is string => !!dir && existsSync(dir));
74
+ }
75
+
76
+ static resourceOptions(options: AkanCodeServicesOptions) {
77
+ // A path that does not exist is reported as a load error rather than skipped, and these two are optional.
78
+ const present = (dir: string) => (existsSync(dir) ? [dir] : []);
79
+ return {
80
+ noExtensions: true,
81
+ // Disables only the engine's default skill locations (`~/.pi/skills`, `.pi/`); `additionalSkillPaths` merge.
82
+ noSkills: true,
83
+ noPromptTemplates: true,
84
+ noThemes: true,
85
+ noContextFiles: !options.profile.context.projectFiles,
86
+ agentsFilesOverride: ({ agentsFiles }: { agentsFiles: AkanContextFile[] }) => ({
87
+ agentsFiles: options.profile.context.projectFiles
88
+ ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd })
89
+ : agentsFiles,
90
+ }),
91
+ appendSystemPrompt: [akanSystemPrompt(options.profile)],
92
+ additionalExtensionPaths: present(akanCodePaths.extensionsDir(options.workspaceRoot)),
93
+ additionalSkillPaths: AkanCodeServices.#skillPaths(options.profile, options.workspaceRoot),
94
+ extensionFactories: options.extensions,
95
+ };
96
+ }
97
+ }
@@ -0,0 +1,94 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export interface AkanContextFile {
5
+ path: string;
6
+ content: string;
7
+ }
8
+
9
+ export interface AkanContextFilesOptions {
10
+ workspaceRoot: string;
11
+ cwd: string;
12
+ }
13
+
14
+ // The engine takes only the first of `AGENTS.override.md · AGENTS.md · CLAUDE.md` per directory and never reads
15
+ // `.cursor/rules`; this appends what it drops, after the engine's own list.
16
+ export class AkanContextFiles {
17
+ /** Cursor's frontmatter block, which is metadata for its own rule picker and not instruction for a model. */
18
+ static readonly #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
19
+
20
+ static extend(base: AkanContextFile[], options: AkanContextFilesOptions): AkanContextFile[] {
21
+ const seen = new Set(base.map((file) => file.path));
22
+ // Keyed by content too: the usual way to carry both names is a symlink or a one-line pointer.
23
+ const contents = new Set(base.map((file) => file.content.trim()));
24
+ const added: AkanContextFile[] = [];
25
+ for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
26
+ if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim()) continue;
27
+ seen.add(file.path);
28
+ contents.add(file.content.trim());
29
+ added.push(file);
30
+ }
31
+ return [...base, ...added];
32
+ }
33
+
34
+ // Not beside an `AGENTS.override.md`: that file exists to displace the others in its directory.
35
+ static #claude(base: AkanContextFile[]) {
36
+ const files: AkanContextFile[] = [];
37
+ for (const file of base) {
38
+ if (!/^AGENTS\.(md|MD)$/.test(path.basename(file.path))) continue;
39
+ for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
40
+ const candidate = path.join(path.dirname(file.path), name);
41
+ const content = AkanContextFiles.#read(candidate);
42
+ if (content !== undefined) files.push({ path: candidate, content });
43
+ }
44
+ }
45
+ return files;
46
+ }
47
+
48
+ // Only `alwaysApply: true` or frontmatter-less rules: Cursor's `globs`/`description`/manual kinds are scoped.
49
+ static #cursor(options: AkanContextFilesOptions) {
50
+ const files: AkanContextFile[] = [];
51
+ const roots = [...new Set([options.workspaceRoot, options.cwd])];
52
+ for (const root of roots) {
53
+ const legacy = AkanContextFiles.#read(path.join(root, ".cursorrules"));
54
+ if (legacy !== undefined) files.push({ path: path.join(root, ".cursorrules"), content: legacy });
55
+ for (const file of AkanContextFiles.#mdcFiles(path.join(root, ".cursor", "rules"))) {
56
+ const raw = AkanContextFiles.#read(file);
57
+ if (raw === undefined) continue;
58
+ const body = AkanContextFiles.#applied(raw);
59
+ if (body !== undefined) files.push({ path: file, content: body });
60
+ }
61
+ }
62
+ return files;
63
+ }
64
+
65
+ static #applied(raw: string) {
66
+ const match = AkanContextFiles.#frontmatter.exec(raw);
67
+ if (!match) return raw;
68
+ if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? "")) return undefined;
69
+ return raw.slice(match[0].length);
70
+ }
71
+
72
+ /** Sorted, because the order rules reach the model in is otherwise the order a directory happens to list. */
73
+ static #mdcFiles(dir: string) {
74
+ if (!existsSync(dir)) return [];
75
+ try {
76
+ return readdirSync(dir, { recursive: true, withFileTypes: true })
77
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".mdc"))
78
+ .map((entry) => path.join(entry.parentPath ?? dir, entry.name))
79
+ .sort();
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
84
+
85
+ static #read(file: string) {
86
+ try {
87
+ if (!existsSync(file) || !statSync(file).isFile()) return undefined;
88
+ return readFileSync(file, "utf8").replace(/^/, "");
89
+ } catch {
90
+ // A rule file that cannot be read costs its own rules. It must not cost the session its start.
91
+ return undefined;
92
+ }
93
+ }
94
+ }