atoo-studio 0.0.1 → 0.0.2

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 (408) hide show
  1. package/LICENSE +21 -0
  2. package/README.github.md +322 -0
  3. package/README.md +112 -0
  4. package/README.npm.md +112 -0
  5. package/bin/atoo-studio.js +90 -0
  6. package/dist/src/agents/claude-code-terminal/adapter.d.ts +42 -0
  7. package/dist/src/agents/claude-code-terminal/adapter.js +166 -0
  8. package/dist/src/agents/claude-code-terminal/index.d.ts +13 -0
  9. package/dist/src/agents/claude-code-terminal/index.js +45 -0
  10. package/dist/src/agents/claude-code-terminal/spawner.d.ts +9 -0
  11. package/dist/src/agents/claude-code-terminal/spawner.js +37 -0
  12. package/dist/src/agents/claude-code-terminal-chatro/adapter.d.ts +51 -0
  13. package/dist/src/agents/claude-code-terminal-chatro/adapter.js +301 -0
  14. package/dist/src/agents/claude-code-terminal-chatro/index.d.ts +13 -0
  15. package/dist/src/agents/claude-code-terminal-chatro/index.js +45 -0
  16. package/dist/src/agents/claude-code-terminal-chatro/jsonl-watcher.d.ts +67 -0
  17. package/dist/src/agents/claude-code-terminal-chatro/jsonl-watcher.js +431 -0
  18. package/dist/src/agents/claude-code-terminal-chatro/spawner.d.ts +9 -0
  19. package/dist/src/agents/claude-code-terminal-chatro/spawner.js +37 -0
  20. package/dist/src/agents/codex-terminal/adapter.d.ts +40 -0
  21. package/dist/src/agents/codex-terminal/adapter.js +160 -0
  22. package/dist/src/agents/codex-terminal/index.d.ts +13 -0
  23. package/dist/src/agents/codex-terminal/index.js +47 -0
  24. package/dist/src/agents/codex-terminal/spawner.d.ts +9 -0
  25. package/dist/src/agents/codex-terminal/spawner.js +56 -0
  26. package/dist/src/agents/codex-terminal-chatro/adapter.d.ts +58 -0
  27. package/dist/src/agents/codex-terminal-chatro/adapter.js +266 -0
  28. package/dist/src/agents/codex-terminal-chatro/index.d.ts +13 -0
  29. package/dist/src/agents/codex-terminal-chatro/index.js +50 -0
  30. package/dist/src/agents/codex-terminal-chatro/jsonl-watcher.d.ts +36 -0
  31. package/dist/src/agents/codex-terminal-chatro/jsonl-watcher.js +205 -0
  32. package/dist/src/agents/codex-terminal-chatro/spawner.d.ts +9 -0
  33. package/dist/src/agents/codex-terminal-chatro/spawner.js +57 -0
  34. package/dist/src/agents/lib/chain-builder.d.ts +21 -0
  35. package/dist/src/agents/lib/chain-builder.js +139 -0
  36. package/dist/src/agents/lib/claude/fs-sessions.d.ts +31 -0
  37. package/dist/src/agents/lib/claude/fs-sessions.js +329 -0
  38. package/dist/src/agents/lib/claude/jsonl-writer.d.ts +32 -0
  39. package/dist/src/agents/lib/claude/jsonl-writer.js +342 -0
  40. package/dist/src/agents/lib/claude/workspace-trust.d.ts +1 -0
  41. package/dist/src/agents/lib/claude/workspace-trust.js +29 -0
  42. package/dist/src/agents/lib/codex/fs-sessions.d.ts +34 -0
  43. package/dist/src/agents/lib/codex/fs-sessions.js +255 -0
  44. package/dist/src/agents/lib/codex/jsonl-mapper.d.ts +11 -0
  45. package/dist/src/agents/lib/codex/jsonl-mapper.js +154 -0
  46. package/dist/src/agents/lib/codex/jsonl-writer.d.ts +8 -0
  47. package/dist/src/agents/lib/codex/jsonl-writer.js +440 -0
  48. package/dist/src/agents/lib/fs-tracking.d.ts +36 -0
  49. package/dist/src/agents/lib/fs-tracking.js +109 -0
  50. package/dist/src/agents/lib/pty-activity-tracker.d.ts +37 -0
  51. package/dist/src/agents/lib/pty-activity-tracker.js +105 -0
  52. package/dist/src/agents/lib/session-id-utils.d.ts +46 -0
  53. package/dist/src/agents/lib/session-id-utils.js +147 -0
  54. package/dist/src/agents/lib/session-precreate.d.ts +17 -0
  55. package/dist/src/agents/lib/session-precreate.js +177 -0
  56. package/dist/src/agents/registry.d.ts +72 -0
  57. package/dist/src/agents/registry.js +337 -0
  58. package/dist/src/agents/types.d.ts +135 -0
  59. package/dist/src/agents/types.js +1 -0
  60. package/dist/src/auth/crypto-key.d.ts +6 -0
  61. package/dist/src/auth/crypto-key.js +45 -0
  62. package/dist/src/auth/middleware.d.ts +18 -0
  63. package/dist/src/auth/middleware.js +54 -0
  64. package/dist/src/auth/password.d.ts +2 -0
  65. package/dist/src/auth/password.js +12 -0
  66. package/dist/src/auth/session.d.ts +10 -0
  67. package/dist/src/auth/session.js +33 -0
  68. package/dist/src/auth/totp.d.ts +12 -0
  69. package/dist/src/auth/totp.js +61 -0
  70. package/dist/src/auth/webauthn.d.ts +6 -0
  71. package/dist/src/auth/webauthn.js +117 -0
  72. package/dist/src/config.d.ts +10 -0
  73. package/dist/src/config.js +16 -0
  74. package/dist/src/database/connection-manager.d.ts +25 -0
  75. package/dist/src/database/connection-manager.js +211 -0
  76. package/dist/src/database/discovery/container.d.ts +6 -0
  77. package/dist/src/database/discovery/container.js +226 -0
  78. package/dist/src/database/discovery/env-parser.d.ts +9 -0
  79. package/dist/src/database/discovery/env-parser.js +525 -0
  80. package/dist/src/database/discovery/local-files.d.ts +6 -0
  81. package/dist/src/database/discovery/local-files.js +58 -0
  82. package/dist/src/database/discovery/port-scan.d.ts +7 -0
  83. package/dist/src/database/discovery/port-scan.js +61 -0
  84. package/dist/src/database/drivers/cassandra.d.ts +12 -0
  85. package/dist/src/database/drivers/cassandra.js +91 -0
  86. package/dist/src/database/drivers/clickhouse.d.ts +11 -0
  87. package/dist/src/database/drivers/clickhouse.js +127 -0
  88. package/dist/src/database/drivers/elasticsearch.d.ts +12 -0
  89. package/dist/src/database/drivers/elasticsearch.js +169 -0
  90. package/dist/src/database/drivers/influxdb.d.ts +14 -0
  91. package/dist/src/database/drivers/influxdb.js +194 -0
  92. package/dist/src/database/drivers/memcached.d.ts +11 -0
  93. package/dist/src/database/drivers/memcached.js +117 -0
  94. package/dist/src/database/drivers/mongodb.d.ts +12 -0
  95. package/dist/src/database/drivers/mongodb.js +128 -0
  96. package/dist/src/database/drivers/mysql.d.ts +11 -0
  97. package/dist/src/database/drivers/mysql.js +112 -0
  98. package/dist/src/database/drivers/neo4j.d.ts +11 -0
  99. package/dist/src/database/drivers/neo4j.js +158 -0
  100. package/dist/src/database/drivers/postgresql.d.ts +11 -0
  101. package/dist/src/database/drivers/postgresql.js +133 -0
  102. package/dist/src/database/drivers/redis.d.ts +11 -0
  103. package/dist/src/database/drivers/redis.js +91 -0
  104. package/dist/src/database/drivers/sqlite.d.ts +10 -0
  105. package/dist/src/database/drivers/sqlite.js +100 -0
  106. package/dist/src/database/query-stream.d.ts +5 -0
  107. package/dist/src/database/query-stream.js +75 -0
  108. package/dist/src/database/types.d.ts +71 -0
  109. package/dist/src/database/types.js +1 -0
  110. package/dist/src/events/index.d.ts +3 -0
  111. package/dist/src/events/index.js +3 -0
  112. package/dist/src/events/types.d.ts +214 -0
  113. package/dist/src/events/types.js +22 -0
  114. package/dist/src/events/wire.d.ts +114 -0
  115. package/dist/src/events/wire.js +296 -0
  116. package/dist/src/fs-monitor-types.d.ts +24 -0
  117. package/dist/src/fs-monitor-types.js +1 -0
  118. package/dist/src/fs-monitor.d.ts +80 -0
  119. package/dist/src/fs-monitor.js +637 -0
  120. package/dist/src/handlers/auth.d.ts +1 -0
  121. package/dist/src/handlers/auth.js +170 -0
  122. package/dist/src/handlers/changes.d.ts +1 -0
  123. package/dist/src/handlers/changes.js +203 -0
  124. package/dist/src/handlers/containers.d.ts +12 -0
  125. package/dist/src/handlers/containers.js +379 -0
  126. package/dist/src/handlers/databases.d.ts +3 -0
  127. package/dist/src/handlers/databases.js +327 -0
  128. package/dist/src/handlers/environments.d.ts +3 -0
  129. package/dist/src/handlers/environments.js +286 -0
  130. package/dist/src/handlers/github.d.ts +1 -0
  131. package/dist/src/handlers/github.js +153 -0
  132. package/dist/src/handlers/projects.d.ts +1 -0
  133. package/dist/src/handlers/projects.js +895 -0
  134. package/dist/src/handlers/ssh.d.ts +1 -0
  135. package/dist/src/handlers/ssh.js +162 -0
  136. package/dist/src/handlers/users.d.ts +1 -0
  137. package/dist/src/handlers/users.js +195 -0
  138. package/dist/src/index.d.ts +1 -0
  139. package/dist/src/index.js +228 -0
  140. package/dist/src/mcp/config.d.ts +32 -0
  141. package/dist/src/mcp/config.js +227 -0
  142. package/dist/src/mcp/server.d.ts +1 -0
  143. package/dist/src/mcp/server.js +574 -0
  144. package/dist/src/serial/cuse-device.d.ts +19 -0
  145. package/dist/src/serial/cuse-device.js +260 -0
  146. package/dist/src/serial/manager.d.ts +63 -0
  147. package/dist/src/serial/manager.js +206 -0
  148. package/dist/src/serial/pty-pair.d.ts +16 -0
  149. package/dist/src/serial/pty-pair.js +68 -0
  150. package/dist/src/services/fs-browser.d.ts +14 -0
  151. package/dist/src/services/fs-browser.js +98 -0
  152. package/dist/src/services/git-ops.d.ts +78 -0
  153. package/dist/src/services/git-ops.js +288 -0
  154. package/dist/src/services/github-ops.d.ts +104 -0
  155. package/dist/src/services/github-ops.js +192 -0
  156. package/dist/src/services/obfuscation.d.ts +2 -0
  157. package/dist/src/services/obfuscation.js +16 -0
  158. package/dist/src/services/preview/headless-backend.d.ts +62 -0
  159. package/dist/src/services/preview/headless-backend.js +698 -0
  160. package/dist/src/services/preview/injected-scripts.d.ts +9 -0
  161. package/dist/src/services/preview/injected-scripts.js +232 -0
  162. package/dist/src/services/preview/preview-backend.d.ts +92 -0
  163. package/dist/src/services/preview/preview-backend.js +15 -0
  164. package/dist/src/services/preview/universal-setter.d.ts +7 -0
  165. package/dist/src/services/preview/universal-setter.js +46 -0
  166. package/dist/src/services/preview-manager.d.ts +50 -0
  167. package/dist/src/services/preview-manager.js +216 -0
  168. package/dist/src/services/project-watcher.d.ts +6 -0
  169. package/dist/src/services/project-watcher.js +307 -0
  170. package/dist/src/services/remote-fs-browser.d.ts +11 -0
  171. package/dist/src/services/remote-fs-browser.js +50 -0
  172. package/dist/src/services/remote-git-ops.d.ts +71 -0
  173. package/dist/src/services/remote-git-ops.js +215 -0
  174. package/dist/src/services/session-search.d.ts +56 -0
  175. package/dist/src/services/session-search.js +303 -0
  176. package/dist/src/services/ssh-manager.d.ts +44 -0
  177. package/dist/src/services/ssh-manager.js +359 -0
  178. package/dist/src/session-writer.d.ts +9 -0
  179. package/dist/src/session-writer.js +66 -0
  180. package/dist/src/spawner.d.ts +56 -0
  181. package/dist/src/spawner.js +135 -0
  182. package/dist/src/state/db.d.ts +214 -0
  183. package/dist/src/state/db.js +897 -0
  184. package/dist/src/state/store.d.ts +37 -0
  185. package/dist/src/state/store.js +108 -0
  186. package/dist/src/state/types.d.ts +13 -0
  187. package/dist/src/state/types.js +1 -0
  188. package/dist/src/web/devtools-proxy.d.ts +7 -0
  189. package/dist/src/web/devtools-proxy.js +176 -0
  190. package/dist/src/web/port-proxy.d.ts +15 -0
  191. package/dist/src/web/port-proxy.js +124 -0
  192. package/dist/src/web/preview-ws.d.ts +5 -0
  193. package/dist/src/web/preview-ws.js +207 -0
  194. package/dist/src/web/server.d.ts +6 -0
  195. package/dist/src/web/server.js +1694 -0
  196. package/dist/src/ws/agent-ws.d.ts +5 -0
  197. package/dist/src/ws/agent-ws.js +93 -0
  198. package/frontend/dist/assets/_basePickBy-B-LibQ4-.js +1 -0
  199. package/frontend/dist/assets/_baseUniq-CprifHap.js +1 -0
  200. package/frontend/dist/assets/_createAssigner-ByDUqGii.js +1 -0
  201. package/frontend/dist/assets/abap-DuT-3z4x.js +1 -0
  202. package/frontend/dist/assets/addon-fit-CxQet2ja.js +1 -0
  203. package/frontend/dist/assets/addon-web-links-D_jRkPIl.js +1 -0
  204. package/frontend/dist/assets/apex-B-em86xX.js +1 -0
  205. package/frontend/dist/assets/api-SUPuHhSY.js +2 -0
  206. package/frontend/dist/assets/arc-Z0_eVteO.js +1 -0
  207. package/frontend/dist/assets/architecture-PBZL5I3N-hvVXGhqd.js +1 -0
  208. package/frontend/dist/assets/architectureDiagram-2XIMDMQ5-DiHPxX4j.js +36 -0
  209. package/frontend/dist/assets/array-CwG8vNfn.js +1 -0
  210. package/frontend/dist/assets/auth-store-R7eW5SVu.js +1 -0
  211. package/frontend/dist/assets/azcli-Bg9wQloi.js +1 -0
  212. package/frontend/dist/assets/bat-BM46z99L.js +1 -0
  213. package/frontend/dist/assets/bicep-DcBsJUfh.js +2 -0
  214. package/frontend/dist/assets/blockDiagram-WCTKOSBZ-C40u_hLo.js +132 -0
  215. package/frontend/dist/assets/c4Diagram-IC4MRINW-Ct7LjWFQ.js +10 -0
  216. package/frontend/dist/assets/cameligo-zw7JTtim.js +1 -0
  217. package/frontend/dist/assets/channel-ClCsE6HN.js +1 -0
  218. package/frontend/dist/assets/chunk-4BX2VUAB-zZ6P90VO.js +1 -0
  219. package/frontend/dist/assets/chunk-55IACEB6-DXllTDQl.js +1 -0
  220. package/frontend/dist/assets/chunk-7E7YKBS2-7zRaOLjj.js +1 -0
  221. package/frontend/dist/assets/chunk-7R4GIKGN-Csst1274.js +80 -0
  222. package/frontend/dist/assets/chunk-C72U2L5F-_JbQPbLN.js +1 -0
  223. package/frontend/dist/assets/chunk-CFjPhJqf.js +1 -0
  224. package/frontend/dist/assets/chunk-EGIJ26TM-B--aFyPw.js +1 -0
  225. package/frontend/dist/assets/chunk-FMBD7UC4-DVR34RNb.js +15 -0
  226. package/frontend/dist/assets/chunk-GEFDOKGD-CnmN6cC8.js +2 -0
  227. package/frontend/dist/assets/chunk-JSJVCQXG-CWxHBzeJ.js +1 -0
  228. package/frontend/dist/assets/chunk-KX2RTZJC-DkRk56s7.js +1 -0
  229. package/frontend/dist/assets/chunk-KYZI473N-DCCsG2dK.js +53 -0
  230. package/frontend/dist/assets/chunk-L3YUKLVL-C-DkZTMr.js +1 -0
  231. package/frontend/dist/assets/chunk-MX3YWQON-OUdzv5sZ.js +1 -0
  232. package/frontend/dist/assets/chunk-NQ4KR5QH-Bpu9FsM7.js +220 -0
  233. package/frontend/dist/assets/chunk-O4XLMI2P-BMLK6_ib.js +7 -0
  234. package/frontend/dist/assets/chunk-OZEHJAEY-CNNiJtG0.js +1 -0
  235. package/frontend/dist/assets/chunk-PQ6SQG4A-evVHD3KM.js +1 -0
  236. package/frontend/dist/assets/chunk-PU5JKC2W-DPFTYuvl.js +70 -0
  237. package/frontend/dist/assets/chunk-QZHKN3VN-JRdddPvu.js +1 -0
  238. package/frontend/dist/assets/chunk-R5LLSJPH-CHQzVVOV.js +1 -0
  239. package/frontend/dist/assets/chunk-WL4C6EOR-BNFU6IIi.js +189 -0
  240. package/frontend/dist/assets/chunk-XIRO2GV7-98T93G85.js +1 -0
  241. package/frontend/dist/assets/chunk-XZSTWKYB-BcW3cyNp.js +94 -0
  242. package/frontend/dist/assets/chunk-YBOYWFTD-BgKO1qAJ.js +1 -0
  243. package/frontend/dist/assets/classDiagram-VBA2DB6C-DikXzgcD.js +1 -0
  244. package/frontend/dist/assets/classDiagram-v2-RAHNMMFH-D7E3tQUK.js +1 -0
  245. package/frontend/dist/assets/clojure-FspFoNNQ.js +1 -0
  246. package/frontend/dist/assets/clone-mOXuZa7C.js +1 -0
  247. package/frontend/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  248. package/frontend/dist/assets/coffee-13n8Bk2W.js +1 -0
  249. package/frontend/dist/assets/cose-bilkent-S5V4N54A-zUOWQqLe.js +1 -0
  250. package/frontend/dist/assets/cpp-BVm2xGEs.js +1 -0
  251. package/frontend/dist/assets/csharp-D2kAWmUm.js +1 -0
  252. package/frontend/dist/assets/csp-Ezvgpf0e.js +1 -0
  253. package/frontend/dist/assets/css-CYxRwcFy.js +3 -0
  254. package/frontend/dist/assets/css.worker-Cd5h-ZOL.js +89 -0
  255. package/frontend/dist/assets/cssMode-CrXej49V.js +1 -0
  256. package/frontend/dist/assets/cypher-jg3SGErc.js +1 -0
  257. package/frontend/dist/assets/cytoscape.esm-kyyvzxNV.js +321 -0
  258. package/frontend/dist/assets/dagre-DH4bgZO7.js +1 -0
  259. package/frontend/dist/assets/dagre-KLK3FWXG-DNSqDkwT.js +4 -0
  260. package/frontend/dist/assets/dart-179jqhK4.js +1 -0
  261. package/frontend/dist/assets/defaultLocale-Dda4OpKy.js +1 -0
  262. package/frontend/dist/assets/diagram-E7M64L7V-RqPNT5Vs.js +24 -0
  263. package/frontend/dist/assets/diagram-IFDJBPK2-B-5NRyaE.js +43 -0
  264. package/frontend/dist/assets/diagram-P4PSJMXO-BrP69Hk0.js +24 -0
  265. package/frontend/dist/assets/dist-CU_Nb1G5.js +1 -0
  266. package/frontend/dist/assets/dockerfile-CIAtSGxS.js +1 -0
  267. package/frontend/dist/assets/ecl-CGVKfDxD.js +1 -0
  268. package/frontend/dist/assets/editor-Br_kD0ds.css +1 -0
  269. package/frontend/dist/assets/editor.api2-YXkDn0Gm.js +872 -0
  270. package/frontend/dist/assets/editor.main-fBaXZjJ0.js +6 -0
  271. package/frontend/dist/assets/elixir-BZ-6w0y3.js +1 -0
  272. package/frontend/dist/assets/erDiagram-INFDFZHY-BYiB9NYg.js +70 -0
  273. package/frontend/dist/assets/flow9-CVuOjTMv.js +1 -0
  274. package/frontend/dist/assets/flowDiagram-PKNHOUZH-Cwq47rsR.js +162 -0
  275. package/frontend/dist/assets/freemarker2-DM-pztJU.js +3 -0
  276. package/frontend/dist/assets/fsharp-q0pGJYr6.js +1 -0
  277. package/frontend/dist/assets/ganttDiagram-A5KZAMGK-Dnx3szD9.js +292 -0
  278. package/frontend/dist/assets/gitGraph-HDMCJU4V-COlTQ7bA.js +1 -0
  279. package/frontend/dist/assets/gitGraphDiagram-K3NZZRJ6-BaUxboNc.js +65 -0
  280. package/frontend/dist/assets/go-dzSPfdEO.js +1 -0
  281. package/frontend/dist/assets/graphlib-kEFlkt3U.js +1 -0
  282. package/frontend/dist/assets/graphql-CG4OUoEV.js +1 -0
  283. package/frontend/dist/assets/handlebars-BbK53Vec.js +1 -0
  284. package/frontend/dist/assets/hcl-Cy14JPk3.js +1 -0
  285. package/frontend/dist/assets/html-DYtTQNOG.js +1 -0
  286. package/frontend/dist/assets/html.worker-BjVEKLoU.js +502 -0
  287. package/frontend/dist/assets/htmlMode-C6GTouth.js +1 -0
  288. package/frontend/dist/assets/index-DMLxes_u.js +157 -0
  289. package/frontend/dist/assets/index-DmzeqkB1.css +1 -0
  290. package/frontend/dist/assets/info-3K5VOQVL-DBtHyA4C.js +1 -0
  291. package/frontend/dist/assets/infoDiagram-LFFYTUFH-yBXLgMPI.js +2 -0
  292. package/frontend/dist/assets/ini-Pbg8HGVD.js +1 -0
  293. package/frontend/dist/assets/init-D6KNwrax.js +1 -0
  294. package/frontend/dist/assets/ishikawaDiagram-PHBUUO56-Bld4two_.js +70 -0
  295. package/frontend/dist/assets/java-BmVu6Qrl.js +1 -0
  296. package/frontend/dist/assets/javascript-PbfQEdcJ.js +1 -0
  297. package/frontend/dist/assets/journeyDiagram-4ABVD52K-4HyMd4R2.js +139 -0
  298. package/frontend/dist/assets/json.worker-DqU5Wxnl.js +58 -0
  299. package/frontend/dist/assets/jsonMode-CASsGppE.js +7 -0
  300. package/frontend/dist/assets/julia-3cGnieBq.js +1 -0
  301. package/frontend/dist/assets/kanban-definition-K7BYSVSG-DpgsZmpG.js +89 -0
  302. package/frontend/dist/assets/katex-CEw3x5bf.js +261 -0
  303. package/frontend/dist/assets/kotlin-BuWkVcfV.js +1 -0
  304. package/frontend/dist/assets/less-CJ_VPy2C.js +2 -0
  305. package/frontend/dist/assets/lexon-BygAuZPu.js +1 -0
  306. package/frontend/dist/assets/line-CA_wh_TY.js +1 -0
  307. package/frontend/dist/assets/linear-BAcLW45z.js +1 -0
  308. package/frontend/dist/assets/liquid-kz84dle6.js +1 -0
  309. package/frontend/dist/assets/lspLanguageFeatures-C7hAHFn1.js +4 -0
  310. package/frontend/dist/assets/lua-C8Xs3dCx.js +1 -0
  311. package/frontend/dist/assets/m3-DTJeKBk4.js +1 -0
  312. package/frontend/dist/assets/markdown-QCgx8JqZ.js +1 -0
  313. package/frontend/dist/assets/math-D0YcMJAn.js +1 -0
  314. package/frontend/dist/assets/mdx-yRw0ap-E.js +1 -0
  315. package/frontend/dist/assets/mermaid-parser.core-DAeTodBQ.js +4 -0
  316. package/frontend/dist/assets/mindmap-definition-YRQLILUH-CoNlFyVl.js +68 -0
  317. package/frontend/dist/assets/mips-DopWaYgE.js +1 -0
  318. package/frontend/dist/assets/monaco.contribution-DeY0Qei-.js +2 -0
  319. package/frontend/dist/assets/msdax-BDis4ARV.js +1 -0
  320. package/frontend/dist/assets/mysql-BV6MLsOI.js +1 -0
  321. package/frontend/dist/assets/objective-c-B1UuzKs6.js +1 -0
  322. package/frontend/dist/assets/ordinal-jM7S0YHN.js +1 -0
  323. package/frontend/dist/assets/packet-RMMSAZCW-FF6-Tmai.js +1 -0
  324. package/frontend/dist/assets/pascal-BkvESCrc.js +1 -0
  325. package/frontend/dist/assets/pascaligo-lTy0kZYr.js +1 -0
  326. package/frontend/dist/assets/path-DNPd7Py7.js +1 -0
  327. package/frontend/dist/assets/perl-CrtUPXLV.js +1 -0
  328. package/frontend/dist/assets/pgsql-B9IbNWx2.js +1 -0
  329. package/frontend/dist/assets/php-CXvQBY2p.js +1 -0
  330. package/frontend/dist/assets/pie-UPGHQEXC-CFvXY2o-.js +1 -0
  331. package/frontend/dist/assets/pieDiagram-SKSYHLDU-CM_hbCcn.js +30 -0
  332. package/frontend/dist/assets/pla-DxBxuqWu.js +1 -0
  333. package/frontend/dist/assets/postiats-OkEuT5YF.js +1 -0
  334. package/frontend/dist/assets/powerquery-CMx5Tq4K.js +1 -0
  335. package/frontend/dist/assets/powershell-CstRxrEc.js +1 -0
  336. package/frontend/dist/assets/preload-helper-D4M6sveU.js +1 -0
  337. package/frontend/dist/assets/protobuf-Bx0Z-uRj.js +2 -0
  338. package/frontend/dist/assets/pug--W8vanWl.js +1 -0
  339. package/frontend/dist/assets/python-DA0rnlw3.js +1 -0
  340. package/frontend/dist/assets/qsharp-CRtr0YbN.js +1 -0
  341. package/frontend/dist/assets/quadrantDiagram-337W2JSQ-B3n3IUhC.js +7 -0
  342. package/frontend/dist/assets/r-C6E1d6iv.js +1 -0
  343. package/frontend/dist/assets/radar-KQ55EAFF-MPZu7SdX.js +1 -0
  344. package/frontend/dist/assets/razor-yd73uata.js +1 -0
  345. package/frontend/dist/assets/redis-Dx13voP3.js +1 -0
  346. package/frontend/dist/assets/redshift-D66HwlyV.js +1 -0
  347. package/frontend/dist/assets/requirementDiagram-Z7DCOOCP-CorP7L7F.js +73 -0
  348. package/frontend/dist/assets/restructuredtext-DQT2NKJ2.js +1 -0
  349. package/frontend/dist/assets/rough.esm-DxAX5Vpo.js +1 -0
  350. package/frontend/dist/assets/ruby-iFXI8hwH.js +1 -0
  351. package/frontend/dist/assets/rust-CSKiei34.js +1 -0
  352. package/frontend/dist/assets/sankeyDiagram-WA2Y5GQK-RDx6Bd-B.js +10 -0
  353. package/frontend/dist/assets/sb-Bo3ttdP2.js +1 -0
  354. package/frontend/dist/assets/scala-BC1D-Nxp.js +1 -0
  355. package/frontend/dist/assets/scheme-Z4OAo4Lv.js +1 -0
  356. package/frontend/dist/assets/scss-BvrdPs6B.js +3 -0
  357. package/frontend/dist/assets/sequenceDiagram-2WXFIKYE-JMqJSFq6.js +145 -0
  358. package/frontend/dist/assets/shell-Bh_aCyF-.js +1 -0
  359. package/frontend/dist/assets/solidity-CWHj6tSe.js +1 -0
  360. package/frontend/dist/assets/sophia-raoNtKtm.js +1 -0
  361. package/frontend/dist/assets/sparql-XzmoGnue.js +1 -0
  362. package/frontend/dist/assets/sql-BD0i9Gvg.js +1 -0
  363. package/frontend/dist/assets/src-Bn-kKzs7.js +1 -0
  364. package/frontend/dist/assets/st-DtVKyms6.js +1 -0
  365. package/frontend/dist/assets/stateDiagram-RAJIS63D-CgFfENdy.js +1 -0
  366. package/frontend/dist/assets/stateDiagram-v2-FVOUBMTO-C4Hh2P-U.js +1 -0
  367. package/frontend/dist/assets/swift--UZs77wT.js +1 -0
  368. package/frontend/dist/assets/systemverilog-CDnBSWUd.js +1 -0
  369. package/frontend/dist/assets/tcl-DdCEuTHZ.js +1 -0
  370. package/frontend/dist/assets/timeline-definition-YZTLITO2-BnatPBR5.js +61 -0
  371. package/frontend/dist/assets/treemap-KZPCXAKY-qb1Pl9la.js +1 -0
  372. package/frontend/dist/assets/ts.worker-DyPAEIuH.js +67719 -0
  373. package/frontend/dist/assets/tsMode-iuvyEpyO.js +11 -0
  374. package/frontend/dist/assets/twig-SSL-Altf.js +1 -0
  375. package/frontend/dist/assets/typescript-17918Hud.js +1 -0
  376. package/frontend/dist/assets/typespec-BT7S0ETg.js +1 -0
  377. package/frontend/dist/assets/vb-CrIgucua.js +1 -0
  378. package/frontend/dist/assets/vennDiagram-LZ73GAT5-DygS4Zzd.js +34 -0
  379. package/frontend/dist/assets/wgsl-BeKc3oEp.js +298 -0
  380. package/frontend/dist/assets/workers-DTfwKVoM.js +1 -0
  381. package/frontend/dist/assets/xml-CBMr_Wbw.js +1 -0
  382. package/frontend/dist/assets/xterm-BrP-ENHg.css +1 -0
  383. package/frontend/dist/assets/xterm-CBX2m0YM.js +36 -0
  384. package/frontend/dist/assets/xychartDiagram-JWTSCODW-D6wY1Jwd.js +7 -0
  385. package/frontend/dist/assets/yaml-CTjCH7Bv.js +1 -0
  386. package/frontend/dist/fonts/inter-300.ttf +0 -0
  387. package/frontend/dist/fonts/inter-400.ttf +0 -0
  388. package/frontend/dist/fonts/inter-500.ttf +0 -0
  389. package/frontend/dist/fonts/inter-600.ttf +0 -0
  390. package/frontend/dist/fonts/inter-700.ttf +0 -0
  391. package/frontend/dist/index.html +49 -0
  392. package/frontend/dist/logo_192x192.png +0 -0
  393. package/frontend/dist/logo_32x32.png +0 -0
  394. package/frontend/dist/logo_512x512.png +0 -0
  395. package/frontend/dist/logo_64x64.png +0 -0
  396. package/frontend/dist/logobg_192x192.png +0 -0
  397. package/frontend/dist/logobg_512x512.png +0 -0
  398. package/frontend/dist/logobg_64x64.png +0 -0
  399. package/frontend/dist/manifest.json +25 -0
  400. package/frontend/dist/sw.js +22 -0
  401. package/package.json +74 -7
  402. package/preload/Makefile +12 -0
  403. package/preload/atoo-studio-preload.c +647 -0
  404. package/preload/atoo-studio-preload.so +0 -0
  405. package/setup-cuse.sh +260 -0
  406. package/setup.sh +81 -0
  407. package/src/serial/native/binding.gyp +10 -0
  408. package/src/serial/native/pty_pair.c +222 -0
