@neurocode-ai/core 1.18.8

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 (490) hide show
  1. package/bin/neurocode +199 -0
  2. package/bunfig.toml +2 -0
  3. package/drizzle.config.ts +10 -0
  4. package/package.json +131 -0
  5. package/schema.json +2071 -0
  6. package/script/fix-node-pty.ts +28 -0
  7. package/script/migration.ts +196 -0
  8. package/src/account/sql.ts +39 -0
  9. package/src/account.ts +101 -0
  10. package/src/agent.ts +111 -0
  11. package/src/aisdk.ts +235 -0
  12. package/src/background-job.ts +365 -0
  13. package/src/catalog.ts +301 -0
  14. package/src/command.ts +64 -0
  15. package/src/config/agent.ts +25 -0
  16. package/src/config/attachments.ts +15 -0
  17. package/src/config/command.ts +12 -0
  18. package/src/config/compaction.ts +15 -0
  19. package/src/config/experimental.ts +18 -0
  20. package/src/config/formatter.ts +12 -0
  21. package/src/config/lsp.ts +18 -0
  22. package/src/config/markdown.ts +36 -0
  23. package/src/config/mcp.ts +48 -0
  24. package/src/config/plugin/agent.ts +179 -0
  25. package/src/config/plugin/command.ts +83 -0
  26. package/src/config/plugin/external.ts +91 -0
  27. package/src/config/plugin/provider.ts +113 -0
  28. package/src/config/plugin/reference.ts +70 -0
  29. package/src/config/plugin/skill.ts +50 -0
  30. package/src/config/plugin.ts +13 -0
  31. package/src/config/provider.ts +71 -0
  32. package/src/config/reference.ts +22 -0
  33. package/src/config/tool-output.ts +9 -0
  34. package/src/config/watcher.ts +7 -0
  35. package/src/config.ts +227 -0
  36. package/src/control-plane/move-session.ts +148 -0
  37. package/src/control-plane/workspace.sql.ts +20 -0
  38. package/src/credential/sql.ts +14 -0
  39. package/src/credential.ts +138 -0
  40. package/src/cross-spawn-spawner.ts +507 -0
  41. package/src/data-migration.sql.ts +6 -0
  42. package/src/database/database.ts +57 -0
  43. package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
  44. package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
  45. package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
  46. package/src/database/migration/20260225215848_workspace.ts +19 -0
  47. package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
  48. package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
  49. package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
  50. package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
  51. package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
  52. package/src/database/migration/20260323234822_events.ts +26 -0
  53. package/src/database/migration/20260410174513_workspace-name.ts +29 -0
  54. package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
  55. package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
  56. package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
  57. package/src/database/migration/20260428004200_add_session_path.ts +11 -0
  58. package/src/database/migration/20260501142318_next_venus.ts +12 -0
  59. package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
  60. package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
  61. package/src/database/migration/20260510033149_session_usage.ts +56 -0
  62. package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
  63. package/src/database/migration/20260511173437_session-metadata.ts +16 -0
  64. package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
  65. package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
  66. package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
  67. package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
  68. package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
  69. package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
  70. package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
  71. package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
  72. package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
  73. package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
  74. package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
  75. package/src/database/migration/20260611035744_credential.ts +25 -0
  76. package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
  77. package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
  78. package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
  79. package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
  80. package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
  81. package/src/database/migration.gen.ts +44 -0
  82. package/src/database/migration.ts +81 -0
  83. package/src/database/path.ts +91 -0
  84. package/src/database/schema.gen.ts +274 -0
  85. package/src/database/schema.sql.ts +10 -0
  86. package/src/database/sqlite.bun.ts +183 -0
  87. package/src/database/sqlite.node.ts +178 -0
  88. package/src/database/sqlite.ts +8 -0
  89. package/src/effect/app-node-builder.ts +23 -0
  90. package/src/effect/app-node-platform.ts +18 -0
  91. package/src/effect/app-node.ts +14 -0
  92. package/src/effect/dfdf +1 -0
  93. package/src/effect/keyed-mutex.ts +45 -0
  94. package/src/effect/layer-node.ts +333 -0
  95. package/src/effect/memo-map.ts +3 -0
  96. package/src/effect/runtime.ts +21 -0
  97. package/src/effect/service-use.ts +43 -0
  98. package/src/event/sql.ts +25 -0
  99. package/src/event.ts +638 -0
  100. package/src/file-mutation.ts +207 -0
  101. package/src/file.ts +6 -0
  102. package/src/filesystem/fff.bun.ts +140 -0
  103. package/src/filesystem/fff.node.ts +138 -0
  104. package/src/filesystem/ignore.ts +67 -0
  105. package/src/filesystem/protected.ts +53 -0
  106. package/src/filesystem/search.ts +239 -0
  107. package/src/filesystem/watcher.ts +140 -0
  108. package/src/filesystem.ts +118 -0
  109. package/src/flag/flag.ts +78 -0
  110. package/src/fs-util.ts +274 -0
  111. package/src/git.ts +987 -0
  112. package/src/github-copilot/README.md +5 -0
  113. package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  114. package/src/github-copilot/chat/get-response-metadata.ts +15 -0
  115. package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  116. package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
  117. package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  118. package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
  119. package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  120. package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  121. package/src/github-copilot/copilot-provider.ts +100 -0
  122. package/src/github-copilot/openai-compatible-error.ts +27 -0
  123. package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
  124. package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  125. package/src/github-copilot/responses/openai-config.ts +18 -0
  126. package/src/github-copilot/responses/openai-error.ts +22 -0
  127. package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
  128. package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
  129. package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
  130. package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
  131. package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
  132. package/src/github-copilot/responses/tool/file-search.ts +127 -0
  133. package/src/github-copilot/responses/tool/image-generation.ts +114 -0
  134. package/src/github-copilot/responses/tool/local-shell.ts +64 -0
  135. package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
  136. package/src/github-copilot/responses/tool/web-search.ts +102 -0
  137. package/src/global.ts +87 -0
  138. package/src/id/id.ts +47 -0
  139. package/src/image/photon.ts +94 -0
  140. package/src/image.ts +81 -0
  141. package/src/installation/version.ts +8 -0
  142. package/src/instruction-context.ts +101 -0
  143. package/src/integration/connection.ts +12 -0
  144. package/src/integration.ts +520 -0
  145. package/src/location-mutation.ts +162 -0
  146. package/src/location-service-map.ts +18 -0
  147. package/src/location-services.ts +115 -0
  148. package/src/location.ts +39 -0
  149. package/src/markdown.d.ts +4 -0
  150. package/src/model.ts +41 -0
  151. package/src/models-dev.ts +260 -0
  152. package/src/npm-config.ts +40 -0
  153. package/src/npm.ts +269 -0
  154. package/src/oauth/page.ts +276 -0
  155. package/src/observability/logging.ts +71 -0
  156. package/src/observability/otlp.ts +79 -0
  157. package/src/observability/shared.ts +1 -0
  158. package/src/observability.ts +24 -0
  159. package/src/patch.ts +197 -0
  160. package/src/permission/saved.ts +79 -0
  161. package/src/permission/sql.ts +20 -0
  162. package/src/permission.ts +310 -0
  163. package/src/plugin/agent.ts +206 -0
  164. package/src/plugin/command/initialize.txt +65 -0
  165. package/src/plugin/command/review.txt +100 -0
  166. package/src/plugin/command.ts +25 -0
  167. package/src/plugin/host.ts +219 -0
  168. package/src/plugin/internal.ts +153 -0
  169. package/src/plugin/layer-map.example.ts +94 -0
  170. package/src/plugin/models-dev.ts +183 -0
  171. package/src/plugin/promise.ts +93 -0
  172. package/src/plugin/provider/alibaba.ts +15 -0
  173. package/src/plugin/provider/amazon-bedrock.ts +126 -0
  174. package/src/plugin/provider/anthropic.ts +27 -0
  175. package/src/plugin/provider/azure.ts +82 -0
  176. package/src/plugin/provider/cerebras.ts +26 -0
  177. package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
  178. package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
  179. package/src/plugin/provider/cohere.ts +15 -0
  180. package/src/plugin/provider/deepinfra.ts +15 -0
  181. package/src/plugin/provider/dynamic.ts +31 -0
  182. package/src/plugin/provider/gateway.ts +15 -0
  183. package/src/plugin/provider/github-copilot.ts +52 -0
  184. package/src/plugin/provider/gitlab.ts +65 -0
  185. package/src/plugin/provider/google-vertex.ts +173 -0
  186. package/src/plugin/provider/google.ts +15 -0
  187. package/src/plugin/provider/groq.ts +15 -0
  188. package/src/plugin/provider/kilo.ts +21 -0
  189. package/src/plugin/provider/llmgateway.ts +26 -0
  190. package/src/plugin/provider/mistral.ts +15 -0
  191. package/src/plugin/provider/nvidia.ts +22 -0
  192. package/src/plugin/provider/openai-compatible.ts +17 -0
  193. package/src/plugin/provider/openai.ts +292 -0
  194. package/src/plugin/provider/opencode.ts +311 -0
  195. package/src/plugin/provider/openrouter.ts +36 -0
  196. package/src/plugin/provider/perplexity.ts +15 -0
  197. package/src/plugin/provider/sap-ai-core.ts +46 -0
  198. package/src/plugin/provider/snowflake-cortex.ts +89 -0
  199. package/src/plugin/provider/togetherai.ts +15 -0
  200. package/src/plugin/provider/venice.ts +15 -0
  201. package/src/plugin/provider/vercel.ts +27 -0
  202. package/src/plugin/provider/xai.ts +22 -0
  203. package/src/plugin/provider/zenmux.ts +21 -0
  204. package/src/plugin/provider.ts +71 -0
  205. package/src/plugin/skill/customize-neurocode.md +452 -0
  206. package/src/plugin/skill.ts +31 -0
  207. package/src/plugin/variant.ts +39 -0
  208. package/src/plugin.ts +167 -0
  209. package/src/policy.ts +49 -0
  210. package/src/process.ts +261 -0
  211. package/src/project/copy-strategies.ts +35 -0
  212. package/src/project/copy.ts +292 -0
  213. package/src/project/directories.ts +158 -0
  214. package/src/project/schema.ts +16 -0
  215. package/src/project/sql.ts +35 -0
  216. package/src/project.ts +136 -0
  217. package/src/provider.ts +25 -0
  218. package/src/pty/protocol.ts +37 -0
  219. package/src/pty/pty.bun.ts +26 -0
  220. package/src/pty/pty.node.ts +29 -0
  221. package/src/pty/pty.ts +25 -0
  222. package/src/pty/schema.ts +1 -0
  223. package/src/pty/ticket.ts +56 -0
  224. package/src/pty.ts +318 -0
  225. package/src/public-event-manifest.ts +7 -0
  226. package/src/question.ts +153 -0
  227. package/src/reference/guidance.ts +69 -0
  228. package/src/reference.ts +125 -0
  229. package/src/repository-cache.ts +259 -0
  230. package/src/repository.ts +214 -0
  231. package/src/ripgrep/binary.ts +132 -0
  232. package/src/ripgrep.ts +281 -0
  233. package/src/schema.ts +79 -0
  234. package/src/session/compaction.ts +241 -0
  235. package/src/session/context-epoch.ts +174 -0
  236. package/src/session/error.ts +24 -0
  237. package/src/session/event.ts +2 -0
  238. package/src/session/execution/local.ts +46 -0
  239. package/src/session/execution.ts +34 -0
  240. package/src/session/history.ts +101 -0
  241. package/src/session/info.ts +50 -0
  242. package/src/session/input.ts +288 -0
  243. package/src/session/message-updater.ts +397 -0
  244. package/src/session/message.ts +2 -0
  245. package/src/session/projector.ts +458 -0
  246. package/src/session/prompt.ts +1 -0
  247. package/src/session/revert.ts +121 -0
  248. package/src/session/run-coordinator.ts +104 -0
  249. package/src/session/runner/index.ts +28 -0
  250. package/src/session/runner/llm.ts +432 -0
  251. package/src/session/runner/max-steps.ts +16 -0
  252. package/src/session/runner/model.ts +218 -0
  253. package/src/session/runner/publish-llm-event.ts +423 -0
  254. package/src/session/runner/to-llm-message.ts +171 -0
  255. package/src/session/schema.ts +9 -0
  256. package/src/session/sql.ts +176 -0
  257. package/src/session/store.ts +63 -0
  258. package/src/session/todo.ts +78 -0
  259. package/src/session.ts +486 -0
  260. package/src/share/sql.ts +13 -0
  261. package/src/shell.ts +226 -0
  262. package/src/skill/discovery.ts +213 -0
  263. package/src/skill/guidance.ts +76 -0
  264. package/src/skill.ts +132 -0
  265. package/src/snapshot.ts +266 -0
  266. package/src/state.ts +128 -0
  267. package/src/system-context/builtins.ts +50 -0
  268. package/src/system-context/index.ts +320 -0
  269. package/src/system-context/registry.ts +49 -0
  270. package/src/tool/AGENTS.md +59 -0
  271. package/src/tool/application-tools.ts +57 -0
  272. package/src/tool/apply-patch.ts +219 -0
  273. package/src/tool/bash.ts +207 -0
  274. package/src/tool/builtins.ts +48 -0
  275. package/src/tool/edit.ts +223 -0
  276. package/src/tool/glob.ts +105 -0
  277. package/src/tool/grep.ts +137 -0
  278. package/src/tool/http-body.ts +30 -0
  279. package/src/tool/question.ts +94 -0
  280. package/src/tool/read-filesystem.ts +366 -0
  281. package/src/tool/read.ts +117 -0
  282. package/src/tool/registry.ts +147 -0
  283. package/src/tool/skill.ts +109 -0
  284. package/src/tool/todowrite.ts +62 -0
  285. package/src/tool/tool.ts +162 -0
  286. package/src/tool/tools.ts +13 -0
  287. package/src/tool/webfetch.ts +218 -0
  288. package/src/tool/websearch.ts +260 -0
  289. package/src/tool/write.ts +101 -0
  290. package/src/tool-output-store.ts +211 -0
  291. package/src/util/array.ts +10 -0
  292. package/src/util/binary.ts +41 -0
  293. package/src/util/effect-flock.ts +284 -0
  294. package/src/util/encode.ts +51 -0
  295. package/src/util/error.ts +70 -0
  296. package/src/util/flock.ts +358 -0
  297. package/src/util/glob.ts +34 -0
  298. package/src/util/hash.ts +11 -0
  299. package/src/util/identifier.ts +1 -0
  300. package/src/util/iife.ts +3 -0
  301. package/src/util/lazy.ts +11 -0
  302. package/src/util/module.ts +10 -0
  303. package/src/util/path.ts +37 -0
  304. package/src/util/retry.ts +42 -0
  305. package/src/util/slug.ts +74 -0
  306. package/src/util/token.ts +5 -0
  307. package/src/util/which.ts +14 -0
  308. package/src/util/wildcard.ts +14 -0
  309. package/src/v1/config/agent.ts +89 -0
  310. package/src/v1/config/attachment.ts +25 -0
  311. package/src/v1/config/command.ts +13 -0
  312. package/src/v1/config/config.ts +192 -0
  313. package/src/v1/config/console-state.ts +16 -0
  314. package/src/v1/config/error.ts +39 -0
  315. package/src/v1/config/formatter.ts +13 -0
  316. package/src/v1/config/layout.ts +6 -0
  317. package/src/v1/config/lsp.ts +80 -0
  318. package/src/v1/config/mcp.ts +63 -0
  319. package/src/v1/config/migrate.ts +254 -0
  320. package/src/v1/config/permission.ts +50 -0
  321. package/src/v1/config/plugin.ts +9 -0
  322. package/src/v1/config/provider-options.ts +227 -0
  323. package/src/v1/config/provider.ts +121 -0
  324. package/src/v1/config/server.ts +19 -0
  325. package/src/v1/config/skills.ts +13 -0
  326. package/src/v1/permission.ts +33 -0
  327. package/src/v1/session.ts +68 -0
  328. package/src/v2-schema.ts +3 -0
  329. package/src/workspace.ts +6 -0
  330. package/sst-env.d.ts +10 -0
  331. package/test/agent.test.ts +131 -0
  332. package/test/application-tools.test.ts +287 -0
  333. package/test/background-job.test.ts +106 -0
  334. package/test/catalog.test.ts +353 -0
  335. package/test/command.test.ts +56 -0
  336. package/test/config/agent.test.ts +349 -0
  337. package/test/config/command.test.ts +83 -0
  338. package/test/config/config.test.ts +798 -0
  339. package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
  340. package/test/config/plugin.test.ts +248 -0
  341. package/test/config/provider-options.test.ts +224 -0
  342. package/test/config/provider.test.ts +269 -0
  343. package/test/config/skill.test.ts +80 -0
  344. package/test/credential.test.ts +36 -0
  345. package/test/database-migration.test.ts +644 -0
  346. package/test/effect/cross-spawn-spawner.test.ts +426 -0
  347. package/test/effect/keyed-mutex.test.ts +73 -0
  348. package/test/effect/layer-node/layer-node-types.test.ts +143 -0
  349. package/test/effect/layer-node/layer-node.test.ts +261 -0
  350. package/test/effect/layer-node/node-build.test.ts +125 -0
  351. package/test/effect/observability.test.ts +109 -0
  352. package/test/event.test.ts +1124 -0
  353. package/test/file-mutation.test.ts +368 -0
  354. package/test/filesystem/filesystem.test.ts +387 -0
  355. package/test/filesystem/ignore.test.ts +10 -0
  356. package/test/filesystem/search.test.ts +44 -0
  357. package/test/filesystem/watcher.test.ts +266 -0
  358. package/test/fixture/effect-flock-worker.ts +60 -0
  359. package/test/fixture/flock-worker.ts +72 -0
  360. package/test/fixture/git.ts +49 -0
  361. package/test/fixture/location.ts +26 -0
  362. package/test/fixture/tmpdir.ts +25 -0
  363. package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
  364. package/test/git.test.ts +164 -0
  365. package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
  366. package/test/github-copilot/copilot-chat-model.test.ts +592 -0
  367. package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
  368. package/test/global.test.ts +16 -0
  369. package/test/instruction-context.test.ts +323 -0
  370. package/test/integration.test.ts +349 -0
  371. package/test/legacy-event-schema.test.ts +16 -0
  372. package/test/lib/effect.ts +53 -0
  373. package/test/lib/tool.ts +20 -0
  374. package/test/location-filesystem.test.ts +69 -0
  375. package/test/location-layer.test.ts +226 -0
  376. package/test/location-mutation.test.ts +178 -0
  377. package/test/location.test.ts +42 -0
  378. package/test/model.test.ts +23 -0
  379. package/test/models.test.ts +290 -0
  380. package/test/move-session.test.ts +235 -0
  381. package/test/npm-config.test.ts +51 -0
  382. package/test/npm.test.ts +84 -0
  383. package/test/oauth-page.test.ts +15 -0
  384. package/test/patch.test.ts +68 -0
  385. package/test/permission.test.ts +315 -0
  386. package/test/plugin/command.test.ts +47 -0
  387. package/test/plugin/fixture.ts +52 -0
  388. package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
  389. package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
  390. package/test/plugin/fixtures/invalid-plugin.ts +1 -0
  391. package/test/plugin/fixtures/models-dev.json +14 -0
  392. package/test/plugin/fixtures/provider-factory.ts +9 -0
  393. package/test/plugin/host.ts +303 -0
  394. package/test/plugin/models-dev.test.ts +170 -0
  395. package/test/plugin/promise.test.ts +67 -0
  396. package/test/plugin/provider-alibaba.test.ts +92 -0
  397. package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
  398. package/test/plugin/provider-anthropic.test.ts +94 -0
  399. package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
  400. package/test/plugin/provider-azure.test.ts +293 -0
  401. package/test/plugin/provider-cerebras.test.ts +136 -0
  402. package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
  403. package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
  404. package/test/plugin/provider-cohere.test.ts +123 -0
  405. package/test/plugin/provider-deepinfra.test.ts +159 -0
  406. package/test/plugin/provider-dynamic.test.ts +184 -0
  407. package/test/plugin/provider-gateway.test.ts +115 -0
  408. package/test/plugin/provider-github-copilot.test.ts +278 -0
  409. package/test/plugin/provider-gitlab.test.ts +308 -0
  410. package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
  411. package/test/plugin/provider-google-vertex.test.ts +387 -0
  412. package/test/plugin/provider-google.test.ts +80 -0
  413. package/test/plugin/provider-groq.test.ts +121 -0
  414. package/test/plugin/provider-kilo.test.ts +99 -0
  415. package/test/plugin/provider-llmgateway.test.ts +80 -0
  416. package/test/plugin/provider-mistral.test.ts +129 -0
  417. package/test/plugin/provider-nvidia.test.ts +97 -0
  418. package/test/plugin/provider-openai-compatible.test.ts +107 -0
  419. package/test/plugin/provider-openai.test.ts +176 -0
  420. package/test/plugin/provider-opencode.test.ts +423 -0
  421. package/test/plugin/provider-openrouter.test.ts +113 -0
  422. package/test/plugin/provider-perplexity.test.ts +122 -0
  423. package/test/plugin/provider-sap-ai-core.test.ts +159 -0
  424. package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
  425. package/test/plugin/provider-togetherai.test.ts +129 -0
  426. package/test/plugin/provider-venice.test.ts +115 -0
  427. package/test/plugin/provider-vercel.test.ts +83 -0
  428. package/test/plugin/provider-xai.test.ts +125 -0
  429. package/test/plugin/provider-zenmux.test.ts +110 -0
  430. package/test/plugin/skill.test.ts +25 -0
  431. package/test/plugin/variant.test.ts +67 -0
  432. package/test/plugin.test.ts +71 -0
  433. package/test/policy.test.ts +85 -0
  434. package/test/preload.ts +5 -0
  435. package/test/process/process.test.ts +368 -0
  436. package/test/project-copy.test.ts +400 -0
  437. package/test/project-directories.test.ts +61 -0
  438. package/test/project.test.ts +221 -0
  439. package/test/provider-mistral.test.ts +282 -0
  440. package/test/provider-xai-responses.test.ts +32 -0
  441. package/test/pty/info-schema.test.ts +32 -0
  442. package/test/pty/protocol.test.ts +27 -0
  443. package/test/pty/pty-session.test.ts +240 -0
  444. package/test/pty/ticket.test.ts +62 -0
  445. package/test/question.test.ts +114 -0
  446. package/test/reference-guidance.test.ts +76 -0
  447. package/test/reference.test.ts +78 -0
  448. package/test/repository-cache.test.ts +153 -0
  449. package/test/repository.test.ts +71 -0
  450. package/test/ripgrep.test.ts +65 -0
  451. package/test/session-compaction.test.ts +27 -0
  452. package/test/session-create.test.ts +425 -0
  453. package/test/session-history.test.ts +165 -0
  454. package/test/session-projector.test.ts +533 -0
  455. package/test/session-prompt.test.ts +584 -0
  456. package/test/session-run-coordinator.test.ts +418 -0
  457. package/test/session-runner-message.test.ts +501 -0
  458. package/test/session-runner-model.test.ts +347 -0
  459. package/test/session-runner-recorded.test.ts +193 -0
  460. package/test/session-runner-tool-events.test.ts +136 -0
  461. package/test/session-runner-tool-registry.test.ts +452 -0
  462. package/test/session-runner.test.ts +3365 -0
  463. package/test/session-todo.test.ts +94 -0
  464. package/test/session-tool-progress.test.ts +158 -0
  465. package/test/shared-schema.test.ts +206 -0
  466. package/test/shell.test.ts +108 -0
  467. package/test/skill/guidance.test.ts +144 -0
  468. package/test/skill-discovery.test.ts +165 -0
  469. package/test/skill.test.ts +125 -0
  470. package/test/snapshot.test.ts +178 -0
  471. package/test/state.test.ts +115 -0
  472. package/test/system-context/builtins.test.ts +129 -0
  473. package/test/system-context/index.test.ts +307 -0
  474. package/test/system-context/registry.test.ts +114 -0
  475. package/test/tool-apply-patch.test.ts +436 -0
  476. package/test/tool-bash.test.ts +437 -0
  477. package/test/tool-edit.test.ts +434 -0
  478. package/test/tool-output-store.test.ts +247 -0
  479. package/test/tool-question.test.ts +162 -0
  480. package/test/tool-read-filesystem.test.ts +118 -0
  481. package/test/tool-read.test.ts +674 -0
  482. package/test/tool-skill.test.ts +149 -0
  483. package/test/tool-todowrite.test.ts +125 -0
  484. package/test/tool-webfetch.test.ts +281 -0
  485. package/test/tool-websearch.test.ts +316 -0
  486. package/test/tool-write.test.ts +305 -0
  487. package/test/util/effect-flock.test.ts +392 -0
  488. package/test/util/flock.test.ts +428 -0
  489. package/test/util/which.test.ts +100 -0
  490. package/tsconfig.json +7 -0
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Local tracking checkouts for remote Git references, one per remote and
3
+ * branch. Each checkout permanently tracks a single ref: the requested branch
4
+ * when the cache key has one, otherwise the remote default branch. Content
5
+ * follows "newest wins": refresh fetches and hard-resets, so readers may
6
+ * observe the checkout move underneath them.
7
+ */
8
+ import path from "path"
9
+ import { Context, Effect, Layer, Schema } from "effect"
10
+ import { FSUtil } from "./fs-util"
11
+ import { Git } from "./git"
12
+ import { Global } from "./global"
13
+ import { Repository } from "./repository"
14
+ import { AbsolutePath } from "./schema"
15
+ import { makeGlobalNode } from "./effect/app-node"
16
+ import { EffectFlock } from "./util/effect-flock"
17
+
18
+ export type Result = {
19
+ readonly repository: string
20
+ readonly host: string
21
+ readonly remote: string
22
+ readonly localPath: string
23
+ readonly status: "cached" | "cloned" | "refreshed"
24
+ readonly head?: string
25
+ readonly branch?: string
26
+ }
27
+
28
+ export type EnsureInput = {
29
+ readonly reference: Repository.RemoteReference
30
+ readonly refresh?: boolean
31
+ readonly branch?: string
32
+ }
33
+
34
+ export class InvalidRepositoryError extends Schema.TaggedErrorClass<InvalidRepositoryError>()(
35
+ "RepositoryCacheInvalidRepositoryError",
36
+ {
37
+ repository: Schema.String,
38
+ message: Schema.String,
39
+ },
40
+ ) {}
41
+
42
+ export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
43
+ "RepositoryCacheInvalidBranchError",
44
+ {
45
+ branch: Schema.String,
46
+ message: Schema.String,
47
+ },
48
+ ) {}
49
+
50
+ export class CloneFailedError extends Schema.TaggedErrorClass<CloneFailedError>()("RepositoryCacheCloneFailedError", {
51
+ repository: Schema.String,
52
+ message: Schema.String,
53
+ }) {}
54
+
55
+ export class FetchFailedError extends Schema.TaggedErrorClass<FetchFailedError>()("RepositoryCacheFetchFailedError", {
56
+ repository: Schema.String,
57
+ message: Schema.String,
58
+ }) {}
59
+
60
+ export class CheckoutFailedError extends Schema.TaggedErrorClass<CheckoutFailedError>()(
61
+ "RepositoryCacheCheckoutFailedError",
62
+ {
63
+ repository: Schema.String,
64
+ branch: Schema.String,
65
+ message: Schema.String,
66
+ },
67
+ ) {}
68
+
69
+ export class ResetFailedError extends Schema.TaggedErrorClass<ResetFailedError>()("RepositoryCacheResetFailedError", {
70
+ repository: Schema.String,
71
+ message: Schema.String,
72
+ }) {}
73
+
74
+ export class LockFailedError extends Schema.TaggedErrorClass<LockFailedError>()("RepositoryCacheLockFailedError", {
75
+ localPath: Schema.String,
76
+ message: Schema.String,
77
+ }) {}
78
+
79
+ export class CacheOperationError extends Schema.TaggedErrorClass<CacheOperationError>()(
80
+ "RepositoryCacheOperationError",
81
+ {
82
+ operation: Schema.String,
83
+ path: Schema.String,
84
+ message: Schema.String,
85
+ },
86
+ ) {}
87
+
88
+ export type Error =
89
+ | InvalidRepositoryError
90
+ | InvalidBranchError
91
+ | CloneFailedError
92
+ | FetchFailedError
93
+ | CheckoutFailedError
94
+ | ResetFailedError
95
+ | LockFailedError
96
+ | CacheOperationError
97
+
98
+ export interface Interface {
99
+ readonly ensure: (input: EnsureInput) => Effect.Effect<Result, Error>
100
+ }
101
+
102
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/RepositoryCache") {}
103
+
104
+ export function isError(error: unknown): error is Error {
105
+ return (
106
+ error instanceof InvalidRepositoryError ||
107
+ error instanceof InvalidBranchError ||
108
+ error instanceof CloneFailedError ||
109
+ error instanceof FetchFailedError ||
110
+ error instanceof CheckoutFailedError ||
111
+ error instanceof ResetFailedError ||
112
+ error instanceof LockFailedError ||
113
+ error instanceof CacheOperationError
114
+ )
115
+ }
116
+
117
+ export const parseRemote = Effect.fn("RepositoryCache.parseRemote")(function* (repository: string) {
118
+ return yield* Effect.try({
119
+ try: () => Repository.parseRemote(repository),
120
+ catch: (error) => new InvalidRepositoryError({ repository, message: errorMessage(error) }),
121
+ })
122
+ })
123
+
124
+ export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
125
+ return yield* Effect.try({
126
+ try: () => Repository.validateBranch(branch),
127
+ catch: (error) => new InvalidBranchError({ branch, message: errorMessage(error) }),
128
+ })
129
+ })
130
+
131
+ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFlock.Service | Global.Service> =
132
+ Layer.effect(
133
+ Service,
134
+ Effect.gen(function* () {
135
+ const fs = yield* FSUtil.Service
136
+ const git = yield* Git.Service
137
+ const flock = yield* EffectFlock.Service
138
+ const global = yield* Global.Service
139
+
140
+ return Service.of({
141
+ ensure: Effect.fn("RepositoryCache.ensure")(function* (input) {
142
+ if (input.branch) yield* validateBranch(input.branch)
143
+
144
+ const repository = input.reference.label
145
+ const localPath = Repository.cachePath(global.repos, input.reference, input.branch)
146
+ const cloneTarget = Repository.parse(input.reference.remote) ?? input.reference
147
+
148
+ return yield* flock
149
+ .withLock(
150
+ Effect.gen(function* () {
151
+ yield* cacheOperation(fs.ensureDir(path.dirname(localPath)), "ensure cache directory", localPath)
152
+
153
+ const existing = yield* git.repo.discover(AbsolutePath.make(localPath))
154
+ const origin = existing ? yield* git.remote.get(existing) : undefined
155
+ const originReference = origin ? Repository.parse(origin) : undefined
156
+ // Discovery walks upward, so an enclosing repository with a
157
+ // matching origin could masquerade as the cache entry; reuse
158
+ // requires the checkout to live exactly at the cache path.
159
+ const worktree = existing ? yield* fs.resolve(localPath) : undefined
160
+ const reuse = Boolean(
161
+ existing &&
162
+ existing.worktree === worktree &&
163
+ originReference &&
164
+ Repository.same(originReference, cloneTarget),
165
+ )
166
+ if (!reuse && (yield* fs.existsSafe(localPath))) {
167
+ yield* cacheOperation(fs.remove(localPath, { recursive: true }), "remove stale cache", localPath)
168
+ }
169
+
170
+ const status = !reuse
171
+ ? ("cloned" as const)
172
+ : input.refresh
173
+ ? ("refreshed" as const)
174
+ : ("cached" as const)
175
+
176
+ if (status === "cloned") {
177
+ yield* git.repo
178
+ .clone({
179
+ remote: input.reference.remote,
180
+ directory: AbsolutePath.make(localPath),
181
+ branch: input.branch,
182
+ })
183
+ .pipe(Effect.mapError((error) => new CloneFailedError({ repository, message: error.message })))
184
+ }
185
+
186
+ if (status === "refreshed") {
187
+ if (!existing)
188
+ return yield* new FetchFailedError({ repository, message: "Repository is unavailable" })
189
+ yield* git.sync
190
+ .fetchRemotes(existing)
191
+ .pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
192
+
193
+ if (input.branch) {
194
+ yield* git.sync
195
+ .fetchBranch(existing, { branch: input.branch })
196
+ .pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
197
+ }
198
+
199
+ // Checking out the tracked ref before resetting keeps the
200
+ // checkout self-healing even if it was left on another
201
+ // branch.
202
+ const branch = input.branch ?? (yield* git.history.defaultRemoteBranch(existing))
203
+ if (branch) {
204
+ yield* git.sync
205
+ .checkoutRemoteBranch(existing, { branch })
206
+ .pipe(
207
+ Effect.mapError(
208
+ (error) => new CheckoutFailedError({ repository, branch, message: error.message }),
209
+ ),
210
+ )
211
+ }
212
+
213
+ const target = branch ?? (yield* git.history.branch(existing))
214
+ yield* git.sync
215
+ .resetHard(existing, target ? `origin/${target}` : "HEAD")
216
+ .pipe(Effect.mapError((error) => new ResetFailedError({ repository, message: error.message })))
217
+ }
218
+
219
+ const checkout = yield* git.repo.discover(AbsolutePath.make(localPath))
220
+
221
+ return {
222
+ repository,
223
+ host: input.reference.host,
224
+ remote: input.reference.remote,
225
+ localPath,
226
+ status,
227
+ head: checkout ? yield* git.history.head(checkout) : undefined,
228
+ branch: checkout ? yield* git.history.branch(checkout) : undefined,
229
+ } satisfies Result
230
+ }),
231
+ `repository-cache:${localPath}`,
232
+ )
233
+ .pipe(
234
+ Effect.mapError((error) =>
235
+ isError(error) ? error : new LockFailedError({ localPath, message: errorMessage(error) }),
236
+ ),
237
+ )
238
+ }),
239
+ })
240
+ }),
241
+ )
242
+
243
+ export const node = makeGlobalNode({
244
+ service: Service,
245
+ layer,
246
+ deps: [EffectFlock.node, FSUtil.node, Git.node, Global.node],
247
+ })
248
+
249
+ function errorMessage(error: unknown) {
250
+ return error instanceof globalThis.Error ? error.message : String(error)
251
+ }
252
+
253
+ function cacheOperation<A, E, R>(effect: Effect.Effect<A, E, R>, operation: string, target: string) {
254
+ return effect.pipe(
255
+ Effect.mapError((error) => new CacheOperationError({ operation, path: target, message: errorMessage(error) })),
256
+ )
257
+ }
258
+
259
+ export * as RepositoryCache from "./repository-cache"
@@ -0,0 +1,214 @@
1
+ import path from "path"
2
+ import { fileURLToPath } from "url"
3
+ import { Schema } from "effect"
4
+
5
+ type BaseReference = {
6
+ readonly host: string
7
+ readonly path: string
8
+ readonly segments: string[]
9
+ readonly owner?: string
10
+ readonly repo: string
11
+ readonly remote: string
12
+ readonly label: string
13
+ }
14
+
15
+ export type RemoteReference = BaseReference & {
16
+ readonly protocol?: string
17
+ }
18
+
19
+ export type FileReference = BaseReference & {
20
+ readonly host: "file"
21
+ readonly protocol: "file:"
22
+ }
23
+
24
+ export type Reference = RemoteReference | FileReference
25
+
26
+ export class InvalidReferenceError extends Schema.TaggedErrorClass<InvalidReferenceError>()(
27
+ "RepositoryInvalidReferenceError",
28
+ {
29
+ repository: Schema.String,
30
+ message: Schema.String,
31
+ },
32
+ ) {}
33
+
34
+ export class UnsupportedLocalRepositoryError extends Schema.TaggedErrorClass<UnsupportedLocalRepositoryError>()(
35
+ "RepositoryUnsupportedLocalRepositoryError",
36
+ {
37
+ repository: Schema.String,
38
+ message: Schema.String,
39
+ },
40
+ ) {}
41
+
42
+ export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()("RepositoryInvalidBranchError", {
43
+ branch: Schema.String,
44
+ message: Schema.String,
45
+ }) {}
46
+
47
+ export type Error = InvalidReferenceError | UnsupportedLocalRepositoryError | InvalidBranchError
48
+
49
+ export function isError(error: unknown): error is Error {
50
+ return (
51
+ error instanceof InvalidReferenceError ||
52
+ error instanceof UnsupportedLocalRepositoryError ||
53
+ error instanceof InvalidBranchError
54
+ )
55
+ }
56
+
57
+ export function parse(input: string): Reference | undefined {
58
+ const cleaned = normalizeInput(input)
59
+ if (!cleaned) return
60
+
61
+ const githubPrefixed = cleaned.match(/^github:([^/\s]+)\/([^/\s]+)$/)
62
+ if (githubPrefixed) return buildRemote({ host: "github.com", segments: [githubPrefixed[1], githubPrefixed[2]] })
63
+
64
+ if (!cleaned.includes("://")) {
65
+ const scp = cleaned.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/)
66
+ if (scp) return buildRemote({ host: scp[1], segments: parts(scp[2]), remote: cleaned })
67
+
68
+ const direct = parts(cleaned)
69
+ if (direct.length >= 2 && hostLike(direct[0])) return buildRemote({ host: direct[0], segments: direct.slice(1) })
70
+ if (direct.length === 2) return buildRemote({ host: "github.com", segments: direct })
71
+ }
72
+
73
+ try {
74
+ const url = new URL(cleaned)
75
+ if (url.protocol === "file:") return buildFile({ url, remote: cleaned })
76
+ const segments = parts(url.pathname)
77
+ return buildRemote({
78
+ host: url.host,
79
+ segments,
80
+ remote: url.host === "github.com" ? githubRemote(segments.join("/")) : cleaned,
81
+ protocol: url.protocol,
82
+ })
83
+ } catch {
84
+ return
85
+ }
86
+ }
87
+
88
+ export function parseRemote(input: string): RemoteReference {
89
+ const reference = parse(input)
90
+ if (!reference) {
91
+ throw new InvalidReferenceError({
92
+ repository: input,
93
+ message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
94
+ })
95
+ }
96
+ if (!isRemote(reference)) {
97
+ throw new UnsupportedLocalRepositoryError({
98
+ repository: input,
99
+ message: "Local file repositories are not supported",
100
+ })
101
+ }
102
+ return reference
103
+ }
104
+
105
+ export function validateBranch(branch: string): void {
106
+ if (/^[A-Za-z0-9/_.-]+$/.test(branch) && !branch.startsWith("-") && !branch.includes("..")) return
107
+ throw new InvalidBranchError({
108
+ branch,
109
+ message: "Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
110
+ })
111
+ }
112
+
113
+ export function isFile(reference: Reference): reference is FileReference {
114
+ return reference.protocol === "file:"
115
+ }
116
+
117
+ export function isRemote(reference: Reference): reference is RemoteReference {
118
+ return !isFile(reference)
119
+ }
120
+
121
+ /**
122
+ * Checkouts are keyed by remote and branch: a branch-specific reference gets
123
+ * its own directory so branchless refreshes can never move it. The branch is
124
+ * percent-encoded because valid branch names may contain `/`.
125
+ */
126
+ export function cachePath(root: string, reference: Reference, branch?: string): string {
127
+ const base = path.join(root, ...reference.host.split(":"), ...reference.segments)
128
+ return branch ? `${base}@${encodeURIComponent(branch)}` : base
129
+ }
130
+
131
+ export function cacheIdentity(reference: Reference): string {
132
+ return `${reference.host}/${reference.path}`
133
+ }
134
+
135
+ export function same(left: Reference, right: Reference): boolean {
136
+ return cacheIdentity(left) === cacheIdentity(right)
137
+ }
138
+
139
+ function normalizeInput(input: string) {
140
+ return input
141
+ .trim()
142
+ .replace(/^git\+/, "")
143
+ .replace(/#.*$/, "")
144
+ .replace(/\/+$/, "")
145
+ }
146
+
147
+ function trimGitSuffix(input: string) {
148
+ return input.replace(/\.git$/, "")
149
+ }
150
+
151
+ function parts(input: string) {
152
+ return input
153
+ .split("/")
154
+ .map((item) => trimGitSuffix(item.trim()))
155
+ .filter(Boolean)
156
+ }
157
+
158
+ function safeHost(input: string) {
159
+ return Boolean(input) && !input.startsWith("-") && !/[\s/\\]/.test(input)
160
+ }
161
+
162
+ function safeSegment(input: string) {
163
+ return input !== "." && input !== ".." && !input.includes(":") && !/[\s/\\]/.test(input)
164
+ }
165
+
166
+ function hostLike(input: string) {
167
+ return input.includes(".") || input.includes(":") || input === "localhost"
168
+ }
169
+
170
+ function withSlash(input: string) {
171
+ return input.endsWith("/") ? input : `${input}/`
172
+ }
173
+
174
+ function githubRemote(pathname: string) {
175
+ const base = process.env.NEUROCODE_REPO_CLONE_GITHUB_BASE_URL
176
+ if (!base) return `https://github.com/${pathname}.git`
177
+ return new URL(`${pathname}.git`, withSlash(base)).href
178
+ }
179
+
180
+ function buildRemote(input: { host: string; segments: string[]; remote?: string; protocol?: string }) {
181
+ const segments = input.segments.map(trimGitSuffix).filter(Boolean)
182
+ if (!safeHost(input.host) || !segments.length || segments.some((segment) => !safeSegment(segment))) return
183
+ const repositoryPath = segments.join("/")
184
+ const host = input.host.toLowerCase()
185
+ return {
186
+ host,
187
+ path: repositoryPath,
188
+ segments,
189
+ owner: segments.length === 2 ? segments[0] : undefined,
190
+ repo: segments[segments.length - 1],
191
+ remote:
192
+ input.remote ?? (host === "github.com" ? githubRemote(repositoryPath) : `https://${host}/${repositoryPath}.git`),
193
+ label: host === "github.com" && segments.length === 2 ? repositoryPath : `${host}/${repositoryPath}`,
194
+ protocol: input.protocol,
195
+ } satisfies RemoteReference
196
+ }
197
+
198
+ function buildFile(input: { url: URL; remote: string }) {
199
+ const filePath = path.normalize(fileURLToPath(input.url))
200
+ const segments = filePath.split(/[\\/]+/).filter(Boolean)
201
+ if (!segments.length) return
202
+ return {
203
+ host: "file",
204
+ path: filePath,
205
+ segments: segments.map((segment) => segment.replace(/:$/, "")),
206
+ owner: undefined,
207
+ repo: trimGitSuffix(segments[segments.length - 1]),
208
+ remote: input.remote,
209
+ label: filePath,
210
+ protocol: "file:",
211
+ } satisfies FileReference
212
+ }
213
+
214
+ export * as Repository from "./repository"
@@ -0,0 +1,132 @@
1
+ import path from "path"
2
+ import { Context, Effect, Layer, Stream } from "effect"
3
+ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
4
+ import { ChildProcess } from "effect/unstable/process"
5
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
6
+ import { CrossSpawnSpawner } from "../cross-spawn-spawner"
7
+ import { makeGlobalNode } from "../effect/app-node"
8
+ import { httpClient } from "../effect/app-node-platform"
9
+ import { FSUtil } from "../fs-util"
10
+ import { Global } from "../global"
11
+ import { which } from "../util/which"
12
+
13
+ export namespace RipgrepBinary {
14
+ const VERSION = "15.1.0"
15
+ const PLATFORM = {
16
+ "arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" },
17
+ "arm64-linux": { platform: "aarch64-unknown-linux-gnu", extension: "tar.gz" },
18
+ "x64-darwin": { platform: "x86_64-apple-darwin", extension: "tar.gz" },
19
+ "x64-linux": { platform: "x86_64-unknown-linux-musl", extension: "tar.gz" },
20
+ "arm64-win32": { platform: "aarch64-pc-windows-msvc", extension: "zip" },
21
+ "ia32-win32": { platform: "i686-pc-windows-msvc", extension: "zip" },
22
+ "x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" },
23
+ } as const
24
+
25
+ interface Interface {
26
+ readonly filepath: Effect.Effect<string, Error>
27
+ }
28
+
29
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/RipgrepBinary") {}
30
+
31
+ const layer = Layer.effect(
32
+ Service,
33
+ Effect.gen(function* () {
34
+ const fs = yield* FSUtil.Service
35
+ const http = HttpClient.filterStatusOk(yield* HttpClient.HttpClient)
36
+ const spawner = yield* ChildProcessSpawner
37
+
38
+ const run = Effect.fnUntraced(function* (command: string, args: string[]) {
39
+ const handle = yield* spawner.spawn(ChildProcess.make(command, args, { extendEnv: true, stdin: "ignore" }))
40
+ const [stdout, stderr, code] = yield* Effect.all(
41
+ [
42
+ Stream.mkString(Stream.decodeText(handle.stdout)),
43
+ Stream.mkString(Stream.decodeText(handle.stderr)),
44
+ handle.exitCode,
45
+ ],
46
+ { concurrency: "unbounded" },
47
+ )
48
+ return { stdout, stderr, code }
49
+ }, Effect.scoped)
50
+
51
+ const extract = Effect.fnUntraced(function* (
52
+ archive: string,
53
+ config: (typeof PLATFORM)[keyof typeof PLATFORM],
54
+ target: string,
55
+ ) {
56
+ const dir = yield* fs.makeTempDirectoryScoped({ directory: Global.Path.bin, prefix: "ripgrep-" })
57
+
58
+ if (config.extension === "zip") {
59
+ const shell = (yield* Effect.sync(() => which("powershell.exe") ?? which("pwsh.exe"))) ?? "powershell.exe"
60
+ const result = yield* run(shell, [
61
+ "-NoProfile",
62
+ "-NonInteractive",
63
+ "-Command",
64
+ `$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -LiteralPath '${archive.replaceAll("'", "''")}' -DestinationPath '${dir.replaceAll("'", "''")}' -Force`,
65
+ ])
66
+ if (result.code !== 0)
67
+ throw new Error(
68
+ result.stderr.trim() || result.stdout.trim() || `ripgrep extraction failed with code ${result.code}`,
69
+ )
70
+ }
71
+
72
+ if (config.extension === "tar.gz") {
73
+ const result = yield* run("tar", ["-xzf", archive, "-C", dir])
74
+ if (result.code !== 0)
75
+ throw new Error(
76
+ result.stderr.trim() || result.stdout.trim() || `ripgrep extraction failed with code ${result.code}`,
77
+ )
78
+ }
79
+
80
+ const extracted = path.join(
81
+ dir,
82
+ `ripgrep-${VERSION}-${config.platform}`,
83
+ process.platform === "win32" ? "rg.exe" : "rg",
84
+ )
85
+ if (!(yield* fs.isFile(extracted))) throw new Error(`ripgrep archive did not contain executable: ${extracted}`)
86
+
87
+ yield* fs.copyFile(extracted, target)
88
+ if (process.platform !== "win32") yield* fs.chmod(target, 0o755)
89
+ }, Effect.scoped)
90
+
91
+ return Service.of({
92
+ filepath: yield* Effect.cached(
93
+ Effect.gen(function* () {
94
+ const system = yield* Effect.sync(() => which(process.platform === "win32" ? "rg.exe" : "rg"))
95
+ if (system && (yield* fs.isFile(system).pipe(Effect.orDie))) return system
96
+
97
+ const target = path.join(Global.Path.bin, `rg${process.platform === "win32" ? ".exe" : ""}`)
98
+ if (yield* fs.isFile(target).pipe(Effect.orDie)) return target
99
+
100
+ const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM
101
+ const config = PLATFORM[platformKey]
102
+ if (!config) throw new Error(`unsupported platform for ripgrep: ${platformKey}`)
103
+
104
+ const filename = `ripgrep-${VERSION}-${config.platform}.${config.extension}`
105
+ const url = `https://github.com/BurntSushi/ripgrep/releases/download/${VERSION}/${filename}`
106
+ const archive = path.join(Global.Path.bin, filename)
107
+
108
+ yield* Effect.logInfo("downloading ripgrep", { url })
109
+ yield* fs.ensureDir(Global.Path.bin).pipe(Effect.orDie)
110
+ const bytes = yield* HttpClientRequest.get(url).pipe(
111
+ http.execute,
112
+ Effect.flatMap((response) => response.arrayBuffer),
113
+ Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))),
114
+ )
115
+ if (bytes.byteLength === 0) throw new Error(`failed to download ripgrep from ${url}`)
116
+
117
+ yield* fs.writeWithDirs(archive, new Uint8Array(bytes))
118
+ yield* extract(archive, config, target)
119
+ yield* fs.remove(archive, { force: true }).pipe(Effect.ignore)
120
+ return target
121
+ }),
122
+ ),
123
+ })
124
+ }),
125
+ )
126
+
127
+ export const node = makeGlobalNode({
128
+ service: Service,
129
+ layer: layer,
130
+ deps: [FSUtil.node, httpClient, CrossSpawnSpawner.node],
131
+ })
132
+ }