factory-factory 0.3.4 → 0.3.6

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 (313) hide show
  1. package/README.md +17 -0
  2. package/dist/client/assets/_basePickBy-CzLmZHC2.js +1 -0
  3. package/dist/client/assets/{_baseUniq-DisTPj-I.js → _baseUniq-B2vXSgXW.js} +1 -1
  4. package/dist/client/assets/arc-Dq49BrkB.js +1 -0
  5. package/dist/client/assets/architectureDiagram-VXUJARFQ-BGubq_o9.js +36 -0
  6. package/dist/client/assets/{blockDiagram-VD42YOAC-DXpmez0y.js → blockDiagram-VD42YOAC-CJ7hB7tD.js} +4 -4
  7. package/dist/client/assets/{c4Diagram-YG6GDRKO-C6sDxR49.js → c4Diagram-YG6GDRKO-C9-3yIl7.js} +3 -3
  8. package/dist/client/assets/channel-DMaON2vY.js +1 -0
  9. package/dist/client/assets/{chunk-4BX2VUAB-CQxlRbYu.js → chunk-4BX2VUAB-LA-tkKjD.js} +1 -1
  10. package/dist/client/assets/{chunk-55IACEB6-DxubFDep.js → chunk-55IACEB6-D8Jq3_Ne.js} +1 -1
  11. package/dist/client/assets/{chunk-B4BG7PRW-BtTQ0im9.js → chunk-B4BG7PRW-BbB1Mssd.js} +1 -1
  12. package/dist/client/assets/{chunk-DI55MBZ5-C2tNYDZj.js → chunk-DI55MBZ5-CpO3WS5m.js} +1 -1
  13. package/dist/client/assets/{chunk-FMBD7UC4-kRQRN6vP.js → chunk-FMBD7UC4-zaxR2CzR.js} +1 -1
  14. package/dist/client/assets/chunk-QN33PNHL-BMmRcEqx.js +1 -0
  15. package/dist/client/assets/{chunk-QZHKN3VN-BfxMFZDN.js → chunk-QZHKN3VN-CbCQqsiw.js} +1 -1
  16. package/dist/client/assets/chunk-TZMSLE5B-BhRzmVVB.js +1 -0
  17. package/dist/client/assets/classDiagram-2ON5EDUG-BKODUnN9.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-WZHVMYZB-BKODUnN9.js +1 -0
  19. package/dist/client/assets/clone-d5fivKBG.js +1 -0
  20. package/dist/client/assets/{cose-bilkent-S5V4N54A-QPsUxyAe.js → cose-bilkent-S5V4N54A-BZtg5BPr.js} +1 -1
  21. package/dist/client/assets/{dagre-6UL2VRFP-BAmsmcnI.js → dagre-6UL2VRFP-DiejfPfI.js} +2 -2
  22. package/dist/client/assets/diagram-PSM6KHXK-CRE7v2e7.js +24 -0
  23. package/dist/client/assets/diagram-QEK2KX5R-DpAOzBlm.js +43 -0
  24. package/dist/client/assets/diagram-S2PKOQOG-BOJvuhJd.js +24 -0
  25. package/dist/client/assets/{erDiagram-Q2GNP2WA-DMKIVzp4.js → erDiagram-Q2GNP2WA-IM11uKRy.js} +4 -4
  26. package/dist/client/assets/{flowDiagram-NV44I4VS-op3xA_nS.js → flowDiagram-NV44I4VS-DTbQf_7g.js} +5 -5
  27. package/dist/client/assets/{ganttDiagram-JELNMOA3-nrnzVCJK.js → ganttDiagram-JELNMOA3-Bqd3rUDW.js} +2 -2
  28. package/dist/client/assets/gitGraphDiagram-V2S2FVAM-Cd1j3VMN.js +65 -0
  29. package/dist/client/assets/{graph-D_lEcPMk.js → graph-D667hHAk.js} +1 -1
  30. package/dist/client/assets/{index-C2qHRaOH.js → index-BXRnTMjJ.js} +170 -171
  31. package/dist/client/assets/index-DllMnbgu.css +1 -0
  32. package/dist/client/assets/infoDiagram-HS3SLOUP-CzpZPq7Y.js +2 -0
  33. package/dist/client/assets/{journeyDiagram-XKPGCS4Q-Bnhe4U1x.js → journeyDiagram-XKPGCS4Q-Br2GUbhE.js} +1 -1
  34. package/dist/client/assets/{kanban-definition-3W4ZIXB7-DpTaTXko.js → kanban-definition-3W4ZIXB7-D0SM1ldH.js} +6 -6
  35. package/dist/client/assets/{layout-HT5qr--Q.js → layout-BPE9Dzp9.js} +1 -1
  36. package/dist/client/assets/{linear-CDmQ8HG8.js → linear-5exZFNBA.js} +1 -1
  37. package/dist/client/assets/{mindmap-definition-VGOIOE7T-zDr7s4o2.js → mindmap-definition-VGOIOE7T-D1W9h2lc.js} +3 -3
  38. package/dist/client/assets/mobile-baseline-BHP-K4kd.js +10 -0
  39. package/dist/client/assets/pieDiagram-ADFJNKIX-DG-umOTY.js +30 -0
  40. package/dist/client/assets/{quadrantDiagram-AYHSOK5B-DzdJzkAJ.js → quadrantDiagram-AYHSOK5B-Cb0ciONq.js} +1 -1
  41. package/dist/client/assets/{requirementDiagram-UZGBJVZJ-ChrLzx8S.js → requirementDiagram-UZGBJVZJ-BKYaJZ83.js} +1 -1
  42. package/dist/client/assets/sankeyDiagram-TZEHDZUN-DkR9L4Vw.js +10 -0
  43. package/dist/client/assets/{sequenceDiagram-WL72ISMW-BHxEpSCU.js → sequenceDiagram-WL72ISMW-Cys03qhv.js} +6 -6
  44. package/dist/client/assets/stateDiagram-FKZM4ZOC-DQWCRtS7.js +1 -0
  45. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-DoWi8XaX.js +1 -0
  46. package/dist/client/assets/terminal-instance-CpEqekOB.js +1 -0
  47. package/dist/client/assets/{timeline-definition-IT6M3QCI-BcCmIxix.js → timeline-definition-IT6M3QCI-C0mVV_zL.js} +4 -4
  48. package/dist/client/assets/treemap-GDKQZRPO-DK-Wlxvy.js +160 -0
  49. package/dist/client/assets/{xychartDiagram-PRI3JC2R-DrgKBPUM.js → xychartDiagram-PRI3JC2R-CFg2MfK9.js} +4 -4
  50. package/dist/client/index.html +3 -3
  51. package/dist/prisma/generated/commonInputTypes.d.ts +69 -39
  52. package/dist/prisma/generated/commonInputTypes.d.ts.map +1 -1
  53. package/dist/prisma/generated/enums.d.ts +6 -0
  54. package/dist/prisma/generated/enums.d.ts.map +1 -1
  55. package/dist/prisma/generated/enums.js +6 -1
  56. package/dist/prisma/generated/enums.js.map +1 -1
  57. package/dist/prisma/generated/internal/class.d.ts.map +1 -1
  58. package/dist/prisma/generated/internal/class.js +12 -4
  59. package/dist/prisma/generated/internal/class.js.map +1 -1
  60. package/dist/prisma/generated/internal/prismaNamespace.d.ts +13 -7
  61. package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
  62. package/dist/prisma/generated/internal/prismaNamespace.js +11 -6
  63. package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
  64. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +10 -5
  65. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  66. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +9 -4
  67. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  68. package/dist/prisma/generated/models/Project.d.ts +63 -1
  69. package/dist/prisma/generated/models/Project.d.ts.map +1 -1
  70. package/dist/prisma/generated/models/Workspace.d.ts +130 -1
  71. package/dist/prisma/generated/models/Workspace.d.ts.map +1 -1
  72. package/dist/prompts/quick-actions/screenshots.md +12 -6
  73. package/dist/prompts/ratchet/dispatch.md +4 -0
  74. package/dist/src/backend/domains/linear/index.d.ts +3 -0
  75. package/dist/src/backend/domains/linear/index.d.ts.map +1 -0
  76. package/dist/src/backend/domains/linear/index.js +3 -0
  77. package/dist/src/backend/domains/linear/index.js.map +1 -0
  78. package/dist/src/backend/domains/linear/linear-client.service.d.ts +40 -0
  79. package/dist/src/backend/domains/linear/linear-client.service.d.ts.map +1 -0
  80. package/dist/src/backend/domains/linear/linear-client.service.js +134 -0
  81. package/dist/src/backend/domains/linear/linear-client.service.js.map +1 -0
  82. package/dist/src/backend/domains/linear/linear-state-sync.service.d.ts +7 -0
  83. package/dist/src/backend/domains/linear/linear-state-sync.service.d.ts.map +1 -0
  84. package/dist/src/backend/domains/linear/linear-state-sync.service.js +29 -0
  85. package/dist/src/backend/domains/linear/linear-state-sync.service.js.map +1 -0
  86. package/dist/src/backend/domains/ratchet/bridges.d.ts +5 -1
  87. package/dist/src/backend/domains/ratchet/bridges.d.ts.map +1 -1
  88. package/dist/src/backend/domains/ratchet/ratchet.service.d.ts +2 -0
  89. package/dist/src/backend/domains/ratchet/ratchet.service.d.ts.map +1 -1
  90. package/dist/src/backend/domains/ratchet/ratchet.service.js +43 -53
  91. package/dist/src/backend/domains/ratchet/ratchet.service.js.map +1 -1
  92. package/dist/src/backend/domains/ratchet/ratchet.service.test.js +61 -18
  93. package/dist/src/backend/domains/ratchet/ratchet.service.test.js.map +1 -1
  94. package/dist/src/backend/domains/session/acp/acp-runtime-manager.d.ts.map +1 -1
  95. package/dist/src/backend/domains/session/acp/acp-runtime-manager.js +5 -1
  96. package/dist/src/backend/domains/session/acp/acp-runtime-manager.js.map +1 -1
  97. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts +13 -2
  98. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts.map +1 -1
  99. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js +839 -106
  100. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js.map +1 -1
  101. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js +735 -8
  102. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js.map +1 -1
  103. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts +20 -0
  104. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts.map +1 -1
  105. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js +16 -0
  106. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js.map +1 -1
  107. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js +15 -0
  108. package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js.map +1 -1
  109. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.d.ts +2 -2
  110. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
  111. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.js +32 -1
  112. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
  113. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.test.js +79 -9
  114. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.test.js.map +1 -1
  115. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.d.ts +2 -2
  116. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.d.ts.map +1 -1
  117. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.js +3 -1
  118. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.js.map +1 -1
  119. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.test.js +10 -1
  120. package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.test.js.map +1 -1
  121. package/dist/src/backend/domains/session/chat/chat-message-handlers/registry.js +2 -2
  122. package/dist/src/backend/domains/session/chat/chat-message-handlers/registry.js.map +1 -1
  123. package/dist/src/backend/domains/session/chat/chat-message-handlers/types.d.ts +1 -0
  124. package/dist/src/backend/domains/session/chat/chat-message-handlers/types.d.ts.map +1 -1
  125. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.d.ts +5 -1
  126. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.d.ts.map +1 -1
  127. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js +39 -13
  128. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js.map +1 -1
  129. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js +81 -0
  130. package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js.map +1 -1
  131. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.d.ts.map +1 -1
  132. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js +12 -59
  133. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js.map +1 -1
  134. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js +11 -21
  135. package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js.map +1 -1
  136. package/dist/src/backend/domains/session/data/session-data.service.d.ts +6 -0
  137. package/dist/src/backend/domains/session/data/session-data.service.d.ts.map +1 -1
  138. package/dist/src/backend/domains/session/lifecycle/session.service.d.ts +7 -0
  139. package/dist/src/backend/domains/session/lifecycle/session.service.d.ts.map +1 -1
  140. package/dist/src/backend/domains/session/lifecycle/session.service.js +105 -6
  141. package/dist/src/backend/domains/session/lifecycle/session.service.js.map +1 -1
  142. package/dist/src/backend/domains/session/lifecycle/session.service.test.js +84 -0
  143. package/dist/src/backend/domains/session/lifecycle/session.service.test.js.map +1 -1
  144. package/dist/src/backend/domains/session/session-domain.service.d.ts +8 -1
  145. package/dist/src/backend/domains/session/session-domain.service.d.ts.map +1 -1
  146. package/dist/src/backend/domains/session/session-domain.service.js +43 -2
  147. package/dist/src/backend/domains/session/session-domain.service.js.map +1 -1
  148. package/dist/src/backend/domains/session/session-domain.service.test.js +21 -0
  149. package/dist/src/backend/domains/session/session-domain.service.test.js.map +1 -1
  150. package/dist/src/backend/domains/session/store/session-store.types.d.ts +1 -1
  151. package/dist/src/backend/domains/session/store/session-store.types.d.ts.map +1 -1
  152. package/dist/src/backend/domains/session/store/session-transcript.d.ts +1 -0
  153. package/dist/src/backend/domains/session/store/session-transcript.d.ts.map +1 -1
  154. package/dist/src/backend/domains/session/store/session-transcript.js +8 -0
  155. package/dist/src/backend/domains/session/store/session-transcript.js.map +1 -1
  156. package/dist/src/backend/domains/workspace/lifecycle/creation.service.d.ts +9 -0
  157. package/dist/src/backend/domains/workspace/lifecycle/creation.service.d.ts.map +1 -1
  158. package/dist/src/backend/domains/workspace/lifecycle/creation.service.js +18 -0
  159. package/dist/src/backend/domains/workspace/lifecycle/creation.service.js.map +1 -1
  160. package/dist/src/backend/domains/workspace/lifecycle/creation.service.test.js +46 -0
  161. package/dist/src/backend/domains/workspace/lifecycle/creation.service.test.js.map +1 -1
  162. package/dist/src/backend/domains/workspace/lifecycle/data.service.d.ts +25 -0
  163. package/dist/src/backend/domains/workspace/lifecycle/data.service.d.ts.map +1 -1
  164. package/dist/src/backend/domains/workspace/query/project-management.service.d.ts +16 -0
  165. package/dist/src/backend/domains/workspace/query/project-management.service.d.ts.map +1 -1
  166. package/dist/src/backend/domains/workspace/query/project-management.service.js.map +1 -1
  167. package/dist/src/backend/domains/workspace/query/workspace-query.service.d.ts +6 -0
  168. package/dist/src/backend/domains/workspace/query/workspace-query.service.d.ts.map +1 -1
  169. package/dist/src/backend/interceptors/pr-detection.interceptor.d.ts.map +1 -1
  170. package/dist/src/backend/interceptors/pr-detection.interceptor.js +5 -1
  171. package/dist/src/backend/interceptors/pr-detection.interceptor.js.map +1 -1
  172. package/dist/src/backend/interceptors/pr-detection.interceptor.test.js +14 -0
  173. package/dist/src/backend/interceptors/pr-detection.interceptor.test.js.map +1 -1
  174. package/dist/src/backend/migrate.d.ts.map +1 -1
  175. package/dist/src/backend/migrate.js +13 -3
  176. package/dist/src/backend/migrate.js.map +1 -1
  177. package/dist/src/backend/migrate.test.js +44 -0
  178. package/dist/src/backend/migrate.test.js.map +1 -1
  179. package/dist/src/backend/orchestration/cli-health.service.d.ts +1 -0
  180. package/dist/src/backend/orchestration/cli-health.service.d.ts.map +1 -1
  181. package/dist/src/backend/orchestration/cli-health.service.js +16 -1
  182. package/dist/src/backend/orchestration/cli-health.service.js.map +1 -1
  183. package/dist/src/backend/orchestration/cli-health.service.test.js +23 -0
  184. package/dist/src/backend/orchestration/cli-health.service.test.js.map +1 -1
  185. package/dist/src/backend/orchestration/data-backup.service.d.ts.map +1 -1
  186. package/dist/src/backend/orchestration/data-backup.service.js +21 -1
  187. package/dist/src/backend/orchestration/data-backup.service.js.map +1 -1
  188. package/dist/src/backend/orchestration/data-backup.service.test.js +19 -9
  189. package/dist/src/backend/orchestration/data-backup.service.test.js.map +1 -1
  190. package/dist/src/backend/orchestration/event-collector.orchestrator.d.ts.map +1 -1
  191. package/dist/src/backend/orchestration/event-collector.orchestrator.js +63 -3
  192. package/dist/src/backend/orchestration/event-collector.orchestrator.js.map +1 -1
  193. package/dist/src/backend/orchestration/event-collector.orchestrator.test.js +49 -3
  194. package/dist/src/backend/orchestration/event-collector.orchestrator.test.js.map +1 -1
  195. package/dist/src/backend/orchestration/linear-config.helper.d.ts +9 -0
  196. package/dist/src/backend/orchestration/linear-config.helper.d.ts.map +1 -0
  197. package/dist/src/backend/orchestration/linear-config.helper.js +23 -0
  198. package/dist/src/backend/orchestration/linear-config.helper.js.map +1 -0
  199. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts +3 -0
  200. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts.map +1 -1
  201. package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts.map +1 -1
  202. package/dist/src/backend/orchestration/workspace-init.orchestrator.js +228 -3
  203. package/dist/src/backend/orchestration/workspace-init.orchestrator.js.map +1 -1
  204. package/dist/src/backend/prompts/ratchet-dispatch.d.ts +8 -1
  205. package/dist/src/backend/prompts/ratchet-dispatch.d.ts.map +1 -1
  206. package/dist/src/backend/prompts/ratchet-dispatch.js +20 -3
  207. package/dist/src/backend/prompts/ratchet-dispatch.js.map +1 -1
  208. package/dist/src/backend/resource_accessors/project.accessor.d.ts +7 -1
  209. package/dist/src/backend/resource_accessors/project.accessor.d.ts.map +1 -1
  210. package/dist/src/backend/resource_accessors/project.accessor.js +8 -1
  211. package/dist/src/backend/resource_accessors/project.accessor.js.map +1 -1
  212. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts +4 -1
  213. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts.map +1 -1
  214. package/dist/src/backend/resource_accessors/workspace.accessor.js +3 -0
  215. package/dist/src/backend/resource_accessors/workspace.accessor.js.map +1 -1
  216. package/dist/src/backend/server.d.ts.map +1 -1
  217. package/dist/src/backend/server.js +11 -9
  218. package/dist/src/backend/server.js.map +1 -1
  219. package/dist/src/backend/services/constants.d.ts +1 -0
  220. package/dist/src/backend/services/constants.d.ts.map +1 -1
  221. package/dist/src/backend/services/constants.js +1 -0
  222. package/dist/src/backend/services/constants.js.map +1 -1
  223. package/dist/src/backend/services/crypto.service.d.ts +10 -0
  224. package/dist/src/backend/services/crypto.service.d.ts.map +1 -0
  225. package/dist/src/backend/services/crypto.service.js +61 -0
  226. package/dist/src/backend/services/crypto.service.js.map +1 -0
  227. package/dist/src/backend/trpc/admin.trpc.d.ts +14 -4
  228. package/dist/src/backend/trpc/admin.trpc.d.ts.map +1 -1
  229. package/dist/src/backend/trpc/index.d.ts +169 -9
  230. package/dist/src/backend/trpc/index.d.ts.map +1 -1
  231. package/dist/src/backend/trpc/index.js +2 -0
  232. package/dist/src/backend/trpc/index.js.map +1 -1
  233. package/dist/src/backend/trpc/linear.trpc.d.ts +44 -0
  234. package/dist/src/backend/trpc/linear.trpc.d.ts.map +1 -0
  235. package/dist/src/backend/trpc/linear.trpc.js +69 -0
  236. package/dist/src/backend/trpc/linear.trpc.js.map +1 -0
  237. package/dist/src/backend/trpc/project.trpc.d.ts +50 -5
  238. package/dist/src/backend/trpc/project.trpc.d.ts.map +1 -1
  239. package/dist/src/backend/trpc/project.trpc.js +46 -18
  240. package/dist/src/backend/trpc/project.trpc.js.map +1 -1
  241. package/dist/src/backend/trpc/session.trpc.d.ts +13 -0
  242. package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
  243. package/dist/src/backend/trpc/session.trpc.js +5 -1
  244. package/dist/src/backend/trpc/session.trpc.js.map +1 -1
  245. package/dist/src/backend/trpc/workspace/git.trpc.d.ts +10 -0
  246. package/dist/src/backend/trpc/workspace/git.trpc.d.ts.map +1 -1
  247. package/dist/src/backend/trpc/workspace/git.trpc.js +25 -0
  248. package/dist/src/backend/trpc/workspace/git.trpc.js.map +1 -1
  249. package/dist/src/backend/trpc/workspace/init.trpc.d.ts +3 -0
  250. package/dist/src/backend/trpc/workspace/init.trpc.d.ts.map +1 -1
  251. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts +8 -0
  252. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts.map +1 -1
  253. package/dist/src/backend/trpc/workspace.trpc.d.ts +49 -0
  254. package/dist/src/backend/trpc/workspace.trpc.d.ts.map +1 -1
  255. package/dist/src/backend/trpc/workspace.trpc.js +20 -4
  256. package/dist/src/backend/trpc/workspace.trpc.js.map +1 -1
  257. package/dist/src/cli/index.js +17 -91
  258. package/dist/src/cli/index.js.map +1 -1
  259. package/dist/src/cli/proxy.d.ts +80 -0
  260. package/dist/src/cli/proxy.d.ts.map +1 -0
  261. package/dist/src/cli/proxy.js +1054 -0
  262. package/dist/src/cli/proxy.js.map +1 -0
  263. package/dist/src/cli/proxy.test.d.ts +2 -0
  264. package/dist/src/cli/proxy.test.d.ts.map +1 -0
  265. package/dist/src/cli/proxy.test.js +249 -0
  266. package/dist/src/cli/proxy.test.js.map +1 -0
  267. package/dist/src/cli/runtime-utils.d.ts +9 -0
  268. package/dist/src/cli/runtime-utils.d.ts.map +1 -0
  269. package/dist/src/cli/runtime-utils.js +86 -0
  270. package/dist/src/cli/runtime-utils.js.map +1 -0
  271. package/dist/src/shared/core/enums.d.ts +6 -0
  272. package/dist/src/shared/core/enums.d.ts.map +1 -1
  273. package/dist/src/shared/core/enums.js +5 -0
  274. package/dist/src/shared/core/enums.js.map +1 -1
  275. package/dist/src/shared/core/index.d.ts +1 -1
  276. package/dist/src/shared/core/index.d.ts.map +1 -1
  277. package/dist/src/shared/core/index.js +1 -1
  278. package/dist/src/shared/core/index.js.map +1 -1
  279. package/dist/src/shared/schemas/export-data.schema.d.ts +19 -1
  280. package/dist/src/shared/schemas/export-data.schema.d.ts.map +1 -1
  281. package/dist/src/shared/schemas/export-data.schema.js +8 -2
  282. package/dist/src/shared/schemas/export-data.schema.js.map +1 -1
  283. package/dist/src/shared/schemas/issue-tracker-config.schema.d.ts +35 -0
  284. package/dist/src/shared/schemas/issue-tracker-config.schema.d.ts.map +1 -0
  285. package/dist/src/shared/schemas/issue-tracker-config.schema.js +29 -0
  286. package/dist/src/shared/schemas/issue-tracker-config.schema.js.map +1 -0
  287. package/dist/tsconfig.backend.tsbuildinfo +1 -1
  288. package/package.json +40 -36
  289. package/prisma/migrations/20260217190642_add_linear_integration/migration.sql +34 -0
  290. package/prisma/schema.prisma +13 -0
  291. package/prompts/quick-actions/screenshots.md +12 -6
  292. package/prompts/ratchet/dispatch.md +4 -0
  293. package/dist/client/assets/_basePickBy-kS1IrKFQ.js +0 -1
  294. package/dist/client/assets/arc-CWpVTRns.js +0 -1
  295. package/dist/client/assets/architectureDiagram-VXUJARFQ-Dp1AYPMt.js +0 -36
  296. package/dist/client/assets/channel-D5BCeybW.js +0 -1
  297. package/dist/client/assets/chunk-QN33PNHL-DP72lCp6.js +0 -1
  298. package/dist/client/assets/chunk-TZMSLE5B-G3wPktxK.js +0 -1
  299. package/dist/client/assets/classDiagram-2ON5EDUG-BUS1JDwr.js +0 -1
  300. package/dist/client/assets/classDiagram-v2-WZHVMYZB-BUS1JDwr.js +0 -1
  301. package/dist/client/assets/clone-C5xhPZw8.js +0 -1
  302. package/dist/client/assets/diagram-PSM6KHXK-QA-3O0wr.js +0 -24
  303. package/dist/client/assets/diagram-QEK2KX5R-VZkRJLXU.js +0 -43
  304. package/dist/client/assets/diagram-S2PKOQOG-DwLmogjW.js +0 -24
  305. package/dist/client/assets/gitGraphDiagram-NY62KEGX-CK2ytvyZ.js +0 -65
  306. package/dist/client/assets/index-D5ywoSOH.css +0 -1
  307. package/dist/client/assets/infoDiagram-WHAUD3N6-DO5X8HkW.js +0 -2
  308. package/dist/client/assets/pieDiagram-ADFJNKIX-BVBJCvYL.js +0 -30
  309. package/dist/client/assets/sankeyDiagram-TZEHDZUN-GIzxIQQB.js +0 -10
  310. package/dist/client/assets/stateDiagram-FKZM4ZOC-ByvdYZKl.js +0 -1
  311. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-ynQtAOKH.js +0 -1
  312. package/dist/client/assets/terminal-instance-xdECz0dx.js +0 -1
  313. package/dist/client/assets/treemap-KMMF4GRG-CVpLLzhu.js +0 -128
