upfynai-code 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/client/dist/assets/AppContent-CRld2UWX.js +513 -0
  2. package/client/dist/assets/CanvasPanel-CB4sweQq.js +34 -0
  3. package/client/dist/assets/CanvasPanel-WhZulBJw.css +1 -0
  4. package/client/dist/assets/DashboardPanel-BXaA-b9z.js +1 -0
  5. package/client/dist/assets/LoginModal-BwkvjfPR.js +19 -0
  6. package/client/dist/assets/{Onboarding-CtIoXiTp.js → Onboarding-2A_5fPxy.js} +1 -1
  7. package/client/dist/assets/{SetupForm-B4p8im5O.js → SetupForm-CH5EA5W0.js} +1 -1
  8. package/client/dist/assets/WorkflowsPanel-CO5g5yGG.js +1 -0
  9. package/client/dist/assets/{ar-SA-G6X2FPQ2-2gfmdvHk.js → ar-SA-G6X2FPQ2-DoJuo98H.js} +2 -2
  10. package/client/dist/assets/{arc-DCZSHhoJ.js → arc-B0wBaTeh.js} +1 -1
  11. package/client/dist/assets/az-AZ-76LH7QW2-xdrt1Z13.js +1 -0
  12. package/client/dist/assets/{bg-BG-XCXSNQG7-D6__XtOK.js → bg-BG-XCXSNQG7-D8NAiF6Y.js} +2 -2
  13. package/client/dist/assets/{blockDiagram-38ab4fdb-Cfbaeyp6.js → blockDiagram-38ab4fdb-DSnyKzK4.js} +2 -2
  14. package/client/dist/assets/{bn-BD-2XOGV67Q-DHNJw3OG.js → bn-BD-2XOGV67Q-B0qWv8_J.js} +2 -2
  15. package/client/dist/assets/{c4Diagram-3d4e48cf-BBCnjOTy.js → c4Diagram-3d4e48cf-DoZJ13XA.js} +2 -2
  16. package/client/dist/assets/{ca-ES-6MX7JW3Y-r5g4o3zQ.js → ca-ES-6MX7JW3Y-RgLhfbZZ.js} +3 -3
  17. package/client/dist/assets/channel-BmO6nY0W.js +1 -0
  18. package/client/dist/assets/classDiagram-70f12bd4-GNyDrRCk.js +2 -0
  19. package/client/dist/assets/classDiagram-v2-f2320105-CxdGhHm2.js +2 -0
  20. package/client/dist/assets/clone-xuHMqFoD.js +1 -0
  21. package/client/dist/assets/{createText-2e5e7dd3-B8jCDmF_.js → createText-2e5e7dd3-DiPywQOa.js} +1 -1
  22. package/client/dist/assets/{cs-CZ-2BRQDIVT-p08jRLRC.js → cs-CZ-2BRQDIVT-BAjmnuoC.js} +2 -2
  23. package/client/dist/assets/{da-DK-5WZEPLOC-CnhOImFf.js → da-DK-5WZEPLOC-JxKVGt8o.js} +2 -2
  24. package/client/dist/assets/{de-DE-XR44H4JA-BunSXZ-Y.js → de-DE-XR44H4JA-CrnRlt4z.js} +2 -2
  25. package/client/dist/assets/{edges-e0da2a9e-CGBBhG8k.js → edges-e0da2a9e-DDsXzXLJ.js} +1 -1
  26. package/client/dist/assets/{el-GR-BZB4AONW-D4wv1oIz.js → el-GR-BZB4AONW-DQd8iogq.js} +2 -2
  27. package/client/dist/assets/{erDiagram-9861fffd-CYaF3q1I.js → erDiagram-9861fffd-CBiCC4rl.js} +2 -2
  28. package/client/dist/assets/{es-ES-U4NZUMDT-CGeTKXgd.js → es-ES-U4NZUMDT-vvUblc5i.js} +2 -2
  29. package/client/dist/assets/{eu-ES-A7QVB2H4-Cayx1TxR.js → eu-ES-A7QVB2H4-De4NNCc1.js} +2 -2
  30. package/client/dist/assets/{fa-IR-HGAKTJCU-CmUg8pmw.js → fa-IR-HGAKTJCU-DFBXqIqq.js} +2 -2
  31. package/client/dist/assets/{fi-FI-Z5N7JZ37-xvHcPhsU.js → fi-FI-Z5N7JZ37-DV9zESPg.js} +2 -2
  32. package/client/dist/assets/{flowDb-956e92f1-C-_LFz70.js → flowDb-956e92f1-BhdSHbdO.js} +1 -1
  33. package/client/dist/assets/{flowDiagram-66a62f08-C1sHdSjn.js → flowDiagram-66a62f08-M-fp1_Ie.js} +2 -2
  34. package/client/dist/assets/flowDiagram-v2-96b9c2cf-C5eiN8Pg.js +1 -0
  35. package/client/dist/assets/{flowchart-elk-definition-4a651766-CNGfpudb.js → flowchart-elk-definition-4a651766-Bp0SonQx.js} +2 -2
  36. package/client/dist/assets/{fr-FR-RHASNOE6-DBoHEcNj.js → fr-FR-RHASNOE6-CKTMXuGk.js} +2 -2
  37. package/client/dist/assets/ganttDiagram-c361ad54-iA737GUS.js +257 -0
  38. package/client/dist/assets/{gitGraphDiagram-72cf32ee-DojCDvlS.js → gitGraphDiagram-72cf32ee-BX-wj-PV.js} +2 -2
  39. package/client/dist/assets/{gl-ES-HMX3MZ6V-p6hrn2cN.js → gl-ES-HMX3MZ6V-Cdiqq4jY.js} +2 -2
  40. package/client/dist/assets/{graph-DXM7lcy1.js → graph-Rxkx3sEa.js} +1 -1
  41. package/client/dist/assets/{he-IL-6SHJWFNN-y2jEX6-0.js → he-IL-6SHJWFNN-gYmR5_KT.js} +2 -2
  42. package/client/dist/assets/{hi-IN-IWLTKZ5I-99pNfyWr.js → hi-IN-IWLTKZ5I-pyqK94AR.js} +2 -2
  43. package/client/dist/assets/{hu-HU-A5ZG7DT2-hygceGMS.js → hu-HU-A5ZG7DT2-DpacJgJy.js} +2 -2
  44. package/client/dist/assets/{id-ID-SAP4L64H-CyIqi1hv.js → id-ID-SAP4L64H-CAvIX-mj.js} +2 -2
  45. package/client/dist/assets/{index-3862675e-4idOQN2N.js → index-3862675e-BX3Fpn6V.js} +1 -1
  46. package/client/dist/assets/{index-BHZfFT_V.js → index-BBlwbHq_.js} +4 -4
  47. package/client/dist/assets/{index-BGmwbRlb.js → index-ClfzLIqY.js} +6 -6
  48. package/client/dist/assets/index-Td4UdtLF.css +1 -0
  49. package/client/dist/assets/{infoDiagram-f8f76790-CFLrHqtc.js → infoDiagram-f8f76790-Ckv8imiv.js} +2 -2
  50. package/client/dist/assets/{it-IT-JPQ66NNP-DzVvVdQI.js → it-IT-JPQ66NNP-BtpNRSce.js} +2 -2
  51. package/client/dist/assets/{ja-JP-DBVTYXUO-BI4fPexV.js → ja-JP-DBVTYXUO-CwJRyY6M.js} +2 -2
  52. package/client/dist/assets/{journeyDiagram-49397b02-C3CFDo8z.js → journeyDiagram-49397b02-DWWZssji.js} +2 -2
  53. package/client/dist/assets/kaa-6HZHGXH3-DIWQEb4A.js +1 -0
  54. package/client/dist/assets/{kab-KAB-ZGHBKWFO-DBI_ri48.js → kab-KAB-ZGHBKWFO-DjGbqhUg.js} +2 -2
  55. package/client/dist/assets/kk-KZ-P5N5QNE5-B_VzJdWf.js +1 -0
  56. package/client/dist/assets/{km-KH-HSX4SM5Z-DOMFSres.js → km-KH-HSX4SM5Z-DUD5mi0o.js} +2 -2
  57. package/client/dist/assets/{ko-KR-MTYHY66A-tb08hXzd.js → ko-KR-MTYHY66A--sDB10db.js} +3 -3
  58. package/client/dist/assets/{ku-TR-6OUDTVRD-DlIQCCY4.js → ku-TR-6OUDTVRD-CKvKrkcX.js} +2 -2
  59. package/client/dist/assets/{layout-B_11mCXA.js → layout-CkB7sSeq.js} +1 -1
  60. package/client/dist/assets/{line-B-qmK_vI.js → line-DC7MA9qY.js} +1 -1
  61. package/client/dist/assets/{linear-Ph6uuYcX.js → linear-C1lBBthf.js} +1 -1
  62. package/client/dist/assets/{lt-LT-XHIRWOB4--qWy24_Z.js → lt-LT-XHIRWOB4-MSZf7xYG.js} +2 -2
  63. package/client/dist/assets/{lv-LV-5QDEKY6T-Bnd_1GDb.js → lv-LV-5QDEKY6T-C-gvvmBB.js} +2 -2
  64. package/client/dist/assets/{mindmap-definition-fc14e90a-Do79tIc0.js → mindmap-definition-fc14e90a-B3O7hztq.js} +2 -2
  65. package/client/dist/assets/{mr-IN-CRQNXWMA-BsV6HaD9.js → mr-IN-CRQNXWMA-XHtBUWQH.js} +2 -2
  66. package/client/dist/assets/my-MM-5M5IBNSE-D9eD2edL.js +1 -0
  67. package/client/dist/assets/{nb-NO-T6EIAALU-Cvf9FdSF.js → nb-NO-T6EIAALU-BlImC6gp.js} +3 -3
  68. package/client/dist/assets/{nl-NL-IS3SIHDZ-DA1yqpXw.js → nl-NL-IS3SIHDZ-CPFhnaSP.js} +2 -2
  69. package/client/dist/assets/{nn-NO-6E72VCQL-89lm3vku.js → nn-NO-6E72VCQL-BMvoJSKQ.js} +2 -2
  70. package/client/dist/assets/{oc-FR-POXYY2M6-BsrjTJQh.js → oc-FR-POXYY2M6-Buye63LS.js} +2 -2
  71. package/client/dist/assets/{pa-IN-N4M65BXN-CczefYaj.js → pa-IN-N4M65BXN-D9uQ3niy.js} +2 -2
  72. package/client/dist/assets/{percentages-BXMCSKIN-Be6p9phi.js → percentages-BXMCSKIN-BzXIakGM.js} +7 -7
  73. package/client/dist/assets/{pieDiagram-8a3498a8-CfblQHdm.js → pieDiagram-8a3498a8-BU38mzx-.js} +3 -3
  74. package/client/dist/assets/{pl-PL-T2D74RX3-DdhH-zcK.js → pl-PL-T2D74RX3-BqM4xdcg.js} +2 -2
  75. package/client/dist/assets/{pt-BR-5N22H2LF-gpwlheL6.js → pt-BR-5N22H2LF-rAjrxGyI.js} +2 -2
  76. package/client/dist/assets/{pt-PT-UZXXM6DQ-Cs87vICi.js → pt-PT-UZXXM6DQ-DXsqcwLt.js} +2 -2
  77. package/client/dist/assets/{quadrantDiagram-120e2f19-CRMSamSP.js → quadrantDiagram-120e2f19-HhK4H1WU.js} +2 -2
  78. package/client/dist/assets/{requirementDiagram-deff3bca-D3LBN016.js → requirementDiagram-deff3bca-aDrcyj-A.js} +2 -2
  79. package/client/dist/assets/{ro-RO-JPDTUUEW-CWTSJ1Dt.js → ro-RO-JPDTUUEW-D_F9UKer.js} +2 -2
  80. package/client/dist/assets/{ru-RU-B4JR7IUQ-Bq7aN2ep.js → ru-RU-B4JR7IUQ-MirqN29p.js} +2 -2
  81. package/client/dist/assets/sankeyDiagram-04a897e0-C6ij7qbQ.js +8 -0
  82. package/client/dist/assets/{sequenceDiagram-704730f1-BRYXVDGX.js → sequenceDiagram-704730f1-C0EKO3th.js} +2 -2
  83. package/client/dist/assets/si-LK-N5RQ5JYF-DyZC3mkC.js +1 -0
  84. package/client/dist/assets/{sk-SK-C5VTKIMK-ByjKQzUb.js → sk-SK-C5VTKIMK-D-ksz-WY.js} +2 -2
  85. package/client/dist/assets/{sl-SI-NN7IZMDC-B8WCyMBU.js → sl-SI-NN7IZMDC-CknuYoQ1.js} +2 -2
  86. package/client/dist/assets/stateDiagram-587899a1-CYoq2VjL.js +1 -0
  87. package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5lbp5px.js +1 -0
  88. package/client/dist/assets/{styles-6aaf32cf-Dr-lfIOW.js → styles-6aaf32cf-Dkfsk8gt.js} +1 -1
  89. package/client/dist/assets/{styles-9a916d00-DS4wRpL7.js → styles-9a916d00-CMYqtcEN.js} +1 -1
  90. package/client/dist/assets/{styles-c10674c1-nKRF6NrH.js → styles-c10674c1-Bp-5OlRU.js} +1 -1
  91. package/client/dist/assets/{subset-shared.chunk-KT79s7KG.js → subset-shared.chunk-kfIB1Zam.js} +3 -3
  92. package/client/dist/assets/subset-worker.chunk-DwQBgc4z.js +1 -0
  93. package/client/dist/assets/{sv-SE-XGPEYMSR-BiIPUVbv.js → sv-SE-XGPEYMSR-DwN13se1.js} +2 -2
  94. package/client/dist/assets/{svgDrawCommon-08f97a94-C3uP9PYr.js → svgDrawCommon-08f97a94-CEgCMqs4.js} +1 -1
  95. package/client/dist/assets/{ta-IN-2NMHFXQM-Cidadso2.js → ta-IN-2NMHFXQM-ejDfFhwa.js} +2 -2
  96. package/client/dist/assets/th-TH-HPSO5L25-Bqc90ZNn.js +2 -0
  97. package/client/dist/assets/{timeline-definition-85554ec2-BSsLsIgF.js → timeline-definition-85554ec2-BmGdKqG0.js} +2 -2
  98. package/client/dist/assets/{tr-TR-DEFEU3FU-DaFcI-KL.js → tr-TR-DEFEU3FU-CJvlPbcW.js} +2 -2
  99. package/client/dist/assets/{uk-UA-QMV73CPH-DkBW36St.js → uk-UA-QMV73CPH-D26-cbWL.js} +3 -3
  100. package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +35 -0
  101. package/client/dist/assets/{vendor-icons-Dh9m_Ydt.js → vendor-icons-aNdOvTr_.js} +159 -119
  102. package/client/dist/assets/{vi-VN-M7AON7JQ-KrtfxOzl.js → vi-VN-M7AON7JQ-MbqIIwYM.js} +2 -2
  103. package/client/dist/assets/{xychartDiagram-e933f94c-CgNgZ4pp.js → xychartDiagram-e933f94c-gfcTauxU.js} +2 -2
  104. package/client/dist/assets/{zh-CN-LNUGB5OW-BQu12RoD.js → zh-CN-LNUGB5OW-BZSmhUdL.js} +3 -3
  105. package/client/dist/assets/zh-HK-E62DVLB3-BJqejpiX.js +1 -0
  106. package/client/dist/assets/{zh-TW-RAJ6MFWO-ffJWgVxn.js → zh-TW-RAJ6MFWO-BBXtV-Uz.js} +2 -2
  107. package/client/dist/index.html +3 -3
  108. package/package.json +5 -2
  109. package/server/cli.js +1 -1
  110. package/server/database/auth.db +0 -0
  111. package/server/database/db.js +203 -1
  112. package/server/index.js +111 -18
  113. package/server/middleware/auth.js +11 -6
  114. package/server/projects.js +95 -202
  115. package/server/relay-client.js +47 -10
  116. package/server/routes/auth.js +6 -0
  117. package/server/routes/dashboard.js +52 -0
  118. package/server/routes/projects.js +38 -35
  119. package/server/routes/voice.js +198 -0
  120. package/server/routes/webhooks.js +166 -0
  121. package/server/routes/workflows.js +118 -0
  122. package/server/services/whisperService.js +84 -0
  123. package/server/services/workflowScheduler.js +186 -0
  124. package/client/dist/assets/AppContent-DTZ2FbvM.js +0 -513
  125. package/client/dist/assets/CanvasPanel-DlTW6Jh6.js +0 -6
  126. package/client/dist/assets/CanvasPanel-q4HEqNtV.css +0 -1
  127. package/client/dist/assets/LoginModal-CWoFm0au.js +0 -19
  128. package/client/dist/assets/az-AZ-76LH7QW2-CDdeucRZ.js +0 -1
  129. package/client/dist/assets/channel-O3ovC0x9.js +0 -1
  130. package/client/dist/assets/classDiagram-70f12bd4-D0lhAcxU.js +0 -2
  131. package/client/dist/assets/classDiagram-v2-f2320105-BuwUsF3F.js +0 -2
  132. package/client/dist/assets/clone-BG9u7vLi.js +0 -1
  133. package/client/dist/assets/flowDiagram-v2-96b9c2cf-Cd0Iascd.js +0 -1
  134. package/client/dist/assets/ganttDiagram-c361ad54-B8HJQqjt.js +0 -257
  135. package/client/dist/assets/index-B8wwD_Xo.css +0 -1
  136. package/client/dist/assets/kaa-6HZHGXH3-fwOleoQB.js +0 -1
  137. package/client/dist/assets/kk-KZ-P5N5QNE5-zpl7uvyF.js +0 -1
  138. package/client/dist/assets/my-MM-5M5IBNSE-kZQURVIi.js +0 -1
  139. package/client/dist/assets/sankeyDiagram-04a897e0-CsFqOQZN.js +0 -8
  140. package/client/dist/assets/si-LK-N5RQ5JYF-BBjcNYQh.js +0 -1
  141. package/client/dist/assets/stateDiagram-587899a1-BHoy9LtD.js +0 -1
  142. package/client/dist/assets/stateDiagram-v2-d93cdb3a-BvMUA6bS.js +0 -1
  143. package/client/dist/assets/subset-worker.chunk-BMx1eyv3.js +0 -1
  144. package/client/dist/assets/th-TH-HPSO5L25-CFNnJwSv.js +0 -2
  145. package/client/dist/assets/vendor-codemirror-langs-BH1ZcKHY.js +0 -20
  146. package/client/dist/assets/vendor-codemirror-rix45NST.js +0 -16
  147. package/client/dist/assets/zh-HK-E62DVLB3-zx9CvERq.js +0 -1