@@ -0,0 +1,897 @@
1
+ import Database from 'better-sqlite3';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ const STORE_DIR = path.join(os.homedir(), '.atoo-studio');
7
+ const DB_PATH = path.join(STORE_DIR, 'atoo-studio.db');
8
+ const OLD_PROJECTS_FILE = path.join(STORE_DIR, 'projects.json');
9
+ class StudioDatabase {
10
+ db;
11
+ constructor() {
12
+ if (!fs.existsSync(STORE_DIR)) {
13
+ fs.mkdirSync(STORE_DIR, { recursive: true });
14
+ }
15
+ this.db = new Database(DB_PATH);
16
+ this.db.pragma('journal_mode = WAL');
17
+ this.db.pragma('foreign_keys = ON');
18
+ this.db.pragma('busy_timeout = 5000');
19
+ this.initSchema();
20
+ this.migrateProjectsSshColumns();
21
+ this.migrateProjectsParentColumn();
22
+ this.migrateWorktreeHistory();
23
+ this.migrate();
24
+ this.migrateUserManagement();
25
+ this.migrateSessionMetadata();
26
+ this.migrateDatabaseConnections();
27
+ this.migrateProjectChanges();
28
+ }
29
+ initSchema() {
30
+ this.db.exec(`
31
+ CREATE TABLE IF NOT EXISTS environments (
32
+ id TEXT PRIMARY KEY,
33
+ name TEXT NOT NULL,
34
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS projects (
38
+ id TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
+ path TEXT NOT NULL,
41
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS project_environment (
45
+ id TEXT PRIMARY KEY,
46
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
47
+ environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
48
+ linked_at TEXT NOT NULL DEFAULT (datetime('now')),
49
+ UNIQUE (project_id, environment_id)
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS environment_settings (
53
+ environment_id TEXT PRIMARY KEY REFERENCES environments(id) ON DELETE CASCADE,
54
+ sidebar_width TEXT DEFAULT '260px',
55
+ sidebar_collapsed INTEGER DEFAULT 0
56
+ );
57
+
58
+ CREATE TABLE IF NOT EXISTS project_settings (
59
+ pe_id TEXT PRIMARY KEY REFERENCES project_environment(id) ON DELETE CASCADE,
60
+ settings_json TEXT NOT NULL DEFAULT '{}',
61
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
62
+ );
63
+
64
+ CREATE TABLE IF NOT EXISTS ssh_connections (
65
+ id TEXT PRIMARY KEY,
66
+ label TEXT NOT NULL,
67
+ host TEXT NOT NULL,
68
+ port INTEGER NOT NULL DEFAULT 22,
69
+ username TEXT NOT NULL,
70
+ auth_method TEXT NOT NULL,
71
+ password_obfuscated TEXT,
72
+ private_key_obfuscated TEXT,
73
+ passphrase_obfuscated TEXT,
74
+ system_key_path TEXT,
75
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
76
+ );
77
+ `);
78
+ }
79
+ migrate() {
80
+ // Migrate project_environment schema: add `id` column if missing
81
+ this.migrateProjectEnvironmentSchema();
82
+ // Migrate from projects.json if it exists and DB is empty
83
+ if (!fs.existsSync(OLD_PROJECTS_FILE))
84
+ return;
85
+ const envCount = this.db.prepare('SELECT COUNT(*) as cnt FROM environments').get();
86
+ if (envCount.cnt > 0)
87
+ return;
88
+ console.log('[db] Migrating from projects.json...');
89
+ try {
90
+ const data = JSON.parse(fs.readFileSync(OLD_PROJECTS_FILE, 'utf-8'));
91
+ const projects = Array.isArray(data) ? data : [];
92
+ if (projects.length === 0)
93
+ return;
94
+ const envId = uuidv4();
95
+ const txn = this.db.transaction(() => {
96
+ // Create "Default" environment
97
+ this.db.prepare('INSERT INTO environments (id, name) VALUES (?, ?)').run(envId, 'Default');
98
+ this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(envId);
99
+ // Import projects
100
+ for (const p of projects) {
101
+ const projectId = p.id || uuidv4();
102
+ const peId = uuidv4();
103
+ this.db.prepare('INSERT INTO projects (id, name, path, created_at) VALUES (?, ?, ?, ?)')
104
+ .run(projectId, p.name, p.path, p.createdAt || new Date().toISOString());
105
+ this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id) VALUES (?, ?, ?)')
106
+ .run(peId, projectId, envId);
107
+ }
108
+ });
109
+ txn();
110
+ // Rename old file
111
+ fs.renameSync(OLD_PROJECTS_FILE, OLD_PROJECTS_FILE + '.bak');
112
+ console.log(`[db] Migrated ${projects.length} projects into "Default" environment`);
113
+ }
114
+ catch (err) {
115
+ console.error('[db] Migration failed:', err);
116
+ }
117
+ }
118
+ migrateProjectsSshColumns() {
119
+ const columns = this.db.prepare("PRAGMA table_info(projects)").all();
120
+ const hasSshCol = columns.some((c) => c.name === 'ssh_connection_id');
121
+ if (hasSshCol || columns.length === 0)
122
+ return;
123
+ this.db.exec(`
124
+ ALTER TABLE projects ADD COLUMN ssh_connection_id TEXT REFERENCES ssh_connections(id);
125
+ ALTER TABLE projects ADD COLUMN remote_path TEXT;
126
+ `);
127
+ console.log('[db] Added ssh_connection_id and remote_path columns to projects');
128
+ }
129
+ migrateProjectsParentColumn() {
130
+ const columns = this.db.prepare("PRAGMA table_info(projects)").all();
131
+ const hasParentCol = columns.some((c) => c.name === 'parent_project_id');
132
+ if (hasParentCol || columns.length === 0)
133
+ return;
134
+ this.db.exec(`
135
+ ALTER TABLE projects ADD COLUMN parent_project_id TEXT REFERENCES projects(id) ON DELETE CASCADE;
136
+ `);
137
+ console.log('[db] Added parent_project_id column to projects');
138
+ }
139
+ migrateWorktreeHistory() {
140
+ this.db.exec(`
141
+ CREATE TABLE IF NOT EXISTS worktree_history (
142
+ id TEXT PRIMARY KEY,
143
+ parent_project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
144
+ worktree_path TEXT NOT NULL,
145
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
146
+ UNIQUE (parent_project_id, worktree_path)
147
+ );
148
+ `);
149
+ }
150
+ migrateProjectEnvironmentSchema() {
151
+ // Check if project_environment table has an `id` column
152
+ const columns = this.db.prepare("PRAGMA table_info(project_environment)").all();
153
+ const hasId = columns.some((c) => c.name === 'id');
154
+ if (hasId || columns.length === 0)
155
+ return; // Already migrated or table doesn't exist yet
156
+ console.log('[db] Migrating project_environment schema to add id column...');
157
+ const txn = this.db.transaction(() => {
158
+ // Read existing PE rows
159
+ const peRows = this.db.prepare('SELECT project_id, environment_id, linked_at FROM project_environment').all();
160
+ // Read existing project_settings rows (old composite key schema)
161
+ let psRows = [];
162
+ try {
163
+ const psColumns = this.db.prepare("PRAGMA table_info(project_settings)").all();
164
+ const hasProjectId = psColumns.some((c) => c.name === 'project_id');
165
+ if (hasProjectId) {
166
+ psRows = this.db.prepare('SELECT project_id, environment_id, settings_json, updated_at FROM project_settings').all();
167
+ }
168
+ }
169
+ catch { }
170
+ // Drop old tables
171
+ this.db.exec('DROP TABLE IF EXISTS project_settings');
172
+ this.db.exec('DROP TABLE IF EXISTS project_environment');
173
+ // Recreate with new schema
174
+ this.db.exec(`
175
+ CREATE TABLE project_environment (
176
+ id TEXT PRIMARY KEY,
177
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
178
+ environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
179
+ linked_at TEXT NOT NULL DEFAULT (datetime('now')),
180
+ UNIQUE (project_id, environment_id)
181
+ );
182
+ CREATE TABLE project_settings (
183
+ pe_id TEXT PRIMARY KEY REFERENCES project_environment(id) ON DELETE CASCADE,
184
+ settings_json TEXT NOT NULL DEFAULT '{}',
185
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
186
+ );
187
+ `);
188
+ // Re-insert PE rows with generated UUIDs, map old composite key to new ID
189
+ const keyToId = new Map();
190
+ for (const row of peRows) {
191
+ const peId = uuidv4();
192
+ keyToId.set(`${row.project_id}/${row.environment_id}`, peId);
193
+ this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id, linked_at) VALUES (?, ?, ?, ?)')
194
+ .run(peId, row.project_id, row.environment_id, row.linked_at);
195
+ }
196
+ // Migrate project_settings to use pe_id
197
+ for (const ps of psRows) {
198
+ const peId = keyToId.get(`${ps.project_id}/${ps.environment_id}`);
199
+ if (peId) {
200
+ this.db.prepare('INSERT INTO project_settings (pe_id, settings_json, updated_at) VALUES (?, ?, ?)')
201
+ .run(peId, ps.settings_json, ps.updated_at);
202
+ }
203
+ }
204
+ console.log(`[db] Migrated ${peRows.length} project_environment rows, ${psRows.length} project_settings rows`);
205
+ });
206
+ txn();
207
+ }
208
+ // ═══════════════════════════════════════════════════
209
+ // ENVIRONMENTS
210
+ // ═══════════════════════════════════════════════════
211
+ listEnvironments() {
212
+ return this.db.prepare(`
213
+ SELECT e.id, e.name, e.created_at,
214
+ (SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
215
+ FROM environments e
216
+ ORDER BY e.created_at
217
+ `).all();
218
+ }
219
+ getEnvironment(id) {
220
+ return this.db.prepare(`
221
+ SELECT e.id, e.name, e.created_at,
222
+ (SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
223
+ FROM environments e WHERE e.id = ?
224
+ `).get(id);
225
+ }
226
+ createEnvironment(name) {
227
+ const id = uuidv4();
228
+ this.db.prepare('INSERT INTO environments (id, name) VALUES (?, ?)').run(id, name);
229
+ this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(id);
230
+ return this.getEnvironment(id);
231
+ }
232
+ deleteEnvironment(id) {
233
+ const result = this.db.prepare('DELETE FROM environments WHERE id = ?').run(id);
234
+ return result.changes > 0;
235
+ }
236
+ // ═══════════════════════════════════════════════════
237
+ // PROJECTS
238
+ // ═══════════════════════════════════════════════════
239
+ getProject(id) {
240
+ return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE id = ?')
241
+ .get(id);
242
+ }
243
+ listAllProjects() {
244
+ return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects ORDER BY created_at')
245
+ .all();
246
+ }
247
+ createProject(name, projectPath, opts) {
248
+ const id = uuidv4();
249
+ const resolved = opts?.sshConnectionId ? projectPath : path.resolve(projectPath);
250
+ this.db.prepare('INSERT INTO projects (id, name, path, ssh_connection_id, remote_path, parent_project_id) VALUES (?, ?, ?, ?, ?, ?)')
251
+ .run(id, name, resolved, opts?.sshConnectionId || null, opts?.remotePath || null, opts?.parentProjectId || null);
252
+ return this.getProject(id);
253
+ }
254
+ deleteProject(id) {
255
+ const result = this.db.prepare('DELETE FROM projects WHERE id = ?').run(id);
256
+ return result.changes > 0;
257
+ }
258
+ getChildProjects(parentId) {
259
+ return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE parent_project_id = ? ORDER BY created_at')
260
+ .all(parentId);
261
+ }
262
+ findProjectByPath(projectPath) {
263
+ return this.db.prepare('SELECT id, name, path, created_at, ssh_connection_id, remote_path, parent_project_id FROM projects WHERE path = ?')
264
+ .get(projectPath);
265
+ }
266
+ // ═══════════════════════════════════════════════════
267
+ // N:N LINKING
268
+ // ═══════════════════════════════════════════════════
269
+ getProjectsForEnvironment(envId) {
270
+ return this.db.prepare(`
271
+ SELECT p.id, p.name, p.path, p.created_at, p.ssh_connection_id, p.remote_path, p.parent_project_id, pe.id as pe_id
272
+ FROM projects p
273
+ JOIN project_environment pe ON pe.project_id = p.id
274
+ WHERE pe.environment_id = ?
275
+ ORDER BY pe.linked_at
276
+ `).all(envId);
277
+ }
278
+ getEnvironmentsForProject(projectId) {
279
+ return this.db.prepare(`
280
+ SELECT e.id, e.name, e.created_at
281
+ FROM environments e
282
+ JOIN project_environment pe ON pe.environment_id = e.id
283
+ WHERE pe.project_id = ?
284
+ `).all(projectId);
285
+ }
286
+ linkProject(projectId, envId) {
287
+ const existing = this.db.prepare('SELECT id FROM project_environment WHERE project_id = ? AND environment_id = ?').get(projectId, envId);
288
+ if (existing)
289
+ return existing.id;
290
+ const peId = uuidv4();
291
+ this.db.prepare('INSERT INTO project_environment (id, project_id, environment_id) VALUES (?, ?, ?)').run(peId, projectId, envId);
292
+ // Also link child projects (worktrees) to the same environment
293
+ const children = this.getChildProjects(projectId);
294
+ for (const child of children) {
295
+ this.linkProject(child.id, envId);
296
+ }
297
+ return peId;
298
+ }
299
+ unlinkProject(peId) {
300
+ const pe = this.getProjectEnvironment(peId);
301
+ this.db.prepare('DELETE FROM project_environment WHERE id = ?').run(peId);
302
+ // Also unlink child projects from this environment
303
+ if (pe) {
304
+ const children = this.getChildProjects(pe.project_id);
305
+ for (const child of children) {
306
+ const childPe = this.db.prepare('SELECT id FROM project_environment WHERE project_id = ? AND environment_id = ?').get(child.id, pe.environment_id);
307
+ if (childPe) {
308
+ this.unlinkProject(childPe.id);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ getProjectEnvironment(peId) {
314
+ return this.db.prepare('SELECT id, project_id, environment_id FROM project_environment WHERE id = ?').get(peId);
315
+ }
316
+ // ═══════════════════════════════════════════════════
317
+ // ENVIRONMENT SETTINGS
318
+ // ═══════════════════════════════════════════════════
319
+ getEnvironmentSettings(envId) {
320
+ return this.db.prepare('SELECT * FROM environment_settings WHERE environment_id = ?')
321
+ .get(envId);
322
+ }
323
+ updateEnvironmentSettings(envId, partial) {
324
+ const current = this.getEnvironmentSettings(envId);
325
+ if (!current)
326
+ return;
327
+ const updates = [];
328
+ const values = [];
329
+ if (partial.sidebar_width !== undefined) {
330
+ updates.push('sidebar_width = ?');
331
+ values.push(partial.sidebar_width);
332
+ }
333
+ if (partial.sidebar_collapsed !== undefined) {
334
+ updates.push('sidebar_collapsed = ?');
335
+ values.push(partial.sidebar_collapsed);
336
+ }
337
+ if (updates.length === 0)
338
+ return;
339
+ values.push(envId);
340
+ this.db.prepare(`UPDATE environment_settings SET ${updates.join(', ')} WHERE environment_id = ?`).run(...values);
341
+ }
342
+ // ═══════════════════════════════════════════════════
343
+ // PROJECT SETTINGS (per env)
344
+ // ═══════════════════════════════════════════════════
345
+ getProjectSettings(peId) {
346
+ const row = this.db.prepare('SELECT settings_json FROM project_settings WHERE pe_id = ?').get(peId);
347
+ if (!row)
348
+ return {};
349
+ try {
350
+ return JSON.parse(row.settings_json);
351
+ }
352
+ catch {
353
+ return {};
354
+ }
355
+ }
356
+ updateProjectSettings(peId, partial) {
357
+ const current = this.getProjectSettings(peId);
358
+ const merged = { ...current, ...partial };
359
+ const json = JSON.stringify(merged);
360
+ this.db.prepare(`
361
+ INSERT INTO project_settings (pe_id, settings_json, updated_at)
362
+ VALUES (?, ?, datetime('now'))
363
+ ON CONFLICT(pe_id) DO UPDATE SET
364
+ settings_json = excluded.settings_json,
365
+ updated_at = excluded.updated_at
366
+ `).run(peId, json);
367
+ }
368
+ // ═══════════════════════════════════════════════════
369
+ // SSH CONNECTIONS
370
+ // ═══════════════════════════════════════════════════
371
+ createSshConnection(config) {
372
+ const id = uuidv4();
373
+ this.db.prepare(`
374
+ INSERT INTO ssh_connections (id, label, host, port, username, auth_method,
375
+ password_obfuscated, private_key_obfuscated, passphrase_obfuscated, system_key_path)
376
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
377
+ `).run(id, config.label, config.host, config.port, config.username, config.auth_method, config.password_obfuscated || null, config.private_key_obfuscated || null, config.passphrase_obfuscated || null, config.system_key_path || null);
378
+ return this.getSshConnection(id);
379
+ }
380
+ getSshConnection(id) {
381
+ return this.db.prepare('SELECT * FROM ssh_connections WHERE id = ?')
382
+ .get(id);
383
+ }
384
+ listSshConnections() {
385
+ return this.db.prepare('SELECT * FROM ssh_connections ORDER BY created_at')
386
+ .all();
387
+ }
388
+ deleteSshConnection(id) {
389
+ const result = this.db.prepare('DELETE FROM ssh_connections WHERE id = ?').run(id);
390
+ return result.changes > 0;
391
+ }
392
+ // ═══════════════════════════════════════════════════
393
+ // WORKTREE HISTORY (persists paths after removal)
394
+ // ═══════════════════════════════════════════════════
395
+ /**
396
+ * Record a worktree path for a parent project. Idempotent — skips duplicates.
397
+ */
398
+ recordWorktreePath(parentProjectId, worktreePath) {
399
+ this.db.prepare(`
400
+ INSERT OR IGNORE INTO worktree_history (id, parent_project_id, worktree_path)
401
+ VALUES (?, ?, ?)
402
+ `).run(uuidv4(), parentProjectId, worktreePath);
403
+ }
404
+ /**
405
+ * Get all project paths related to a given path (main project + all worktrees, current and historical).
406
+ * Works bidirectionally: pass a main project path or a worktree path.
407
+ */
408
+ getAllRelatedProjectPaths(projectPath) {
409
+ // First, find the project by path
410
+ const project = this.findProjectByPath(projectPath);
411
+ if (!project) {
412
+ // Path might be a historical worktree that's been deleted from projects table
413
+ const hist = this.db.prepare('SELECT parent_project_id FROM worktree_history WHERE worktree_path = ?').get(projectPath);
414
+ if (!hist)
415
+ return [projectPath]; // Unknown path, just return itself
416
+ const parent = this.getProject(hist.parent_project_id);
417
+ if (!parent)
418
+ return [projectPath];
419
+ return this.collectAllPaths(parent.id, parent.path);
420
+ }
421
+ // If this is a child project (worktree), resolve to parent
422
+ const parentId = project.parent_project_id;
423
+ if (parentId) {
424
+ const parent = this.getProject(parentId);
425
+ if (parent) {
426
+ return this.collectAllPaths(parent.id, parent.path);
427
+ }
428
+ }
429
+ // This is a root project — collect all its paths
430
+ return this.collectAllPaths(project.id, project.path);
431
+ }
432
+ collectAllPaths(parentProjectId, parentPath) {
433
+ const paths = new Set();
434
+ paths.add(parentPath);
435
+ // Current child projects (active worktrees)
436
+ const children = this.getChildProjects(parentProjectId);
437
+ for (const child of children) {
438
+ paths.add(child.path);
439
+ }
440
+ // Historical worktree paths (includes removed worktrees)
441
+ const historical = this.db.prepare('SELECT worktree_path FROM worktree_history WHERE parent_project_id = ?').all(parentProjectId);
442
+ for (const row of historical) {
443
+ paths.add(row.worktree_path);
444
+ }
445
+ return Array.from(paths);
446
+ }
447
+ // ═══════════════════════════════════════════════════
448
+ // USER MANAGEMENT MIGRATION
449
+ // ═══════════════════════════════════════════════════
450
+ migrateUserManagement() {
451
+ this.db.exec(`
452
+ CREATE TABLE IF NOT EXISTS users (
453
+ id TEXT PRIMARY KEY,
454
+ username TEXT NOT NULL UNIQUE COLLATE NOCASE,
455
+ display_name TEXT NOT NULL,
456
+ role TEXT NOT NULL CHECK (role IN ('admin', 'basic')),
457
+ password_hash TEXT NOT NULL,
458
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
459
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
460
+ );
461
+
462
+ CREATE TABLE IF NOT EXISTS auth_sessions (
463
+ id TEXT PRIMARY KEY,
464
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
465
+ expires_at TEXT NOT NULL,
466
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
467
+ ip_address TEXT,
468
+ user_agent TEXT
469
+ );
470
+
471
+ CREATE TABLE IF NOT EXISTS totp_secrets (
472
+ user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
473
+ secret_encrypted TEXT NOT NULL,
474
+ verified INTEGER NOT NULL DEFAULT 0,
475
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
476
+ );
477
+
478
+ CREATE TABLE IF NOT EXISTS passkeys (
479
+ id TEXT PRIMARY KEY,
480
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
481
+ credential_id TEXT NOT NULL UNIQUE,
482
+ public_key TEXT NOT NULL,
483
+ counter INTEGER NOT NULL DEFAULT 0,
484
+ transports TEXT,
485
+ device_name TEXT,
486
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
487
+ );
488
+
489
+ CREATE TABLE IF NOT EXISTS environment_shares (
490
+ id TEXT PRIMARY KEY,
491
+ environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
492
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
493
+ shared_by TEXT NOT NULL REFERENCES users(id),
494
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
495
+ UNIQUE (environment_id, user_id)
496
+ );
497
+ `);
498
+ // Add owner_user_id column to environments if missing
499
+ const columns = this.db.prepare("PRAGMA table_info(environments)").all();
500
+ const hasOwnerCol = columns.some((c) => c.name === 'owner_user_id');
501
+ if (!hasOwnerCol && columns.length > 0) {
502
+ this.db.exec('ALTER TABLE environments ADD COLUMN owner_user_id TEXT REFERENCES users(id)');
503
+ console.log('[db] Added owner_user_id column to environments');
504
+ }
505
+ }
506
+ // ═══════════════════════════════════════════════════
507
+ // USERS
508
+ // ═══════════════════════════════════════════════════
509
+ getUserCount() {
510
+ return this.db.prepare('SELECT COUNT(*) as cnt FROM users').get().cnt;
511
+ }
512
+ createUser(username, displayName, role, passwordHash) {
513
+ const id = uuidv4();
514
+ this.db.prepare('INSERT INTO users (id, username, display_name, role, password_hash) VALUES (?, ?, ?, ?, ?)').run(id, username, displayName, role, passwordHash);
515
+ return this.getUser(id);
516
+ }
517
+ getUser(id) {
518
+ return this.db.prepare('SELECT * FROM users WHERE id = ?').get(id);
519
+ }
520
+ getUserByUsername(username) {
521
+ return this.db.prepare('SELECT * FROM users WHERE username = ?').get(username);
522
+ }
523
+ listUsers() {
524
+ return this.db.prepare('SELECT id, username, display_name, role, created_at, updated_at FROM users ORDER BY created_at').all();
525
+ }
526
+ updateUser(id, updates) {
527
+ const parts = [];
528
+ const values = [];
529
+ if (updates.display_name !== undefined) {
530
+ parts.push('display_name = ?');
531
+ values.push(updates.display_name);
532
+ }
533
+ if (updates.role !== undefined) {
534
+ parts.push('role = ?');
535
+ values.push(updates.role);
536
+ }
537
+ if (parts.length === 0)
538
+ return false;
539
+ parts.push("updated_at = datetime('now')");
540
+ values.push(id);
541
+ const result = this.db.prepare(`UPDATE users SET ${parts.join(', ')} WHERE id = ?`).run(...values);
542
+ return result.changes > 0;
543
+ }
544
+ updateUserPassword(id, passwordHash) {
545
+ const result = this.db.prepare("UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?").run(passwordHash, id);
546
+ return result.changes > 0;
547
+ }
548
+ deleteUser(id) {
549
+ const result = this.db.prepare('DELETE FROM users WHERE id = ?').run(id);
550
+ return result.changes > 0;
551
+ }
552
+ // ═══════════════════════════════════════════════════
553
+ // AUTH SESSIONS
554
+ // ═══════════════════════════════════════════════════
555
+ createAuthSession(id, userId, expiresAt, ip, userAgent) {
556
+ this.db.prepare('INSERT INTO auth_sessions (id, user_id, expires_at, ip_address, user_agent) VALUES (?, ?, ?, ?, ?)').run(id, userId, expiresAt, ip, userAgent);
557
+ }
558
+ getAuthSessionUser(sessionId) {
559
+ const row = this.db.prepare(`
560
+ SELECT u.* FROM users u
561
+ JOIN auth_sessions s ON s.user_id = u.id
562
+ WHERE s.id = ? AND s.expires_at > datetime('now')
563
+ `).get(sessionId);
564
+ return row || null;
565
+ }
566
+ deleteAuthSession(sessionId) {
567
+ this.db.prepare('DELETE FROM auth_sessions WHERE id = ?').run(sessionId);
568
+ }
569
+ deleteAllUserAuthSessions(userId) {
570
+ this.db.prepare('DELETE FROM auth_sessions WHERE user_id = ?').run(userId);
571
+ }
572
+ // ═══════════════════════════════════════════════════
573
+ // TOTP SECRETS
574
+ // ═══════════════════════════════════════════════════
575
+ saveTotpSecret(userId, secretEncrypted) {
576
+ this.db.prepare(`
577
+ INSERT INTO totp_secrets (user_id, secret_encrypted, verified)
578
+ VALUES (?, ?, 0)
579
+ ON CONFLICT(user_id) DO UPDATE SET secret_encrypted = excluded.secret_encrypted, verified = 0
580
+ `).run(userId, secretEncrypted);
581
+ }
582
+ getTotpSecret(userId) {
583
+ return this.db.prepare('SELECT * FROM totp_secrets WHERE user_id = ?').get(userId) || null;
584
+ }
585
+ markTotpVerified(userId) {
586
+ this.db.prepare('UPDATE totp_secrets SET verified = 1 WHERE user_id = ?').run(userId);
587
+ }
588
+ deleteTotpSecret(userId) {
589
+ this.db.prepare('DELETE FROM totp_secrets WHERE user_id = ?').run(userId);
590
+ }
591
+ // ═══════════════════════════════════════════════════
592
+ // PASSKEYS
593
+ // ═══════════════════════════════════════════════════
594
+ createPasskey(pk) {
595
+ this.db.prepare(`
596
+ INSERT INTO passkeys (id, user_id, credential_id, public_key, counter, transports, device_name)
597
+ VALUES (?, ?, ?, ?, ?, ?, ?)
598
+ `).run(pk.id, pk.user_id, pk.credential_id, pk.public_key, pk.counter, pk.transports, pk.device_name);
599
+ }
600
+ listPasskeys(userId) {
601
+ return this.db.prepare('SELECT * FROM passkeys WHERE user_id = ? ORDER BY created_at').all(userId);
602
+ }
603
+ findPasskeyByCredentialId(credentialId) {
604
+ return this.db.prepare('SELECT * FROM passkeys WHERE credential_id = ?').get(credentialId) || null;
605
+ }
606
+ updatePasskeyCounter(id, counter) {
607
+ this.db.prepare('UPDATE passkeys SET counter = ? WHERE id = ?').run(counter, id);
608
+ }
609
+ deletePasskey(id, userId) {
610
+ const result = this.db.prepare('DELETE FROM passkeys WHERE id = ? AND user_id = ?').run(id, userId);
611
+ return result.changes > 0;
612
+ }
613
+ deleteAllUserPasskeys(userId) {
614
+ this.db.prepare('DELETE FROM passkeys WHERE user_id = ?').run(userId);
615
+ }
616
+ getUserIdsWithPasskeys() {
617
+ return this.db.prepare('SELECT DISTINCT user_id FROM passkeys').all().map(r => r.user_id);
618
+ }
619
+ // ═══════════════════════════════════════════════════
620
+ // ENVIRONMENT OWNERSHIP & SHARING
621
+ // ═══════════════════════════════════════════════════
622
+ listEnvironmentsForUser(userId) {
623
+ return this.db.prepare(`
624
+ SELECT e.id, e.name, e.created_at, e.owner_user_id,
625
+ (SELECT COUNT(*) FROM project_environment pe WHERE pe.environment_id = e.id) as project_count
626
+ FROM environments e
627
+ WHERE e.owner_user_id = ?
628
+ OR e.id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?)
629
+ ORDER BY e.created_at
630
+ `).all(userId, userId);
631
+ }
632
+ createEnvironmentWithOwner(name, ownerUserId) {
633
+ const id = uuidv4();
634
+ this.db.prepare('INSERT INTO environments (id, name, owner_user_id) VALUES (?, ?, ?)').run(id, name, ownerUserId);
635
+ this.db.prepare('INSERT INTO environment_settings (environment_id) VALUES (?)').run(id);
636
+ return this.getEnvironment(id);
637
+ }
638
+ assignUnownedEnvironments(userId) {
639
+ this.db.prepare('UPDATE environments SET owner_user_id = ? WHERE owner_user_id IS NULL').run(userId);
640
+ }
641
+ canAccessEnvironment(userId, envId) {
642
+ const row = this.db.prepare(`
643
+ SELECT 1 FROM environments
644
+ WHERE id = ? AND (owner_user_id = ? OR id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?))
645
+ `).get(envId, userId, userId);
646
+ return !!row;
647
+ }
648
+ canAccessProject(userId, projectId) {
649
+ const row = this.db.prepare(`
650
+ SELECT 1 FROM project_environment pe
651
+ JOIN environments e ON e.id = pe.environment_id
652
+ WHERE pe.project_id = ?
653
+ AND (e.owner_user_id = ? OR e.id IN (SELECT environment_id FROM environment_shares WHERE user_id = ?))
654
+ LIMIT 1
655
+ `).get(projectId, userId, userId);
656
+ return !!row;
657
+ }
658
+ getEnvironmentOwner(envId) {
659
+ const row = this.db.prepare('SELECT owner_user_id FROM environments WHERE id = ?').get(envId);
660
+ return row?.owner_user_id || null;
661
+ }
662
+ shareEnvironment(envId, userId, sharedBy) {
663
+ const id = uuidv4();
664
+ this.db.prepare(`
665
+ INSERT OR IGNORE INTO environment_shares (id, environment_id, user_id, shared_by)
666
+ VALUES (?, ?, ?, ?)
667
+ `).run(id, envId, userId, sharedBy);
668
+ }
669
+ unshareEnvironment(envId, userId) {
670
+ const result = this.db.prepare('DELETE FROM environment_shares WHERE environment_id = ? AND user_id = ?').run(envId, userId);
671
+ return result.changes > 0;
672
+ }
673
+ listEnvironmentShares(envId) {
674
+ return this.db.prepare(`
675
+ SELECT es.*, u.username, u.display_name
676
+ FROM environment_shares es
677
+ JOIN users u ON u.id = es.user_id
678
+ WHERE es.environment_id = ?
679
+ ORDER BY es.created_at
680
+ `).all(envId);
681
+ }
682
+ // ═══════════════════════════════════════════════════
683
+ // SESSION METADATA
684
+ // ═══════════════════════════════════════════════════
685
+ migrateSessionMetadata() {
686
+ this.db.exec(`
687
+ CREATE TABLE IF NOT EXISTS session_metadata (
688
+ session_uuid TEXT PRIMARY KEY,
689
+ name TEXT,
690
+ description TEXT,
691
+ tags_json TEXT NOT NULL DEFAULT '[]',
692
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
693
+ );
694
+ `);
695
+ // Migrate data from old session_tags table if it exists
696
+ try {
697
+ const oldTags = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session_tags'").get();
698
+ if (oldTags) {
699
+ const rows = this.db.prepare('SELECT session_uuid, GROUP_CONCAT(tag) as tags FROM session_tags GROUP BY session_uuid').all();
700
+ for (const row of rows) {
701
+ const tags = row.tags.split(',');
702
+ this.db.prepare(`
703
+ INSERT OR IGNORE INTO session_metadata (session_uuid, tags_json)
704
+ VALUES (?, ?)
705
+ `).run(row.session_uuid, JSON.stringify(tags));
706
+ }
707
+ this.db.exec('DROP TABLE session_tags');
708
+ }
709
+ }
710
+ catch { }
711
+ }
712
+ getSessionMetadata(sessionUuid) {
713
+ const row = this.db.prepare('SELECT session_uuid, name, description, tags_json FROM session_metadata WHERE session_uuid = ?').get(sessionUuid);
714
+ if (!row)
715
+ return null;
716
+ return {
717
+ sessionUuid: row.session_uuid,
718
+ name: row.name || undefined,
719
+ description: row.description || undefined,
720
+ tags: JSON.parse(row.tags_json || '[]'),
721
+ };
722
+ }
723
+ getMetadataForSessions(sessionUuids) {
724
+ if (!sessionUuids.length)
725
+ return {};
726
+ const placeholders = sessionUuids.map(() => '?').join(',');
727
+ const rows = this.db.prepare(`SELECT session_uuid, name, description, tags_json FROM session_metadata WHERE session_uuid IN (${placeholders})`).all(...sessionUuids);
728
+ const result = {};
729
+ for (const row of rows) {
730
+ result[row.session_uuid] = {
731
+ sessionUuid: row.session_uuid,
732
+ name: row.name || undefined,
733
+ description: row.description || undefined,
734
+ tags: JSON.parse(row.tags_json || '[]'),
735
+ };
736
+ }
737
+ return result;
738
+ }
739
+ setSessionMetadata(sessionUuid, updates) {
740
+ const existing = this.getSessionMetadata(sessionUuid);
741
+ const name = updates.name !== undefined ? updates.name : (existing?.name || null);
742
+ const description = updates.description !== undefined ? updates.description : (existing?.description || null);
743
+ const tags = updates.tags !== undefined ? updates.tags : (existing?.tags || []);
744
+ this.db.prepare(`
745
+ INSERT INTO session_metadata (session_uuid, name, description, tags_json, updated_at)
746
+ VALUES (?, ?, ?, ?, datetime('now'))
747
+ ON CONFLICT(session_uuid) DO UPDATE SET
748
+ name = excluded.name,
749
+ description = excluded.description,
750
+ tags_json = excluded.tags_json,
751
+ updated_at = excluded.updated_at
752
+ `).run(sessionUuid, name, description, JSON.stringify(tags));
753
+ }
754
+ // ═══════════════════════════════════════════════════
755
+ // ═══════════════════════════════════════════════════
756
+ // DATABASE CONNECTIONS (saved connections for Database Explorer)
757
+ // ═══════════════════════════════════════════════════
758
+ migrateDatabaseConnections() {
759
+ this.db.exec(`
760
+ CREATE TABLE IF NOT EXISTS saved_db_connections (
761
+ id TEXT PRIMARY KEY,
762
+ name TEXT NOT NULL,
763
+ db_type TEXT NOT NULL,
764
+ params_json TEXT NOT NULL,
765
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
766
+ last_used_at TEXT
767
+ )
768
+ `);
769
+ }
770
+ getSavedDbConnections() {
771
+ return this.db.prepare(`
772
+ SELECT id, name, db_type, params_json FROM saved_db_connections ORDER BY last_used_at DESC NULLS LAST, name
773
+ `).all().map((r) => ({
774
+ id: r.id,
775
+ name: r.name,
776
+ db_type: r.db_type,
777
+ params: JSON.parse(r.params_json),
778
+ }));
779
+ }
780
+ saveDbConnection(id, name, dbType, params) {
781
+ this.db.prepare(`
782
+ INSERT INTO saved_db_connections (id, name, db_type, params_json)
783
+ VALUES (?, ?, ?, ?)
784
+ ON CONFLICT(id) DO UPDATE SET name = excluded.name, db_type = excluded.db_type, params_json = excluded.params_json
785
+ `).run(id, name, dbType, JSON.stringify(params));
786
+ }
787
+ touchDbConnection(id) {
788
+ this.db.prepare(`UPDATE saved_db_connections SET last_used_at = datetime('now') WHERE id = ?`).run(id);
789
+ }
790
+ deleteDbConnection(id) {
791
+ this.db.prepare(`DELETE FROM saved_db_connections WHERE id = ?`).run(id);
792
+ }
793
+ // ═══════════════════════════════════════════════════
794
+ // PROJECT CHANGES ("What has been done" tracking)
795
+ // ═══════════════════════════════════════════════════
796
+ migrateProjectChanges() {
797
+ this.db.exec(`
798
+ CREATE TABLE IF NOT EXISTS project_changes (
799
+ id TEXT PRIMARY KEY,
800
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
801
+ short_description TEXT NOT NULL DEFAULT '',
802
+ long_description TEXT NOT NULL DEFAULT '',
803
+ tags_json TEXT NOT NULL DEFAULT '[]',
804
+ approx_files_affected INTEGER NOT NULL DEFAULT 0,
805
+ session_id TEXT,
806
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
807
+ )
808
+ `);
809
+ // Migrate from old schema that had a single 'description' column
810
+ const columns = this.db.prepare("PRAGMA table_info(project_changes)").all();
811
+ const hasOldDesc = columns.some((c) => c.name === 'description');
812
+ const hasShortDesc = columns.some((c) => c.name === 'short_description');
813
+ if (hasOldDesc && !hasShortDesc) {
814
+ this.db.exec(`
815
+ ALTER TABLE project_changes ADD COLUMN short_description TEXT NOT NULL DEFAULT '';
816
+ ALTER TABLE project_changes ADD COLUMN long_description TEXT NOT NULL DEFAULT '';
817
+ ALTER TABLE project_changes ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]';
818
+ `);
819
+ // Move old description into long_description, truncate for short
820
+ const rows = this.db.prepare('SELECT id, description FROM project_changes').all();
821
+ const stmt = this.db.prepare('UPDATE project_changes SET short_description = ?, long_description = ? WHERE id = ?');
822
+ for (const row of rows) {
823
+ const short = (row.description || '').split(/[.—\-\n]/)[0].trim().split(/\s+/).slice(0, 10).join(' ');
824
+ stmt.run(short, row.description || '', row.id);
825
+ }
826
+ console.log(`[db] Migrated ${rows.length} project_changes rows to new schema`);
827
+ }
828
+ // Drop old 'description' column if it still exists (it has NOT NULL and breaks INSERTs)
829
+ if (hasOldDesc) {
830
+ try {
831
+ this.db.exec('ALTER TABLE project_changes DROP COLUMN description');
832
+ console.log('[db] Dropped old description column from project_changes');
833
+ }
834
+ catch {
835
+ // SQLite < 3.35.0 doesn't support DROP COLUMN — recreate table
836
+ const txn = this.db.transaction(() => {
837
+ const existing = this.db.prepare('SELECT id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at FROM project_changes').all();
838
+ this.db.exec('DROP TABLE project_changes');
839
+ this.db.exec(`
840
+ CREATE TABLE project_changes (
841
+ id TEXT PRIMARY KEY,
842
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
843
+ short_description TEXT NOT NULL DEFAULT '',
844
+ long_description TEXT NOT NULL DEFAULT '',
845
+ tags_json TEXT NOT NULL DEFAULT '[]',
846
+ approx_files_affected INTEGER NOT NULL DEFAULT 0,
847
+ session_id TEXT,
848
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
849
+ )
850
+ `);
851
+ const ins = this.db.prepare('INSERT INTO project_changes (id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
852
+ for (const r of existing) {
853
+ ins.run(r.id, r.project_id, r.short_description || '', r.long_description || '', r.tags_json || '[]', r.approx_files_affected || 0, r.session_id, r.created_at);
854
+ }
855
+ console.log(`[db] Recreated project_changes table without old description column (${existing.length} rows preserved)`);
856
+ });
857
+ txn();
858
+ }
859
+ }
860
+ }
861
+ PC_COLS = 'id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id, created_at';
862
+ listProjectChanges(projectId) {
863
+ return this.db.prepare(`SELECT ${this.PC_COLS} FROM project_changes WHERE project_id = ? ORDER BY created_at DESC`).all(projectId);
864
+ }
865
+ getProjectChange(id) {
866
+ return this.db.prepare(`SELECT ${this.PC_COLS} FROM project_changes WHERE id = ?`).get(id);
867
+ }
868
+ createProjectChange(projectId, shortDesc, longDesc, tags, approxFilesAffected, sessionId) {
869
+ const id = uuidv4();
870
+ this.db.prepare('INSERT INTO project_changes (id, project_id, short_description, long_description, tags_json, approx_files_affected, session_id) VALUES (?, ?, ?, ?, ?, ?, ?)').run(id, projectId, shortDesc, longDesc, JSON.stringify(tags), approxFilesAffected, sessionId || null);
871
+ return this.getProjectChange(id);
872
+ }
873
+ updateProjectChange(id, shortDesc, longDesc, tags, approxFilesAffected) {
874
+ const result = this.db.prepare('UPDATE project_changes SET short_description = ?, long_description = ?, tags_json = ?, approx_files_affected = ? WHERE id = ?').run(shortDesc, longDesc, JSON.stringify(tags), approxFilesAffected, id);
875
+ return result.changes > 0;
876
+ }
877
+ deleteProjectChange(id) {
878
+ const result = this.db.prepare('DELETE FROM project_changes WHERE id = ?').run(id);
879
+ return result.changes > 0;
880
+ }
881
+ deleteAllProjectChanges(projectId) {
882
+ const result = this.db.prepare('DELETE FROM project_changes WHERE project_id = ?').run(projectId);
883
+ return result.changes;
884
+ }
885
+ // LIFECYCLE
886
+ // ═══════════════════════════════════════════════════
887
+ close() {
888
+ try {
889
+ this.db.close();
890
+ console.log('[db] Database closed');
891
+ }
892
+ catch (err) {
893
+ console.error('[db] Error closing database:', err);
894
+ }
895
+ }
896
+ }
897
+ export const db = new StudioDatabase();