bonecode 1.0.0 → 1.2.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 (840) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -50
  3. package/bin/bonecode +47 -42
  4. package/bone/output/agent/.dockerignore +7 -7
  5. package/bone/output/agent/.env.example +36 -36
  6. package/bone/output/agent/.github/workflows/ci.yaml +58 -58
  7. package/bone/output/agent/AgentDomain.bone.map +349 -349
  8. package/bone/output/agent/AgentDomain.postman_collection.json +957 -957
  9. package/bone/output/agent/Dockerfile +22 -22
  10. package/bone/output/agent/README.md +47 -47
  11. package/bone/output/agent/admin/index.html +739 -739
  12. package/bone/output/agent/docker-compose.yaml +22 -22
  13. package/bone/output/agent/k8s/deployment.yaml +75 -75
  14. package/bone/output/agent/migrations/agent.sql +36 -36
  15. package/bone/output/agent/migrations/agent_instance.sql +36 -36
  16. package/bone/output/agent/migrations/audit_log.sql +18 -18
  17. package/bone/output/agent/migrations/build_step.sql +34 -34
  18. package/bone/output/agent/migrations/event_outbox.sql +31 -31
  19. package/bone/output/agent/migrations/plan.sql +30 -30
  20. package/bone/output/agent/migrations/task.sql +30 -30
  21. package/bone/output/agent/migrations/tool_call.sql +33 -33
  22. package/bone/output/agent/openapi.yaml +1116 -1116
  23. package/bone/output/agent/package.json +35 -35
  24. package/bone/output/agent/schema.graphql +233 -233
  25. package/bone/output/agent/sdk/client.ts +231 -231
  26. package/bone/output/agent/src/algorithms.ts +2 -2
  27. package/bone/output/agent/src/audit.ts +44 -44
  28. package/bone/output/agent/src/auth.ts +57 -57
  29. package/bone/output/agent/src/cron.ts +12 -12
  30. package/bone/output/agent/src/db.ts +31 -31
  31. package/bone/output/agent/src/debug.ts +66 -66
  32. package/bone/output/agent/src/events.ts +243 -243
  33. package/bone/output/agent/src/extensions.ts +54 -54
  34. package/bone/output/agent/src/failure_rules.ts +322 -322
  35. package/bone/output/agent/src/flows.ts +168 -168
  36. package/bone/output/agent/src/health.ts +43 -43
  37. package/bone/output/agent/src/index.ts +99 -99
  38. package/bone/output/agent/src/logger.ts +69 -66
  39. package/bone/output/agent/src/metrics.ts +75 -75
  40. package/bone/output/agent/src/migrate.ts +351 -351
  41. package/bone/output/agent/src/migration_diff.ts +108 -108
  42. package/bone/output/agent/src/notify.ts +125 -125
  43. package/bone/output/agent/src/routes/plan.ts +91 -91
  44. package/bone/output/agent/src/routes/task.ts +105 -105
  45. package/bone/output/agent/src/routes/tool_call.ts +166 -166
  46. package/bone/output/agent/src/schemas.ts +384 -384
  47. package/bone/output/agent/src/state_machines/agent_instance.ts +24 -24
  48. package/bone/output/agent/src/state_machines/build_step.ts +22 -22
  49. package/bone/output/agent/src/state_machines/plan.ts +22 -22
  50. package/bone/output/agent/src/state_machines/task.ts +22 -22
  51. package/bone/output/agent/src/state_machines/tool_call.ts +22 -22
  52. package/bone/output/agent/src/tests.ts +361 -361
  53. package/bone/output/agent/src/websocket.ts +200 -200
  54. package/bone/output/agent/tsconfig.json +24 -24
  55. package/bone/output/rag/.dockerignore +7 -7
  56. package/bone/output/rag/.env.example +36 -36
  57. package/bone/output/rag/.github/workflows/ci.yaml +58 -58
  58. package/bone/output/rag/Dockerfile +22 -22
  59. package/bone/output/rag/RAGDomain.bone.map +286 -286
  60. package/bone/output/rag/RAGDomain.postman_collection.json +922 -922
  61. package/bone/output/rag/README.md +47 -47
  62. package/bone/output/rag/admin/index.html +817 -817
  63. package/bone/output/rag/docker-compose.yaml +22 -22
  64. package/bone/output/rag/k8s/deployment.yaml +75 -75
  65. package/bone/output/rag/migrations/audit_log.sql +18 -18
  66. package/bone/output/rag/migrations/code_chunk.sql +34 -34
  67. package/bone/output/rag/migrations/code_file.sql +33 -33
  68. package/bone/output/rag/migrations/event_outbox.sql +31 -31
  69. package/bone/output/rag/migrations/indexing_job.sql +33 -33
  70. package/bone/output/rag/migrations/knowledge_base.sql +35 -35
  71. package/bone/output/rag/migrations/memory_entry.sql +34 -34
  72. package/bone/output/rag/openapi.yaml +1097 -1097
  73. package/bone/output/rag/package.json +35 -35
  74. package/bone/output/rag/schema.graphql +245 -245
  75. package/bone/output/rag/sdk/client.ts +234 -234
  76. package/bone/output/rag/src/algorithms.ts +2 -2
  77. package/bone/output/rag/src/audit.ts +37 -37
  78. package/bone/output/rag/src/auth.ts +57 -57
  79. package/bone/output/rag/src/cron.ts +12 -12
  80. package/bone/output/rag/src/db.ts +31 -31
  81. package/bone/output/rag/src/debug.ts +66 -66
  82. package/bone/output/rag/src/events.ts +243 -243
  83. package/bone/output/rag/src/extensions.ts +350 -350
  84. package/bone/output/rag/src/failure_rules.ts +314 -314
  85. package/bone/output/rag/src/flows.ts +239 -239
  86. package/bone/output/rag/src/health.ts +43 -43
  87. package/bone/output/rag/src/index.ts +94 -94
  88. package/bone/output/rag/src/logger.ts +69 -66
  89. package/bone/output/rag/src/metrics.ts +75 -75
  90. package/bone/output/rag/src/migrate.ts +363 -363
  91. package/bone/output/rag/src/migration_diff.ts +108 -108
  92. package/bone/output/rag/src/notify.ts +99 -99
  93. package/bone/output/rag/src/routes/code_chunk.ts +75 -75
  94. package/bone/output/rag/src/routes/code_file.ts +101 -101
  95. package/bone/output/rag/src/routes/indexing_job.ts +87 -87
  96. package/bone/output/rag/src/routes/knowledge_base.ts +230 -230
  97. package/bone/output/rag/src/routes/memory_entry.ts +87 -87
  98. package/bone/output/rag/src/schemas.ts +394 -394
  99. package/bone/output/rag/src/state_machines/code_file.ts +23 -23
  100. package/bone/output/rag/src/state_machines/indexing_job.ts +22 -22
  101. package/bone/output/rag/src/state_machines/knowledge_base.ts +23 -23
  102. package/bone/output/rag/src/state_machines/memory_entry.ts +20 -20
  103. package/bone/output/rag/src/tests.ts +339 -339
  104. package/bone/output/rag/tsconfig.json +24 -24
  105. package/bone/output/session/.dockerignore +7 -7
  106. package/bone/output/session/.env.example +36 -36
  107. package/bone/output/session/.github/workflows/ci.yaml +58 -58
  108. package/bone/output/session/Dockerfile +22 -22
  109. package/bone/output/session/README.md +47 -47
  110. package/bone/output/session/SessionDomain.bone.map +349 -349
  111. package/bone/output/session/SessionDomain.postman_collection.json +957 -957
  112. package/bone/output/session/admin/index.html +666 -666
  113. package/bone/output/session/docker-compose.yaml +22 -22
  114. package/bone/output/session/k8s/deployment.yaml +75 -75
  115. package/bone/output/session/migrations/audit_log.sql +18 -18
  116. package/bone/output/session/migrations/event_outbox.sql +31 -31
  117. package/bone/output/session/migrations/message.sql +31 -31
  118. package/bone/output/session/migrations/part.sql +28 -28
  119. package/bone/output/session/migrations/permission.sql +28 -28
  120. package/bone/output/session/migrations/project.sql +28 -28
  121. package/bone/output/session/migrations/session.sql +38 -38
  122. package/bone/output/session/openapi.yaml +1101 -1101
  123. package/bone/output/session/package.json +35 -35
  124. package/bone/output/session/schema.graphql +222 -222
  125. package/bone/output/session/sdk/client.ts +225 -225
  126. package/bone/output/session/src/algorithms.ts +2 -2
  127. package/bone/output/session/src/audit.ts +44 -44
  128. package/bone/output/session/src/auth.ts +57 -57
  129. package/bone/output/session/src/cron.ts +12 -12
  130. package/bone/output/session/src/db.ts +31 -31
  131. package/bone/output/session/src/debug.ts +66 -66
  132. package/bone/output/session/src/events.ts +270 -270
  133. package/bone/output/session/src/extensions.ts +215 -215
  134. package/bone/output/session/src/failure_rules.ts +283 -283
  135. package/bone/output/session/src/flows.ts +168 -168
  136. package/bone/output/session/src/health.ts +43 -43
  137. package/bone/output/session/src/index.ts +99 -99
  138. package/bone/output/session/src/logger.ts +67 -66
  139. package/bone/output/session/src/metrics.ts +75 -75
  140. package/bone/output/session/src/migrate.ts +331 -331
  141. package/bone/output/session/src/migration_diff.ts +108 -108
  142. package/bone/output/session/src/notify.ts +112 -112
  143. package/bone/output/session/src/routes/message.ts +93 -93
  144. package/bone/output/session/src/routes/part.ts +79 -79
  145. package/bone/output/session/src/routes/permission.ts +79 -79
  146. package/bone/output/session/src/routes/project.ts +79 -79
  147. package/bone/output/session/src/routes/session.ts +294 -294
  148. package/bone/output/session/src/schemas.ts +357 -357
  149. package/bone/output/session/src/state_machines/session.ts +23 -23
  150. package/bone/output/session/src/tests.ts +325 -325
  151. package/bone/output/session/src/websocket.ts +223 -200
  152. package/bone/output/session/tsconfig.json +24 -24
  153. package/bone/output/workspace/.dockerignore +7 -7
  154. package/bone/output/workspace/.env.example +36 -36
  155. package/bone/output/workspace/.github/workflows/ci.yaml +58 -58
  156. package/bone/output/workspace/Dockerfile +22 -22
  157. package/bone/output/workspace/README.md +45 -45
  158. package/bone/output/workspace/WorkspaceDomain.bone.map +188 -188
  159. package/bone/output/workspace/WorkspaceDomain.postman_collection.json +620 -620
  160. package/bone/output/workspace/admin/index.html +484 -484
  161. package/bone/output/workspace/docker-compose.yaml +22 -22
  162. package/bone/output/workspace/k8s/deployment.yaml +75 -75
  163. package/bone/output/workspace/migrations/audit_log.sql +18 -18
  164. package/bone/output/workspace/migrations/codebase.sql +34 -34
  165. package/bone/output/workspace/migrations/event_outbox.sql +31 -31
  166. package/bone/output/workspace/migrations/snapshot.sql +32 -32
  167. package/bone/output/workspace/migrations/workspace.sql +33 -33
  168. package/bone/output/workspace/openapi.yaml +721 -721
  169. package/bone/output/workspace/package.json +35 -35
  170. package/bone/output/workspace/schema.graphql +153 -153
  171. package/bone/output/workspace/sdk/client.ts +155 -155
  172. package/bone/output/workspace/src/algorithms.ts +2 -2
  173. package/bone/output/workspace/src/audit.ts +37 -37
  174. package/bone/output/workspace/src/auth.ts +57 -57
  175. package/bone/output/workspace/src/cron.ts +12 -12
  176. package/bone/output/workspace/src/db.ts +31 -31
  177. package/bone/output/workspace/src/debug.ts +66 -66
  178. package/bone/output/workspace/src/events.ts +243 -243
  179. package/bone/output/workspace/src/extensions.ts +44 -44
  180. package/bone/output/workspace/src/failure_rules.ts +152 -152
  181. package/bone/output/workspace/src/health.ts +43 -43
  182. package/bone/output/workspace/src/index.ts +88 -88
  183. package/bone/output/workspace/src/logger.ts +69 -66
  184. package/bone/output/workspace/src/metrics.ts +75 -75
  185. package/bone/output/workspace/src/migrate.ts +219 -219
  186. package/bone/output/workspace/src/migration_diff.ts +108 -108
  187. package/bone/output/workspace/src/notify.ts +73 -73
  188. package/bone/output/workspace/src/routes/codebase.ts +87 -87
  189. package/bone/output/workspace/src/routes/snapshot.ts +127 -127
  190. package/bone/output/workspace/src/routes/workspace.ts +190 -190
  191. package/bone/output/workspace/src/schemas.ts +231 -231
  192. package/bone/output/workspace/src/state_machines/codebase.ts +21 -21
  193. package/bone/output/workspace/src/state_machines/snapshot.ts +20 -20
  194. package/bone/output/workspace/src/state_machines/workspace.ts +21 -21
  195. package/bone/output/workspace/src/tests.ts +248 -248
  196. package/bone/output/workspace/tsconfig.json +24 -24
  197. package/compat/opencode_adapter.ts +282 -34
  198. package/dist/bone/output/agent/src/algorithms.d.ts +1 -0
  199. package/dist/bone/output/agent/src/algorithms.js +3 -0
  200. package/dist/bone/output/agent/src/algorithms.js.map +1 -0
  201. package/dist/bone/output/agent/src/audit.d.ts +3 -0
  202. package/dist/bone/output/agent/src/audit.js +40 -0
  203. package/dist/bone/output/agent/src/audit.js.map +1 -0
  204. package/dist/bone/output/agent/src/auth.d.ts +8 -0
  205. package/dist/bone/output/agent/src/auth.js +56 -0
  206. package/dist/bone/output/agent/src/auth.js.map +1 -0
  207. package/dist/bone/output/agent/src/db.d.ts +6 -0
  208. package/dist/bone/output/agent/src/db.js +63 -0
  209. package/dist/bone/output/agent/src/db.js.map +1 -0
  210. package/dist/bone/output/agent/src/events.d.ts +25 -0
  211. package/dist/bone/output/agent/src/events.js +184 -0
  212. package/dist/bone/output/agent/src/events.js.map +1 -0
  213. package/dist/bone/output/agent/src/logger.d.ts +28 -0
  214. package/dist/bone/output/agent/src/logger.js +45 -0
  215. package/dist/bone/output/agent/src/logger.js.map +1 -0
  216. package/dist/bone/output/agent/src/metrics.d.ts +5 -0
  217. package/dist/bone/output/agent/src/metrics.js +60 -0
  218. package/dist/bone/output/agent/src/metrics.js.map +1 -0
  219. package/dist/bone/output/agent/src/routes/agent_instance.d.ts +1 -0
  220. package/dist/bone/output/agent/src/routes/agent_instance.js +253 -0
  221. package/dist/bone/output/agent/src/routes/agent_instance.js.map +1 -0
  222. package/dist/bone/output/agent/src/routes/build_step.d.ts +1 -0
  223. package/dist/bone/output/agent/src/routes/build_step.js +133 -0
  224. package/dist/bone/output/agent/src/routes/build_step.js.map +1 -0
  225. package/dist/bone/output/agent/src/routes/plan.d.ts +1 -0
  226. package/dist/bone/output/agent/src/routes/plan.js +119 -0
  227. package/dist/bone/output/agent/src/routes/plan.js.map +1 -0
  228. package/dist/bone/output/agent/src/routes/task.d.ts +1 -0
  229. package/dist/bone/output/agent/src/routes/task.js +133 -0
  230. package/dist/bone/output/agent/src/routes/task.js.map +1 -0
  231. package/dist/bone/output/agent/src/routes/tool_call.d.ts +1 -0
  232. package/dist/bone/output/agent/src/routes/tool_call.js +190 -0
  233. package/dist/bone/output/agent/src/routes/tool_call.js.map +1 -0
  234. package/dist/bone/output/agent/src/state_machines/agent_instance.d.ts +9 -0
  235. package/dist/bone/output/agent/src/state_machines/agent_instance.js +22 -0
  236. package/dist/bone/output/agent/src/state_machines/agent_instance.js.map +1 -0
  237. package/dist/bone/output/agent/src/state_machines/build_step.d.ts +9 -0
  238. package/dist/bone/output/agent/src/state_machines/build_step.js +20 -0
  239. package/dist/bone/output/agent/src/state_machines/build_step.js.map +1 -0
  240. package/dist/bone/output/agent/src/state_machines/plan.d.ts +9 -0
  241. package/dist/bone/output/agent/src/state_machines/plan.js +20 -0
  242. package/dist/bone/output/agent/src/state_machines/plan.js.map +1 -0
  243. package/dist/bone/output/agent/src/state_machines/task.d.ts +9 -0
  244. package/dist/bone/output/agent/src/state_machines/task.js +20 -0
  245. package/dist/bone/output/agent/src/state_machines/task.js.map +1 -0
  246. package/dist/bone/output/agent/src/state_machines/tool_call.d.ts +9 -0
  247. package/dist/bone/output/agent/src/state_machines/tool_call.js +20 -0
  248. package/dist/bone/output/agent/src/state_machines/tool_call.js.map +1 -0
  249. package/dist/bone/output/rag/src/algorithms.d.ts +1 -0
  250. package/dist/bone/output/rag/src/algorithms.js +3 -0
  251. package/dist/bone/output/rag/src/algorithms.js.map +1 -0
  252. package/dist/bone/output/rag/src/auth.d.ts +8 -0
  253. package/dist/bone/output/rag/src/auth.js +56 -0
  254. package/dist/bone/output/rag/src/auth.js.map +1 -0
  255. package/dist/bone/output/rag/src/db.d.ts +6 -0
  256. package/dist/bone/output/rag/src/db.js +63 -0
  257. package/dist/bone/output/rag/src/db.js.map +1 -0
  258. package/dist/bone/output/rag/src/events.d.ts +25 -0
  259. package/dist/bone/output/rag/src/events.js +184 -0
  260. package/dist/bone/output/rag/src/events.js.map +1 -0
  261. package/dist/bone/output/rag/src/extensions.d.ts +83 -0
  262. package/dist/bone/output/rag/src/extensions.js +329 -0
  263. package/dist/bone/output/rag/src/extensions.js.map +1 -0
  264. package/dist/bone/output/rag/src/flows.d.ts +24 -0
  265. package/dist/bone/output/rag/src/flows.js +236 -0
  266. package/dist/bone/output/rag/src/flows.js.map +1 -0
  267. package/dist/bone/output/rag/src/logger.d.ts +28 -0
  268. package/dist/bone/output/rag/src/logger.js +45 -0
  269. package/dist/bone/output/rag/src/logger.js.map +1 -0
  270. package/dist/bone/output/rag/src/metrics.d.ts +5 -0
  271. package/dist/bone/output/rag/src/metrics.js +60 -0
  272. package/dist/bone/output/rag/src/metrics.js.map +1 -0
  273. package/dist/bone/output/rag/src/routes/code_chunk.d.ts +1 -0
  274. package/dist/bone/output/rag/src/routes/code_chunk.js +100 -0
  275. package/dist/bone/output/rag/src/routes/code_chunk.js.map +1 -0
  276. package/dist/bone/output/rag/src/routes/code_file.d.ts +1 -0
  277. package/dist/bone/output/rag/src/routes/code_file.js +127 -0
  278. package/dist/bone/output/rag/src/routes/code_file.js.map +1 -0
  279. package/dist/bone/output/rag/src/routes/indexing_job.d.ts +1 -0
  280. package/dist/bone/output/rag/src/routes/indexing_job.js +113 -0
  281. package/dist/bone/output/rag/src/routes/indexing_job.js.map +1 -0
  282. package/dist/bone/output/rag/src/routes/knowledge_base.d.ts +1 -0
  283. package/dist/bone/output/rag/src/routes/knowledge_base.js +242 -0
  284. package/dist/bone/output/rag/src/routes/knowledge_base.js.map +1 -0
  285. package/dist/bone/output/rag/src/routes/memory_entry.d.ts +1 -0
  286. package/dist/bone/output/rag/src/routes/memory_entry.js +113 -0
  287. package/dist/bone/output/rag/src/routes/memory_entry.js.map +1 -0
  288. package/dist/bone/output/rag/src/state_machines/code_file.d.ts +9 -0
  289. package/dist/bone/output/rag/src/state_machines/code_file.js +21 -0
  290. package/dist/bone/output/rag/src/state_machines/code_file.js.map +1 -0
  291. package/dist/bone/output/rag/src/state_machines/indexing_job.d.ts +9 -0
  292. package/dist/bone/output/rag/src/state_machines/indexing_job.js +20 -0
  293. package/dist/bone/output/rag/src/state_machines/indexing_job.js.map +1 -0
  294. package/dist/bone/output/rag/src/state_machines/knowledge_base.d.ts +9 -0
  295. package/dist/bone/output/rag/src/state_machines/knowledge_base.js +21 -0
  296. package/dist/bone/output/rag/src/state_machines/knowledge_base.js.map +1 -0
  297. package/dist/bone/output/rag/src/state_machines/memory_entry.d.ts +9 -0
  298. package/dist/bone/output/rag/src/state_machines/memory_entry.js +18 -0
  299. package/dist/bone/output/rag/src/state_machines/memory_entry.js.map +1 -0
  300. package/dist/bone/output/session/src/algorithms.d.ts +1 -0
  301. package/dist/bone/output/session/src/algorithms.js +3 -0
  302. package/dist/bone/output/session/src/algorithms.js.map +1 -0
  303. package/dist/bone/output/session/src/audit.d.ts +3 -0
  304. package/dist/bone/output/session/src/audit.js +40 -0
  305. package/dist/bone/output/session/src/audit.js.map +1 -0
  306. package/dist/bone/output/session/src/auth.d.ts +8 -0
  307. package/dist/bone/output/session/src/auth.js +56 -0
  308. package/dist/bone/output/session/src/auth.js.map +1 -0
  309. package/dist/bone/output/session/src/db.d.ts +6 -0
  310. package/dist/bone/output/session/src/db.js +63 -0
  311. package/dist/bone/output/session/src/db.js.map +1 -0
  312. package/dist/bone/output/session/src/events.d.ts +26 -0
  313. package/dist/bone/output/session/src/events.js +212 -0
  314. package/dist/bone/output/session/src/events.js.map +1 -0
  315. package/dist/bone/output/session/src/extensions.d.ts +41 -0
  316. package/dist/bone/output/session/src/extensions.js +217 -0
  317. package/dist/bone/output/session/src/extensions.js.map +1 -0
  318. package/dist/bone/output/session/src/logger.d.ts +28 -0
  319. package/dist/bone/output/session/src/logger.js +44 -0
  320. package/dist/bone/output/session/src/logger.js.map +1 -0
  321. package/dist/bone/output/session/src/metrics.d.ts +5 -0
  322. package/dist/bone/output/session/src/metrics.js +60 -0
  323. package/dist/bone/output/session/src/metrics.js.map +1 -0
  324. package/dist/bone/output/session/src/routes/message.d.ts +1 -0
  325. package/dist/bone/output/session/src/routes/message.js +120 -0
  326. package/dist/bone/output/session/src/routes/message.js.map +1 -0
  327. package/dist/bone/output/session/src/routes/part.d.ts +1 -0
  328. package/dist/bone/output/session/src/routes/part.js +106 -0
  329. package/dist/bone/output/session/src/routes/part.js.map +1 -0
  330. package/dist/bone/output/session/src/routes/permission.d.ts +1 -0
  331. package/dist/bone/output/session/src/routes/permission.js +106 -0
  332. package/dist/bone/output/session/src/routes/permission.js.map +1 -0
  333. package/dist/bone/output/session/src/routes/project.d.ts +1 -0
  334. package/dist/bone/output/session/src/routes/project.js +106 -0
  335. package/dist/bone/output/session/src/routes/project.js.map +1 -0
  336. package/dist/bone/output/session/src/routes/session.d.ts +1 -0
  337. package/dist/bone/output/session/src/routes/session.js +308 -0
  338. package/dist/bone/output/session/src/routes/session.js.map +1 -0
  339. package/dist/bone/output/session/src/state_machines/session.d.ts +9 -0
  340. package/dist/bone/output/session/src/state_machines/session.js +21 -0
  341. package/dist/bone/output/session/src/state_machines/session.js.map +1 -0
  342. package/dist/bone/output/session/src/websocket.d.ts +15 -0
  343. package/dist/bone/output/session/src/websocket.js +215 -0
  344. package/dist/bone/output/session/src/websocket.js.map +1 -0
  345. package/dist/bone/output/workspace/src/algorithms.d.ts +1 -0
  346. package/dist/bone/output/workspace/src/algorithms.js +3 -0
  347. package/dist/bone/output/workspace/src/algorithms.js.map +1 -0
  348. package/dist/bone/output/workspace/src/auth.d.ts +8 -0
  349. package/dist/bone/output/workspace/src/auth.js +56 -0
  350. package/dist/bone/output/workspace/src/auth.js.map +1 -0
  351. package/dist/bone/output/workspace/src/db.d.ts +6 -0
  352. package/dist/bone/output/workspace/src/db.js +63 -0
  353. package/dist/bone/output/workspace/src/db.js.map +1 -0
  354. package/dist/bone/output/workspace/src/events.d.ts +25 -0
  355. package/dist/bone/output/workspace/src/events.js +184 -0
  356. package/dist/bone/output/workspace/src/events.js.map +1 -0
  357. package/dist/bone/output/workspace/src/logger.d.ts +28 -0
  358. package/dist/bone/output/workspace/src/logger.js +45 -0
  359. package/dist/bone/output/workspace/src/logger.js.map +1 -0
  360. package/dist/bone/output/workspace/src/metrics.d.ts +5 -0
  361. package/dist/bone/output/workspace/src/metrics.js +60 -0
  362. package/dist/bone/output/workspace/src/metrics.js.map +1 -0
  363. package/dist/bone/output/workspace/src/routes/codebase.d.ts +1 -0
  364. package/dist/bone/output/workspace/src/routes/codebase.js +113 -0
  365. package/dist/bone/output/workspace/src/routes/codebase.js.map +1 -0
  366. package/dist/bone/output/workspace/src/routes/snapshot.d.ts +1 -0
  367. package/dist/bone/output/workspace/src/routes/snapshot.js +151 -0
  368. package/dist/bone/output/workspace/src/routes/snapshot.js.map +1 -0
  369. package/dist/bone/output/workspace/src/routes/workspace.d.ts +1 -0
  370. package/dist/bone/output/workspace/src/routes/workspace.js +209 -0
  371. package/dist/bone/output/workspace/src/routes/workspace.js.map +1 -0
  372. package/dist/bone/output/workspace/src/state_machines/codebase.d.ts +9 -0
  373. package/dist/bone/output/workspace/src/state_machines/codebase.js +19 -0
  374. package/dist/bone/output/workspace/src/state_machines/codebase.js.map +1 -0
  375. package/dist/bone/output/workspace/src/state_machines/snapshot.d.ts +9 -0
  376. package/dist/bone/output/workspace/src/state_machines/snapshot.js +18 -0
  377. package/dist/bone/output/workspace/src/state_machines/snapshot.js.map +1 -0
  378. package/dist/bone/output/workspace/src/state_machines/workspace.d.ts +9 -0
  379. package/dist/bone/output/workspace/src/state_machines/workspace.js +19 -0
  380. package/dist/bone/output/workspace/src/state_machines/workspace.js.map +1 -0
  381. package/dist/compat/opencode_adapter.d.ts +25 -0
  382. package/dist/compat/opencode_adapter.js +599 -0
  383. package/dist/compat/opencode_adapter.js.map +1 -0
  384. package/dist/extensions/chunker.d.ts +24 -0
  385. package/dist/extensions/chunker.js +360 -0
  386. package/dist/extensions/chunker.js.map +1 -0
  387. package/dist/extensions/embedding_provider.d.ts +18 -0
  388. package/dist/extensions/embedding_provider.js +150 -0
  389. package/dist/extensions/embedding_provider.js.map +1 -0
  390. package/dist/extensions/llm_provider.d.ts +33 -0
  391. package/dist/extensions/llm_provider.js +338 -0
  392. package/dist/extensions/llm_provider.js.map +1 -0
  393. package/dist/extensions/mcp_bridge.d.ts +44 -0
  394. package/dist/extensions/mcp_bridge.js +151 -0
  395. package/dist/extensions/mcp_bridge.js.map +1 -0
  396. package/dist/extensions/rag_search.d.ts +38 -0
  397. package/dist/extensions/rag_search.js +242 -0
  398. package/dist/extensions/rag_search.js.map +1 -0
  399. package/dist/extensions/snapshot.d.ts +14 -0
  400. package/dist/extensions/snapshot.js +158 -0
  401. package/dist/extensions/snapshot.js.map +1 -0
  402. package/dist/extensions/tool_executor.d.ts +28 -0
  403. package/dist/extensions/tool_executor.js +268 -0
  404. package/dist/extensions/tool_executor.js.map +1 -0
  405. package/dist/src/cli.d.ts +15 -0
  406. package/dist/src/cli.js +687 -0
  407. package/dist/src/cli.js.map +1 -0
  408. package/dist/src/config.d.ts +44 -0
  409. package/dist/src/config.js +165 -0
  410. package/dist/src/config.js.map +1 -0
  411. package/dist/src/context_builder.d.ts +51 -0
  412. package/dist/src/context_builder.js +558 -0
  413. package/dist/src/context_builder.js.map +1 -0
  414. package/dist/src/db_adapter.d.ts +24 -0
  415. package/dist/src/db_adapter.js +341 -0
  416. package/dist/src/db_adapter.js.map +1 -0
  417. package/dist/src/engine/session/compaction_logic.d.ts +11 -0
  418. package/dist/src/engine/session/compaction_logic.js +113 -0
  419. package/dist/src/engine/session/compaction_logic.js.map +1 -0
  420. package/dist/src/engine/session/instruction_loader.d.ts +5 -0
  421. package/dist/src/engine/session/instruction_loader.js +78 -0
  422. package/dist/src/engine/session/instruction_loader.js.map +1 -0
  423. package/dist/src/engine/session/overflow_check.d.ts +14 -0
  424. package/dist/src/engine/session/overflow_check.js +45 -0
  425. package/dist/src/engine/session/overflow_check.js.map +1 -0
  426. package/dist/src/engine/session/prompt.d.ts +45 -0
  427. package/dist/src/engine/session/prompt.js +584 -0
  428. package/dist/src/engine/session/prompt.js.map +1 -0
  429. package/dist/src/engine/session/provider_transform.d.ts +59 -0
  430. package/dist/src/engine/session/provider_transform.js +193 -0
  431. package/dist/src/engine/session/provider_transform.js.map +1 -0
  432. package/dist/src/engine/session/retry_logic.d.ts +12 -0
  433. package/dist/src/engine/session/retry_logic.js +72 -0
  434. package/dist/src/engine/session/retry_logic.js.map +1 -0
  435. package/dist/src/engine/session/system_prompt.d.ts +9 -0
  436. package/dist/src/engine/session/system_prompt.js +96 -0
  437. package/dist/src/engine/session/system_prompt.js.map +1 -0
  438. package/dist/src/engine/session/tool_registry.d.ts +5 -0
  439. package/dist/src/engine/session/tool_registry.js +117 -0
  440. package/dist/src/engine/session/tool_registry.js.map +1 -0
  441. package/dist/src/export.d.ts +13 -0
  442. package/dist/src/export.js +103 -0
  443. package/dist/src/export.js.map +1 -0
  444. package/dist/src/mdns.d.ts +7 -0
  445. package/dist/src/mdns.js +60 -0
  446. package/dist/src/mdns.js.map +1 -0
  447. package/dist/src/rag_worker.d.ts +38 -0
  448. package/dist/src/rag_worker.js +435 -0
  449. package/dist/src/rag_worker.js.map +1 -0
  450. package/dist/src/server.d.ts +11 -0
  451. package/dist/src/server.js +214 -0
  452. package/dist/src/server.js.map +1 -0
  453. package/dist/src/stats.d.ts +45 -0
  454. package/dist/src/stats.js +233 -0
  455. package/dist/src/stats.js.map +1 -0
  456. package/dist/src/tui.d.ts +29 -0
  457. package/dist/src/tui.js +1053 -0
  458. package/dist/src/tui.js.map +1 -0
  459. package/package.json +21 -5
  460. package/src/cli.ts +314 -113
  461. package/src/db_adapter.ts +354 -0
  462. package/src/engine/account/account.sql.ts +39 -39
  463. package/src/engine/account/account.ts +456 -456
  464. package/src/engine/account/repo.ts +166 -166
  465. package/src/engine/account/schema.ts +99 -99
  466. package/src/engine/account/url.ts +8 -8
  467. package/src/engine/acp/README.md +174 -174
  468. package/src/engine/acp/agent.ts +1968 -1968
  469. package/src/engine/acp/runtime.ts +22 -22
  470. package/src/engine/acp/session.ts +122 -122
  471. package/src/engine/acp/types.ts +24 -24
  472. package/src/engine/agent/agent.ts +463 -463
  473. package/src/engine/agent/generate.txt +75 -75
  474. package/src/engine/agent/prompt/compaction.txt +9 -9
  475. package/src/engine/agent/prompt/explore.txt +18 -18
  476. package/src/engine/agent/prompt/scout.txt +36 -36
  477. package/src/engine/agent/prompt/summary.txt +11 -11
  478. package/src/engine/agent/prompt/title.txt +44 -44
  479. package/src/engine/agent/subagent-permissions.ts +34 -34
  480. package/src/engine/auth/index.ts +96 -96
  481. package/src/engine/background/background/job.ts +200 -200
  482. package/src/engine/background/job.ts +200 -200
  483. package/src/engine/bus/bus-event.ts +45 -45
  484. package/src/engine/bus/global.ts +22 -22
  485. package/src/engine/bus/index.ts +203 -203
  486. package/src/engine/command/command/index.ts +181 -181
  487. package/src/engine/command/command/template/initialize.txt +66 -66
  488. package/src/engine/command/command/template/review.txt +101 -101
  489. package/src/engine/command/index.ts +181 -181
  490. package/src/engine/command/template/initialize.txt +66 -66
  491. package/src/engine/command/template/review.txt +101 -101
  492. package/src/engine/config/agent.ts +172 -172
  493. package/src/engine/config/attachment.ts +25 -25
  494. package/src/engine/config/command.ts +62 -62
  495. package/src/engine/config/config.ts +833 -833
  496. package/src/engine/config/console-state.ts +14 -14
  497. package/src/engine/config/entry-name.ts +16 -16
  498. package/src/engine/config/error.ts +23 -23
  499. package/src/engine/config/formatter.ts +13 -13
  500. package/src/engine/config/layout.ts +6 -6
  501. package/src/engine/config/lsp.ts +43 -43
  502. package/src/engine/config/managed.ts +71 -71
  503. package/src/engine/config/markdown.ts +96 -96
  504. package/src/engine/config/mcp.ts +56 -56
  505. package/src/engine/config/model-id.ts +5 -5
  506. package/src/engine/config/parse.ts +79 -79
  507. package/src/engine/config/paths.ts +45 -45
  508. package/src/engine/config/permission.ts +58 -58
  509. package/src/engine/config/plugin.ts +84 -84
  510. package/src/engine/config/provider.ts +111 -111
  511. package/src/engine/config/reference.ts +23 -23
  512. package/src/engine/config/server.ts +19 -19
  513. package/src/engine/config/skills.ts +14 -14
  514. package/src/engine/config/variable.ts +90 -90
  515. package/src/engine/control-plane/adapters/index.ts +41 -41
  516. package/src/engine/control-plane/adapters/worktree.ts +96 -96
  517. package/src/engine/control-plane/dev/README.md +19 -19
  518. package/src/engine/control-plane/dev/debug-workspace-plugin.ts +73 -73
  519. package/src/engine/control-plane/schema.ts +14 -14
  520. package/src/engine/control-plane/types.ts +59 -59
  521. package/src/engine/control-plane/util.ts +39 -39
  522. package/src/engine/control-plane/workspace-adapter-runtime.ts +51 -51
  523. package/src/engine/control-plane/workspace-context.ts +26 -26
  524. package/src/engine/control-plane/workspace.sql.ts +20 -20
  525. package/src/engine/control-plane/workspace.ts +1072 -1072
  526. package/src/engine/data-migration.ts +161 -161
  527. package/src/engine/effect/app-runtime.ts +143 -143
  528. package/src/engine/effect/bootstrap-runtime.ts +29 -29
  529. package/src/engine/effect/bridge.ts +84 -84
  530. package/src/engine/effect/config-service.ts +67 -67
  531. package/src/engine/effect/instance-ref.ts +11 -11
  532. package/src/engine/effect/instance-registry.ts +12 -12
  533. package/src/engine/effect/instance-state.ts +72 -72
  534. package/src/engine/effect/promise.ts +17 -17
  535. package/src/engine/effect/run-service.ts +47 -47
  536. package/src/engine/effect/runner.ts +217 -217
  537. package/src/engine/effect/runtime-flags.ts +74 -74
  538. package/src/engine/effect/service-use.ts +38 -38
  539. package/src/engine/env/index.ts +37 -37
  540. package/src/engine/event-v2-bridge.ts +89 -89
  541. package/src/engine/file/file/ignore.ts +81 -81
  542. package/src/engine/file/file/index.ts +651 -651
  543. package/src/engine/file/file/protected.ts +59 -59
  544. package/src/engine/file/file/ripgrep.ts +481 -481
  545. package/src/engine/file/file/watcher.ts +167 -167
  546. package/src/engine/file/ignore.ts +81 -81
  547. package/src/engine/file/index.ts +651 -651
  548. package/src/engine/file/protected.ts +59 -59
  549. package/src/engine/file/ripgrep.ts +481 -481
  550. package/src/engine/file/watcher.ts +167 -167
  551. package/src/engine/format/format/formatter.ts +404 -404
  552. package/src/engine/format/format/index.ts +209 -209
  553. package/src/engine/format/formatter.ts +404 -404
  554. package/src/engine/format/index.ts +209 -209
  555. package/src/engine/git/git/index.ts +347 -347
  556. package/src/engine/git/index.ts +347 -347
  557. package/src/engine/id/id.ts +80 -80
  558. package/src/engine/ide/index.ts +70 -70
  559. package/src/engine/image/image/image.ts +176 -176
  560. package/src/engine/image/image.ts +176 -176
  561. package/src/engine/index.ts +251 -251
  562. package/src/engine/installation/index.ts +327 -327
  563. package/src/engine/lsp/client.ts +707 -707
  564. package/src/engine/lsp/diagnostic.ts +29 -29
  565. package/src/engine/lsp/language.ts +121 -121
  566. package/src/engine/lsp/launch.ts +21 -21
  567. package/src/engine/lsp/lsp/client.ts +707 -707
  568. package/src/engine/lsp/lsp/diagnostic.ts +29 -29
  569. package/src/engine/lsp/lsp/language.ts +121 -121
  570. package/src/engine/lsp/lsp/launch.ts +21 -21
  571. package/src/engine/lsp/lsp/lsp.ts +507 -507
  572. package/src/engine/lsp/lsp/server.ts +2064 -2064
  573. package/src/engine/lsp/lsp.ts +507 -507
  574. package/src/engine/lsp/server.ts +2064 -2064
  575. package/src/engine/mcp/auth.ts +146 -146
  576. package/src/engine/mcp/index.ts +958 -958
  577. package/src/engine/mcp/mcp/auth.ts +146 -146
  578. package/src/engine/mcp/mcp/index.ts +958 -958
  579. package/src/engine/mcp/mcp/oauth-callback.ts +232 -232
  580. package/src/engine/mcp/mcp/oauth-provider.ts +214 -214
  581. package/src/engine/mcp/oauth-callback.ts +232 -232
  582. package/src/engine/mcp/oauth-provider.ts +214 -214
  583. package/src/engine/node.ts +6 -6
  584. package/src/engine/patch/index.ts +689 -689
  585. package/src/engine/patch/patch/index.ts +689 -689
  586. package/src/engine/permission/arity.ts +163 -163
  587. package/src/engine/permission/evaluate.ts +15 -15
  588. package/src/engine/permission/index.ts +306 -306
  589. package/src/engine/permission/permission/arity.ts +163 -163
  590. package/src/engine/permission/permission/evaluate.ts +15 -15
  591. package/src/engine/permission/permission/index.ts +306 -306
  592. package/src/engine/permission/permission/schema.ts +13 -13
  593. package/src/engine/permission/schema.ts +13 -13
  594. package/src/engine/plugin/azure.ts +26 -26
  595. package/src/engine/plugin/cloudflare.ts +76 -76
  596. package/src/engine/plugin/codex.ts +622 -622
  597. package/src/engine/plugin/digitalocean.ts +411 -411
  598. package/src/engine/plugin/github-copilot/copilot.ts +394 -394
  599. package/src/engine/plugin/github-copilot/models.ts +196 -196
  600. package/src/engine/plugin/index.ts +295 -295
  601. package/src/engine/plugin/install.ts +439 -439
  602. package/src/engine/plugin/loader.ts +216 -216
  603. package/src/engine/plugin/meta.ts +188 -188
  604. package/src/engine/plugin/shared.ts +323 -323
  605. package/src/engine/project/bootstrap-service.ts +9 -9
  606. package/src/engine/project/bootstrap.ts +75 -75
  607. package/src/engine/project/instance-context.ts +24 -24
  608. package/src/engine/project/instance-layer.ts +11 -11
  609. package/src/engine/project/instance-runtime.ts +16 -16
  610. package/src/engine/project/instance-store.ts +193 -193
  611. package/src/engine/project/project.sql.ts +17 -17
  612. package/src/engine/project/project.ts +537 -537
  613. package/src/engine/project/schema.ts +13 -13
  614. package/src/engine/project/vcs.ts +405 -405
  615. package/src/engine/provider/auth.ts +225 -225
  616. package/src/engine/provider/error.ts +204 -204
  617. package/src/engine/provider/model-status.ts +8 -8
  618. package/src/engine/provider/provider.ts +1843 -1843
  619. package/src/engine/provider/schema.ts +30 -30
  620. package/src/engine/provider/transform.ts +1376 -1376
  621. package/src/engine/pty/index.ts +365 -365
  622. package/src/engine/pty/input.ts +24 -24
  623. package/src/engine/pty/pty/index.ts +365 -365
  624. package/src/engine/pty/pty/input.ts +24 -24
  625. package/src/engine/pty/pty/pty.bun.ts +26 -26
  626. package/src/engine/pty/pty/pty.node.ts +27 -27
  627. package/src/engine/pty/pty/pty.ts +25 -25
  628. package/src/engine/pty/pty/schema.ts +14 -14
  629. package/src/engine/pty/pty/ticket.ts +68 -68
  630. package/src/engine/pty/pty.bun.ts +26 -26
  631. package/src/engine/pty/pty.node.ts +27 -27
  632. package/src/engine/pty/pty.ts +25 -25
  633. package/src/engine/pty/schema.ts +14 -14
  634. package/src/engine/pty/ticket.ts +68 -68
  635. package/src/engine/question/index.ts +213 -213
  636. package/src/engine/question/question/index.ts +213 -213
  637. package/src/engine/question/question/schema.ts +10 -10
  638. package/src/engine/question/schema.ts +10 -10
  639. package/src/engine/reference/reference/reference.ts +241 -241
  640. package/src/engine/reference/reference/repository-cache.ts +147 -147
  641. package/src/engine/reference/reference.ts +241 -241
  642. package/src/engine/reference/repository-cache.ts +147 -147
  643. package/src/engine/session/compaction.ts +651 -651
  644. package/src/engine/session/instruction.ts +238 -238
  645. package/src/engine/session/llm.ts +459 -459
  646. package/src/engine/session/message-error.ts +14 -14
  647. package/src/engine/session/message-v2.ts +1202 -1202
  648. package/src/engine/session/message.ts +146 -146
  649. package/src/engine/session/overflow.ts +32 -32
  650. package/src/engine/session/processor.ts +823 -823
  651. package/src/engine/session/prompt/anthropic.txt +105 -105
  652. package/src/engine/session/prompt/beast.txt +147 -147
  653. package/src/engine/session/prompt/build-switch.txt +5 -5
  654. package/src/engine/session/prompt/codex.txt +79 -79
  655. package/src/engine/session/prompt/copilot-gpt-5.txt +143 -143
  656. package/src/engine/session/prompt/default.txt +105 -105
  657. package/src/engine/session/prompt/gemini.txt +155 -155
  658. package/src/engine/session/prompt/gpt.txt +107 -107
  659. package/src/engine/session/prompt/kimi.txt +95 -95
  660. package/src/engine/session/prompt/max-steps.txt +15 -15
  661. package/src/engine/session/prompt/plan-reminder-anthropic.txt +67 -67
  662. package/src/engine/session/prompt/plan.txt +26 -26
  663. package/src/engine/session/prompt/trinity.txt +97 -97
  664. package/src/engine/session/prompt.ts +66 -9
  665. package/src/engine/session/retry.ts +200 -200
  666. package/src/engine/session/revert.ts +162 -162
  667. package/src/engine/session/run-state.ts +153 -153
  668. package/src/engine/session/schema.ts +26 -26
  669. package/src/engine/session/session.sql.ts +137 -137
  670. package/src/engine/session/session.ts +1011 -1011
  671. package/src/engine/session/status.ts +94 -94
  672. package/src/engine/session/summary.ts +164 -164
  673. package/src/engine/session/system.ts +84 -84
  674. package/src/engine/session/todo.ts +81 -81
  675. package/src/engine/share/session.ts +61 -61
  676. package/src/engine/share/share-next.ts +376 -376
  677. package/src/engine/share/share.sql.ts +13 -13
  678. package/src/engine/shell/shell/shell.ts +215 -215
  679. package/src/engine/shell/shell.ts +215 -215
  680. package/src/engine/skill/discovery.ts +116 -116
  681. package/src/engine/skill/index.ts +336 -336
  682. package/src/engine/skill/prompt/customize-opencode.md +377 -377
  683. package/src/engine/skill/skill/discovery.ts +116 -116
  684. package/src/engine/skill/skill/index.ts +336 -336
  685. package/src/engine/skill/skill/prompt/customize-opencode.md +377 -377
  686. package/src/engine/snapshot/index.ts +762 -762
  687. package/src/engine/snapshot/snapshot/index.ts +762 -762
  688. package/src/engine/sync/README.md +179 -179
  689. package/src/engine/sync/event.sql.ts +17 -17
  690. package/src/engine/sync/index.ts +410 -410
  691. package/src/engine/sync/schema.ts +11 -11
  692. package/src/engine/temporary.ts +33 -33
  693. package/src/engine/tool/apply_patch.ts +313 -313
  694. package/src/engine/tool/apply_patch.txt +33 -33
  695. package/src/engine/tool/edit.ts +711 -711
  696. package/src/engine/tool/edit.txt +10 -10
  697. package/src/engine/tool/external-directory.ts +49 -49
  698. package/src/engine/tool/glob.ts +103 -103
  699. package/src/engine/tool/glob.txt +6 -6
  700. package/src/engine/tool/grep.ts +156 -156
  701. package/src/engine/tool/grep.txt +8 -8
  702. package/src/engine/tool/invalid.ts +21 -21
  703. package/src/engine/tool/json-schema.ts +164 -164
  704. package/src/engine/tool/lsp.ts +113 -113
  705. package/src/engine/tool/lsp.txt +24 -24
  706. package/src/engine/tool/mcp-websearch.ts +96 -96
  707. package/src/engine/tool/plan-enter.txt +14 -14
  708. package/src/engine/tool/plan-exit.txt +13 -13
  709. package/src/engine/tool/plan.ts +78 -78
  710. package/src/engine/tool/question.ts +44 -44
  711. package/src/engine/tool/question.txt +10 -10
  712. package/src/engine/tool/read.ts +337 -337
  713. package/src/engine/tool/read.txt +14 -14
  714. package/src/engine/tool/registry.ts +472 -472
  715. package/src/engine/tool/repo_clone.ts +80 -80
  716. package/src/engine/tool/repo_clone.txt +5 -5
  717. package/src/engine/tool/repo_overview.ts +279 -279
  718. package/src/engine/tool/repo_overview.txt +4 -4
  719. package/src/engine/tool/schema.ts +14 -14
  720. package/src/engine/tool/shell/id.ts +19 -19
  721. package/src/engine/tool/shell/prompt.ts +295 -295
  722. package/src/engine/tool/shell/shell.txt +77 -77
  723. package/src/engine/tool/shell.ts +647 -647
  724. package/src/engine/tool/skill.ts +75 -75
  725. package/src/engine/tool/skill.txt +5 -5
  726. package/src/engine/tool/task.ts +337 -337
  727. package/src/engine/tool/task.txt +58 -58
  728. package/src/engine/tool/task_status.ts +179 -179
  729. package/src/engine/tool/task_status.txt +13 -13
  730. package/src/engine/tool/todo.ts +57 -57
  731. package/src/engine/tool/todowrite.txt +167 -167
  732. package/src/engine/tool/tool/apply_patch.ts +313 -313
  733. package/src/engine/tool/tool/apply_patch.txt +33 -33
  734. package/src/engine/tool/tool/edit.ts +711 -711
  735. package/src/engine/tool/tool/edit.txt +10 -10
  736. package/src/engine/tool/tool/external-directory.ts +49 -49
  737. package/src/engine/tool/tool/glob.ts +103 -103
  738. package/src/engine/tool/tool/glob.txt +6 -6
  739. package/src/engine/tool/tool/grep.ts +156 -156
  740. package/src/engine/tool/tool/grep.txt +8 -8
  741. package/src/engine/tool/tool/invalid.ts +21 -21
  742. package/src/engine/tool/tool/json-schema.ts +164 -164
  743. package/src/engine/tool/tool/lsp.ts +113 -113
  744. package/src/engine/tool/tool/lsp.txt +24 -24
  745. package/src/engine/tool/tool/mcp-websearch.ts +96 -96
  746. package/src/engine/tool/tool/plan-enter.txt +14 -14
  747. package/src/engine/tool/tool/plan-exit.txt +13 -13
  748. package/src/engine/tool/tool/plan.ts +78 -78
  749. package/src/engine/tool/tool/question.ts +44 -44
  750. package/src/engine/tool/tool/question.txt +10 -10
  751. package/src/engine/tool/tool/read.ts +337 -337
  752. package/src/engine/tool/tool/read.txt +14 -14
  753. package/src/engine/tool/tool/registry.ts +472 -472
  754. package/src/engine/tool/tool/repo_clone.ts +80 -80
  755. package/src/engine/tool/tool/repo_clone.txt +5 -5
  756. package/src/engine/tool/tool/repo_overview.ts +279 -279
  757. package/src/engine/tool/tool/repo_overview.txt +4 -4
  758. package/src/engine/tool/tool/schema.ts +14 -14
  759. package/src/engine/tool/tool/shell/id.ts +19 -19
  760. package/src/engine/tool/tool/shell/prompt.ts +295 -295
  761. package/src/engine/tool/tool/shell/shell.txt +77 -77
  762. package/src/engine/tool/tool/shell.ts +647 -647
  763. package/src/engine/tool/tool/skill.ts +75 -75
  764. package/src/engine/tool/tool/skill.txt +5 -5
  765. package/src/engine/tool/tool/task.ts +337 -337
  766. package/src/engine/tool/tool/task.txt +58 -58
  767. package/src/engine/tool/tool/task_status.ts +179 -179
  768. package/src/engine/tool/tool/task_status.txt +13 -13
  769. package/src/engine/tool/tool/todo.ts +57 -57
  770. package/src/engine/tool/tool/todowrite.txt +167 -167
  771. package/src/engine/tool/tool/tool.ts +164 -164
  772. package/src/engine/tool/tool/truncate.ts +160 -160
  773. package/src/engine/tool/tool/truncation-dir.ts +4 -4
  774. package/src/engine/tool/tool/webfetch.ts +192 -192
  775. package/src/engine/tool/tool/webfetch.txt +13 -13
  776. package/src/engine/tool/tool/websearch.ts +143 -143
  777. package/src/engine/tool/tool/websearch.txt +14 -14
  778. package/src/engine/tool/tool/write.ts +104 -104
  779. package/src/engine/tool/tool/write.txt +8 -8
  780. package/src/engine/tool/tool.ts +164 -164
  781. package/src/engine/tool/truncate.ts +160 -160
  782. package/src/engine/tool/truncation-dir.ts +4 -4
  783. package/src/engine/tool/webfetch.ts +192 -192
  784. package/src/engine/tool/webfetch.txt +13 -13
  785. package/src/engine/tool/websearch.ts +143 -143
  786. package/src/engine/tool/websearch.txt +14 -14
  787. package/src/engine/tool/write.ts +104 -104
  788. package/src/engine/tool/write.txt +8 -8
  789. package/src/engine/util/archive.ts +17 -17
  790. package/src/engine/util/bom.ts +31 -31
  791. package/src/engine/util/data-url.ts +9 -9
  792. package/src/engine/util/defer.ts +10 -10
  793. package/src/engine/util/effect-http-client.ts +11 -11
  794. package/src/engine/util/error.ts +88 -88
  795. package/src/engine/util/filesystem.ts +252 -252
  796. package/src/engine/util/format.ts +20 -20
  797. package/src/engine/util/iife.ts +3 -3
  798. package/src/engine/util/lazy.ts +20 -20
  799. package/src/engine/util/local-context.ts +25 -25
  800. package/src/engine/util/locale.ts +86 -86
  801. package/src/engine/util/media.ts +26 -26
  802. package/src/engine/util/process.ts +176 -176
  803. package/src/engine/util/queue.ts +32 -32
  804. package/src/engine/util/record.ts +3 -3
  805. package/src/engine/util/repository.ts +158 -158
  806. package/src/engine/util/rpc.ts +66 -66
  807. package/src/engine/util/signal.ts +12 -12
  808. package/src/engine/util/timeout.ts +13 -13
  809. package/src/engine/util/token.ts +7 -7
  810. package/src/engine/util/util/archive.ts +17 -17
  811. package/src/engine/util/util/bom.ts +31 -31
  812. package/src/engine/util/util/data-url.ts +9 -9
  813. package/src/engine/util/util/defer.ts +10 -10
  814. package/src/engine/util/util/effect-http-client.ts +11 -11
  815. package/src/engine/util/util/error.ts +88 -88
  816. package/src/engine/util/util/filesystem.ts +252 -252
  817. package/src/engine/util/util/format.ts +20 -20
  818. package/src/engine/util/util/iife.ts +3 -3
  819. package/src/engine/util/util/lazy.ts +20 -20
  820. package/src/engine/util/util/local-context.ts +25 -25
  821. package/src/engine/util/util/locale.ts +86 -86
  822. package/src/engine/util/util/media.ts +26 -26
  823. package/src/engine/util/util/process.ts +176 -176
  824. package/src/engine/util/util/queue.ts +32 -32
  825. package/src/engine/util/util/record.ts +3 -3
  826. package/src/engine/util/util/repository.ts +158 -158
  827. package/src/engine/util/util/rpc.ts +66 -66
  828. package/src/engine/util/util/signal.ts +12 -12
  829. package/src/engine/util/util/timeout.ts +13 -13
  830. package/src/engine/util/util/token.ts +7 -7
  831. package/src/engine/util/util/which.ts +14 -14
  832. package/src/engine/util/util/wildcard.ts +59 -59
  833. package/src/engine/util/which.ts +14 -14
  834. package/src/engine/util/wildcard.ts +59 -59
  835. package/src/engine/worktree/index.ts +621 -621
  836. package/src/export.ts +122 -0
  837. package/src/mdns.ts +53 -0
  838. package/src/server.ts +151 -156
  839. package/src/stats.ts +290 -0
  840. package/src/tui.ts +964 -480