@@ -170,6 +170,63 @@ CREATE TABLE IF NOT EXISTS payments (
170
170
  CREATE INDEX IF NOT EXISTS idx_payments_user_id ON payments(user_id);
171
171
  CREATE INDEX IF NOT EXISTS idx_payments_order_id ON payments(razorpay_order_id);
172
172
  CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status);
173
+
174
+ CREATE TABLE IF NOT EXISTS webhooks (
175
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
176
+ user_id INTEGER NOT NULL,
177
+ name TEXT NOT NULL,
178
+ url TEXT NOT NULL,
179
+ method TEXT NOT NULL DEFAULT 'POST',
180
+ headers TEXT DEFAULT '{}',
181
+ description TEXT,
182
+ is_active BOOLEAN DEFAULT 1,
183
+ last_triggered DATETIME,
184
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
185
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
186
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
187
+ );
188
+
189
+ CREATE INDEX IF NOT EXISTS idx_webhooks_user_id ON webhooks(user_id);
190
+ CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(is_active);
191
+
192
+ CREATE TABLE IF NOT EXISTS workflows (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ user_id INTEGER NOT NULL,
195
+ name TEXT NOT NULL,
196
+ description TEXT,
197
+ steps TEXT NOT NULL DEFAULT '[]',
198
+ schedule TEXT DEFAULT NULL,
199
+ schedule_enabled BOOLEAN DEFAULT 0,
200
+ schedule_timezone TEXT DEFAULT 'UTC',
201
+ is_active BOOLEAN DEFAULT 1,
202
+ last_run DATETIME,
203
+ next_run DATETIME,
204
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
205
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
206
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
207
+ );
208
+
209
+ CREATE INDEX IF NOT EXISTS idx_workflows_user_id ON workflows(user_id);
210
+ CREATE INDEX IF NOT EXISTS idx_workflows_active ON workflows(is_active);
211
+
212
+ CREATE TABLE IF NOT EXISTS workflow_runs (
213
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
214
+ workflow_id INTEGER NOT NULL,
215
+ user_id INTEGER NOT NULL,
216
+ status TEXT NOT NULL DEFAULT 'pending',
217
+ steps_completed INTEGER DEFAULT 0,
218
+ total_steps INTEGER DEFAULT 0,
219
+ result TEXT,
220
+ error TEXT,
221
+ started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
222
+ completed_at DATETIME,
223
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON DELETE CASCADE,
224
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
225
+ );
226
+
227
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_workflow_id ON workflow_runs(workflow_id);
228
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_user_id ON workflow_runs(user_id);
229
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);
173
230
  `;
174
231
 
175
232
  // ─── Migrations ─────────────────────────────────────────────────────────────────
@@ -603,4 +660,149 @@ const relayTokensDb = {
603
660
  }
604
661
  };
605
662
 
606
- export { db, initializeDatabase, userDb, apiKeysDb, credentialsDb, relayTokensDb, githubTokensDb, subscriptionDb, paymentDb, PLAN_DURATIONS };
663
+ // ─── Webhook DB ──────────────────────────────────────────────────────────────
664
+
665
+ const webhookDb = {
666
+ getAll: async (userId) => {
667
+ const result = await db.execute({
668
+ sql: 'SELECT * FROM webhooks WHERE user_id = ? ORDER BY created_at DESC',
669
+ args: [userId]
670
+ });
671
+ return result.rows;
672
+ },
673
+
674
+ getById: async (id, userId) => {
675
+ const result = await db.execute({
676
+ sql: 'SELECT * FROM webhooks WHERE id = ? AND user_id = ?',
677
+ args: [id, userId]
678
+ });
679
+ return getRow(result);
680
+ },
681
+
682
+ create: async (userId, { name, url, method, headers, description }) => {
683
+ const result = await db.execute({
684
+ sql: 'INSERT INTO webhooks (user_id, name, url, method, headers, description) VALUES (?, ?, ?, ?, ?, ?)',
685
+ args: [userId, name, url, method || 'POST', headers || '{}', description || null]
686
+ });
687
+ return { id: Number(result.lastInsertRowid), name, url, method: method || 'POST' };
688
+ },
689
+
690
+ update: async (id, userId, { name, url, method, headers, description }) => {
691
+ const result = await db.execute({
692
+ sql: 'UPDATE webhooks SET name = ?, url = ?, method = ?, headers = ?, description = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?',
693
+ args: [name, url, method, headers || '{}', description || null, id, userId]
694
+ });
695
+ return result.rowsAffected > 0;
696
+ },
697
+
698
+ delete: async (id, userId) => {
699
+ await ensureForeignKeys();
700
+ const result = await db.execute({
701
+ sql: 'DELETE FROM webhooks WHERE id = ? AND user_id = ?',
702
+ args: [id, userId]
703
+ });
704
+ return result.rowsAffected > 0;
705
+ },
706
+
707
+ updateLastTriggered: async (id) => {
708
+ await db.execute({
709
+ sql: 'UPDATE webhooks SET last_triggered = CURRENT_TIMESTAMP WHERE id = ?',
710
+ args: [id]
711
+ });
712
+ }
713
+ };
714
+
715
+ // ─── Workflow DB ─────────────────────────────────────────────────────────────
716
+
717
+ const workflowDb = {
718
+ getAll: async (userId) => {
719
+ const result = await db.execute({
720
+ sql: 'SELECT * FROM workflows WHERE user_id = ? ORDER BY created_at DESC',
721
+ args: [userId]
722
+ });
723
+ return result.rows;
724
+ },
725
+
726
+ getById: async (id, userId) => {
727
+ const result = await db.execute({
728
+ sql: 'SELECT * FROM workflows WHERE id = ? AND user_id = ?',
729
+ args: [id, userId]
730
+ });
731
+ return getRow(result);
732
+ },
733
+
734
+ create: async (userId, { name, description, steps, schedule, schedule_enabled, schedule_timezone }) => {
735
+ const result = await db.execute({
736
+ sql: 'INSERT INTO workflows (user_id, name, description, steps, schedule, schedule_enabled, schedule_timezone) VALUES (?, ?, ?, ?, ?, ?, ?)',
737
+ args: [userId, name, description || null, JSON.stringify(steps || []), schedule || null, schedule_enabled ? 1 : 0, schedule_timezone || 'UTC']
738
+ });
739
+ return { id: Number(result.lastInsertRowid), name };
740
+ },
741
+
742
+ update: async (id, userId, { name, description, steps, schedule, schedule_enabled, schedule_timezone }) => {
743
+ const result = await db.execute({
744
+ sql: 'UPDATE workflows SET name = ?, description = ?, steps = ?, schedule = ?, schedule_enabled = ?, schedule_timezone = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?',
745
+ args: [name, description || null, JSON.stringify(steps || []), schedule || null, schedule_enabled ? 1 : 0, schedule_timezone || 'UTC', id, userId]
746
+ });
747
+ return result.rowsAffected > 0;
748
+ },
749
+
750
+ delete: async (id, userId) => {
751
+ await ensureForeignKeys();
752
+ const result = await db.execute({
753
+ sql: 'DELETE FROM workflows WHERE id = ? AND user_id = ?',
754
+ args: [id, userId]
755
+ });
756
+ return result.rowsAffected > 0;
757
+ },
758
+
759
+ updateLastRun: async (id) => {
760
+ await db.execute({
761
+ sql: 'UPDATE workflows SET last_run = CURRENT_TIMESTAMP WHERE id = ?',
762
+ args: [id]
763
+ });
764
+ },
765
+
766
+ getScheduled: async () => {
767
+ const result = await db.execute(
768
+ 'SELECT * FROM workflows WHERE schedule IS NOT NULL AND schedule_enabled = 1 AND is_active = 1'
769
+ );
770
+ return result.rows;
771
+ },
772
+
773
+ updateNextRun: async (id, nextRun) => {
774
+ await db.execute({
775
+ sql: 'UPDATE workflows SET next_run = ? WHERE id = ?',
776
+ args: [nextRun, id]
777
+ });
778
+ },
779
+
780
+ createRun: async (workflowId, userId, totalSteps) => {
781
+ const result = await db.execute({
782
+ sql: 'INSERT INTO workflow_runs (workflow_id, user_id, status, total_steps) VALUES (?, ?, ?, ?)',
783
+ args: [workflowId, userId, 'running', totalSteps]
784
+ });
785
+ return { id: Number(result.lastInsertRowid) };
786
+ },
787
+
788
+ updateRun: async (runId, { status, stepsCompleted, result: runResult, error }) => {
789
+ const sets = ['status = ?'];
790
+ const args = [status];
791
+ if (stepsCompleted !== undefined) { sets.push('steps_completed = ?'); args.push(stepsCompleted); }
792
+ if (runResult !== undefined) { sets.push('result = ?'); args.push(typeof runResult === 'string' ? runResult : JSON.stringify(runResult)); }
793
+ if (error !== undefined) { sets.push('error = ?'); args.push(error); }
794
+ if (status === 'completed' || status === 'failed') { sets.push('completed_at = CURRENT_TIMESTAMP'); }
795
+ args.push(runId);
796
+ await db.execute({ sql: `UPDATE workflow_runs SET ${sets.join(', ')} WHERE id = ?`, args });
797
+ },
798
+
799
+ getRuns: async (workflowId, userId) => {
800
+ const result = await db.execute({
801
+ sql: 'SELECT * FROM workflow_runs WHERE workflow_id = ? AND user_id = ? ORDER BY started_at DESC LIMIT 20',
802
+ args: [workflowId, userId]
803
+ });
804
+ return result.rows;
805
+ }
806
+ };
807
+
808
+ export { db, initializeDatabase, userDb, apiKeysDb, credentialsDb, relayTokensDb, githubTokensDb, subscriptionDb, paymentDb, webhookDb, workflowDb, PLAN_DURATIONS };
package/server/index.js CHANGED
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  // Load environment variables before other imports execute
3
3
  import './load-env.js';
4
+
5
+ // Strip Claude Code session markers so spawned CLI processes don't fail with
6
+ // "cannot be launched inside another Claude Code session" errors.
7
+ delete process.env.CLAUDECODE;
8
+ delete process.env.CLAUDE_CODE;
4
9
  import crypto from 'crypto';
5
10
  import fs from 'fs';
6
11
  import path from 'path';
12
+ import jwt from 'jsonwebtoken';
7
13
  import { fileURLToPath } from 'url';
8
14
  import { dirname } from 'path';
9
15
 
@@ -70,8 +76,13 @@ import cliAuthRoutes from './routes/cli-auth.js';
70
76
  import userRoutes from './routes/user.js';
71
77
  import codexRoutes from './routes/codex.js';
72
78
  import paymentRoutes from './routes/payments.js';
79
+ import webhookRoutes from './routes/webhooks.js';
80
+ import workflowRoutes from './routes/workflows.js';
81
+ import voiceRoutes from './routes/voice.js';
82
+ import dashboardRoutes from './routes/dashboard.js';
83
+ import { initScheduler } from './services/workflowScheduler.js';
73
84
  import { initializeDatabase, relayTokensDb, subscriptionDb, credentialsDb, userDb } from './database/db.js';
74
- import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
85
+ import { validateApiKey, authenticateToken, authenticateWebSocket, JWT_SECRET } from './middleware/auth.js';
75
86
  import { IS_PLATFORM, IS_LOCAL } from './constants/config.js';
76
87
  import { execSync } from 'child_process';
77
88
 
@@ -353,7 +364,13 @@ app.locals.wss = wss;
353
364
  // Security headers — protect against common web attacks
354
365
  app.use((req, res, next) => {
355
366
  res.setHeader('X-Content-Type-Options', 'nosniff');
356
- res.setHeader('X-Frame-Options', 'DENY');
367
+ // Allow framing from our own frontend domains (Vercel embeds Railway in an iframe)
368
+ const allowedFrameOrigins = (process.env.CORS_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean);
369
+ if (allowedFrameOrigins.length > 0) {
370
+ res.setHeader('Content-Security-Policy', `frame-ancestors 'self' ${allowedFrameOrigins.join(' ')}`);
371
+ } else {
372
+ res.setHeader('X-Frame-Options', 'SAMEORIGIN');
373
+ }
357
374
  res.setHeader('X-XSS-Protection', '1; mode=block');
358
375
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
359
376
  if (process.env.NODE_ENV === 'production') {
@@ -499,6 +516,10 @@ app.use('/api/codex', authenticateToken, codexRoutes);
499
516
 
500
517
  // Payment & Subscription Routes (protected)
501
518
  app.use('/api/payments', authenticateToken, paymentRoutes);
519
+ app.use('/api/webhooks', authenticateToken, webhookRoutes);
520
+ app.use('/api/workflows', authenticateToken, workflowRoutes);
521
+ app.use('/api/voice', authenticateToken, voiceRoutes);
522
+ app.use('/api/dashboard', authenticateToken, dashboardRoutes);
502
523
 
503
524
  // Agent API Routes (uses API key authentication)
504
525
  app.use('/api/agent', agentRoutes);
@@ -533,6 +554,10 @@ app.delete('/api/relay/tokens/:id', authenticateToken, async (req, res) => {
533
554
  });
534
555
 
535
556
  app.get('/api/relay/status', authenticateToken, (req, res) => {
557
+ // In local mode, always connected — SDK runs directly on this machine
558
+ if (IS_LOCAL) {
559
+ return res.json({ connected: true, local: true, connectedAt: Date.now() });
560
+ }
536
561
  const relay = relayConnections.get(Number(req.user.id));
537
562
  res.json({
538
563
  connected: !!(relay && relay.ws.readyState === 1),
@@ -1375,7 +1400,8 @@ async function handleRelayConnection(ws, token, request) {
1375
1400
  relayConnections.set(userId, {
1376
1401
  ws, user: tokenData, connectedAt: Date.now(), anthropicApiKey,
1377
1402
  version: relayVersion, machine: relayMachine, platform: relayPlatform, cwd: relayCwd,
1378
- agents: null // populated when client sends agent-capabilities
1403
+ agents: null, // populated when client sends agent-capabilities
1404
+ lastPong: Date.now(),
1379
1405
  });
1380
1406
 
1381
1407
  ws.send(JSON.stringify({
@@ -1452,6 +1478,8 @@ async function handleRelayConnection(ws, token, request) {
1452
1478
 
1453
1479
  // Heartbeat
1454
1480
  if (data.type === 'ping') {
1481
+ const relay = relayConnections.get(userId);
1482
+ if (relay) relay.lastPong = Date.now();
1455
1483
  ws.send(JSON.stringify({ type: 'pong' }));
1456
1484
  return;
1457
1485
  }
@@ -1460,7 +1488,29 @@ async function handleRelayConnection(ws, token, request) {
1460
1488
  }
1461
1489
  });
1462
1490
 
1491
+ // Server-side heartbeat: ping relay client every 45s, terminate if no pong in 90s
1492
+ const relayHeartbeat = setInterval(() => {
1493
+ const relay = relayConnections.get(userId);
1494
+ if (!relay || relay.ws !== ws) {
1495
+ clearInterval(relayHeartbeat);
1496
+ return;
1497
+ }
1498
+ // If no ping received from client in 90s, consider connection stale
1499
+ if (Date.now() - relay.lastPong > 90000) {
1500
+ clearInterval(relayHeartbeat);
1501
+ ws.terminate();
1502
+ return;
1503
+ }
1504
+ // Send server-side ping to keep connection alive through proxies
1505
+ try {
1506
+ if (ws.readyState === 1) {
1507
+ ws.send(JSON.stringify({ type: 'server-ping' }));
1508
+ }
1509
+ } catch { /* ignore */ }
1510
+ }, 45000);
1511
+
1463
1512
  ws.on('close', () => {
1513
+ clearInterval(relayHeartbeat);
1464
1514
  relayConnections.delete(userId);
1465
1515
  // Clean up pending requests for this user
1466
1516
  for (const [reqId, pending] of pendingRelayRequests) {
@@ -1470,7 +1520,6 @@ async function handleRelayConnection(ws, token, request) {
1470
1520
  pendingRelayRequests.delete(reqId);
1471
1521
  }
1472
1522
  }
1473
- // relay disconnected
1474
1523
 
1475
1524
  // Broadcast relay status
1476
1525
  for (const client of connectedClients) {
@@ -1482,8 +1531,8 @@ async function handleRelayConnection(ws, token, request) {
1482
1531
  }
1483
1532
  });
1484
1533
 
1485
- ws.on('error', (err) => {
1486
- // relay error
1534
+ ws.on('error', () => {
1535
+ clearInterval(relayHeartbeat);
1487
1536
  });
1488
1537
  }
1489
1538
 
@@ -1602,10 +1651,14 @@ async function routeViaRelay(userId, action, data, writer, eventMap = {}) {
1602
1651
  viaRelay: true
1603
1652
  });
1604
1653
  } catch (error) {
1654
+ const isRelayLost = error.message?.includes('Relay disconnected') || error.message?.includes('No relay connection') || error.message?.includes('Relay request timed out');
1605
1655
  writer.send({
1606
1656
  type: errorType,
1607
- error: error.message,
1608
- sessionId
1657
+ error: isRelayLost
1658
+ ? 'Your machine disconnected. Please reconnect with "uc connect" and try again.'
1659
+ : error.message,
1660
+ sessionId,
1661
+ relayDisconnected: isRelayLost
1609
1662
  });
1610
1663
  }
1611
1664
  }
@@ -1635,6 +1688,7 @@ function handleShellConnection(ws) {
1635
1688
  const provider = data.provider || 'claude';
1636
1689
  const initialCommand = data.initialCommand;
1637
1690
  const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell';
1691
+ const shellType = data.shellType || null;
1638
1692
  urlDetectionBuffer = '';
1639
1693
  announcedAuthUrls.clear();
1640
1694
 
@@ -1716,11 +1770,17 @@ function handleShellConnection(ws) {
1716
1770
  // Prepare the shell command adapted to the platform and provider
1717
1771
  let shellCommand;
1718
1772
  if (isPlainShell) {
1719
- // Plain shell mode - just run the initial command in the project directory
1720
- if (os.platform() === 'win32') {
1721
- shellCommand = `Set-Location -Path "${projectPath}"; ${initialCommand}`;
1773
+ // Plain shell mode - run initial command or open interactive shell
1774
+ const usesPowerShell = !shellType || shellType === 'powershell';
1775
+ if (initialCommand) {
1776
+ if (usesPowerShell) {
1777
+ shellCommand = `Set-Location -Path "${projectPath}"; ${initialCommand}`;
1778
+ } else {
1779
+ shellCommand = `cd "${projectPath}" && ${initialCommand}`;
1780
+ }
1722
1781
  } else {
1723
- shellCommand = `cd "${projectPath}" && ${initialCommand}`;
1782
+ // Interactive shell tab spawn shell directly (no command wrapper)
1783
+ shellCommand = null;
1724
1784
  }
1725
1785
  } else if (provider === 'cursor') {
1726
1786
  // Use cursor-agent command
@@ -1758,9 +1818,19 @@ function handleShellConnection(ws) {
1758
1818
 
1759
1819
  console.log('🔧 Executing shell command:', shellCommand);
1760
1820
 
1761
- // Use appropriate shell based on platform
1762
- const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
1763
- const shellArgs = os.platform() === 'win32' ? ['-Command', shellCommand] : ['-c', shellCommand];
1821
+ // Use appropriate shell based on platform and requested shellType
1822
+ const shellMap = {
1823
+ 'powershell': { cmd: 'powershell.exe', args: ['-Command'] },
1824
+ 'cmd': { cmd: 'cmd.exe', args: ['/c'] },
1825
+ 'bash': { cmd: 'bash', args: ['-c'] },
1826
+ };
1827
+ const defaultShell = os.platform() === 'win32'
1828
+ ? { cmd: 'powershell.exe', args: ['-Command'] }
1829
+ : { cmd: 'bash', args: ['-c'] };
1830
+ const selectedShell = (shellType && shellMap[shellType]) || defaultShell;
1831
+ const shell = selectedShell.cmd;
1832
+ // If shellCommand is null, spawn an interactive shell with no args
1833
+ const shellArgs = shellCommand ? [...selectedShell.args, shellCommand] : [];
1764
1834
 
1765
1835
  // Use terminal dimensions from client if provided, otherwise use defaults
1766
1836
  const termCols = data.cols || 80;
@@ -1771,7 +1841,7 @@ function handleShellConnection(ws) {
1771
1841
  name: 'xterm-256color',
1772
1842
  cols: termCols,
1773
1843
  rows: termRows,
1774
- cwd: os.homedir(),
1844
+ cwd: shellCommand ? os.homedir() : projectPath,
1775
1845
  env: {
1776
1846
  ...process.env,
1777
1847
  TERM: 'xterm-256color',
@@ -2360,9 +2430,29 @@ app.get('*', (req, res) => {
2360
2430
  return res.status(404).send('Not found');
2361
2431
  }
2362
2432
 
2433
+ // If a JWT token is in the query param and no session cookie exists,
2434
+ // set the cookie now so the client-side AuthContext can authenticate on subsequent API calls.
2435
+ if (req.query?.token && !req.cookies?.session) {
2436
+ try {
2437
+ const decoded = jwt.verify(req.query.token, JWT_SECRET);
2438
+ if (decoded?.userId) {
2439
+ const isSecure = process.env.NODE_ENV === 'production' || !!process.env.RAILWAY_ENVIRONMENT;
2440
+ res.cookie('session', req.query.token, {
2441
+ httpOnly: true,
2442
+ secure: isSecure,
2443
+ sameSite: isSecure ? 'none' : 'strict',
2444
+ maxAge: 30 * 24 * 60 * 60 * 1000,
2445
+ path: '/',
2446
+ });
2447
+ }
2448
+ } catch (e) {
2449
+ // Invalid token — just serve the page without setting cookie
2450
+ }
2451
+ }
2452
+
2363
2453
  // Only serve index.html for HTML routes, not for static assets
2364
2454
  // Static assets should already be handled by express.static middleware above
2365
- const indexPath = path.join(__dirname, '../dist/index.html');
2455
+ const indexPath = path.join(__dirname, '../client/dist/index.html');
2366
2456
 
2367
2457
  // Check if dist/index.html exists (production build available)
2368
2458
  if (fs.existsSync(indexPath)) {
@@ -2482,7 +2572,7 @@ async function startServer() {
2482
2572
  }
2483
2573
 
2484
2574
  // Check if running in production mode (dist folder exists OR NODE_ENV/RAILWAY set)
2485
- const distIndexPath = path.join(__dirname, '../dist/index.html');
2575
+ const distIndexPath = path.join(__dirname, '../client/dist/index.html');
2486
2576
  const isProduction = fs.existsSync(distIndexPath) || process.env.NODE_ENV === 'production' || !!process.env.RAILWAY_ENVIRONMENT;
2487
2577
 
2488
2578
  // Log Claude implementation mode
@@ -2505,6 +2595,9 @@ async function startServer() {
2505
2595
  console.log(`${c.info('[INFO]')} MCP Server: ${c.bright('http://0.0.0.0:' + PORT + '/mcp')}`);
2506
2596
  console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
2507
2597
  console.log(`${c.tip('[TIP]')} Run "uc status" for full configuration details`);
2598
+
2599
+ // Start workflow cron scheduler
2600
+ initScheduler().catch(err => console.warn('[Scheduler]', err.message));
2508
2601
  console.log('');
2509
2602
 
2510
2603
  // Start watching the projects folder for changes (skip on Vercel)
@@ -33,10 +33,8 @@ const extractToken = (req) => {
33
33
  const authHeader = req.headers['authorization'];
34
34
  if (authHeader?.startsWith('Bearer ')) return authHeader.slice(7);
35
35
 
36
- // 3. Query param — ONLY for SSE EventSource (which cannot set custom headers)
37
- // Restricted to GET requests with Accept: text/event-stream to minimize exposure
38
- if (req.query?.token && req.method === 'GET' &&
39
- (req.headers.accept || '').includes('text/event-stream')) {
36
+ // 3. Query param — for GET requests (SSE EventSource + iframe embedding)
37
+ if (req.query?.token && req.method === 'GET') {
40
38
  return req.query.token;
41
39
  }
42
40
 
@@ -68,6 +66,10 @@ const authenticateToken = async (req, res, next) => {
68
66
  const user = await userDb.getUserById(decoded.userId);
69
67
  if (!user) return res.status(401).json({ error: 'Invalid token. User not found.' });
70
68
  req.user = user;
69
+ // If token came from query param, set session cookie for subsequent requests (iframe auto-auth)
70
+ if (req.query?.token && !req.cookies?.session) {
71
+ res.cookie('session', token, COOKIE_OPTIONS);
72
+ }
71
73
  next();
72
74
  } catch (error) {
73
75
  return res.status(403).json({ error: 'Invalid or expired token' });
@@ -85,10 +87,13 @@ const generateToken = (user) => {
85
87
 
86
88
  // Cookie config for httpOnly session
87
89
  // Works for both self-hosted (same origin) and split deploy (Vercel proxy → Railway)
90
+ const isSecureEnv = process.env.NODE_ENV === 'production' || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT;
88
91
  const COOKIE_OPTIONS = {
89
92
  httpOnly: true,
90
- secure: process.env.NODE_ENV === 'production' || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT,
91
- sameSite: 'strict',
93
+ secure: isSecureEnv,
94
+ // 'none' required for cross-origin iframe embedding (Vercel frontend → Railway backend)
95
+ // 'strict' used in local/dev mode where everything is same-origin
96
+ sameSite: isSecureEnv ? 'none' : 'strict',
92
97
  maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
93
98
  path: '/',
94
99
  };