@@ -0,0 +1,1054 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { createHmac, randomBytes, randomInt, timingSafeEqual } from 'node:crypto';
3
+ import { existsSync } from 'node:fs';
4
+ import { createServer as createHttpServer, request as httpRequest, } from 'node:http';
5
+ import { createConnection } from 'node:net';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { promisify } from 'node:util';
9
+ import chalk from 'chalk';
10
+ import { runMigrations as runDbMigrations } from '../backend/migrate.js';
11
+ import { ensureDataDir, findAvailablePort, treeKillAsync, waitForPort } from './runtime-utils.js';
12
+ const execFileAsync = promisify(execFile);
13
+ const PASSWORD_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
14
+ const PASSWORD_LENGTH = 6;
15
+ const SESSION_COOKIE_NAME = 'ff_proxy_session';
16
+ const LOGIN_PATH = '/__proxy_auth/login';
17
+ const LOCAL_HOST = '127.0.0.1';
18
+ const MAX_TUNNEL_OUTPUT_BUFFER_CHARS = 8192;
19
+ const LOCKOUT_THRESHOLD_PER_IP = 5;
20
+ const LOCKOUT_WINDOW_MS = 5 * 60 * 1000;
21
+ const MAX_BRUTE_FORCE_TRACKED_IPS = 5000;
22
+ function generatePassword(length = PASSWORD_LENGTH) {
23
+ let output = '';
24
+ for (let i = 0; i < length; i += 1) {
25
+ const index = randomInt(PASSWORD_CHARS.length);
26
+ output += PASSWORD_CHARS[index] ?? PASSWORD_CHARS[0] ?? 'A';
27
+ }
28
+ return output;
29
+ }
30
+ function generateMagicToken() {
31
+ return randomBytes(16).toString('hex');
32
+ }
33
+ function signValue(value, secret) {
34
+ return createHmac('sha256', secret).update(value).digest('hex');
35
+ }
36
+ function escapeHtml(value) {
37
+ return value
38
+ .replaceAll('&', '&amp;')
39
+ .replaceAll('<', '&lt;')
40
+ .replaceAll('>', '&gt;')
41
+ .replaceAll('"', '&quot;')
42
+ .replaceAll("'", '&#39;');
43
+ }
44
+ function matchesMagicToken(candidate, expected) {
45
+ const candidateBuffer = Buffer.from(candidate, 'utf8');
46
+ const expectedBuffer = Buffer.from(expected, 'utf8');
47
+ if (candidateBuffer.length !== expectedBuffer.length) {
48
+ return false;
49
+ }
50
+ return timingSafeEqual(candidateBuffer, expectedBuffer);
51
+ }
52
+ function createSessionValue(secret) {
53
+ const id = randomBytes(16).toString('hex');
54
+ return { id, signature: signValue(id, secret) };
55
+ }
56
+ function verifySessionValue(value, secret) {
57
+ if (!value) {
58
+ return false;
59
+ }
60
+ const separator = value.indexOf('.');
61
+ if (separator <= 0 || separator === value.length - 1) {
62
+ return false;
63
+ }
64
+ const id = value.slice(0, separator);
65
+ const providedSignature = value.slice(separator + 1);
66
+ const expectedSignature = signValue(id, secret);
67
+ const expectedBuffer = Buffer.from(expectedSignature);
68
+ const providedBuffer = Buffer.from(providedSignature);
69
+ if (expectedBuffer.length !== providedBuffer.length) {
70
+ return false;
71
+ }
72
+ return timingSafeEqual(expectedBuffer, providedBuffer);
73
+ }
74
+ function parseCookieHeader(cookieHeader) {
75
+ if (!cookieHeader) {
76
+ return {};
77
+ }
78
+ return cookieHeader
79
+ .split(';')
80
+ .map((part) => part.trim())
81
+ .filter(Boolean)
82
+ .reduce((acc, part) => {
83
+ const separator = part.indexOf('=');
84
+ if (separator <= 0) {
85
+ return acc;
86
+ }
87
+ const key = part.slice(0, separator).trim();
88
+ const value = part.slice(separator + 1).trim();
89
+ if (key) {
90
+ acc[key] = value;
91
+ }
92
+ return acc;
93
+ }, {});
94
+ }
95
+ function extractTryCloudflareUrl(input) {
96
+ const match = input.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/gi);
97
+ if (!match || match.length === 0) {
98
+ return null;
99
+ }
100
+ return match[0] ?? null;
101
+ }
102
+ function appendBoundedOutputBuffer(existing, chunk, maxChars = MAX_TUNNEL_OUTPUT_BUFFER_CHARS) {
103
+ const combined = `${existing}${chunk}`;
104
+ if (combined.length <= maxChars) {
105
+ return combined;
106
+ }
107
+ return combined.slice(-maxChars);
108
+ }
109
+ function sanitizePathWithoutToken(rawUrl) {
110
+ const parsed = new URL(rawUrl, 'http://proxy.local');
111
+ parsed.searchParams.delete('token');
112
+ const search = parsed.searchParams.toString();
113
+ return `${parsed.pathname}${search ? `?${search}` : ''}`;
114
+ }
115
+ function toSafeRedirectPath(path) {
116
+ if (!path.startsWith('/') ||
117
+ path.startsWith('//') ||
118
+ path.startsWith('/\\') ||
119
+ /[\r\n]/.test(path)) {
120
+ return '/';
121
+ }
122
+ return path;
123
+ }
124
+ function matchesPassword(candidate, expected) {
125
+ return matchesMagicToken(candidate, expected);
126
+ }
127
+ function getClientIp(req) {
128
+ const cfConnectingIp = req.headers['cf-connecting-ip'];
129
+ if (typeof cfConnectingIp === 'string' && cfConnectingIp.trim()) {
130
+ return cfConnectingIp.trim();
131
+ }
132
+ const xForwardedFor = req.headers['x-forwarded-for'];
133
+ if (typeof xForwardedFor === 'string' && xForwardedFor.trim()) {
134
+ return xForwardedFor.split(',')[0]?.trim() || 'unknown';
135
+ }
136
+ return req.socket.remoteAddress || 'unknown';
137
+ }
138
+ class BruteForceGuard {
139
+ attemptsByIp = new Map();
140
+ evictStaleEntries(nowMs) {
141
+ for (const [ip, state] of this.attemptsByIp.entries()) {
142
+ const lockExpired = state.lockedUntilMs <= nowMs;
143
+ const stale = nowMs - state.lastSeenMs > LOCKOUT_WINDOW_MS;
144
+ if (lockExpired && stale) {
145
+ this.attemptsByIp.delete(ip);
146
+ }
147
+ }
148
+ while (this.attemptsByIp.size > MAX_BRUTE_FORCE_TRACKED_IPS) {
149
+ const oldest = this.attemptsByIp.keys().next().value;
150
+ if (!oldest) {
151
+ break;
152
+ }
153
+ this.attemptsByIp.delete(oldest);
154
+ }
155
+ }
156
+ isLocked(ip, nowMs = Date.now()) {
157
+ this.evictStaleEntries(nowMs);
158
+ const state = this.attemptsByIp.get(ip);
159
+ if (!state || state.lockedUntilMs <= 0) {
160
+ return { locked: false, retryAfterSeconds: 0 };
161
+ }
162
+ if (state.lockedUntilMs <= nowMs) {
163
+ this.attemptsByIp.delete(ip);
164
+ return { locked: false, retryAfterSeconds: 0 };
165
+ }
166
+ return {
167
+ locked: true,
168
+ retryAfterSeconds: Math.max(1, Math.ceil((state.lockedUntilMs - nowMs) / 1000)),
169
+ };
170
+ }
171
+ registerFailure(ip, nowMs = Date.now()) {
172
+ this.evictStaleEntries(nowMs);
173
+ const current = this.attemptsByIp.get(ip) ?? { failures: 0, lockedUntilMs: 0, lastSeenMs: 0 };
174
+ const normalized = current.lockedUntilMs > 0 && current.lockedUntilMs <= nowMs
175
+ ? { failures: 0, lockedUntilMs: 0, lastSeenMs: nowMs }
176
+ : current;
177
+ const nextFailures = normalized.failures + 1;
178
+ const ipLocked = nextFailures >= LOCKOUT_THRESHOLD_PER_IP;
179
+ this.attemptsByIp.delete(ip);
180
+ this.attemptsByIp.set(ip, {
181
+ failures: ipLocked ? 0 : nextFailures,
182
+ lockedUntilMs: ipLocked ? nowMs + LOCKOUT_WINDOW_MS : 0,
183
+ lastSeenMs: nowMs,
184
+ });
185
+ return { ipLocked };
186
+ }
187
+ }
188
+ function getDefaultDatabasePath() {
189
+ return process.env.DATABASE_PATH || join(homedir(), 'factory-factory', 'data.db');
190
+ }
191
+ function runMigrations(projectRoot, databasePath) {
192
+ const migrationsPath = join(projectRoot, 'prisma', 'migrations');
193
+ runDbMigrations({
194
+ databasePath,
195
+ migrationsPath,
196
+ log: () => {
197
+ },
198
+ });
199
+ }
200
+ async function killProcessTree(proc) {
201
+ if (!proc.pid) {
202
+ return;
203
+ }
204
+ try {
205
+ await treeKillAsync(proc.pid, 'SIGTERM');
206
+ }
207
+ catch {
208
+ }
209
+ }
210
+ async function ensureCloudflaredInstalled() {
211
+ try {
212
+ await execFileAsync('cloudflared', ['--version'], { timeout: 5000 });
213
+ }
214
+ catch {
215
+ console.error(chalk.red('\n❌ cloudflared is not installed.\n'));
216
+ console.error('Install it:');
217
+ console.error(' macOS: brew install cloudflared');
218
+ console.error(' Linux: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared');
219
+ console.error(' Windows: winget install Cloudflare.cloudflared');
220
+ console.error('\nThen re-run this command.');
221
+ process.exit(1);
222
+ }
223
+ }
224
+ async function startFactoryFactoryServer(params) {
225
+ const frontendDist = join(params.projectRoot, 'dist', 'client');
226
+ const backendDist = join(params.projectRoot, 'dist', 'src', 'backend', 'index.js');
227
+ if (!existsSync(frontendDist)) {
228
+ throw new Error('Frontend not built. Run `ff build` or `pnpm build` first.');
229
+ }
230
+ if (!existsSync(backendDist)) {
231
+ throw new Error('Backend not built. Run `ff build` or `pnpm build` first.');
232
+ }
233
+ const backend = spawn('node', [backendDist], {
234
+ cwd: params.projectRoot,
235
+ env: {
236
+ ...process.env,
237
+ NODE_ENV: 'production',
238
+ DATABASE_PATH: params.databasePath,
239
+ BACKEND_PORT: params.requestedPort.toString(),
240
+ BACKEND_HOST: LOCAL_HOST,
241
+ FRONTEND_STATIC_PATH: frontendDist,
242
+ },
243
+ stdio: ['ignore', 'pipe', 'pipe'],
244
+ });
245
+ backend.stderr?.on('data', (chunk) => {
246
+ const message = chunk.toString().trim();
247
+ if (!message) {
248
+ return;
249
+ }
250
+ process.stderr.write(chalk.red(` [server] ${message}\n`));
251
+ });
252
+ let startupBuffer = '';
253
+ let actualPort;
254
+ try {
255
+ actualPort = await new Promise((resolve, reject) => {
256
+ const timeout = setTimeout(() => {
257
+ reject(new Error('Timed out waiting for server startup signal'));
258
+ }, 30_000);
259
+ timeout.unref?.();
260
+ const cleanup = () => {
261
+ clearTimeout(timeout);
262
+ backend.stdout?.off('data', onStdoutData);
263
+ backend.off('exit', onExit);
264
+ backend.off('error', onError);
265
+ };
266
+ const onStdoutData = (chunk) => {
267
+ startupBuffer += chunk.toString();
268
+ const match = startupBuffer.match(/BACKEND_PORT:(\d+)/);
269
+ if (match?.[1]) {
270
+ const parsed = Number.parseInt(match[1], 10);
271
+ if (Number.isNaN(parsed)) {
272
+ cleanup();
273
+ reject(new Error('Received invalid backend port from server startup'));
274
+ return;
275
+ }
276
+ cleanup();
277
+ resolve(parsed);
278
+ return;
279
+ }
280
+ if (startupBuffer.length > 4096) {
281
+ startupBuffer = startupBuffer.slice(-2048);
282
+ }
283
+ };
284
+ const onExit = (code, signal) => {
285
+ cleanup();
286
+ reject(new Error(`Server exited before startup completed (code: ${code ?? 'null'}, signal: ${signal ?? 'null'})`));
287
+ };
288
+ const onError = (error) => {
289
+ cleanup();
290
+ reject(new Error(`Failed to start backend server: ${error.message}`));
291
+ };
292
+ backend.stdout?.on('data', onStdoutData);
293
+ backend.once('exit', onExit);
294
+ backend.once('error', onError);
295
+ });
296
+ await waitForPort(actualPort, LOCAL_HOST);
297
+ }
298
+ catch (error) {
299
+ await killProcessTree(backend);
300
+ throw error;
301
+ }
302
+ return { process: backend, port: actualPort };
303
+ }
304
+ function createLoginPage(errorMessage) {
305
+ const escapedErrorMessage = errorMessage ? escapeHtml(errorMessage) : '';
306
+ const errorHtml = escapedErrorMessage
307
+ ? `<p style="color:#dc2626;font-family:system-ui, sans-serif;margin:0 0 16px 0;">${escapedErrorMessage}</p>`
308
+ : '';
309
+ return `<!doctype html>
310
+ <html lang="en">
311
+ <head>
312
+ <meta charset="utf-8" />
313
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
314
+ <title>Factory Factory Proxy</title>
315
+ <style>
316
+ :root { color-scheme: light; }
317
+ body {
318
+ margin: 0;
319
+ min-height: 100vh;
320
+ display: grid;
321
+ place-items: center;
322
+ background: radial-gradient(circle at top, #f5f7ff, #eef2ff 45%, #e2e8f0 100%);
323
+ font-family: system-ui, sans-serif;
324
+ color: #0f172a;
325
+ }
326
+ .card {
327
+ width: min(420px, calc(100vw - 24px));
328
+ background: rgba(255, 255, 255, 0.95);
329
+ border: 1px solid rgba(15, 23, 42, 0.08);
330
+ border-radius: 18px;
331
+ padding: 24px;
332
+ box-shadow: 0 20px 45px rgba(15, 23, 42, 0.15);
333
+ box-sizing: border-box;
334
+ }
335
+ h1 {
336
+ margin: 0 0 8px 0;
337
+ font-size: 22px;
338
+ font-weight: 650;
339
+ letter-spacing: -0.02em;
340
+ }
341
+ p {
342
+ margin: 0 0 20px 0;
343
+ color: #334155;
344
+ line-height: 1.5;
345
+ font-size: 14px;
346
+ }
347
+ form {
348
+ display: grid;
349
+ gap: 18px;
350
+ }
351
+ .otp {
352
+ display: grid;
353
+ grid-template-columns: repeat(6, minmax(0, 1fr));
354
+ gap: 10px;
355
+ }
356
+ .otp input {
357
+ width: 100%;
358
+ min-width: 0;
359
+ aspect-ratio: 1 / 1;
360
+ border: 1px solid #cbd5e1;
361
+ border-radius: 10px;
362
+ text-align: center;
363
+ font-size: 24px;
364
+ font-weight: 700;
365
+ text-transform: uppercase;
366
+ background: #fff;
367
+ box-sizing: border-box;
368
+ }
369
+ .otp input:focus {
370
+ outline: 2px solid #3b82f6;
371
+ outline-offset: 1px;
372
+ border-color: #3b82f6;
373
+ }
374
+ button {
375
+ border: 0;
376
+ border-radius: 10px;
377
+ padding: 12px 14px;
378
+ font-size: 14px;
379
+ font-weight: 600;
380
+ background: #0f172a;
381
+ color: #fff;
382
+ cursor: pointer;
383
+ }
384
+ button:hover { background: #1e293b; }
385
+ @media (max-width: 420px) {
386
+ .card {
387
+ border-radius: 14px;
388
+ padding: 16px;
389
+ }
390
+ .otp {
391
+ gap: 6px;
392
+ }
393
+ .otp input {
394
+ font-size: 20px;
395
+ }
396
+ }
397
+ </style>
398
+ </head>
399
+ <body>
400
+ <main class="card">
401
+ <h1>Enter Access Code</h1>
402
+ <p>This Factory Factory demo is password-protected.</p>
403
+ ${errorHtml}
404
+ <form method="post" action="${LOGIN_PATH}" id="login-form">
405
+ <input type="hidden" name="password" id="password" />
406
+ <div class="otp" aria-label="Password fields">
407
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
408
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
409
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
410
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
411
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
412
+ <input inputmode="text" maxlength="1" autocomplete="one-time-code" />
413
+ </div>
414
+ <button type="submit">Continue</button>
415
+ </form>
416
+ </main>
417
+ <script>
418
+ const form = document.getElementById('login-form');
419
+ const hidden = document.getElementById('password');
420
+ const inputs = Array.from(form.querySelectorAll('.otp input'));
421
+
422
+ const normalize = (value) => value.replace(/[^a-z0-9]/gi, '').toUpperCase().slice(-1);
423
+
424
+ inputs.forEach((input, index) => {
425
+ input.addEventListener('input', () => {
426
+ input.value = normalize(input.value);
427
+ if (input.value && index < inputs.length - 1) {
428
+ inputs[index + 1].focus();
429
+ }
430
+ });
431
+
432
+ input.addEventListener('keydown', (event) => {
433
+ if (event.key === 'Backspace' && !input.value && index > 0) {
434
+ inputs[index - 1].focus();
435
+ }
436
+ });
437
+
438
+ input.addEventListener('paste', (event) => {
439
+ const pasted = (event.clipboardData || window.clipboardData).getData('text') || '';
440
+ const chars = pasted.replace(/[^a-z0-9]/gi, '').toUpperCase().slice(0, inputs.length).split('');
441
+ if (chars.length === 0) {
442
+ return;
443
+ }
444
+ event.preventDefault();
445
+ inputs.forEach((otpInput, otpIndex) => {
446
+ otpInput.value = chars[otpIndex] || '';
447
+ });
448
+ const nextIndex = Math.min(chars.length, inputs.length - 1);
449
+ inputs[nextIndex].focus();
450
+ });
451
+ });
452
+
453
+ form.addEventListener('submit', (event) => {
454
+ const value = inputs.map((input) => normalize(input.value)).join('');
455
+ hidden.value = value;
456
+ if (value.length !== inputs.length) {
457
+ event.preventDefault();
458
+ inputs.find((input) => !input.value)?.focus();
459
+ }
460
+ });
461
+
462
+ inputs[0].focus();
463
+ </script>
464
+ </body>
465
+ </html>`;
466
+ }
467
+ function parseFormBody(rawBody) {
468
+ const params = new URLSearchParams(rawBody);
469
+ const output = {};
470
+ for (const [key, value] of params.entries()) {
471
+ output[key] = value;
472
+ }
473
+ return output;
474
+ }
475
+ function mergeSetCookieValues(existing, incoming) {
476
+ const existingValues = typeof existing === 'undefined' ? [] : Array.isArray(existing) ? existing : [String(existing)];
477
+ const incomingValues = Array.isArray(incoming) ? incoming : [incoming];
478
+ return [...existingValues, ...incomingValues];
479
+ }
480
+ function writeRateLimitResponse(res, retryAfterSeconds) {
481
+ res.statusCode = 429;
482
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
483
+ res.end(createLoginPage(`Too many attempts. Try again in ${retryAfterSeconds} seconds.`));
484
+ }
485
+ function writeUnauthorizedResponse(res, message, ipLocked = false) {
486
+ res.statusCode = ipLocked ? 429 : 401;
487
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
488
+ res.end(createLoginPage(message));
489
+ }
490
+ function createAuthCookie(session) {
491
+ return `${SESSION_COOKIE_NAME}=${session.id}.${session.signature}; HttpOnly; Secure; SameSite=Lax; Path=/`;
492
+ }
493
+ function authenticateRequest(params) {
494
+ const rawUrl = params.req.url || '/';
495
+ const parsed = new URL(rawUrl, 'http://proxy.local');
496
+ const sanitizedPath = sanitizePathWithoutToken(rawUrl);
497
+ const cookies = parseCookieHeader(params.req.headers.cookie);
498
+ const session = cookies[SESSION_COOKIE_NAME];
499
+ const hasValidSession = verifySessionValue(session, params.cookieSecret);
500
+ const token = parsed.searchParams.get('token');
501
+ if (token && matchesMagicToken(token, params.magicToken)) {
502
+ return {
503
+ authenticated: true,
504
+ viaToken: true,
505
+ invalidToken: false,
506
+ sanitizedPath,
507
+ };
508
+ }
509
+ if (token) {
510
+ if (hasValidSession) {
511
+ return {
512
+ authenticated: true,
513
+ viaToken: false,
514
+ invalidToken: false,
515
+ sanitizedPath,
516
+ };
517
+ }
518
+ return {
519
+ authenticated: false,
520
+ viaToken: false,
521
+ invalidToken: true,
522
+ sanitizedPath,
523
+ };
524
+ }
525
+ return {
526
+ authenticated: hasValidSession,
527
+ viaToken: false,
528
+ invalidToken: false,
529
+ sanitizedPath: rawUrl,
530
+ };
531
+ }
532
+ function removeHopByHopHeaders(headers) {
533
+ const disallowed = new Set([
534
+ 'connection',
535
+ 'keep-alive',
536
+ 'proxy-authenticate',
537
+ 'proxy-authorization',
538
+ 'te',
539
+ 'trailer',
540
+ 'transfer-encoding',
541
+ 'upgrade',
542
+ ]);
543
+ const cleaned = {};
544
+ for (const [key, value] of Object.entries(headers)) {
545
+ if (disallowed.has(key.toLowerCase())) {
546
+ continue;
547
+ }
548
+ if (key.toLowerCase() === 'cookie' && typeof value === 'string') {
549
+ const cookies = parseCookieHeader(value);
550
+ delete cookies[SESSION_COOKIE_NAME];
551
+ const cookieValue = Object.entries(cookies)
552
+ .map(([cookieKey, cookieVal]) => `${cookieKey}=${cookieVal}`)
553
+ .join('; ');
554
+ if (cookieValue) {
555
+ cleaned[key] = cookieValue;
556
+ }
557
+ continue;
558
+ }
559
+ cleaned[key] = value;
560
+ }
561
+ return cleaned;
562
+ }
563
+ async function readRequestBody(req) {
564
+ const chunks = [];
565
+ for await (const chunk of req) {
566
+ const value = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
567
+ chunks.push(value);
568
+ const size = chunks.reduce((acc, current) => acc + current.length, 0);
569
+ if (size > 4096) {
570
+ throw new Error('Request body too large');
571
+ }
572
+ }
573
+ return Buffer.concat(chunks).toString('utf8');
574
+ }
575
+ function setSessionCookie(res, cookieSecret) {
576
+ const session = createSessionValue(cookieSecret);
577
+ res.setHeader('Set-Cookie', createAuthCookie(session));
578
+ }
579
+ function writeLoginPage(res) {
580
+ res.statusCode = 200;
581
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
582
+ res.end(createLoginPage());
583
+ }
584
+ function registerFailureAndRespond(params) {
585
+ const failure = params.guard.registerFailure(params.ip);
586
+ if (failure.ipLocked) {
587
+ const retry = params.guard.isLocked(params.ip);
588
+ writeUnauthorizedResponse(params.res, `Too many failed attempts. Try again in ${retry.retryAfterSeconds} seconds.`, true);
589
+ return 'handled';
590
+ }
591
+ writeUnauthorizedResponse(params.res, params.message);
592
+ return 'handled';
593
+ }
594
+ async function handleLoginSubmission(params) {
595
+ if (params.lockState.locked) {
596
+ writeRateLimitResponse(params.res, params.lockState.retryAfterSeconds);
597
+ return 'handled';
598
+ }
599
+ let submittedPassword = '';
600
+ try {
601
+ const body = await readRequestBody(params.req);
602
+ const form = parseFormBody(body);
603
+ submittedPassword = (form.password || '').trim().toUpperCase();
604
+ }
605
+ catch {
606
+ submittedPassword = '';
607
+ }
608
+ if (submittedPassword && matchesPassword(submittedPassword, params.password)) {
609
+ setSessionCookie(params.res, params.cookieSecret);
610
+ params.res.statusCode = 302;
611
+ params.res.setHeader('Location', '/');
612
+ params.res.end();
613
+ return 'handled';
614
+ }
615
+ return registerFailureAndRespond({
616
+ guard: params.guard,
617
+ ip: params.ip,
618
+ res: params.res,
619
+ message: 'Invalid password. Please try again.',
620
+ });
621
+ }
622
+ function proxyAuthenticatedHttpRequest(params) {
623
+ const endProxyErrorResponse = () => {
624
+ if (!params.res.headersSent) {
625
+ params.res.statusCode = 502;
626
+ }
627
+ if (!params.res.writableEnded) {
628
+ params.res.end('Proxy error');
629
+ }
630
+ };
631
+ const upstreamHeaders = removeHopByHopHeaders(params.req.headers);
632
+ const upstreamRequest = httpRequest({
633
+ host: LOCAL_HOST,
634
+ port: params.upstreamPort,
635
+ method: params.req.method,
636
+ path: params.path,
637
+ headers: {
638
+ ...upstreamHeaders,
639
+ host: `localhost:${params.upstreamPort}`,
640
+ },
641
+ }, (upstreamResponse) => {
642
+ if (upstreamResponse.statusCode) {
643
+ params.res.statusCode = upstreamResponse.statusCode;
644
+ }
645
+ for (const [header, value] of Object.entries(upstreamResponse.headers)) {
646
+ if (typeof value !== 'undefined') {
647
+ if (header.toLowerCase() === 'set-cookie') {
648
+ const existingCookieHeader = params.res.getHeader('set-cookie');
649
+ const mergedCookieHeader = mergeSetCookieValues(existingCookieHeader, value);
650
+ params.res.setHeader('set-cookie', mergedCookieHeader);
651
+ }
652
+ else {
653
+ params.res.setHeader(header, value);
654
+ }
655
+ }
656
+ }
657
+ upstreamResponse.on('error', endProxyErrorResponse);
658
+ upstreamResponse.on('aborted', endProxyErrorResponse);
659
+ upstreamResponse.pipe(params.res);
660
+ });
661
+ upstreamRequest.on('error', endProxyErrorResponse);
662
+ params.req.on('error', () => {
663
+ if (!(upstreamRequest.destroyed || upstreamRequest.writableEnded)) {
664
+ upstreamRequest.destroy();
665
+ }
666
+ endProxyErrorResponse();
667
+ });
668
+ params.req.pipe(upstreamRequest);
669
+ }
670
+ async function handleAuthHttpRequest(params) {
671
+ const ip = getClientIp(params.req);
672
+ const lockState = params.guard.isLocked(ip);
673
+ const isLoginPath = params.req.url?.startsWith(LOGIN_PATH) ?? false;
674
+ const isLoginSubmission = params.req.method === 'POST' && isLoginPath;
675
+ if (lockState.locked && !isLoginSubmission) {
676
+ writeRateLimitResponse(params.res, lockState.retryAfterSeconds);
677
+ return;
678
+ }
679
+ const auth = authenticateRequest({
680
+ req: params.req,
681
+ cookieSecret: params.cookieSecret,
682
+ magicToken: params.magicToken,
683
+ });
684
+ if (isLoginSubmission) {
685
+ await handleLoginSubmission({
686
+ req: params.req,
687
+ res: params.res,
688
+ lockState,
689
+ guard: params.guard,
690
+ ip,
691
+ password: params.password,
692
+ cookieSecret: params.cookieSecret,
693
+ });
694
+ return;
695
+ }
696
+ if (!auth.authenticated) {
697
+ if (auth.invalidToken) {
698
+ registerFailureAndRespond({
699
+ guard: params.guard,
700
+ ip,
701
+ res: params.res,
702
+ message: 'Invalid token. Please use the shared link again.',
703
+ });
704
+ return;
705
+ }
706
+ writeLoginPage(params.res);
707
+ return;
708
+ }
709
+ if (auth.viaToken) {
710
+ setSessionCookie(params.res, params.cookieSecret);
711
+ }
712
+ proxyAuthenticatedHttpRequest({
713
+ req: params.req,
714
+ res: params.res,
715
+ upstreamPort: params.upstreamPort,
716
+ path: auth.sanitizedPath,
717
+ });
718
+ }
719
+ function createAuthProxy(params) {
720
+ const bruteForceGuard = new BruteForceGuard();
721
+ const cookieSecret = randomBytes(32);
722
+ const activeSockets = new Set();
723
+ const server = createHttpServer((req, res) => {
724
+ void handleAuthHttpRequest({
725
+ req,
726
+ res,
727
+ upstreamPort: params.upstreamPort,
728
+ password: params.password,
729
+ magicToken: params.magicToken,
730
+ cookieSecret,
731
+ guard: bruteForceGuard,
732
+ }).catch((error) => {
733
+ process.stderr.write(chalk.red(` [proxy] ${error.message}\n`));
734
+ if (!res.headersSent) {
735
+ res.statusCode = 500;
736
+ res.end('Proxy error');
737
+ }
738
+ });
739
+ });
740
+ server.on('connection', (socket) => {
741
+ activeSockets.add(socket);
742
+ socket.once('close', () => {
743
+ activeSockets.delete(socket);
744
+ });
745
+ });
746
+ server.on('upgrade', (req, socket, head) => {
747
+ const ip = getClientIp(req);
748
+ const lockState = bruteForceGuard.isLocked(ip);
749
+ if (lockState.locked) {
750
+ socket.write('HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n');
751
+ socket.destroy();
752
+ return;
753
+ }
754
+ const auth = authenticateRequest({
755
+ req,
756
+ cookieSecret,
757
+ magicToken: params.magicToken,
758
+ });
759
+ if (!auth.authenticated) {
760
+ if (auth.invalidToken) {
761
+ bruteForceGuard.registerFailure(ip);
762
+ }
763
+ socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
764
+ socket.destroy();
765
+ return;
766
+ }
767
+ const upstreamSocket = createConnection(params.upstreamPort, LOCAL_HOST, () => {
768
+ const headers = {
769
+ ...removeHopByHopHeaders(req.headers),
770
+ host: `localhost:${params.upstreamPort}`,
771
+ connection: 'Upgrade',
772
+ upgrade: req.headers.upgrade,
773
+ };
774
+ const requestPath = toSafeRedirectPath(auth.sanitizedPath || '/');
775
+ const requestLine = `${req.method || 'GET'} ${requestPath} HTTP/${req.httpVersion}`;
776
+ const headerLines = Object.entries(headers)
777
+ .flatMap(([key, value]) => {
778
+ if (typeof value === 'undefined') {
779
+ return [];
780
+ }
781
+ if (Array.isArray(value)) {
782
+ return value.map((item) => `${key}: ${item}`);
783
+ }
784
+ return [`${key}: ${value}`];
785
+ })
786
+ .join('\r\n');
787
+ upstreamSocket.write(`${requestLine}\r\n${headerLines}\r\n\r\n`);
788
+ if (head.length > 0) {
789
+ upstreamSocket.write(head);
790
+ }
791
+ socket.pipe(upstreamSocket).pipe(socket);
792
+ });
793
+ upstreamSocket.on('error', () => {
794
+ socket.destroy();
795
+ });
796
+ socket.on('error', () => {
797
+ upstreamSocket.destroy();
798
+ });
799
+ });
800
+ return new Promise((resolve, reject) => {
801
+ server.once('error', reject);
802
+ server.listen(0, LOCAL_HOST, () => {
803
+ const address = server.address();
804
+ if (!address || typeof address === 'string') {
805
+ reject(new Error('Could not determine auth proxy port'));
806
+ return;
807
+ }
808
+ resolve({
809
+ port: address.port,
810
+ close: async () => {
811
+ await new Promise((closeResolve) => {
812
+ const timeout = setTimeout(() => {
813
+ closeResolve();
814
+ }, 1000);
815
+ timeout.unref?.();
816
+ server.close(() => {
817
+ clearTimeout(timeout);
818
+ closeResolve();
819
+ });
820
+ for (const socket of activeSockets) {
821
+ socket.destroy();
822
+ }
823
+ });
824
+ },
825
+ });
826
+ });
827
+ });
828
+ }
829
+ async function startCloudflaredTunnel(targetUrl) {
830
+ const cloudflared = spawn('cloudflared', ['tunnel', '--url', targetUrl], {
831
+ stdio: ['ignore', 'pipe', 'pipe'],
832
+ });
833
+ let resolvedUrl = null;
834
+ let outputBuffer = '';
835
+ const waitForUrl = new Promise((resolve, reject) => {
836
+ const timeout = setTimeout(() => {
837
+ cleanup();
838
+ reject(new Error('Timed out waiting for cloudflared tunnel URL'));
839
+ }, 30_000);
840
+ timeout.unref?.();
841
+ const onData = (data) => {
842
+ outputBuffer = appendBoundedOutputBuffer(outputBuffer, data.toString());
843
+ const extracted = extractTryCloudflareUrl(outputBuffer);
844
+ if (extracted && !resolvedUrl) {
845
+ resolvedUrl = extracted;
846
+ cleanup();
847
+ resolve(extracted);
848
+ }
849
+ };
850
+ const onExit = (code) => {
851
+ if (!resolvedUrl) {
852
+ cleanup();
853
+ reject(new Error(`cloudflared exited before URL was available (code ${code ?? 'unknown'})`));
854
+ }
855
+ };
856
+ const onError = (error) => {
857
+ cleanup();
858
+ reject(new Error(`Failed to start cloudflared: ${error.message}`));
859
+ };
860
+ const cleanup = () => {
861
+ clearTimeout(timeout);
862
+ cloudflared.stdout?.off('data', onData);
863
+ cloudflared.stderr?.off('data', onData);
864
+ cloudflared.off('exit', onExit);
865
+ cloudflared.off('error', onError);
866
+ };
867
+ cloudflared.stdout?.on('data', onData);
868
+ cloudflared.stderr?.on('data', onData);
869
+ cloudflared.once('exit', onExit);
870
+ cloudflared.once('error', onError);
871
+ });
872
+ try {
873
+ const publicUrl = await waitForUrl;
874
+ return { proc: cloudflared, publicUrl };
875
+ }
876
+ catch (error) {
877
+ await killProcessTree(cloudflared);
878
+ throw error;
879
+ }
880
+ }
881
+ function createExitPromise(proc, name, shutdownState) {
882
+ return new Promise((resolve, reject) => {
883
+ proc.once('exit', (code, signal) => {
884
+ if (shutdownState.shuttingDown) {
885
+ resolve();
886
+ return;
887
+ }
888
+ reject(new Error(`${name} exited unexpectedly (code: ${code ?? 'null'}, signal: ${signal ?? 'null'})`));
889
+ });
890
+ proc.once('error', (error) => {
891
+ if (shutdownState.shuttingDown) {
892
+ resolve();
893
+ return;
894
+ }
895
+ reject(new Error(`${name} failed: ${error.message}`));
896
+ });
897
+ });
898
+ }
899
+ export async function runProxyCommand({ options, projectRoot, }) {
900
+ const usePrivateMode = options.private ?? false;
901
+ const shutdownState = { shuttingDown: false };
902
+ const processes = [];
903
+ let authProxyClose = null;
904
+ let signalHandlersAttached = false;
905
+ const shutdown = async (exitCode, message) => {
906
+ if (shutdownState.shuttingDown) {
907
+ return;
908
+ }
909
+ shutdownState.shuttingDown = true;
910
+ detachSignalHandlers();
911
+ if (message) {
912
+ if (exitCode === 0) {
913
+ console.log(chalk.yellow(`\n${message}`));
914
+ }
915
+ else {
916
+ console.error(chalk.red(`\n${message}`));
917
+ }
918
+ }
919
+ if (authProxyClose) {
920
+ try {
921
+ await authProxyClose();
922
+ }
923
+ catch {
924
+ }
925
+ }
926
+ await Promise.all(processes.map(async ({ proc }) => killProcessTree(proc)));
927
+ process.exit(exitCode);
928
+ };
929
+ const onSigint = () => {
930
+ void shutdown(0, 'SIGINT received, shutting down...');
931
+ };
932
+ const onSigterm = () => {
933
+ void shutdown(0, 'SIGTERM received, shutting down...');
934
+ };
935
+ const detachSignalHandlers = () => {
936
+ if (!signalHandlersAttached) {
937
+ return;
938
+ }
939
+ process.off('SIGINT', onSigint);
940
+ process.off('SIGTERM', onSigterm);
941
+ signalHandlersAttached = false;
942
+ };
943
+ const attachSignalHandlers = () => {
944
+ if (signalHandlersAttached) {
945
+ return;
946
+ }
947
+ process.on('SIGINT', onSigint);
948
+ process.on('SIGTERM', onSigterm);
949
+ signalHandlersAttached = true;
950
+ };
951
+ await ensureCloudflaredInstalled();
952
+ const databasePath = getDefaultDatabasePath();
953
+ ensureDataDir(databasePath);
954
+ try {
955
+ runMigrations(projectRoot, databasePath);
956
+ }
957
+ catch (error) {
958
+ console.error(chalk.red(`\n❌ Migration failed: ${error.message}`));
959
+ detachSignalHandlers();
960
+ process.exit(1);
961
+ return;
962
+ }
963
+ const requestedPort = await findAvailablePort(3001, { maxAttempts: 50 });
964
+ console.log(chalk.blue(`Starting server on port ${requestedPort}...`));
965
+ let backend;
966
+ attachSignalHandlers();
967
+ try {
968
+ backend = await startFactoryFactoryServer({
969
+ projectRoot,
970
+ databasePath,
971
+ requestedPort,
972
+ });
973
+ }
974
+ catch (error) {
975
+ console.error(chalk.red(`\n❌ ${error.message}`));
976
+ detachSignalHandlers();
977
+ process.exit(1);
978
+ return;
979
+ }
980
+ processes.push({ name: 'server', proc: backend.process });
981
+ let targetPort = backend.port;
982
+ let password = null;
983
+ let directToken = null;
984
+ if (usePrivateMode) {
985
+ password = generatePassword();
986
+ directToken = generateMagicToken();
987
+ let authProxy;
988
+ try {
989
+ authProxy = await createAuthProxy({
990
+ upstreamPort: backend.port,
991
+ password,
992
+ magicToken: directToken,
993
+ });
994
+ }
995
+ catch (error) {
996
+ await shutdown(1, `❌ ${error.message}`);
997
+ return;
998
+ }
999
+ targetPort = authProxy.port;
1000
+ authProxyClose = authProxy.close;
1001
+ }
1002
+ console.log(chalk.blue('Starting tunnel...'));
1003
+ let tunnel;
1004
+ try {
1005
+ tunnel = await startCloudflaredTunnel(`http://${LOCAL_HOST}:${targetPort}`);
1006
+ }
1007
+ catch (error) {
1008
+ await shutdown(1, `❌ ${error.message}`);
1009
+ return;
1010
+ }
1011
+ processes.push({ name: 'cloudflared', proc: tunnel.proc });
1012
+ if (usePrivateMode && password && directToken) {
1013
+ console.log(chalk.green(`🌍 Public URL: ${tunnel.publicUrl}`));
1014
+ console.log(chalk.green(`🔑 Password: ${password}`));
1015
+ console.log(chalk.green(`🔗 Direct link: ${tunnel.publicUrl}?token=${directToken}\n`));
1016
+ console.log('Share the password or direct link with people you want to grant access.');
1017
+ console.log('Press Ctrl+C to stop.');
1018
+ }
1019
+ else {
1020
+ console.log(chalk.green(`🌍 Public URL: ${tunnel.publicUrl}\n`));
1021
+ console.log('Press Ctrl+C to stop.');
1022
+ }
1023
+ try {
1024
+ await Promise.race([
1025
+ createExitPromise(backend.process, 'Factory Factory server', shutdownState),
1026
+ createExitPromise(tunnel.proc, 'cloudflared', shutdownState),
1027
+ ]);
1028
+ }
1029
+ catch (error) {
1030
+ await shutdown(1, `❌ ${error.message}`);
1031
+ return;
1032
+ }
1033
+ }
1034
+ export const proxyInternals = {
1035
+ BruteForceGuard,
1036
+ createSessionValue,
1037
+ createAuthCookie,
1038
+ authenticateRequest,
1039
+ createLoginPage,
1040
+ escapeHtml,
1041
+ extractTryCloudflareUrl,
1042
+ appendBoundedOutputBuffer,
1043
+ generatePassword,
1044
+ generateMagicToken,
1045
+ mergeSetCookieValues,
1046
+ matchesMagicToken,
1047
+ matchesPassword,
1048
+ createAuthProxy,
1049
+ toSafeRedirectPath,
1050
+ parseCookieHeader,
1051
+ signValue,
1052
+ verifySessionValue,
1053
+ };
1054
+ //# sourceMappingURL=proxy.js.map