package/src/tui.ts CHANGED
@@ -1,16 +1,16 @@
1
- /**
2
- * BoneCode Interactive Terminal UI
1
+ /**
2
+ * BoneCode TUI terminal interface modeled after OpenCode
3
+ *
4
+ * Three core behaviours:
5
+ * 1. Tool activity is displayed as concise status lines
6
+ * (← Edit src/foo.ts, → Read package.json, $ npm test) — never raw code dumps.
7
+ * Assistant text is shown inline; tool calls and tool outputs are summarized.
3
8
  *
4
- * Split-footer terminal session:
5
- * - Scrollback output fills the top of the terminal
6
- * - A persistent prompt sits at the bottom
7
- * - Streaming LLM output appears above the prompt in real time
8
- * - Ctrl+C interrupts the current request (does not exit)
9
- * - /new starts a fresh session, /exit or Ctrl+D quits
10
- * - Up/Down arrows navigate prompt history (readline built-in)
11
- * - @<path> autocomplete for files in the current worktree
9
+ * 2. Typing "/" shows an inline command menu above the prompt with
10
+ * arrow-key selection and tab/enter to insert.
12
11
  *
13
- * Built on Node's built-in readline no external TUI framework needed.
12
+ * 3. Ctrl+C aborts the in-flight LLM stream by aborting the fetch
13
+ * AND notifying the server to cancel the agent loop.
14
14
  */
