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,895 @@
1
+ import { Router } from 'express';
2
+ import fs from 'fs';
3
+ import fsp from 'fs/promises';
4
+ import path from 'path';
5
+ import archiver from 'archiver';
6
+ import { db } from '../state/db.js';
7
+ import { getFileTree, readFileContent } from '../services/fs-browser.js';
8
+ import { getRemoteFileTree, readRemoteFileContent } from '../services/remote-fs-browser.js';
9
+ import * as gitOps from '../services/git-ops.js';
10
+ import * as remoteGitOps from '../services/remote-git-ops.js';
11
+ import { sshManager } from '../services/ssh-manager.js';
12
+ import { watchProject, unwatchProject, reconcileWorktrees } from '../services/project-watcher.js';
13
+ export const projectsRouter = Router();
14
+ // Helper: get project context (local or remote)
15
+ function getProjectContext(projectId) {
16
+ const project = db.getProject(projectId);
17
+ if (!project)
18
+ return null;
19
+ if (project.ssh_connection_id) {
20
+ return { cwd: project.remote_path || project.path, connectionId: project.ssh_connection_id };
21
+ }
22
+ return { cwd: project.path };
23
+ }
24
+ // ═══════════════════════════════════════════════════
25
+ // PROJECT ENDPOINTS
26
+ // ═══════════════════════════════════════════════════
27
+ // Convenience: list all projects across all environments
28
+ projectsRouter.get('/api/projects', async (_req, res) => {
29
+ const projects = db.listAllProjects();
30
+ const withGit = await Promise.all(projects.map(async (p) => {
31
+ let isGit = false;
32
+ try {
33
+ await fsp.access(path.join(p.path, '.git'));
34
+ isGit = true;
35
+ }
36
+ catch { }
37
+ return { ...p, isGit };
38
+ }));
39
+ res.json(withGit);
40
+ });
41
+ projectsRouter.delete('/api/projects/:id', (req, res) => {
42
+ const deleted = db.deleteProject(req.params.id);
43
+ if (!deleted)
44
+ return res.status(404).json({ error: 'Project not found' });
45
+ res.json({ success: true });
46
+ });
47
+ // Get environments a project is linked to
48
+ projectsRouter.get('/api/projects/:id/environments', (req, res) => {
49
+ const project = db.getProject(req.params.id);
50
+ if (!project)
51
+ return res.status(404).json({ error: 'Project not found' });
52
+ const envs = db.getEnvironmentsForProject(req.params.id);
53
+ res.json(envs);
54
+ });
55
+ // Sessions filtered by project cwd
56
+ projectsRouter.get('/api/projects/:id/sessions', (req, res) => {
57
+ const project = db.getProject(req.params.id);
58
+ if (!project)
59
+ return res.status(404).json({ error: 'Project not found' });
60
+ res.json([]);
61
+ });
62
+ // ═══════════════════════════════════════════════════
63
+ // FILE ENDPOINTS
64
+ // ═══════════════════════════════════════════════════
65
+ projectsRouter.get('/api/projects/:id/files', async (req, res) => {
66
+ const ctx = getProjectContext(req.params.id);
67
+ if (!ctx)
68
+ return res.status(404).json({ error: 'Project not found' });
69
+ try {
70
+ const rootPath = req.query.rootPath || ctx.cwd;
71
+ const showHidden = req.query.showHidden === 'true';
72
+ const maxDepth = req.query.maxDepth != null ? parseInt(req.query.maxDepth, 10) : undefined;
73
+ if (ctx.connectionId) {
74
+ const tree = await getRemoteFileTree(ctx.connectionId, rootPath);
75
+ res.json(tree);
76
+ }
77
+ else {
78
+ const tree = await getFileTree(rootPath, 0, showHidden, maxDepth);
79
+ res.json(tree);
80
+ // Start watching when project root is first accessed (lazy — avoids watching all projects on startup)
81
+ if (!req.query.rootPath && !ctx.connectionId) {
82
+ setImmediate(() => watchProject(req.params.id, ctx.cwd));
83
+ }
84
+ }
85
+ }
86
+ catch (err) {
87
+ res.status(500).json({ error: err.message });
88
+ }
89
+ });
90
+ projectsRouter.get('/api/files', async (req, res) => {
91
+ const filePath = req.query.path;
92
+ const connId = req.query.ssh_connection_id;
93
+ if (!filePath)
94
+ return res.status(400).json({ error: 'path query parameter required' });
95
+ try {
96
+ if (connId) {
97
+ const { content, lang } = await readRemoteFileContent(connId, filePath);
98
+ res.json({ content, lang, path: filePath });
99
+ }
100
+ else {
101
+ const resolved = path.resolve(filePath);
102
+ const result = readFileContent(resolved);
103
+ res.json({ content: result.content, lang: result.lang, path: resolved, isBinary: result.isBinary, size: result.size });
104
+ }
105
+ }
106
+ catch (err) {
107
+ res.status(404).json({ error: err.message });
108
+ }
109
+ });
110
+ // Serve raw file bytes (for images, hex view, etc.)
111
+ projectsRouter.get('/api/files/raw', (req, res) => {
112
+ const filePath = req.query.path;
113
+ if (!filePath)
114
+ return res.status(400).json({ error: 'path required' });
115
+ try {
116
+ const resolved = path.resolve(filePath);
117
+ if (!fs.existsSync(resolved))
118
+ return res.status(404).json({ error: 'not found' });
119
+ const stat = fs.statSync(resolved);
120
+ // For hex view: support range requests for chunked loading
121
+ const offset = parseInt(req.query.offset) || 0;
122
+ const length = parseInt(req.query.length) || 0;
123
+ if (length > 0) {
124
+ // Return a chunk as base64 for hex viewer
125
+ const fd = fs.openSync(resolved, 'r');
126
+ const buf = Buffer.alloc(Math.min(length, 1024 * 1024)); // cap at 1MB per chunk
127
+ const bytesRead = fs.readSync(fd, buf, 0, buf.length, offset);
128
+ fs.closeSync(fd);
129
+ return res.json({ data: buf.subarray(0, bytesRead).toString('base64'), size: stat.size, bytesRead });
130
+ }
131
+ // Stream full file with correct content type
132
+ const ext = path.extname(resolved).toLowerCase().replace('.', '');
133
+ const mimeMap = {
134
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
135
+ webp: 'image/webp', svg: 'image/svg+xml', bmp: 'image/bmp', ico: 'image/x-icon',
136
+ avif: 'image/avif', tiff: 'image/tiff', tif: 'image/tiff',
137
+ pdf: 'application/pdf', zip: 'application/zip', gz: 'application/gzip',
138
+ mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg',
139
+ mp4: 'video/mp4', webm: 'video/webm',
140
+ woff: 'font/woff', woff2: 'font/woff2', ttf: 'font/ttf', otf: 'font/otf',
141
+ json: 'application/json', xml: 'application/xml',
142
+ html: 'text/html', css: 'text/css', js: 'text/javascript', txt: 'text/plain',
143
+ };
144
+ const ct = mimeMap[ext] || 'application/octet-stream';
145
+ res.setHeader('Content-Type', ct);
146
+ res.setHeader('Content-Length', stat.size);
147
+ fs.createReadStream(resolved).pipe(res);
148
+ }
149
+ catch (err) {
150
+ res.status(500).json({ error: err.message });
151
+ }
152
+ });
153
+ // Stream a directory as a zip file (for drag-out folder download)
154
+ projectsRouter.get('/api/files/zip', (req, res) => {
155
+ const dirPath = req.query.path;
156
+ if (!dirPath)
157
+ return res.status(400).json({ error: 'path required' });
158
+ try {
159
+ const resolved = path.resolve(dirPath);
160
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
161
+ return res.status(404).json({ error: 'directory not found' });
162
+ }
163
+ const dirName = path.basename(resolved);
164
+ res.setHeader('Content-Type', 'application/zip');
165
+ res.setHeader('Content-Disposition', `attachment; filename="${dirName}.zip"`);
166
+ const archive = archiver('zip', { zlib: { level: 5 } });
167
+ archive.on('error', (err) => res.status(500).end());
168
+ archive.pipe(res);
169
+ archive.directory(resolved, false);
170
+ archive.finalize();
171
+ }
172
+ catch (err) {
173
+ res.status(500).json({ error: err.message });
174
+ }
175
+ });
176
+ // Write bytes at specific offsets (hex editor save)
177
+ projectsRouter.patch('/api/files/raw', (req, res) => {
178
+ const { path: filePath, edits } = req.body;
179
+ if (!filePath || !Array.isArray(edits))
180
+ return res.status(400).json({ error: 'path and edits[] required' });
181
+ try {
182
+ const resolved = path.resolve(filePath);
183
+ const fd = fs.openSync(resolved, 'r+');
184
+ for (const edit of edits) {
185
+ const buf = Buffer.from([edit.value & 0xFF]);
186
+ fs.writeSync(fd, buf, 0, 1, edit.offset);
187
+ }
188
+ fs.closeSync(fd);
189
+ const stat = fs.statSync(resolved);
190
+ res.json({ success: true, size: stat.size });
191
+ }
192
+ catch (err) {
193
+ res.status(500).json({ error: err.message });
194
+ }
195
+ });
196
+ // Search bytes in file (hex editor search)
197
+ projectsRouter.post('/api/files/raw/search', (req, res) => {
198
+ const { path: filePath, pattern, fromOffset = 0, maxResults = 100 } = req.body;
199
+ if (!filePath || !pattern)
200
+ return res.status(400).json({ error: 'path and pattern required' });
201
+ try {
202
+ const resolved = path.resolve(filePath);
203
+ const needle = Buffer.from(pattern, 'base64');
204
+ if (needle.length === 0)
205
+ return res.json({ matches: [] });
206
+ const stat = fs.statSync(resolved);
207
+ const fd = fs.openSync(resolved, 'r');
208
+ const matches = [];
209
+ const scanChunk = 256 * 1024;
210
+ let pos = fromOffset;
211
+ while (pos < stat.size && matches.length < maxResults) {
212
+ const readLen = Math.min(scanChunk + needle.length - 1, stat.size - pos);
213
+ const buf = Buffer.alloc(readLen);
214
+ const bytesRead = fs.readSync(fd, buf, 0, readLen, pos);
215
+ const data = buf.subarray(0, bytesRead);
216
+ let idx = 0;
217
+ while (idx <= data.length - needle.length && matches.length < maxResults) {
218
+ const found = data.indexOf(needle, idx);
219
+ if (found === -1)
220
+ break;
221
+ matches.push(pos + found);
222
+ idx = found + 1;
223
+ }
224
+ pos += scanChunk;
225
+ }
226
+ fs.closeSync(fd);
227
+ res.json({ matches, size: stat.size });
228
+ }
229
+ catch (err) {
230
+ res.status(500).json({ error: err.message });
231
+ }
232
+ });
233
+ projectsRouter.put('/api/files', async (req, res) => {
234
+ const { path: filePath, content, ssh_connection_id } = req.body;
235
+ if (!filePath || typeof content !== 'string') {
236
+ return res.status(400).json({ error: 'path and content are required' });
237
+ }
238
+ try {
239
+ if (ssh_connection_id) {
240
+ await sshManager.sftpWriteFile(ssh_connection_id, filePath, content);
241
+ res.json({ success: true, path: filePath });
242
+ }
243
+ else {
244
+ const resolved = path.resolve(filePath);
245
+ fs.writeFileSync(resolved, content, 'utf-8');
246
+ res.json({ success: true, path: resolved });
247
+ }
248
+ }
249
+ catch (err) {
250
+ res.status(500).json({ error: err.message });
251
+ }
252
+ });
253
+ projectsRouter.post('/api/files/rename', async (req, res) => {
254
+ const { from, to, ssh_connection_id } = req.body;
255
+ if (!from || !to)
256
+ return res.status(400).json({ error: 'from and to are required' });
257
+ try {
258
+ if (ssh_connection_id) {
259
+ await sshManager.sftpRename(ssh_connection_id, from, to);
260
+ }
261
+ else {
262
+ fs.renameSync(path.resolve(from), path.resolve(to));
263
+ }
264
+ res.json({ success: true });
265
+ }
266
+ catch (err) {
267
+ res.status(500).json({ error: err.message });
268
+ }
269
+ });
270
+ projectsRouter.post('/api/files/create', async (req, res) => {
271
+ const { path: filePath, type, ssh_connection_id } = req.body;
272
+ if (!filePath)
273
+ return res.status(400).json({ error: 'path is required' });
274
+ try {
275
+ if (ssh_connection_id) {
276
+ if (type === 'dir') {
277
+ await sshManager.exec(ssh_connection_id, `mkdir -p '${filePath.replace(/'/g, "'\\''")}'`);
278
+ }
279
+ else {
280
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
281
+ if (dir)
282
+ await sshManager.exec(ssh_connection_id, `mkdir -p '${dir.replace(/'/g, "'\\''")}'`);
283
+ await sshManager.sftpWriteFile(ssh_connection_id, filePath, '');
284
+ }
285
+ }
286
+ else {
287
+ const resolved = path.resolve(filePath);
288
+ if (type === 'dir') {
289
+ fs.mkdirSync(resolved, { recursive: true });
290
+ }
291
+ else {
292
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
293
+ fs.writeFileSync(resolved, '');
294
+ }
295
+ }
296
+ res.json({ success: true });
297
+ }
298
+ catch (err) {
299
+ res.status(500).json({ error: err.message });
300
+ }
301
+ });
302
+ projectsRouter.delete('/api/files', async (req, res) => {
303
+ const filePath = req.query.path;
304
+ const connId = req.query.ssh_connection_id;
305
+ if (!filePath)
306
+ return res.status(400).json({ error: 'path query parameter required' });
307
+ try {
308
+ if (connId) {
309
+ await sshManager.exec(connId, `rm -rf '${filePath.replace(/'/g, "'\\''")}'`);
310
+ }
311
+ else {
312
+ const resolved = path.resolve(filePath);
313
+ const stat = fs.statSync(resolved);
314
+ if (stat.isDirectory()) {
315
+ fs.rmSync(resolved, { recursive: true, force: true });
316
+ }
317
+ else {
318
+ fs.unlinkSync(resolved);
319
+ }
320
+ }
321
+ res.json({ success: true });
322
+ }
323
+ catch (err) {
324
+ res.status(500).json({ error: err.message });
325
+ }
326
+ });
327
+ projectsRouter.post('/api/files/move', async (req, res) => {
328
+ const { from, to, ssh_connection_id } = req.body;
329
+ if (!from || !to)
330
+ return res.status(400).json({ error: 'from and to are required' });
331
+ try {
332
+ if (ssh_connection_id) {
333
+ await sshManager.sftpRename(ssh_connection_id, from, to);
334
+ }
335
+ else {
336
+ fs.renameSync(path.resolve(from), path.resolve(to));
337
+ }
338
+ res.json({ success: true });
339
+ }
340
+ catch (err) {
341
+ res.status(500).json({ error: err.message });
342
+ }
343
+ });
344
+ // Binary file save (for screenshots etc.)
345
+ projectsRouter.post('/api/files/binary', async (req, res) => {
346
+ const { path: filePath, data, ssh_connection_id } = req.body;
347
+ if (!filePath || !data) {
348
+ return res.status(400).json({ error: 'path and data (base64) are required' });
349
+ }
350
+ try {
351
+ const buf = Buffer.from(data, 'base64');
352
+ if (ssh_connection_id) {
353
+ await sshManager.sftpWriteFile(ssh_connection_id, filePath, buf.toString('binary'));
354
+ }
355
+ else {
356
+ const resolved = path.resolve(filePath);
357
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
358
+ fs.writeFileSync(resolved, buf);
359
+ }
360
+ res.json({ success: true, path: filePath });
361
+ }
362
+ catch (err) {
363
+ res.status(500).json({ error: err.message });
364
+ }
365
+ });
366
+ // Streaming file upload — bypasses body-parser, handles files of any size
367
+ projectsRouter.post('/api/files/upload', (req, res) => {
368
+ const filePath = req.query.path;
369
+ const sshConnectionId = req.query.ssh_connection_id;
370
+ if (!filePath) {
371
+ return res.status(400).json({ error: 'path query parameter is required' });
372
+ }
373
+ if (sshConnectionId) {
374
+ // For SSH: collect into buffer then write via SFTP
375
+ const chunks = [];
376
+ req.on('data', (chunk) => chunks.push(chunk));
377
+ req.on('end', async () => {
378
+ try {
379
+ const buf = Buffer.concat(chunks);
380
+ await sshManager.sftpWriteFile(sshConnectionId, filePath, buf.toString('binary'));
381
+ res.json({ success: true, path: filePath });
382
+ }
383
+ catch (err) {
384
+ res.status(500).json({ error: err.message });
385
+ }
386
+ });
387
+ req.on('error', (err) => res.status(500).json({ error: err.message }));
388
+ }
389
+ else {
390
+ // Stream directly to disk
391
+ try {
392
+ const resolved = path.resolve(filePath);
393
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
394
+ const ws = fs.createWriteStream(resolved);
395
+ req.pipe(ws);
396
+ ws.on('finish', () => res.json({ success: true, path: filePath }));
397
+ ws.on('error', (err) => res.status(500).json({ error: err.message }));
398
+ }
399
+ catch (err) {
400
+ res.status(500).json({ error: err.message });
401
+ }
402
+ }
403
+ });
404
+ // Screenshot via Puppeteer
405
+ let screenshotBrowser = null;
406
+ projectsRouter.post('/api/screenshot', async (req, res) => {
407
+ const { url, width = 1280, height = 720, fullPage = false } = req.body;
408
+ if (!url)
409
+ return res.status(400).json({ error: 'url is required' });
410
+ try {
411
+ const puppeteer = await import('puppeteer');
412
+ if (!screenshotBrowser) {
413
+ screenshotBrowser = await puppeteer.default.launch({
414
+ headless: true,
415
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
416
+ });
417
+ }
418
+ const page = await screenshotBrowser.newPage();
419
+ await page.setViewport({ width, height, deviceScaleFactor: 1 });
420
+ await page.goto(url, { waitUntil: 'networkidle2', timeout: 15000 });
421
+ const image = await page.screenshot({ fullPage, encoding: 'base64', type: 'png' });
422
+ await page.close();
423
+ res.json({ image, width, height });
424
+ }
425
+ catch (err) {
426
+ res.status(500).json({ error: err.message });
427
+ }
428
+ });
429
+ // ═══════════════════════════════════════════════════
430
+ // GIT ENDPOINTS (scoped to project cwd)
431
+ // ═══════════════════════════════════════════════════
432
+ function getProjectCwd(req, res) {
433
+ const project = db.getProject(req.params.id);
434
+ if (!project) {
435
+ res.status(404).json({ error: 'Project not found' });
436
+ return null;
437
+ }
438
+ const cwd = req.query.cwd || (project.ssh_connection_id ? (project.remote_path || project.path) : project.path);
439
+ if (project.ssh_connection_id) {
440
+ return { cwd, connectionId: project.ssh_connection_id };
441
+ }
442
+ return { cwd };
443
+ }
444
+ projectsRouter.get('/api/projects/:id/git/status', async (req, res) => {
445
+ const ctx = getProjectCwd(req, res);
446
+ if (!ctx)
447
+ return;
448
+ try {
449
+ const status = ctx.connectionId
450
+ ? await remoteGitOps.gitStatus(ctx.connectionId, ctx.cwd)
451
+ : await gitOps.gitStatus(ctx.cwd);
452
+ res.json(status);
453
+ }
454
+ catch (err) {
455
+ res.status(500).json({ error: err.message });
456
+ }
457
+ });
458
+ projectsRouter.get('/api/projects/:id/git/log', async (req, res) => {
459
+ const ctx = getProjectCwd(req, res);
460
+ if (!ctx)
461
+ return;
462
+ try {
463
+ const branch = req.query.branch;
464
+ const count = parseInt(req.query.count) || 30;
465
+ const log = ctx.connectionId
466
+ ? await remoteGitOps.gitLog(ctx.connectionId, ctx.cwd, branch, count)
467
+ : await gitOps.gitLog(ctx.cwd, branch, count);
468
+ res.json(log);
469
+ }
470
+ catch (err) {
471
+ res.status(500).json({ error: err.message });
472
+ }
473
+ });
474
+ projectsRouter.get('/api/projects/:id/git/commit-files', async (req, res) => {
475
+ const ctx = getProjectCwd(req, res);
476
+ if (!ctx)
477
+ return;
478
+ try {
479
+ const hash = req.query.hash;
480
+ if (!hash)
481
+ return res.status(400).json({ error: 'hash query parameter required' });
482
+ const files = ctx.connectionId
483
+ ? await remoteGitOps.gitCommitFiles(ctx.connectionId, ctx.cwd, hash)
484
+ : await gitOps.gitCommitFiles(ctx.cwd, hash);
485
+ res.json(files);
486
+ }
487
+ catch (err) {
488
+ res.status(500).json({ error: err.message });
489
+ }
490
+ });
491
+ projectsRouter.get('/api/projects/:id/git/branches', async (req, res) => {
492
+ const ctx = getProjectCwd(req, res);
493
+ if (!ctx)
494
+ return;
495
+ try {
496
+ const result = ctx.connectionId
497
+ ? await remoteGitOps.gitBranches(ctx.connectionId, ctx.cwd)
498
+ : await gitOps.gitBranches(ctx.cwd);
499
+ res.json(result);
500
+ }
501
+ catch (err) {
502
+ res.status(500).json({ error: err.message });
503
+ }
504
+ });
505
+ projectsRouter.get('/api/projects/:id/git/remotes', async (req, res) => {
506
+ const ctx = getProjectCwd(req, res);
507
+ if (!ctx)
508
+ return;
509
+ try {
510
+ const remotes = ctx.connectionId
511
+ ? await remoteGitOps.gitRemotes(ctx.connectionId, ctx.cwd)
512
+ : await gitOps.gitRemotes(ctx.cwd);
513
+ res.json(remotes);
514
+ }
515
+ catch (err) {
516
+ res.status(500).json({ error: err.message });
517
+ }
518
+ });
519
+ projectsRouter.get('/api/projects/:id/git/stash', async (req, res) => {
520
+ const ctx = getProjectCwd(req, res);
521
+ if (!ctx)
522
+ return;
523
+ try {
524
+ const stashes = ctx.connectionId
525
+ ? await remoteGitOps.gitStashList(ctx.connectionId, ctx.cwd)
526
+ : await gitOps.gitStashList(ctx.cwd);
527
+ res.json(stashes);
528
+ }
529
+ catch (err) {
530
+ res.status(500).json({ error: err.message });
531
+ }
532
+ });
533
+ projectsRouter.get('/api/projects/:id/git/show', async (req, res) => {
534
+ const ctx = getProjectCwd(req, res);
535
+ if (!ctx)
536
+ return;
537
+ try {
538
+ const file = req.query.file;
539
+ const ref = req.query.ref || 'HEAD';
540
+ if (!file)
541
+ return res.status(400).json({ error: 'file parameter required' });
542
+ const content = ctx.connectionId
543
+ ? await remoteGitOps.gitShowFile(ctx.connectionId, ctx.cwd, file, ref)
544
+ : await gitOps.gitShowFile(ctx.cwd, file, ref);
545
+ res.json({ content });
546
+ }
547
+ catch (err) {
548
+ res.status(404).json({ error: err.message });
549
+ }
550
+ });
551
+ projectsRouter.get('/api/projects/:id/git/diff', async (req, res) => {
552
+ const ctx = getProjectCwd(req, res);
553
+ if (!ctx)
554
+ return;
555
+ try {
556
+ const file = req.query.file;
557
+ const diff = ctx.connectionId
558
+ ? await remoteGitOps.gitDiff(ctx.connectionId, ctx.cwd, file)
559
+ : await gitOps.gitDiff(ctx.cwd, file);
560
+ res.json({ diff });
561
+ }
562
+ catch (err) {
563
+ res.status(500).json({ error: err.message });
564
+ }
565
+ });
566
+ projectsRouter.get('/api/projects/:id/git/blame', async (req, res) => {
567
+ const ctx = getProjectCwd(req, res);
568
+ if (!ctx)
569
+ return;
570
+ try {
571
+ const file = req.query.file;
572
+ if (!file)
573
+ return res.status(400).json({ error: 'file parameter required' });
574
+ const blame = ctx.connectionId
575
+ ? await remoteGitOps.gitBlame(ctx.connectionId, ctx.cwd, file)
576
+ : await gitOps.gitBlame(ctx.cwd, file);
577
+ res.json({ blame });
578
+ }
579
+ catch (err) {
580
+ res.status(500).json({ error: err.message });
581
+ }
582
+ });
583
+ projectsRouter.get('/api/projects/:id/git/file-log', async (req, res) => {
584
+ const ctx = getProjectCwd(req, res);
585
+ if (!ctx)
586
+ return;
587
+ try {
588
+ const file = req.query.file;
589
+ if (!file)
590
+ return res.status(400).json({ error: 'file parameter required' });
591
+ const log = ctx.connectionId
592
+ ? await remoteGitOps.gitFileLog(ctx.connectionId, ctx.cwd, file)
593
+ : await gitOps.gitFileLog(ctx.cwd, file);
594
+ res.json(log);
595
+ }
596
+ catch (err) {
597
+ res.status(500).json({ error: err.message });
598
+ }
599
+ });
600
+ projectsRouter.post('/api/projects/:id/git/checkout', async (req, res) => {
601
+ const ctx = getProjectCwd(req, res);
602
+ if (!ctx)
603
+ return;
604
+ try {
605
+ const { branch } = req.body;
606
+ if (!branch)
607
+ return res.status(400).json({ error: 'branch is required' });
608
+ ctx.connectionId
609
+ ? await remoteGitOps.gitCheckout(ctx.connectionId, ctx.cwd, branch)
610
+ : await gitOps.gitCheckout(ctx.cwd, branch);
611
+ res.json({ success: true });
612
+ }
613
+ catch (err) {
614
+ res.status(500).json({ error: err.message });
615
+ }
616
+ });
617
+ projectsRouter.post('/api/projects/:id/git/commit', async (req, res) => {
618
+ const ctx = getProjectCwd(req, res);
619
+ if (!ctx)
620
+ return;
621
+ try {
622
+ const { message } = req.body;
623
+ if (!message)
624
+ return res.status(400).json({ error: 'message is required' });
625
+ ctx.connectionId
626
+ ? await remoteGitOps.gitCommit(ctx.connectionId, ctx.cwd, message)
627
+ : await gitOps.gitCommit(ctx.cwd, message);
628
+ res.json({ success: true });
629
+ }
630
+ catch (err) {
631
+ res.status(500).json({ error: err.message });
632
+ }
633
+ });
634
+ projectsRouter.post('/api/projects/:id/git/push', async (req, res) => {
635
+ const ctx = getProjectCwd(req, res);
636
+ if (!ctx)
637
+ return;
638
+ try {
639
+ ctx.connectionId
640
+ ? await remoteGitOps.gitPush(ctx.connectionId, ctx.cwd)
641
+ : await gitOps.gitPush(ctx.cwd);
642
+ res.json({ success: true });
643
+ }
644
+ catch (err) {
645
+ res.status(500).json({ error: err.message });
646
+ }
647
+ });
648
+ projectsRouter.post('/api/projects/:id/git/stash', async (req, res) => {
649
+ const ctx = getProjectCwd(req, res);
650
+ if (!ctx)
651
+ return;
652
+ try {
653
+ ctx.connectionId
654
+ ? await remoteGitOps.gitStash(ctx.connectionId, ctx.cwd)
655
+ : await gitOps.gitStash(ctx.cwd);
656
+ res.json({ success: true });
657
+ }
658
+ catch (err) {
659
+ res.status(500).json({ error: err.message });
660
+ }
661
+ });
662
+ projectsRouter.post('/api/projects/:id/git/stash/apply', async (req, res) => {
663
+ const ctx = getProjectCwd(req, res);
664
+ if (!ctx)
665
+ return;
666
+ try {
667
+ const { id } = req.body;
668
+ if (!id)
669
+ return res.status(400).json({ error: 'id is required' });
670
+ ctx.connectionId
671
+ ? await remoteGitOps.gitStashApply(ctx.connectionId, ctx.cwd, id)
672
+ : await gitOps.gitStashApply(ctx.cwd, id);
673
+ res.json({ success: true });
674
+ }
675
+ catch (err) {
676
+ res.status(500).json({ error: err.message });
677
+ }
678
+ });
679
+ projectsRouter.post('/api/projects/:id/git/stash/drop', async (req, res) => {
680
+ const ctx = getProjectCwd(req, res);
681
+ if (!ctx)
682
+ return;
683
+ try {
684
+ const { id } = req.body;
685
+ if (!id)
686
+ return res.status(400).json({ error: 'id is required' });
687
+ ctx.connectionId
688
+ ? await remoteGitOps.gitStashDrop(ctx.connectionId, ctx.cwd, id)
689
+ : await gitOps.gitStashDrop(ctx.cwd, id);
690
+ res.json({ success: true });
691
+ }
692
+ catch (err) {
693
+ res.status(500).json({ error: err.message });
694
+ }
695
+ });
696
+ projectsRouter.post('/api/projects/:id/git/branch', async (req, res) => {
697
+ const ctx = getProjectCwd(req, res);
698
+ if (!ctx)
699
+ return;
700
+ try {
701
+ const { name } = req.body;
702
+ if (!name)
703
+ return res.status(400).json({ error: 'name is required' });
704
+ ctx.connectionId
705
+ ? await remoteGitOps.gitCreateBranch(ctx.connectionId, ctx.cwd, name)
706
+ : await gitOps.gitCreateBranch(ctx.cwd, name);
707
+ res.json({ success: true });
708
+ }
709
+ catch (err) {
710
+ res.status(500).json({ error: err.message });
711
+ }
712
+ });
713
+ projectsRouter.post('/api/projects/:id/git/fetch', async (req, res) => {
714
+ const ctx = getProjectCwd(req, res);
715
+ if (!ctx)
716
+ return;
717
+ try {
718
+ ctx.connectionId
719
+ ? await remoteGitOps.gitFetch(ctx.connectionId, ctx.cwd)
720
+ : await gitOps.gitFetch(ctx.cwd);
721
+ res.json({ success: true });
722
+ }
723
+ catch (err) {
724
+ res.status(500).json({ error: err.message });
725
+ }
726
+ });
727
+ projectsRouter.post('/api/projects/:id/git/remotes', async (req, res) => {
728
+ const ctx = getProjectCwd(req, res);
729
+ if (!ctx)
730
+ return;
731
+ try {
732
+ const { name, url } = req.body;
733
+ if (!name || !url)
734
+ return res.status(400).json({ error: 'name and url are required' });
735
+ ctx.connectionId
736
+ ? await remoteGitOps.gitAddRemote(ctx.connectionId, ctx.cwd, name, url)
737
+ : await gitOps.gitAddRemote(ctx.cwd, name, url);
738
+ res.json({ success: true });
739
+ }
740
+ catch (err) {
741
+ res.status(500).json({ error: err.message });
742
+ }
743
+ });
744
+ projectsRouter.delete('/api/projects/:id/git/remotes/:name', async (req, res) => {
745
+ const ctx = getProjectCwd(req, res);
746
+ if (!ctx)
747
+ return;
748
+ try {
749
+ ctx.connectionId
750
+ ? await remoteGitOps.gitRemoveRemote(ctx.connectionId, ctx.cwd, req.params.name)
751
+ : await gitOps.gitRemoveRemote(ctx.cwd, req.params.name);
752
+ res.json({ success: true });
753
+ }
754
+ catch (err) {
755
+ res.status(500).json({ error: err.message });
756
+ }
757
+ });
758
+ projectsRouter.put('/api/projects/:id/git/remotes/:name', async (req, res) => {
759
+ const ctx = getProjectCwd(req, res);
760
+ if (!ctx)
761
+ return;
762
+ try {
763
+ const { url } = req.body;
764
+ if (!url)
765
+ return res.status(400).json({ error: 'url is required' });
766
+ ctx.connectionId
767
+ ? await remoteGitOps.gitEditRemote(ctx.connectionId, ctx.cwd, req.params.name, url)
768
+ : await gitOps.gitEditRemote(ctx.cwd, req.params.name, url);
769
+ res.json({ success: true });
770
+ }
771
+ catch (err) {
772
+ res.status(500).json({ error: err.message });
773
+ }
774
+ });
775
+ projectsRouter.post('/api/projects/:id/git/revert', async (req, res) => {
776
+ const ctx = getProjectCwd(req, res);
777
+ if (!ctx)
778
+ return;
779
+ try {
780
+ const { file } = req.body;
781
+ ctx.connectionId
782
+ ? await remoteGitOps.gitRevert(ctx.connectionId, ctx.cwd, file)
783
+ : await gitOps.gitRevert(ctx.cwd, file);
784
+ res.json({ success: true });
785
+ }
786
+ catch (err) {
787
+ res.status(500).json({ error: err.message });
788
+ }
789
+ });
790
+ projectsRouter.post('/api/projects/:id/git/stage', async (req, res) => {
791
+ const ctx = getProjectCwd(req, res);
792
+ if (!ctx)
793
+ return;
794
+ try {
795
+ const { file } = req.body;
796
+ if (!file)
797
+ return res.status(400).json({ error: 'file is required' });
798
+ ctx.connectionId
799
+ ? await remoteGitOps.gitStageFile(ctx.connectionId, ctx.cwd, file)
800
+ : await gitOps.gitStageFile(ctx.cwd, file);
801
+ res.json({ success: true });
802
+ }
803
+ catch (err) {
804
+ res.status(500).json({ error: err.message });
805
+ }
806
+ });
807
+ projectsRouter.post('/api/projects/:id/git/unstage', async (req, res) => {
808
+ const ctx = getProjectCwd(req, res);
809
+ if (!ctx)
810
+ return;
811
+ try {
812
+ const { file } = req.body;
813
+ if (!file)
814
+ return res.status(400).json({ error: 'file is required' });
815
+ ctx.connectionId
816
+ ? await remoteGitOps.gitUnstageFile(ctx.connectionId, ctx.cwd, file)
817
+ : await gitOps.gitUnstageFile(ctx.cwd, file);
818
+ res.json({ success: true });
819
+ }
820
+ catch (err) {
821
+ res.status(500).json({ error: err.message });
822
+ }
823
+ });
824
+ projectsRouter.get('/api/projects/:id/git/worktrees', async (req, res) => {
825
+ const ctx = getProjectCwd(req, res);
826
+ if (!ctx)
827
+ return;
828
+ try {
829
+ const worktrees = ctx.connectionId
830
+ ? await remoteGitOps.gitWorktreeList(ctx.connectionId, ctx.cwd)
831
+ : await gitOps.gitWorktreeList(ctx.cwd);
832
+ res.json(worktrees);
833
+ }
834
+ catch (err) {
835
+ res.status(500).json({ error: err.message });
836
+ }
837
+ });
838
+ projectsRouter.post('/api/projects/:id/git/worktrees', async (req, res) => {
839
+ const ctx = getProjectCwd(req, res);
840
+ if (!ctx)
841
+ return;
842
+ try {
843
+ const { path: wtPath, branch, newBranch } = req.body;
844
+ if (!wtPath)
845
+ return res.status(400).json({ error: 'path is required' });
846
+ ctx.connectionId
847
+ ? await remoteGitOps.gitWorktreeAdd(ctx.connectionId, ctx.cwd, wtPath, branch, newBranch)
848
+ : await gitOps.gitWorktreeAdd(ctx.cwd, wtPath, branch, newBranch);
849
+ // Reconcile will auto-create the linked project via the watcher,
850
+ // but also do it immediately so the response is ready
851
+ if (!ctx.connectionId) {
852
+ reconcileWorktrees(req.params.id, ctx.cwd);
853
+ }
854
+ res.json({ success: true });
855
+ }
856
+ catch (err) {
857
+ res.status(500).json({ error: err.message });
858
+ }
859
+ });
860
+ projectsRouter.delete('/api/projects/:id/git/worktrees', async (req, res) => {
861
+ const ctx = getProjectCwd(req, res);
862
+ if (!ctx)
863
+ return;
864
+ try {
865
+ const worktreePath = req.query.path;
866
+ const deleteBranch = req.query.deleteBranch;
867
+ if (!worktreePath)
868
+ return res.status(400).json({ error: 'path query parameter required' });
869
+ // Find and remove the linked child project
870
+ const childProject = db.findProjectByPath(worktreePath);
871
+ if (childProject) {
872
+ unwatchProject(childProject.id);
873
+ db.deleteProject(childProject.id);
874
+ }
875
+ ctx.connectionId
876
+ ? await remoteGitOps.gitWorktreeRemove(ctx.connectionId, ctx.cwd, worktreePath)
877
+ : await gitOps.gitWorktreeRemove(ctx.cwd, worktreePath);
878
+ // Optionally delete the associated branch
879
+ if (deleteBranch) {
880
+ try {
881
+ ctx.connectionId
882
+ ? await remoteGitOps.gitBranchDelete(ctx.connectionId, ctx.cwd, deleteBranch)
883
+ : await gitOps.gitBranchDelete(ctx.cwd, deleteBranch);
884
+ }
885
+ catch (branchErr) {
886
+ // Non-fatal: worktree was removed but branch deletion failed
887
+ return res.json({ success: true, branchDeleteError: branchErr.message });
888
+ }
889
+ }
890
+ res.json({ success: true });
891
+ }
892
+ catch (err) {
893
+ res.status(500).json({ error: err.message });
894
+ }
895
+ });