15
15
 
16
16
  import * as path from "path";
@@ -18,67 +18,144 @@ import * as fs from "fs";
18
18
  import * as readline from "readline";
19
19
  import * as http from "http";
20
20
 
21
- // ─── ANSI helpers ─────────────────────────────────────────────────────────────
21
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
22
22
 
23
23
  const ESC = "\x1b";
24
- const RESET = `${ESC}[0m`;
25
- const BOLD = `${ESC}[1m`;
26
- const DIM = `${ESC}[2m`;
27
- const CYAN = `${ESC}[36m`;
28
- const GREEN = `${ESC}[32m`;
29
- const YELLOW = `${ESC}[33m`;
30
- const RED = `${ESC}[31m`;
31
- const CLEAR_LINE = `${ESC}[2K\r`;
32
- const CURSOR_UP = `${ESC}[1A`;
33
-
34
- function stripAnsi(str: string): string {
35
- return str.replace(/\x1b\[[0-9;]*[mGKHF]/g, "");
24
+ const R = `${ESC}[0m`;
25
+ const BOLD = `${ESC}[1m`;
26
+ const DIM = `${ESC}[2m`;
27
+ const CYAN = `${ESC}[96m`;
28
+ const GREEN = `${ESC}[92m`;
29
+ const YELLOW= `${ESC}[93m`;
30
+ const RED = `${ESC}[91m`;
31
+ const GRAY = `${ESC}[90m`;
32
+ const WHITE = `${ESC}[97m`;
33
+ const BLUE = `${ESC}[94m`;
34
+
35
+ // Box drawing
36
+ const VERT = "┃";
37
+ const CORN = "╹";
38
+ const SHADE = "▀";
39
+ const BLOCK = "▣";
40
+ const ARROW_R = "→";
41
+ const ARROW_L = "←";
42
+ const DOLLAR = "$";
43
+ const STAR = "✱";
44
+ const PCT = "%";
45
+ const GEAR = "⚙";
46
+ const HASH = "#";
47
+
48
+ function cols(): number { return process.stdout.columns || 80; }
49
+ function out(s: string) { process.stdout.write(s); }
50
+ function nl(s = "") { process.stdout.write(s + "\n"); }
51
+ function clearLine() { out(`\r${ESC}[2K`); }
52
+
53
+ // ─── Logo ─────────────────────────────────────────────────────────────────────
54
+
55
+ function printLogo() {
56
+ nl();
57
+ nl(`${GRAY}${BOLD}█▀▀▄ █▀▀█ █▀▀█ █▀▀▀ █▀▀▀ █▀▀█ █▀▀▄ █▀▀▀${R}`);
58
+ nl(`${GRAY}${BOLD}█▀▀▄ █ █ █ █ █▀▀ █ █ █ █ █ █▀▀ ${R}`);
59
+ nl(`${GRAY}${BOLD}▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀▀${R}`);
60
+ nl();
36
61
  }
37
62
 
38
- function truncate(str: string, width: number): string {
39
- const clean = stripAnsi(str);
40
- if (clean.length <= width) return str;
41
- return str.slice(0, width - 1) + "";
63
+ // ─── Package version ──────────────────────────────────────────────────────────
64
+
65
+ const PKG_ROOT = (() => {
66
+ const fromSrc = path.resolve(__dirname, "..");
67
+ const fromDist = path.resolve(__dirname, "..", "..");
68
+ if (fs.existsSync(path.join(fromDist, "package.json"))) return fromDist;
69
+ return fromSrc;
70
+ })();
71
+
72
+ function getVersion(): string {
73
+ try { return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8")).version; }
74
+ catch { return "0.0.0"; }
42
75
  }
43
76
 
44
- // ─── Config ───────────────────────────────────────────────────────────────────
77
+ // ─── Slash command catalog ────────────────────────────────────────────────────
45
78
 
46
- const PKG_ROOT = path.resolve(__dirname, "..");
79
+ interface Command {
80
+ name: string;
81
+ description: string;
82
+ args?: string;
83
+ }
47
84
 
48
- function getVersion(): string {
49
- try {
50
- return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8")).version;
51
- } catch {
52
- return "0.0.0";
85
+ const COMMANDS: Command[] = [
86
+ { name: "/new", description: "Start a new session" },
87
+ { name: "/session", description: "Show current session ID" },
88
+ { name: "/sessions", description: "List recent sessions" },
89
+ { name: "/model", description: "Switch model", args: "<provider/model>" },
90
+ { name: "/provider", description: "Switch provider", args: "<id>" },
91
+ { name: "/providers", description: "List all providers" },
92
+ { name: "/clear", description: "Clear screen" },
93
+ { name: "/history", description: "Show last 10 prompts" },
94
+ { name: "/help", description: "Show this help" },
95
+ { name: "/exit", description: "Exit BoneCode" },
96
+ ];
97
+
98
+ // ─── @file autocomplete helpers ───────────────────────────────────────────────
99
+
100
+ const IGNORED_DIRS = new Set([
101
+ "node_modules", ".git", "dist", "build", ".next", "__pycache__",
102
+ ".venv", "venv", "target", "vendor", ".cache", "coverage",
103
+ ]);
104
+ const CODE_EXTS = new Set([
105
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs",
106
+ ".java", ".kt", ".cs", ".cpp", ".c", ".h", ".rb", ".php", ".swift",
107
+ ".md", ".mdx", ".json", ".yaml", ".yml", ".toml", ".env", ".sql", ".sh", ".graphql",
108
+ ]);
109
+
110
+ function listFiles(worktree: string, prefix: string): string[] {
111
+ const results: string[] = [];
112
+ function walk(dir: string, depth: number) {
113
+ if (results.length >= 50 || depth > 4) return;
114
+ let entries: fs.Dirent[];
115
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
116
+ for (const e of entries) {
117
+ if (results.length >= 50) break;
118
+ if (IGNORED_DIRS.has(e.name) || e.name.startsWith(".")) continue;
119
+ const rel = path.relative(worktree, path.join(dir, e.name));
120
+ if (e.isDirectory()) {
121
+ if (!prefix || rel.startsWith(prefix) || prefix.startsWith(rel)) {
122
+ results.push(rel + "/");
123
+ walk(path.join(dir, e.name), depth + 1);
124
+ }
125
+ } else if (CODE_EXTS.has(path.extname(e.name).toLowerCase())) {
126
+ if (!prefix || rel.startsWith(prefix)) results.push(rel);
127
+ }
128
+ }
53
129
  }
130
+ walk(worktree, 0);
131
+ return results.sort();
54
132
  }
55
133
 
56
- // ─── TUI State ────────────────────────────────────────────────────────────────
57
-
58
- interface TUIState {
59
- sessionId: string | null;
60
- model: string;
61
- provider: string;
62
- port: number;
63
- token: string;
64
- worktree: string;
65
- promptHistory: string[];
66
- streaming: boolean;
67
- abortController: AbortController | null;
68
- turnCount: number;
69
- totalTokens: number;
134
+ function buildCompleter(worktree: string) {
135
+ return (line: string): [string[], string] => {
136
+ if (line.startsWith("/")) {
137
+ // Tab-complete commands too (in addition to the inline menu)
138
+ const matches = COMMANDS.map(c => c.name).filter(c => c.startsWith(line));
139
+ return [matches, line];
140
+ }
141
+ const atIdx = line.lastIndexOf("@");
142
+ if (atIdx !== -1) {
143
+ const prefix = line.slice(atIdx + 1);
144
+ const completions = listFiles(worktree, prefix).map(f => line.slice(0, atIdx + 1) + f);
145
+ return [completions, line];
146
+ }
147
+ return [[], line];
148
+ };
70
149
  }
71
150
 
72
- // ─── Server health check ──────────────────────────────────────────────────────
151
+ // ─── Server health ────────────────────────────────────────────────────────────
73
152
 
74
- async function waitForServer(port: number, maxWaitMs = 30_000): Promise<boolean> {
153
+ async function waitForServer(port: number, maxMs = 30_000): Promise<boolean> {
75
154
  const start = Date.now();
76
- while (Date.now() - start < maxWaitMs) {
155
+ while (Date.now() - start < maxMs) {
77
156
  try {
78
157
  const ok = await new Promise<boolean>((resolve) => {
79
- const req = http.get(`http://localhost:${port}/health`, (res) => {
80
- resolve(res.statusCode === 200);
81
- });
158
+ const req = http.get(`http://localhost:${port}/health`, (res) => resolve(res.statusCode === 200));
82
159
  req.on("error", () => resolve(false));
83
160
  req.setTimeout(1000, () => { req.destroy(); resolve(false); });
84
161
  });
@@ -89,7 +166,13 @@ async function waitForServer(port: number, maxWaitMs = 30_000): Promise<boolean>
89
166
  return false;
90
167
  }
91
168
 
92
- // ─── API helpers ──────────────────────────────────────────────────────────────
169
+ // ─── API ──────────────────────────────────────────────────────────────────────
170
+
171
+ async function apiGet<T>(url: string, token: string): Promise<T> {
172
+ const r = await fetch(url, { headers: { "Authorization": `Bearer ${token}` } });
173
+ if (!r.ok) throw new Error(`API ${r.status}`);
174
+ return r.json() as Promise<T>;
175
+ }
93
176
 
94
177
  async function apiPost(url: string, body: object, token: string): Promise<Response> {
95
178
  return fetch(url, {
@@ -99,538 +182,939 @@ async function apiPost(url: string, body: object, token: string): Promise<Respon
99
182
  });
100
183
  }
101
184
 
102
- async function apiGetJson<T = unknown>(url: string, token: string): Promise<T> {
103
- const r = await fetch(url, { headers: { "Authorization": `Bearer ${token}` } });
104
- if (!r.ok) throw new Error(`API error ${r.status}: ${await r.text()}`);
105
- return r.json() as Promise<T>;
185
+ async function apiDelete(url: string, token: string): Promise<void> {
186
+ await fetch(url, { method: "DELETE", headers: { "Authorization": `Bearer ${token}` } });
106
187
  }
107
188
 
108
- // ─── Session management ───────────────────────────────────────────────────────
109
-
110
- async function createSession(state: TUIState, title: string): Promise<string> {
111
- const r = await apiPost(
112
- `http://localhost:${state.port}/v2/session`,
113
- { title, directory: state.worktree },
114
- state.token
115
- );
189
+ async function createSession(port: number, token: string, worktree: string, title: string): Promise<string> {
190
+ const r = await apiPost(`http://localhost:${port}/v2/session`, { title, directory: worktree }, token);
116
191
  if (!r.ok) throw new Error(`Failed to create session: ${await r.text()}`);
117
192
  const sess = await r.json() as { id?: string };
118
- if (!sess.id) throw new Error("Server returned session without ID");
193
+ if (!sess.id) throw new Error("No session ID returned");
119
194
  return sess.id;
120
195
  }
121
196
 
122
- // ─── @file autocomplete ───────────────────────────────────────────────────────
197
+ // ─── Tool display rules — modeled after opencode's tool.ts ────────────────────
123
198
 
124
- const IGNORED_DIRS = new Set([
125
- "node_modules", ".git", "dist", "build", ".next", "__pycache__",
126
- ".venv", "venv", "target", "vendor", ".cache", "coverage",
127
- ]);
199
+ interface ToolDisplay {
200
+ icon: string;
201
+ color: string;
202
+ title: string;
203
+ detail?: string;
204
+ }
128
205
 
129
- const CODE_EXTS = new Set([
130
- ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
131
- ".py", ".go", ".rs", ".java", ".kt", ".cs", ".cpp", ".c", ".h",
132
- ".rb", ".php", ".swift", ".scala",
133
- ".md", ".mdx", ".json", ".yaml", ".yml", ".toml", ".env",
134
- ".sql", ".sh", ".bash", ".graphql",
135
- ]);
206
+ function relPath(p: string | undefined, worktree: string): string {
207
+ if (!p) return "";
208
+ try {
209
+ if (path.isAbsolute(p)) {
210
+ const rel = path.relative(worktree, p);
211
+ if (!rel.startsWith("..")) return rel.replace(/\\/g, "/");
212
+ }
213
+ return p.replace(/\\/g, "/");
214
+ } catch { return p; }
215
+ }
136
216
 
137
- function listFilesForAutocomplete(worktree: string, prefix: string): string[] {
138
- const results: string[] = [];
139
- const MAX = 50;
217
+ function describeTool(toolName: string, input: any, worktree: string): ToolDisplay {
218
+ const n = (toolName || "").toLowerCase();
219
+ const inp = input || {};
220
+
221
+ // Map BoneCode's tool names AND opencode's names
222
+ const isWrite = n === "write" || n === "write_file";
223
+ const isEdit = n === "edit" || n === "edit_file";
224
+ const isRead = n === "read" || n === "read_file";
225
+ const isShell = n === "bash" || n === "shell" || n === "run_command";
226
+ const isGlob = n === "glob";
227
+ const isGrep = n === "grep" || n === "search_files";
228
+ const isList = n === "list_directory" || n === "list" || n === "ls";
229
+ const isWebfetch = n === "webfetch";
230
+ const isWebsearch = n === "websearch";
231
+ const isPatch = n === "apply_patch" || n === "patch";
232
+ const isTodo = n === "todo_write" || n === "todowrite" || n === "todo";
233
+ const isTask = n === "task" || n === "subagent";
234
+
235
+ if (isWrite) {
236
+ return { icon: ARROW_L, color: CYAN, title: "Write", detail: relPath(inp.path || inp.filePath, worktree) };
237
+ }
238
+ if (isEdit) {
239
+ return { icon: ARROW_L, color: CYAN, title: "Edit", detail: relPath(inp.path || inp.filePath, worktree) };
240
+ }
241
+ if (isPatch) {
242
+ return { icon: PCT, color: CYAN, title: "Patch", detail: inp.patch ? `${String(inp.patch).split("\n").length} lines` : "" };
243
+ }
244
+ if (isRead) {
245
+ let detail = relPath(inp.path || inp.filePath, worktree);
246
+ if (inp.start_line || inp.end_line) {
247
+ detail += ` ${GRAY}[${inp.start_line || 1}-${inp.end_line || ""}]${R}`;
248
+ }
249
+ return { icon: ARROW_R, color: GRAY, title: "Read", detail };
250
+ }
251
+ if (isShell) {
252
+ const cmd = String(inp.command || "").trim();
253
+ const short = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
254
+ return { icon: DOLLAR, color: YELLOW, title: short || "Shell" };
255
+ }
256
+ if (isGlob) {
257
+ return { icon: STAR, color: GRAY, title: "Glob", detail: inp.pattern || "" };
258
+ }
259
+ if (isGrep) {
260
+ const pattern = inp.pattern || "";
261
+ const glob = inp.glob ? ` in ${inp.glob}` : "";
262
+ return { icon: STAR, color: GRAY, title: "Grep", detail: `"${pattern}"${glob}` };
263
+ }
264
+ if (isList) {
265
+ return { icon: ARROW_R, color: GRAY, title: "List", detail: relPath(inp.path, worktree) };
266
+ }
267
+ if (isWebfetch) {
268
+ return { icon: PCT, color: BLUE, title: "WebFetch", detail: inp.url || "" };
269
+ }
270
+ if (isWebsearch) {
271
+ return { icon: PCT, color: BLUE, title: "WebSearch", detail: inp.query ? `"${inp.query}"` : "" };
272
+ }
273
+ if (isTodo) {
274
+ const todos = Array.isArray(inp.todos) ? inp.todos : [];
275
+ const done = todos.filter((t: any) => t.status === "completed").length;
276
+ return { icon: HASH, color: GRAY, title: "Todos", detail: `${done}/${todos.length} done` };
277
+ }
278
+ if (isTask) {
279
+ return { icon: HASH, color: CYAN, title: "Task", detail: inp.description || "" };
280
+ }
140
281
 
141
- function walk(dir: string, depth: number) {
142
- if (results.length >= MAX || depth > 4) return;
143
- let entries: fs.Dirent[];
144
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
145
- catch { return; }
282
+ // Fallback
283
+ return { icon: GEAR, color: GRAY, title: toolName };
284
+ }
146
285
 
147
- for (const e of entries) {
148
- if (results.length >= MAX) break;
149
- if (IGNORED_DIRS.has(e.name) || e.name.startsWith(".")) continue;
150
- const rel = path.relative(worktree, path.join(dir, e.name));
151
- if (e.isDirectory()) {
152
- if (!prefix || rel.startsWith(prefix) || prefix.startsWith(rel)) {
153
- results.push(rel + "/");
154
- walk(path.join(dir, e.name), depth + 1);
155
- }
156
- } else if (CODE_EXTS.has(path.extname(e.name).toLowerCase())) {
157
- if (!prefix || rel.startsWith(prefix)) {
158
- results.push(rel);
159
- }
160
- }
161
- }
286
+ function renderToolStart(d: ToolDisplay) {
287
+ const detail = d.detail ? ` ${GRAY}${d.detail}${R}` : "";
288
+ nl(` ${d.color}${d.icon}${R} ${WHITE}${d.title}${R}${detail}`);
289
+ }
290
+
291
+ function renderToolDone(d: ToolDisplay, ms?: number) {
292
+ const detail = d.detail ? ` ${GRAY}${d.detail}${R}` : "";
293
+ const time = ms !== undefined ? ` ${GRAY}${(ms / 1000).toFixed(1)}s${R}` : "";
294
+ // Replace the "..." with a checkmark (or just write a new line if not interactive)
295
+ nl(` ${GREEN}✓${R} ${GRAY}${d.title}${R}${detail}${time}`);
296
+ }
297
+
298
+ function renderToolError(d: ToolDisplay, err: string) {
299
+ const detail = d.detail ? ` ${GRAY}${d.detail}${R}` : "";
300
+ nl(` ${RED}✗${R} ${GRAY}${d.title}${R}${detail} ${RED}${err}${R}`);
301
+ }
302
+
303
+ // ─── User message rendering ───────────────────────────────────────────────────
304
+
305
+ function renderUserMessage(text: string) {
306
+ nl();
307
+ for (const line of text.split("\n")) {
308
+ nl(`${GRAY}${VERT}${R} ${WHITE}${line}${R}`);
309
+ }
310
+ nl();
311
+ }
312
+
313
+ function renderTurnEnd(model: string, elapsedMs: number, interrupted: boolean) {
314
+ const elapsed = (elapsedMs / 1000).toFixed(1);
315
+ const dur = interrupted ? `${YELLOW}interrupted${R}` : `${GRAY}${elapsed}s${R}`;
316
+ nl();
317
+ nl(` ${CYAN}${BLOCK}${R} ${WHITE}Build${R}${GRAY} · ${model} · ${R}${dur}`);
318
+ }
319
+
320
+ // ─── Help / providers / commands menu ─────────────────────────────────────────
321
+
322
+ function printHelp() {
323
+ nl();
324
+ nl(`${CYAN}${BOLD}BoneCode${R} ${GRAY}commands${R}`);
325
+ nl();
326
+ for (const c of COMMANDS) {
327
+ const args = c.args ? ` ${GRAY}${c.args}${R}` : "";
328
+ nl(` ${CYAN}${c.name.padEnd(12)}${R}${args.padEnd(20)} ${GRAY}${c.description}${R}`);
162
329
  }
330
+ nl();
331
+ nl(` ${CYAN}Ctrl+C${R} ${GRAY}Interrupt current request${R}`);
332
+ nl(` ${CYAN}Ctrl+D${R} ${GRAY}Exit BoneCode${R}`);
333
+ nl(` ${CYAN}↑ / ↓${R} ${GRAY}Prompt history${R}`);
334
+ nl(` ${CYAN}@<path>${R} ${GRAY}Attach a file (Tab completes)${R}`);
335
+ nl();
336
+ }
163
337
 
164
- walk(worktree, 0);
165
- return results.sort();
338
+ async function fetchProviders(port: number, token: string): Promise<any[]> {
339
+ try { return await apiGet<any[]>(`http://localhost:${port}/v2/provider`, token); }
340
+ catch { return []; }
166
341
  }
167
342
 
168
- function buildCompleter(worktree: string) {
169
- return function completer(line: string): [string[], string] {
170
- // Only trigger on @ mentions
171
- const atIdx = line.lastIndexOf("@");
172
- if (atIdx === -1) return [[], line];
343
+ function printProviders(providers: any[], currentProvider: string) {
344
+ const width = cols();
345
+ nl();
346
+ nl(`${CYAN}${BOLD}Providers${R}`);
347
+ nl(`${GRAY}${"─".repeat(Math.min(width, 56))}${R}`);
348
+ for (const p of providers) {
349
+ const active = p.id === currentProvider;
350
+ const dot = active ? `${GREEN}●${R}` : `${GRAY}○${R}`;
351
+ const free = p.free ? ` ${GREEN}free${R}` : "";
352
+ const local = p.id === "local" ? ` ${CYAN}local config${R}` : "";
353
+ nl(` ${dot} ${active ? CYAN + BOLD : WHITE}${p.id}${R}${free}${local} ${GRAY}${p.name}${R}`);
354
+ if (p.freeNote) nl(` ${GRAY}${p.freeNote}${R}`);
355
+ if (p.models?.length) nl(` ${GRAY}${p.models.slice(0, 3).join(" ")}${R}`);
356
+ if (p.keyEnv) nl(` ${GRAY}${p.keyEnv}${R}`);
357
+ }
358
+ nl(`${GRAY}${"─".repeat(Math.min(width, 56))}${R}`);
359
+ nl(` ${GRAY}/provider <id> /model <id>${R}`);
360
+ nl();
361
+ }
173
362
 
174
- const prefix = line.slice(atIdx + 1);
175
- const files = listFilesForAutocomplete(worktree, prefix);
176
- const completions = files.map(f => line.slice(0, atIdx + 1) + f);
177
- return [completions, line];
178
- };
363
+ // ─── Streaming ────────────────────────────────────────────────────────────────
364
+
365
+ interface StreamResult {
366
+ text: string;
367
+ tokens: number;
368
+ elapsedMs: number;
369
+ error?: string;
370
+ interrupted: boolean;
179
371
  }
180
372
 
181
- // ─── Streaming output ─────────────────────────────────────────────────────────
373
+ interface ToolTrack {
374
+ callId: string;
375
+ display: ToolDisplay;
376
+ startedAt: number;
377
+ }
182
378
 
183
- async function streamPrompt(
184
- state: TUIState,
185
- message: string,
186
- onDelta: (text: string) => void,
187
- onDone: (info: { tokens?: number; error?: string }) => void
188
- ): Promise<void> {
189
- if (!state.sessionId) throw new Error("No active session");
379
+ /**
380
+ * Stateful code-fence collapser.
381
+ *
382
+ * The LLM streams text token-by-token, including ```python\n...\n``` blocks.
383
+ * We don't want to dump that raw to the terminal because:
384
+ * (a) The code is going to be saved via a tool call anyway, so showing it
385
+ * twice (raw stream + saved file path) is redundant.
386
+ * (b) It can be 100s of lines long and overwhelms the TUI.
387
+ *
388
+ * Strategy: buffer everything between ``` and ```. When the closing fence
389
+ * arrives, emit a one-line marker like [code: python, 42 lines]
390
+ * instead of the buffered content.
391
+ *
392
+ * State machine:
393
+ * "text" — pass deltas through verbatim (with partial-fence detection)
394
+ * "fence" — buffer everything; emit marker on close fence
395
+ */
396
+ function makeCodeFenceCollapser() {
397
+ let mode: "text" | "fence" = "text";
398
+ let lang = "";
399
+ let buffered = ""; // bytes accumulated inside a fence
400
+ let pending = ""; // partial-fence buffer (when we see backticks but don't know yet)
401
+
402
+ const lines = (s: string) => s.split("\n").length;
403
+
404
+ function flushPending(): string {
405
+ const out = pending;
406
+ pending = "";
407
+ return out;
408
+ }
409
+
410
+ return {
411
+ feed(chunk: string): string {
412
+ let result = "";
413
+ let i = 0;
414
+ const buf = pending + chunk;
415
+ pending = "";
416
+
417
+ while (i < buf.length) {
418
+ if (mode === "text") {
419
+ // Look for ``` to enter fence mode
420
+ const fenceIdx = buf.indexOf("```", i);
421
+ if (fenceIdx === -1) {
422
+ // No fence in remaining text — emit it all, but hold the last 2 chars
423
+ // in case they're part of an upcoming fence.
424
+ const safeEnd = Math.max(i, buf.length - 2);
425
+ result += buf.slice(i, safeEnd);
426
+ pending = buf.slice(safeEnd);
427
+ i = buf.length;
428
+ break;
429
+ }
430
+ // Emit text up to the fence
431
+ result += buf.slice(i, fenceIdx);
432
+ // Read the language (everything until newline)
433
+ const nlIdx = buf.indexOf("\n", fenceIdx + 3);
434
+ if (nlIdx === -1) {
435
+ // Don't have the full opening line yet — buffer
436
+ pending = buf.slice(fenceIdx);
437
+ i = buf.length;
438
+ break;
439
+ }
440
+ lang = buf.slice(fenceIdx + 3, nlIdx).trim() || "code";
441
+ i = nlIdx + 1;
442
+ mode = "fence";
443
+ buffered = "";
444
+ // Print a leading marker (will be amended when we close)
445
+ result += `${GRAY}┃ code: ${lang}…${R}`;
446
+ continue;
447
+ }
448
+
449
+ // mode === "fence" — look for closing ```
450
+ const closeIdx = buf.indexOf("```", i);
451
+ if (closeIdx === -1) {
452
+ buffered += buf.slice(i);
453
+ // Hold last 2 chars in case they're part of an upcoming close fence
454
+ const safeEnd = Math.max(i, buf.length - 2);
455
+ buffered = buffered.slice(0, buffered.length - (buf.length - safeEnd));
456
+ pending = buf.slice(safeEnd);
457
+ i = buf.length;
458
+ break;
459
+ }
460
+ // Closing fence found
461
+ buffered += buf.slice(i, closeIdx);
462
+ const lineCount = lines(buffered.replace(/\n+$/, ""));
463
+ // Replace the placeholder we already wrote with the final marker.
464
+ // Carriage return + clear line + reprint marker.
465
+ result += `\r${ESC}[2K ${GRAY}┃ code: ${lang}, ${lineCount} line${lineCount === 1 ? "" : "s"}${R}\n`;
466
+ i = closeIdx + 3;
467
+ // Skip optional newline after closing fence
468
+ if (buf[i] === "\n") i++;
469
+ mode = "text";
470
+ lang = "";
471
+ buffered = "";
472
+ }
473
+
474
+ return result;
475
+ },
476
+ flush(): string {
477
+ const tail = flushPending();
478
+ if (mode === "fence") {
479
+ // Stream ended mid-fence — close it with an approximate count
480
+ const lineCount = lines(buffered.replace(/\n+$/, ""));
481
+ mode = "text";
482
+ return tail + `\r${ESC}[2K ${GRAY}┃ code: ${lang}, ${lineCount}+ lines${R}\n`;
483
+ }
484
+ return tail;
485
+ },
486
+ };
487
+ }
190
488
 
191
- const ctrl = new AbortController();
192
- state.abortController = ctrl;
489
+ async function streamPrompt(opts: {
490
+ port: number; token: string; sessionId: string;
491
+ model: string; provider: string; message: string;
492
+ worktree: string;
493
+ abortSignal: AbortSignal;
494
+ }): Promise<StreamResult> {
495
+ const { port, token, sessionId, model, provider, message, worktree, abortSignal } = opts;
496
+ const t0 = Date.now();
497
+ let fullText = "";
498
+ let tokens = 0;
499
+ let inAssistantText = false;
500
+ let tools = new Map<string, ToolTrack>();
501
+
502
+ // Code-fence collapser — replaces ```...``` blocks with [code: lang, N lines]
503
+ // markers across delta boundaries.
504
+ const fence = makeCodeFenceCollapser();
505
+ const collapseCodeFences = (chunk: string): string => fence.feed(chunk);
506
+
507
+ // Track which message the deltas belong to so we can detect tool boundaries
508
+ // The compat adapter emits these event types:
509
+ // part.delta — text chunk
510
+ // tool.requested — tool call started
511
+ // message.updated — message saved
512
+ // session.updated — session state changed
513
+ // error — error event
514
+
515
+ const flushTextLine = () => {
516
+ if (inAssistantText) {
517
+ // Make sure assistant text ends with a newline before the next thing
518
+ if (fullText && !fullText.endsWith("\n")) out("\n");
519
+ inAssistantText = false;
520
+ }
521
+ };
193
522
 
194
523
  try {
195
- const r = await fetch(`http://localhost:${state.port}/v2/session/${state.sessionId}/prompt`, {
524
+ const r = await fetch(`http://localhost:${port}/v2/session/${sessionId}/prompt`, {
196
525
  method: "POST",
197
- headers: {
198
- "Content-Type": "application/json",
199
- "Authorization": `Bearer ${state.token}`,
200
- },
201
- body: JSON.stringify({ content: message, modelID: state.model, providerID: state.provider }),
202
- signal: ctrl.signal,
526
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
527
+ body: JSON.stringify({ content: message, modelID: model, providerID: provider }),
528
+ signal: abortSignal,
203
529
  });
204
-
205
530
  if (!r.ok) {
206
- onDone({ error: `HTTP ${r.status}: ${await r.text()}` });
207
- return;
531
+ return { text: "", tokens: 0, elapsedMs: Date.now() - t0, error: `HTTP ${r.status}: ${await r.text()}`, interrupted: false };
208
532
  }
209
533
 
210
534
  const reader = r.body!.getReader();
211
- const decoder = new TextDecoder();
212
- let buffer = "";
535
+ const dec = new TextDecoder();
536
+ let buf = "";
213
537
 
214
538
  while (true) {
215
539
  const { value, done } = await reader.read();
216
540
  if (done) break;
217
- buffer += decoder.decode(value, { stream: true });
541
+ buf += dec.decode(value, { stream: true });
542
+ const lines = buf.split("\n");
543
+ buf = lines.pop() || "";
218
544
 
219
- const lines = buffer.split("\n");
220
- buffer = lines.pop() || "";
545
+ for (const raw of lines) {
546
+ if (!raw.startsWith("data: ")) continue;
547
+ const json = raw.slice(6).trim();
548
+ if (!json || json === "[DONE]") continue;
221
549
 
222
- for (const line of lines) {
223
- if (!line.startsWith("data: ")) continue;
224
550
  try {
225
- const event = JSON.parse(line.slice(6));
226
- if (event.type === "part.delta" && event.delta?.type === "text") {
227
- onDelta(event.delta.text);
551
+ const ev = JSON.parse(json);
552
+
553
+ // Text delta — assistant is generating prose
554
+ if (ev.type === "part.delta" && ev.delta?.type === "text") {
555
+ const text = ev.delta.text || "";
556
+ if (!text) continue;
557
+ if (!inAssistantText) {
558
+ // Start of assistant text — print the indent prefix once
559
+ out(` `);
560
+ inAssistantText = true;
561
+ }
562
+ // Process the delta through the code-fence collapser so streaming
563
+ // code blocks appear as "[code: lang]" placeholders instead of
564
+ // dumping raw source.
565
+ const piece = collapseCodeFences(text);
566
+ // Print with leading-newline indenting (so each new line gets the 3-space prefix)
567
+ const indented = piece.replace(/\n/g, `\n `);
568
+ out(indented);
569
+ fullText += text;
570
+ continue;
571
+ }
572
+
573
+ // Tool requested — show concise activity line
574
+ if (ev.type === "tool.requested") {
575
+ flushTextLine();
576
+ const callId = ev.tool_call_id || ev.id || `${ev.tool_name}-${Date.now()}`;
577
+ const display = describeTool(ev.tool_name || "tool", ev.tool_input || {}, worktree);
578
+ tools.set(callId, { callId, display, startedAt: Date.now() });
579
+ renderToolStart(display);
580
+ continue;
581
+ }
582
+
583
+ // Tool completed
584
+ if (ev.type === "tool.completed" || ev.type === "tool.success") {
585
+ flushTextLine();
586
+ const callId = ev.tool_call_id || ev.id || "";
587
+ const tracked = tools.get(callId);
588
+ if (tracked) {
589
+ const ms = Date.now() - tracked.startedAt;
590
+ renderToolDone(tracked.display, ms);
591
+ tools.delete(callId);
592
+ }
593
+ continue;
594
+ }
595
+
596
+ // Tool failed
597
+ if (ev.type === "tool.failed" || ev.type === "tool.error") {
598
+ flushTextLine();
599
+ const callId = ev.tool_call_id || ev.id || "";
600
+ const tracked = tools.get(callId);
601
+ const err = ev.error || ev.message || "failed";
602
+ if (tracked) {
603
+ renderToolError(tracked.display, err);
604
+ tools.delete(callId);
605
+ } else {
606
+ nl(` ${RED}✗ ${err}${R}`);
607
+ }
608
+ continue;
609
+ }
610
+
611
+ // Retry notification
612
+ if (ev.type === "session.retry") {
613
+ flushTextLine();
614
+ nl(` ${YELLOW}⟳ Retry ${ev.attempt || ""}: ${ev.message || ""}${R}`);
615
+ continue;
616
+ }
617
+
618
+ // Server-side error
619
+ if (ev.type === "error") {
620
+ flushTextLine();
621
+ return {
622
+ text: fullText,
623
+ tokens,
624
+ elapsedMs: Date.now() - t0,
625
+ error: ev.properties?.message || "error",
626
+ interrupted: false,
627
+ };
228
628
  }
229
- if (event.type === "error") {
230
- onDone({ error: event.properties?.message || "Unknown error" });
231
- return;
629
+
630
+ // Compaction
631
+ if (ev.type === "session.compacted") {
632
+ flushTextLine();
633
+ nl(` ${BLUE}⊕ Context compacted${R}`);
634
+ continue;
232
635
  }
233
- } catch { /* non-JSON SSE line */ }
636
+ } catch {
637
+ // Ignore malformed events
638
+ }
234
639
  }
235
640
  }
236
641
 
237
- // Poll for final stored response to get token counts
238
- await new Promise(r => setTimeout(r, 800));
239
- try {
240
- const msgs = await apiGetJson<any[]>(
241
- `http://localhost:${state.port}/v2/session/${state.sessionId}/message`,
242
- state.token
243
- );
244
- const lastAssistant = msgs.filter(m => m.role === "assistant").slice(-1)[0];
245
- const tokens = (lastAssistant?.tokens?.input || 0) + (lastAssistant?.tokens?.output || 0);
246
- onDone({ tokens });
247
- } catch {
248
- onDone({});
642
+ flushTextLine();
643
+
644
+ // Drain any tools that didn't get a completion event
645
+ for (const tracked of tools.values()) {
646
+ const ms = Date.now() - tracked.startedAt;
647
+ renderToolDone(tracked.display, ms);
249
648
  }
649
+ tools.clear();
650
+
651
+ // Flush any unclosed code fence
652
+ const tail = fence.flush();
653
+ if (tail) out(tail);
654
+
655
+ // Fetch the final message to get token totals
656
+ try {
657
+ const msgs = await apiGet<any[]>(`http://localhost:${port}/v2/session/${sessionId}/message`, token);
658
+ const last = msgs.filter((m: any) => m.role === "assistant").slice(-1)[0];
659
+ tokens = (last?.tokens?.input || 0) + (last?.tokens?.output || 0);
660
+ // If we didn't get any text deltas but the stored message has text, render it now
661
+ if (!fullText && last?.parts) {
662
+ for (const p of last.parts) {
663
+ if (p.type === "text" && p.text) {
664
+ out(` ${p.text.replace(/\n/g, `\n `)}\n`);
665
+ fullText = p.text;
666
+ break;
667
+ }
668
+ }
669
+ }
670
+ } catch {}
671
+
672
+ return { text: fullText, tokens, elapsedMs: Date.now() - t0, interrupted: false };
250
673
  } catch (e: any) {
674
+ flushTextLine();
251
675
  if (e.name === "AbortError") {
252
- onDone({ error: "interrupted" });
253
- } else {
254
- onDone({ error: e.message });
676
+ return { text: fullText, tokens, elapsedMs: Date.now() - t0, interrupted: true };
255
677
  }
256
- } finally {
257
- state.abortController = null;
678
+ return { text: fullText, tokens, elapsedMs: Date.now() - t0, error: e.message, interrupted: false };
258
679
  }
259
680
  }
260
681
 
261
- // ─── Terminal rendering ───────────────────────────────────────────────────────
682
+ // ─── Slash command menu — inline, above the prompt ────────────────────────────
262
683
 
263
- class TUIRenderer {
264
- private promptLine = "";
265
- private statusLine = "";
266
- private streamBuffer = "";
267
- private cols: number;
684
+ interface MenuState {
685
+ visible: boolean;
686
+ options: Command[];
687
+ selected: number;
688
+ rowsRendered: number;
689
+ }
268
690
 
269
- constructor() {
270
- this.cols = process.stdout.columns || 80;
271
- process.stdout.on("resize", () => {
272
- this.cols = process.stdout.columns || 80;
273
- });
274
- }
691
+ function filterCommands(query: string): Command[] {
692
+ if (!query || query === "/") return COMMANDS;
693
+ const q = query.toLowerCase();
694
+ return COMMANDS.filter(c => c.name.toLowerCase().startsWith(q));
695
+ }
275
696
 
276
- print(text: string, newline = true): void {
277
- this.clearPromptArea();
278
- process.stdout.write(text + (newline ? "\n" : ""));
279
- this.redrawPromptArea();
697
+ function renderCommandMenu(state: MenuState): number {
698
+ if (!state.visible || state.options.length === 0) return 0;
699
+ const max = Math.min(state.options.length, 8);
700
+ for (let i = 0; i < max; i++) {
701
+ const c = state.options[i]!;
702
+ const selected = i === state.selected;
703
+ const prefix = selected ? `${CYAN}▌${R} ` : ` `;
704
+ const name = selected ? `${CYAN}${BOLD}${c.name}${R}` : `${WHITE}${c.name}${R}`;
705
+ const args = c.args ? ` ${GRAY}${c.args}${R}` : "";
706
+ const desc = `${GRAY}${c.description}${R}`;
707
+ nl(`${prefix}${name}${args} ${desc}`);
280
708
  }
281
-
282
- delta(text: string): void {
283
- this.clearPromptArea();
284
- process.stdout.write(text);
285
- this.streamBuffer += text;
286
- this.redrawPromptArea();
709
+ if (state.options.length > max) {
710
+ nl(` ${GRAY}... ${state.options.length - max} more (keep typing)${R}`);
711
+ return max + 1;
287
712
  }
713
+ return max;
714
+ }
288
715
 
289
- endStream(): void {
290
- if (this.streamBuffer && !this.streamBuffer.endsWith("\n")) {
291
- process.stdout.write("\n");
292
- }
293
- this.streamBuffer = "";
294
- this.redrawPromptArea();
716
+ function clearMenu(rows: number) {
717
+ if (rows <= 0) return;
718
+ // Move up `rows` lines and clear each one
719
+ for (let i = 0; i < rows; i++) {
720
+ out(`${ESC}[1A${ESC}[2K`);
295
721
  }
722
+ }
296
723
 
297
- setStatus(text: string): void {
298
- this.statusLine = text;
299
- this.redrawPromptArea();
300
- }
724
+ // ─── Main TUI loop ────────────────────────────────────────────────────────────
301
725
 
302
- setPrompt(text: string): void {
303
- this.promptLine = text;
304
- this.redrawPromptArea();
726
+ export async function runTUI(opts: {
727
+ port: number; token: string; model: string;
728
+ provider: string; worktree: string; sessionId?: string;
729
+ }): Promise<void> {
730
+ let { model, provider } = opts;
731
+ const { port, token, worktree } = opts;
732
+ let sessionId = opts.sessionId || null;
733
+ const history: string[] = [];
734
+ let abort: AbortController | null = null;
735
+ let streaming = false;
736
+
737
+ // Initial session
738
+ if (!sessionId) {
739
+ try { sessionId = await createSession(port, token, worktree, "BoneCode Session"); }
740
+ catch (e: any) {
741
+ process.stderr.write(`${RED}Failed to create session: ${e.message}${R}\n`);
742
+ process.exit(1);
743
+ }
305
744
  }
306
745
 
307
- private clearPromptArea(): void {
308
- process.stdout.write(CLEAR_LINE);
309
- process.stdout.write(CURSOR_UP + CLEAR_LINE);
310
- }
746
+ // Header
747
+ printLogo();
748
+ nl(` ${GRAY}v${getVersion()} · ${model} · ${path.basename(worktree)}${R}`);
749
+ nl(` ${GRAY}session ${sessionId.slice(0, 8)}${R}`);
750
+ nl();
751
+ nl(` ${GRAY}Type ${R}${CYAN}/${R}${GRAY} to see commands · Ctrl+C interrupt · Ctrl+D exit${R}`);
752
+ nl();
753
+
754
+ // ─── Slash command menu state ──────────────────────────────────────────────
755
+ const menu: MenuState = {
756
+ visible: false,
757
+ options: [],
758
+ selected: 0,
759
+ rowsRendered: 0,
760
+ };
311
761
 
312
- private redrawPromptArea(): void {
313
- const statusText = truncate(this.statusLine, this.cols - 2);
314
- const promptText = truncate(this.promptLine, this.cols - 2);
315
- process.stdout.write(`${DIM}${statusText}${RESET}\n`);
316
- process.stdout.write(promptText);
317
- }
762
+ // Set up readline AFTER setting up SIGINT so we control it
763
+ const rl = readline.createInterface({
764
+ input: process.stdin,
765
+ output: process.stdout,
766
+ terminal: true,
767
+ historySize: 200,
768
+ completer: buildCompleter(worktree),
769
+ });
318
770
 
319
- initPromptArea(): void {
320
- process.stdout.write("\n\n");
321
- this.clearPromptArea();
322
- this.redrawPromptArea();
323
- }
771
+ const promptStr = () => `${CYAN}${BOLD}>${R} `;
324
772
 
325
- separator(char = "─"): void {
326
- this.print(`${DIM}${char.repeat(Math.min(this.cols, 60))}${RESET}`);
327
- }
773
+ // ─── Ctrl+C handling ──────────────────────────────────────────────────────
774
+ // When streaming: abort the request AND notify server
775
+ // When idle: clear menu/input or hint to use /exit
776
+ const onSigint = async () => {
777
+ if (streaming && abort) {
778
+ abort.abort();
779
+ // Also tell the server to cancel the agent loop
780
+ try {
781
+ await fetch(`http://localhost:${port}/v2/session/${sessionId}/cancel`, {
782
+ method: "POST",
783
+ headers: { "Authorization": `Bearer ${token}` },
784
+ });
785
+ } catch { /* server may not have the endpoint, abort is enough */ }
786
+ // Don't reprompt here — the streamPrompt finally block will handle UI
787
+ return;
788
+ }
328
789
 
329
- banner(model: string, worktree: string): void {
330
- const VERSION = getVersion();
331
- const dir = path.basename(worktree) || worktree;
332
- this.print(`\n${CYAN}${BOLD}BoneCode${RESET} ${DIM}v${VERSION}${RESET}`);
333
- this.print(`${DIM} model: ${RESET}${model}`);
334
- this.print(`${DIM} dir: ${RESET}${dir}`);
335
- this.print(`${DIM} type ${RESET}${CYAN}/help${RESET}${DIM} for commands · ${RESET}${CYAN}Ctrl+C${RESET}${DIM} to interrupt · ${RESET}${CYAN}Ctrl+D${RESET}${DIM} to exit${RESET}`);
336
- this.separator();
337
- }
338
- }
790
+ // Idle: clear menu if visible, else hint
791
+ if (menu.visible) {
792
+ clearMenu(menu.rowsRendered);
793
+ menu.visible = false;
794
+ menu.rowsRendered = 0;
795
+ menu.selected = 0;
796
+ }
797
+ out(`\n${GRAY}(Ctrl+D or /exit to quit)${R}\n`);
798
+ rl.setPrompt(promptStr());
799
+ rl.prompt();
800
+ };
339
801
 
340
- // ─── Input handling ───────────────────────────────────────────────────────────
341
-
342
- class TUIInput {
343
- private rl: readline.Interface;
344
- private sigintHandler: (() => void) | null = null;
345
-
346
- constructor(
347
- private renderer: TUIRenderer,
348
- private state: TUIState,
349
- private onSubmit: (text: string) => Promise<void>
350
- ) {
351
- this.rl = readline.createInterface({
352
- input: process.stdin,
353
- output: process.stdout,
354
- terminal: true,
355
- historySize: 100,
356
- completer: buildCompleter(state.worktree),
357
- });
802
+ // Detach readline's default SIGINT (which closes the line buffer)
803
+ // and route it to our handler.
804
+ rl.on("SIGINT", onSigint);
805
+
806
+ rl.on("close", () => {
807
+ if (streaming && abort) abort.abort();
808
+ nl(`\n${GRAY}Goodbye.${R}`);
809
+ process.exit(0);
810
+ });
358
811
 
359
- // Seed readline history from state
360
- for (const h of state.promptHistory.slice().reverse()) {
361
- (this.rl as any).history?.unshift(h);
812
+ // ─── Live menu update on every keystroke ──────────────────────────────────
813
+ // We hook into the keypress events of stdin to redraw the menu.
814
+ // The menu sits ABOVE the prompt line.
815
+ const stdin: any = (rl as any).input;
816
+
817
+ const updateMenu = () => {
818
+ if (streaming) return;
819
+
820
+ const line: string = (rl as any).line || "";
821
+ const startsWithSlash = line.startsWith("/");
822
+ const shouldShow = startsWithSlash && !line.includes(" ");
823
+
824
+ if (!shouldShow) {
825
+ if (menu.visible) {
826
+ // Need to clear the menu — but we need to preserve the current input line.
827
+ // Strategy: save current line content & cursor, clear menu lines above,
828
+ // then restore the prompt and content.
829
+ const cursor = (rl as any).cursor || 0;
830
+ out(`\r${ESC}[2K`); // clear current prompt line
831
+ clearMenu(menu.rowsRendered); // clear menu lines above
832
+ menu.visible = false;
833
+ menu.rowsRendered = 0;
834
+ menu.selected = 0;
835
+ // Redraw prompt + line
836
+ out(promptStr() + line);
837
+ // Restore cursor position
838
+ const drawn = line.length;
839
+ if (cursor < drawn) {
840
+ out(`${ESC}[${drawn - cursor}D`);
841
+ }
842
+ }
843
+ return;
362
844
  }
363
845
 
364
- this.rl.on("line", async (line) => {
365
- const text = line.trim();
366
- if (text) {
367
- state.promptHistory.push(text);
368
- if (state.promptHistory.length > 100) state.promptHistory.shift();
846
+ // Filter and reset selection if list changed
847
+ const newOptions = filterCommands(line);
848
+ if (newOptions.length === 0) {
849
+ // Clear menu if visible
850
+ if (menu.visible) {
851
+ const cursor = (rl as any).cursor || 0;
852
+ out(`\r${ESC}[2K`);
853
+ clearMenu(menu.rowsRendered);
854
+ menu.visible = false;
855
+ menu.rowsRendered = 0;
856
+ out(promptStr() + line);
857
+ if (cursor < line.length) out(`${ESC}[${line.length - cursor}D`);
369
858
  }
370
- await this.onSubmit(text);
371
- this.prompt();
372
- });
859
+ return;
860
+ }
373
861
 
374
- this.rl.on("close", () => {
375
- this.cleanup();
376
- renderer.print(`\n${DIM}Goodbye.${RESET}`);
377
- process.exit(0);
378
- });
862
+ // Save current input line
863
+ const cursor = (rl as any).cursor || 0;
864
+ out(`\r${ESC}[2K`); // clear prompt line
865
+ clearMenu(menu.rowsRendered); // clear old menu
379
866
 
380
- // Register SIGINT handler — stored so we can remove it on cleanup
381
- this.sigintHandler = () => {
382
- if (state.abortController) {
383
- state.abortController.abort();
384
- renderer.print(`\n${YELLOW}⟳ Interrupted${RESET}`);
385
- this.prompt();
386
- } else {
387
- renderer.print(`\n${DIM}(Use Ctrl+D or /exit to quit)${RESET}`);
388
- this.prompt();
389
- }
390
- };
391
- process.on("SIGINT", this.sigintHandler);
392
- }
867
+ menu.options = newOptions;
868
+ menu.selected = Math.min(menu.selected, newOptions.length - 1);
869
+ if (menu.selected < 0) menu.selected = 0;
870
+ menu.visible = true;
393
871
 
394
- prompt(): void {
395
- const sessionLabel = this.state.sessionId
396
- ? `${DIM}[${this.state.sessionId.slice(0, 8)}]${RESET} `
397
- : "";
398
- const promptStr = `${CYAN}${BOLD}>${RESET} `;
399
- const full = `${sessionLabel}${promptStr}`;
400
- this.renderer.setPrompt(full);
401
- this.rl.setPrompt(full);
402
- this.rl.prompt(true);
403
- }
872
+ // Render the menu (each item ends in newline)
873
+ menu.rowsRendered = renderCommandMenu(menu);
404
874
 
405
- cleanup(): void {
406
- if (this.sigintHandler) {
407
- process.removeListener("SIGINT", this.sigintHandler);
408
- this.sigintHandler = null;
875
+ // Redraw prompt + line
876
+ out(promptStr() + line);
877
+ if (cursor < line.length) {
878
+ out(`${ESC}[${line.length - cursor}D`);
409
879
  }
410
- }
880
+ };
411
881
 
412
- close(): void {
413
- this.cleanup();
414
- this.rl.close();
415
- }
416
- }
882
+ // Capture special keys for menu navigation
883
+ const onKeypress = (_chunk: any, key: any) => {
884
+ if (!key) return;
885
+ if (streaming) return;
417
886
 
418
- // ─── Command handlers ─────────────────────────────────────────────────────────
419
-
420
- async function handleCommand(cmd: string, state: TUIState, renderer: TUIRenderer): Promise<void> {
421
- const parts = cmd.slice(1).trim().split(/\s+/);
422
- const name = parts[0]?.toLowerCase() || "";
423
- const args = parts.slice(1);
424
-
425
- switch (name) {
426
- case "help":
427
- case "h":
428
- renderer.print(`
429
- ${CYAN}${BOLD}BoneCode Commands${RESET}
430
- ${CYAN}/new${RESET} Start a new session
431
- ${CYAN}/session${RESET} Show current session ID
432
- ${CYAN}/model <id>${RESET} Switch model (e.g. /model gpt-4o or /model anthropic/claude-sonnet-4-5)
433
- ${CYAN}/clear${RESET} Clear the screen
434
- ${CYAN}/history${RESET} Show last 10 prompts
435
- ${CYAN}/exit${RESET}, ${CYAN}/quit${RESET} Exit BoneCode
436
- ${CYAN}Ctrl+C${RESET} Interrupt current request (stays in session)
437
- ${CYAN}Ctrl+D${RESET} Exit BoneCode
438
- ${CYAN}↑ / ↓${RESET} Navigate prompt history
439
- ${CYAN}@<path>${RESET} Attach a file — press Tab to autocomplete`);
440
- break;
441
-
442
- case "new":
443
- renderer.print(`${DIM}Starting new session...${RESET}`);
444
- try {
445
- state.sessionId = await createSession(state, "New Session");
446
- state.turnCount = 0;
447
- renderer.print(`${GREEN}✓ New session: ${state.sessionId.slice(0, 8)}...${RESET}`);
448
- renderer.separator();
449
- } catch (e: any) {
450
- renderer.print(`${RED}✗ Failed to create session: ${e.message}${RESET}`);
887
+ if (menu.visible && menu.options.length > 0) {
888
+ // Arrow up/down navigate the menu
889
+ if (key.name === "up") {
890
+ menu.selected = (menu.selected - 1 + menu.options.length) % menu.options.length;
891
+ updateMenu();
892
+ return;
451
893
  }
452
- break;
453
-
454
- case "session":
455
- if (state.sessionId) {
456
- renderer.print(`${DIM}Session: ${RESET}${state.sessionId}`);
457
- } else {
458
- renderer.print(`${YELLOW}No active session${RESET}`);
894
+ if (key.name === "down") {
895
+ menu.selected = (menu.selected + 1) % menu.options.length;
896
+ updateMenu();
897
+ return;
459
898
  }
460
- break;
461
-
462
- case "model":
463
- if (args[0]) {
464
- if (args[0].includes("/")) {
465
- const slashIdx = args[0].indexOf("/");
466
- state.provider = args[0].slice(0, slashIdx);
467
- state.model = args[0].slice(slashIdx + 1);
468
- } else {
469
- state.model = args[0];
899
+ // Tab: replace input with the selected command
900
+ if (key.name === "tab") {
901
+ const sel = menu.options[menu.selected];
902
+ if (sel) {
903
+ // Replace the line buffer with the command + space
904
+ (rl as any).line = sel.name + " ";
905
+ (rl as any).cursor = sel.name.length + 1;
906
+ updateMenu();
907
+ out(`\r${ESC}[2K`);
908
+ out(promptStr() + (rl as any).line);
470
909
  }
471
- renderer.print(`${GREEN}✓ Model: ${state.provider}/${state.model}${RESET}`);
472
- renderer.setStatus(`${DIM}${state.provider}/${state.model}${RESET}`);
473
- } else {
474
- renderer.print(`${DIM}Current model: ${RESET}${state.provider}/${state.model}`);
475
- }
476
- break;
477
-
478
- case "clear":
479
- process.stdout.write(`${ESC}[2J${ESC}[H`);
480
- renderer.banner(`${state.provider}/${state.model}`, state.worktree);
481
- renderer.initPromptArea();
482
- break;
483
-
484
- case "history":
485
- if (state.promptHistory.length === 0) {
486
- renderer.print(`${DIM}No history yet${RESET}`);
487
- } else {
488
- renderer.print(`${DIM}Recent prompts:${RESET}`);
489
- state.promptHistory.slice(-10).forEach((h, i) => {
490
- renderer.print(` ${DIM}${i + 1}.${RESET} ${truncate(h, 70)}`);
491
- });
910
+ return;
492
911
  }
493
- break;
912
+ }
494
913
 
495
- case "exit":
496
- case "quit":
497
- case "q":
498
- renderer.print(`\n${DIM}Goodbye.${RESET}`);
499
- process.exit(0);
500
- break;
914
+ // Default: trigger menu update on the next tick (after readline updates the buffer)
915
+ setImmediate(updateMenu);
916
+ };
501
917
 
502
- default:
503
- renderer.print(`${YELLOW}Unknown command: /${name}${RESET} — type ${CYAN}/help${RESET} for commands`);
504
- }
505
- }
918
+ stdin.on("keypress", onKeypress);
506
919
 
507
- // ─── Main TUI loop ────────────────────────────────────────────────────────────
920
+ rl.setPrompt(promptStr());
921
+ rl.prompt();
508
922
 
509
- export async function runTUI(opts: {
510
- port: number;
511
- token: string;
512
- model: string;
513
- provider: string;
514
- worktree: string;
515
- sessionId?: string;
516
- }): Promise<void> {
517
- const state: TUIState = {
518
- sessionId: opts.sessionId || null,
519
- model: opts.model,
520
- provider: opts.provider,
521
- port: opts.port,
522
- token: opts.token,
523
- worktree: opts.worktree,
524
- promptHistory: [],
525
- streaming: false,
526
- abortController: null,
527
- turnCount: 0,
528
- totalTokens: 0,
529
- };
530
-
531
- const renderer = new TUIRenderer();
923
+ // ─── Main input loop ──────────────────────────────────────────────────────
924
+ for await (const rawLine of rl) {
925
+ const text = rawLine.trim();
532
926
 
533
- // Create initial session
534
- if (!state.sessionId) {
535
- try {
536
- state.sessionId = await createSession(state, "BoneCode Session");
537
- } catch (e: any) {
538
- process.stderr.write(`${RED}Failed to create session: ${e.message}${RESET}\n`);
539
- process.exit(1);
927
+ // Clear any visible menu before processing
928
+ if (menu.visible) {
929
+ menu.visible = false;
930
+ menu.rowsRendered = 0;
931
+ menu.selected = 0;
540
932
  }
541
- }
542
933
 
543
- renderer.banner(`${state.provider}/${state.model}`, state.worktree);
544
- renderer.setStatus(`${DIM}${state.provider}/${state.model} · session ${state.sessionId!.slice(0, 8)}${RESET}`);
545
- renderer.initPromptArea();
934
+ if (!text) {
935
+ rl.setPrompt(promptStr());
936
+ rl.prompt();
937
+ continue;
938
+ }
546
939
 
547
- const tuiInput = new TUIInput(renderer, state, async (text: string) => {
548
- if (!text) return;
940
+ history.push(text);
941
+ if (history.length > 200) history.shift();
549
942
 
943
+ // ── Slash commands ──────────────────────────────────────────────────────
550
944
  if (text.startsWith("/")) {
551
- await handleCommand(text, state, renderer);
552
- return;
945
+ const parts = text.slice(1).trim().split(/\s+/);
946
+ const cmd = parts[0]?.toLowerCase() || "";
947
+ const args = parts.slice(1);
948
+
949
+ switch (cmd) {
950
+ case "help": case "h":
951
+ printHelp();
952
+ break;
953
+
954
+ case "new":
955
+ try {
956
+ sessionId = await createSession(port, token, worktree, "New Session");
957
+ nl(`${GREEN}✓${R} ${GRAY}session ${sessionId.slice(0, 8)}${R}`);
958
+ } catch (e: any) { nl(`${RED}✗ ${e.message}${R}`); }
959
+ break;
960
+
961
+ case "session":
962
+ nl(`${GRAY}${sessionId}${R}`);
963
+ break;
964
+
965
+ case "sessions": {
966
+ try {
967
+ const list = await apiGet<any[]>(`http://localhost:${port}/v2/session?limit=10`, token);
968
+ if (!list.length) { nl(`${GRAY}No sessions${R}`); break; }
969
+ nl();
970
+ for (const s of list) {
971
+ const active = s.id === sessionId;
972
+ const dot = active ? `${GREEN}●${R}` : `${GRAY}○${R}`;
973
+ const t = new Date(s.time?.updated || Date.now()).toLocaleString();
974
+ nl(` ${dot} ${WHITE}${(s.title || "untitled").slice(0, 50).padEnd(50)}${R} ${GRAY}${t}${R}`);
975
+ }
976
+ nl();
977
+ } catch (e: any) { nl(`${RED}✗ ${e.message}${R}`); }
978
+ break;
979
+ }
980
+
981
+ case "model":
982
+ if (args[0]) {
983
+ if (args[0] === "local") {
984
+ provider = process.env.DEFAULT_PROVIDER || "openai_compatible";
985
+ model = process.env.DEFAULT_MODEL || "local-model";
986
+ } else if (args[0].includes("/")) {
987
+ const i = args[0].indexOf("/");
988
+ provider = args[0].slice(0, i);
989
+ model = args[0].slice(i + 1);
990
+ } else {
991
+ model = args[0];
992
+ }
993
+ nl(`${GREEN}✓${R} ${WHITE}${provider}/${model}${R}`);
994
+ } else {
995
+ nl(`${GRAY}${provider}/${model}${R}`);
996
+ }
997
+ break;
998
+
999
+ case "provider":
1000
+ if (args[0]) {
1001
+ if (args[0] === "local") {
1002
+ provider = process.env.DEFAULT_PROVIDER || "openai_compatible";
1003
+ model = process.env.DEFAULT_MODEL || "local-model";
1004
+ nl(`${GREEN}✓${R} ${WHITE}${provider}/${model}${R}`);
1005
+ } else {
1006
+ provider = args[0];
1007
+ nl(`${GREEN}✓${R} ${WHITE}${provider}${R} ${GRAY}use /model <id> to set model${R}`);
1008
+ }
1009
+ } else {
1010
+ nl(`${GRAY}${provider}${R}`);
1011
+ }
1012
+ break;
1013
+
1014
+ case "providers": {
1015
+ const list = await fetchProviders(port, token);
1016
+ if (list.length) printProviders(list, provider);
1017
+ else nl(`${YELLOW}Could not fetch providers${R}`);
1018
+ break;
1019
+ }
1020
+
1021
+ case "clear":
1022
+ process.stdout.write(`${ESC}[2J${ESC}[H`);
1023
+ printLogo();
1024
+ break;
1025
+
1026
+ case "history":
1027
+ if (!history.length) nl(`${GRAY}No history${R}`);
1028
+ else history.slice(-10).forEach((h, i) => nl(` ${GRAY}${i + 1}.${R} ${h.slice(0, 80)}`));
1029
+ break;
1030
+
1031
+ case "exit": case "quit": case "q":
1032
+ nl(`\n${GRAY}Goodbye.${R}`);
1033
+ process.exit(0);
1034
+ break;
1035
+
1036
+ default:
1037
+ nl(`${YELLOW}Unknown: /${cmd}${R} ${GRAY}Type /help for available commands${R}`);
1038
+ }
1039
+
1040
+ rl.setPrompt(promptStr());
1041
+ rl.prompt();
1042
+ continue;
553
1043
  }
554
1044
 
555
- // Ensure session exists
556
- if (!state.sessionId) {
557
- try {
558
- state.sessionId = await createSession(state, text.slice(0, 50));
559
- } catch (e: any) {
560
- renderer.print(`${RED}✗ Failed to create session: ${e.message}${RESET}`);
561
- return;
1045
+ // ── Prompt to the agent ─────────────────────────────────────────────────
1046
+ if (!sessionId) {
1047
+ try { sessionId = await createSession(port, token, worktree, text.slice(0, 50)); }
1048
+ catch (e: any) {
1049
+ nl(`${RED} ${e.message}${R}`);
1050
+ rl.setPrompt(promptStr());
1051
+ rl.prompt();
1052
+ continue;
562
1053
  }
563
1054
  }
564
1055
 
565
1056
  // Resolve @file mentions to absolute paths
566
- const resolvedText = text.replace(/@([\w./\-]+)/g, (match, filePath) => {
567
- const abs = path.isAbsolute(filePath)
568
- ? filePath
569
- : path.resolve(state.worktree, filePath);
1057
+ const resolved = text.replace(/@([\w./\-]+)/g, (match, fp) => {
1058
+ const abs = path.isAbsolute(fp) ? fp : path.resolve(worktree, fp);
570
1059
  return fs.existsSync(abs) ? `@${abs}` : match;
571
1060
  });
572
1061
 
573
- renderer.print(`\n${CYAN}${BOLD}You${RESET}`);
574
- renderer.print(resolvedText !== text ? `${text} ${DIM}(files resolved)${RESET}` : text);
575
- renderer.separator("·");
576
- renderer.print(`${GREEN}${BOLD}BoneCode${RESET} ${DIM}(${state.model})${RESET}`);
577
-
578
- state.streaming = true;
579
- const startMs = Date.now();
580
- renderer.setStatus(`${YELLOW}⟳ thinking...${RESET}`);
581
-
582
- await streamPrompt(
583
- state,
584
- resolvedText,
585
- (delta) => {
586
- // Strip markdown fences — they don't render well in terminals
587
- const clean = delta.replace(/^```\w*\n?/gm, "").replace(/^```\n?/gm, "");
588
- if (clean) renderer.delta(clean);
589
- },
590
- (info) => {
591
- renderer.endStream();
592
- state.streaming = false;
593
- state.turnCount++;
594
-
595
- const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
596
- if (info.tokens) state.totalTokens += info.tokens;
597
-
598
- if (info.error && info.error !== "interrupted") {
599
- renderer.print(`${RED}✗ ${info.error}${RESET}`);
600
- }
1062
+ renderUserMessage(text);
601
1063
 
602
- renderer.separator();
1064
+ // Show "..." spinner so the user knows something is happening
1065
+ out(` ${GRAY}thinking...${R}`);
603
1066
 
604
- const tokenStr = info.tokens ? ` · ${info.tokens} tok` : "";
605
- const statusStr = info.error === "interrupted"
606
- ? `${YELLOW}interrupted${RESET}`
607
- : `${DIM}${elapsed}s${tokenStr}${RESET}`;
608
- renderer.setStatus(
609
- `${DIM}${state.provider}/${state.model} · turn ${state.turnCount}${RESET} · ${statusStr}`
610
- );
611
- }
612
- );
613
- });
1067
+ rl.pause();
1068
+ streaming = true;
1069
+ abort = new AbortController();
1070
+
1071
+ const t0 = Date.now();
1072
+ const result = await streamPrompt({
1073
+ port, token, sessionId,
1074
+ model, provider,
1075
+ message: resolved,
1076
+ worktree,
1077
+ abortSignal: abort.signal,
1078
+ });
1079
+
1080
+ streaming = false;
1081
+ abort = null;
614
1082
 
615
- tuiInput.prompt();
1083
+ // Clear the thinking spinner if no output happened
1084
+ if (!result.text && !result.error) {
1085
+ clearLine();
1086
+ } else {
1087
+ // Make sure the next render starts on a clean line
1088
+ const cur = (process.stdout as any).cursor;
1089
+ if (cur && cur.col !== 0) out("\n");
1090
+ }
616
1091
 
617
- // Keep process alive until readline closes
618
- await new Promise<void>(() => {});
1092
+ renderTurnEnd(model, result.elapsedMs, result.interrupted);
1093
+
1094
+ if (result.error && !result.interrupted) {
1095
+ nl(` ${RED}✗ ${result.error}${R}`);
1096
+ }
1097
+
1098
+ nl();
1099
+
1100
+ rl.resume();
1101
+ rl.setPrompt(promptStr());
1102
+ rl.prompt();
1103
+ }
1104
+
1105
+ stdin.removeListener("keypress", onKeypress);
619
1106
  }
620
1107
 
621
1108
  // ─── Entry point ──────────────────────────────────────────────────────────────
622
1109
 
623
1110
  export async function startInteractiveTUI(opts: {
624
- port: number;
625
- token: string;
626
- model: string;
627
- provider: string;
628
- worktree: string;
1111
+ port: number; token: string; model: string;
1112
+ provider: string; worktree: string;
629
1113
  }): Promise<void> {
630
- const serverReady = await waitForServer(opts.port, 30_000);
631
- if (!serverReady) {
632
- process.stderr.write(`${RED}Error: BoneCode server not responding on port ${opts.port}${RESET}\n`);
633
- process.stderr.write(`${DIM}Start it with: bonecode serve${RESET}\n`);
1114
+ const ready = await waitForServer(opts.port, 30_000);
1115
+ if (!ready) {
1116
+ process.stderr.write(`${RED}BoneCode server not responding on port ${opts.port}${R}\n`);
1117
+ process.stderr.write(`${GRAY}Start with: bonecode serve${R}\n`);
634
1118
  process.exit(1);
635
1119
  }
636
1120
  await runTUI(opts);