fuse-io 0.1.30__tar.gz → 0.1.49__tar.gz

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 (289) hide show
  1. {fuse_io-0.1.30 → fuse_io-0.1.49}/MANIFEST.in +1 -0
  2. {fuse_io-0.1.30/fuse_io.egg-info → fuse_io-0.1.49}/PKG-INFO +1 -5
  3. fuse_io-0.1.49/fuse/ai/router.py +43 -0
  4. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/ai/service.py +78 -26
  5. fuse_io-0.1.49/fuse/alembic/versions/7a0fd1dc4902_remove_superuser.py +33 -0
  6. fuse_io-0.1.49/fuse/alembic/versions/7edc63646d26_add_credentials_table.py +41 -0
  7. fuse_io-0.1.49/fuse/alembic/versions/debec76c1cf8_initial_migration.py +114 -0
  8. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/api.py +0 -2
  9. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/crud_user.py +0 -2
  10. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/dependencies.py +0 -6
  11. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/router.py +1 -25
  12. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/schemas.py +0 -1
  13. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/service.py +0 -3
  14. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/cli.py +25 -4
  15. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/config.py +7 -38
  16. fuse_io-0.1.49/fuse/credentials/models.py +26 -0
  17. fuse_io-0.1.49/fuse/credentials/router.py +382 -0
  18. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/credentials/service.py +92 -48
  19. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/database.py +3 -4
  20. fuse_io-0.1.49/fuse/initial_fuse.db +0 -0
  21. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/logger.py +0 -1
  22. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/main.py +0 -7
  23. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/models.py +2 -1
  24. fuse_io-0.1.49/fuse/users/router.py +129 -0
  25. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/pagination.py +2 -2
  26. fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/agent.py +125 -0
  27. fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/chat_model.py +40 -0
  28. fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/memory.py +33 -0
  29. fuse_io-0.1.49/fuse/workflows/engine/nodes/ai/tool.py +33 -0
  30. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/base.py +1 -0
  31. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/email.py +2 -1
  32. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/form.py +2 -1
  33. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/manual.py +2 -1
  34. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/rss.py +2 -1
  35. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/schedule.py +2 -1
  36. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/webhook.py +2 -1
  37. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/triggers/whatsapp.py +2 -1
  38. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/schemas.py +19 -3
  39. {fuse_io-0.1.30 → fuse_io-0.1.49/fuse_io.egg-info}/PKG-INFO +1 -5
  40. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/SOURCES.txt +8 -16
  41. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/requires.txt +0 -4
  42. {fuse_io-0.1.30 → fuse_io-0.1.49}/pyproject.toml +1 -5
  43. {fuse_io-0.1.30 → fuse_io-0.1.49}/uv.lock +1 -160
  44. fuse_io-0.1.30/fuse/ai/router.py +0 -35
  45. fuse_io-0.1.30/fuse/alembic/versions/1439949b74ad_add_edge_handles.py +0 -31
  46. fuse_io-0.1.30/fuse/alembic/versions/1a31ce608336_add_cascade_delete_relationships.py +0 -37
  47. fuse_io-0.1.30/fuse/alembic/versions/2c7ca0ca6779_add_hashed_password_field_to_user_model.py +0 -29
  48. fuse_io-0.1.30/fuse/alembic/versions/82e10e9b2e3f_add_spec_column_to_workflownode_and_.py +0 -34
  49. fuse_io-0.1.30/fuse/alembic/versions/9426be80e78e_add_workflows.py +0 -66
  50. fuse_io-0.1.30/fuse/alembic/versions/9c0a54914c78_add_max_length_for_string_varchar_.py +0 -65
  51. fuse_io-0.1.30/fuse/alembic/versions/ceedbe88ef0d_add_v2_config_columns_to_workflow.py +0 -33
  52. fuse_io-0.1.30/fuse/alembic/versions/d98dd8ec85a3_edit_replace_id_integers_in_all_models_.py +0 -99
  53. fuse_io-0.1.30/fuse/alembic/versions/dd21742ef7b0_add_execution_models.py +0 -54
  54. fuse_io-0.1.30/fuse/alembic/versions/e2412789c190_initialize_models.py +0 -54
  55. fuse_io-0.1.30/fuse/credentials/router.py +0 -199
  56. fuse_io-0.1.30/fuse/items/__init__.py +0 -9
  57. fuse_io-0.1.30/fuse/items/crud_item.py +0 -44
  58. fuse_io-0.1.30/fuse/items/models.py +0 -19
  59. fuse_io-0.1.30/fuse/items/router.py +0 -97
  60. fuse_io-0.1.30/fuse/items/schemas.py +0 -34
  61. fuse_io-0.1.30/fuse/items/service.py +0 -57
  62. fuse_io-0.1.30/fuse/users/router.py +0 -234
  63. fuse_io-0.1.30/fuse/workflows/engine/nodes/ai/agent.py +0 -100
  64. {fuse_io-0.1.30 → fuse_io-0.1.49}/.python-version +0 -0
  65. {fuse_io-0.1.30 → fuse_io-0.1.49}/LICENSE +0 -0
  66. {fuse_io-0.1.30 → fuse_io-0.1.49}/README.md +0 -0
  67. {fuse_io-0.1.30 → fuse_io-0.1.49}/docs/pypi_publishing_guide.md +0 -0
  68. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/__init__.py +0 -0
  69. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/ai/__init__.py +0 -0
  70. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/README +0 -0
  71. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/env.py +0 -0
  72. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/script.py.mako +0 -0
  73. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic/versions/.keep +0 -0
  74. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/alembic.ini +0 -0
  75. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/__init__.py +0 -0
  76. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/models.py +0 -0
  77. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/auth/utils.py +0 -0
  78. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/backend_pre_start.py +0 -0
  79. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/base.py +0 -0
  80. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/credentials/__init__.py +0 -0
  81. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/initial_data.py +0 -0
  82. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/404/index.html +0 -0
  83. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/404.html +0 -0
  84. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next.__PAGE__.txt +0 -0
  85. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._full.txt +0 -0
  86. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._head.txt +0 -0
  87. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._index.txt +0 -0
  88. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/__next._tree.txt +0 -0
  89. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/bAsbGnh27hobCWq6gYKAq/_buildManifest.js +0 -0
  90. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/bAsbGnh27hobCWq6gYKAq/_ssgManifest.js +0 -0
  91. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/162-d9110b81661c7d7d.js +0 -0
  92. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/232.97b298320d3351e9.js +0 -0
  93. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/24-4ee5417b9b9b92a9.js +0 -0
  94. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/275-f12d66089f519b01.js +0 -0
  95. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/324-771510774da7c693.js +0 -0
  96. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/361-1ce2845fa2b2c4a4.js +0 -0
  97. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/393-d1b3ea65900fdc5e.js +0 -0
  98. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/4-bdc965d7dcca4cab.js +0 -0
  99. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/4bd1b696-01b4a2ffa8bac205.js +0 -0
  100. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/501-397a9f42b1c4da0e.js +0 -0
  101. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/547-1833756cc09c3c28.js +0 -0
  102. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/794-803d64bb1ca09460.js +0 -0
  103. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/814-18bcbd34c09817f1.js +0 -0
  104. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/821-04976ebb62352e45.js +0 -0
  105. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/93-2937d48fc52107ef.js +0 -0
  106. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/ai-create-example/page-0a2e0f0b4460de7c.js +0 -0
  107. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/dashboard/page-7e9db37c6737695c.js +0 -0
  108. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/layout-5c636818aa3b7ed1.js +0 -0
  109. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/settings/page-00bc4735cc93b9be.js +0 -0
  110. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/(main)/workflows/page-7c3c63a11ae33eed.js +0 -0
  111. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/_global-error/page-116e68079fa76e6f.js +0 -0
  112. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/_not-found/page-bae0d4902c5585d3.js +0 -0
  113. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/layout-116e68079fa76e6f.js +0 -0
  114. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/login/page-546ffaa564d327e9.js +0 -0
  115. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/auth/register/page-2748b1af5b1b642a.js +0 -0
  116. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/credentials-test/page-1d2c37deaa189b43.js +0 -0
  117. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/error-033f2fd59b73a2d7.js +0 -0
  118. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/global-error-dcd149dc42c5186b.js +0 -0
  119. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/layout-d0d5a6a85d213394.js +0 -0
  120. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/loading-116e68079fa76e6f.js +0 -0
  121. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/oauth/callback/page-eecedf564f689d93.js +0 -0
  122. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/page-606905d313079604.js +0 -0
  123. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/error-66ff5093f583ae94.js +0 -0
  124. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/loading-116e68079fa76e6f.js +0 -0
  125. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/[id]/page-5459cee4cb66f5bc.js +0 -0
  126. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/error-61dbcd88cc0eff5c.js +0 -0
  127. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/app/workflows/loading-116e68079fa76e6f.js +0 -0
  128. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/c37d3baf-0fedbf5b6ffcd550.js +0 -0
  129. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/framework-3311683cffde0ebf.js +0 -0
  130. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/main-app-cf50eb2a7e26453d.js +0 -0
  131. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/main-d74776772a65895d.js +0 -0
  132. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  133. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/chunks/webpack-b28a02ce1f174559.js +0 -0
  134. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/css/59d0dfc36eeea694.css +0 -0
  135. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/css/d1e1fa7731bfdccb.css +0 -0
  136. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/636a5ac981f94f8b-s.p.woff2 +0 -0
  137. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/6fe53d21e6e7ebd8-s.woff2 +0 -0
  138. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/8ebc6e9dde468c4a-s.woff2 +0 -0
  139. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_next/static/media/9e7b0a821b9dfcb4-s.woff2 +0 -0
  140. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._full.txt +0 -0
  141. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._head.txt +0 -0
  142. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._index.txt +0 -0
  143. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._not-found.__PAGE__.txt +0 -0
  144. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._not-found.txt +0 -0
  145. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/__next._tree.txt +0 -0
  146. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/index.html +0 -0
  147. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/_not-found/index.txt +0 -0
  148. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.ai-create-example.__PAGE__.txt +0 -0
  149. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.ai-create-example.txt +0 -0
  150. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next.!KG1haW4p.txt +0 -0
  151. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._full.txt +0 -0
  152. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._head.txt +0 -0
  153. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._index.txt +0 -0
  154. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/__next._tree.txt +0 -0
  155. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/index.html +0 -0
  156. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/ai-create-example/index.txt +0 -0
  157. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._full.txt +0 -0
  158. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._head.txt +0 -0
  159. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._index.txt +0 -0
  160. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next._tree.txt +0 -0
  161. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.login.__PAGE__.txt +0 -0
  162. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.login.txt +0 -0
  163. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/__next.auth.txt +0 -0
  164. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/index.html +0 -0
  165. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/login/index.txt +0 -0
  166. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._full.txt +0 -0
  167. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._head.txt +0 -0
  168. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._index.txt +0 -0
  169. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next._tree.txt +0 -0
  170. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.register.__PAGE__.txt +0 -0
  171. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.register.txt +0 -0
  172. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/__next.auth.txt +0 -0
  173. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/index.html +0 -0
  174. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/auth/register/index.txt +0 -0
  175. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._full.txt +0 -0
  176. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._head.txt +0 -0
  177. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._index.txt +0 -0
  178. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next._tree.txt +0 -0
  179. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next.credentials-test.__PAGE__.txt +0 -0
  180. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/__next.credentials-test.txt +0 -0
  181. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/index.html +0 -0
  182. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/credentials-test/index.txt +0 -0
  183. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.dashboard.__PAGE__.txt +0 -0
  184. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.dashboard.txt +0 -0
  185. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next.!KG1haW4p.txt +0 -0
  186. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._full.txt +0 -0
  187. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._head.txt +0 -0
  188. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._index.txt +0 -0
  189. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/__next._tree.txt +0 -0
  190. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/index.html +0 -0
  191. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/dashboard/index.txt +0 -0
  192. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/index.html +0 -0
  193. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/index.txt +0 -0
  194. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._full.txt +0 -0
  195. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._head.txt +0 -0
  196. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._index.txt +0 -0
  197. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next._tree.txt +0 -0
  198. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.callback.__PAGE__.txt +0 -0
  199. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.callback.txt +0 -0
  200. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/__next.oauth.txt +0 -0
  201. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/index.html +0 -0
  202. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/oauth/callback/index.txt +0 -0
  203. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.settings.__PAGE__.txt +0 -0
  204. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.settings.txt +0 -0
  205. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next.!KG1haW4p.txt +0 -0
  206. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._full.txt +0 -0
  207. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._head.txt +0 -0
  208. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._index.txt +0 -0
  209. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/__next._tree.txt +0 -0
  210. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/index.html +0 -0
  211. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/settings/index.txt +0 -0
  212. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.txt +0 -0
  213. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.workflows.__PAGE__.txt +0 -0
  214. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next.!KG1haW4p.workflows.txt +0 -0
  215. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._full.txt +0 -0
  216. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._head.txt +0 -0
  217. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._index.txt +0 -0
  218. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/__next._tree.txt +0 -0
  219. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/index.html +0 -0
  220. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/index.txt +0 -0
  221. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._full.txt +0 -0
  222. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._head.txt +0 -0
  223. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._index.txt +0 -0
  224. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next._tree.txt +0 -0
  225. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.$d$id.__PAGE__.txt +0 -0
  226. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.$d$id.txt +0 -0
  227. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/__next.workflows.txt +0 -0
  228. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/index.html +0 -0
  229. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/static/workflows/new/index.txt +0 -0
  230. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/users/__init__.py +0 -0
  231. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/cache.py +0 -0
  232. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/circuit_breaker.py +0 -0
  233. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/code_sanitizer.py +0 -0
  234. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/feature_flags.py +0 -0
  235. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/health.py +0 -0
  236. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/rate_limit.py +0 -0
  237. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/redis_client.py +0 -0
  238. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/request_id.py +0 -0
  239. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/utils/security.py +0 -0
  240. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/worker.py +0 -0
  241. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/__init__.py +0 -0
  242. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/code_execution.py +0 -0
  243. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/crud_workflow.py +0 -0
  244. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/__init__.py +0 -0
  245. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/constants.py +0 -0
  246. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/core.py +0 -0
  247. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/error_handler.py +0 -0
  248. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/errors.py +0 -0
  249. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/executor.py +0 -0
  250. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/graph.py +0 -0
  251. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/__init__.py +0 -0
  252. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/code.py +0 -0
  253. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/data.py +0 -0
  254. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/discord.py +0 -0
  255. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/email.py +0 -0
  256. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/google_sheets.py +0 -0
  257. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/http_request.py +0 -0
  258. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/slack.py +0 -0
  259. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/utility.py +0 -0
  260. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/actions/whatsapp.py +0 -0
  261. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/ai/__init__.py +0 -0
  262. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/ai/llm.py +0 -0
  263. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/__init__.py +0 -0
  264. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/delay.py +0 -0
  265. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/if_node.py +0 -0
  266. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/loop.py +0 -0
  267. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/merge.py +0 -0
  268. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/parallel.py +0 -0
  269. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/pause.py +0 -0
  270. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/logic/switch.py +0 -0
  271. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/nodes/registry.py +0 -0
  272. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/periodic_scheduler.py +0 -0
  273. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/code.py +0 -0
  274. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/http.py +0 -0
  275. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/runtime/internal.py +0 -0
  276. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/scheduler.py +0 -0
  277. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/engine/state.py +0 -0
  278. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/logger.py +0 -0
  279. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/models.py +0 -0
  280. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/router.py +0 -0
  281. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/service.py +0 -0
  282. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/types.py +0 -0
  283. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse/workflows/utils/templating.py +0 -0
  284. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/dependency_links.txt +0 -0
  285. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/entry_points.txt +0 -0
  286. {fuse_io-0.1.30 → fuse_io-0.1.49}/fuse_io.egg-info/top_level.txt +0 -0
  287. {fuse_io-0.1.30 → fuse_io-0.1.49}/scripts/format.sh +0 -0
  288. {fuse_io-0.1.30 → fuse_io-0.1.49}/scripts/lint.sh +0 -0
  289. {fuse_io-0.1.30 → fuse_io-0.1.49}/setup.cfg +0 -0
@@ -18,3 +18,4 @@ global-exclude build/*
18
18
  global-exclude *.egg-info
19
19
  global-exclude fuse.db
20
20
  global-exclude .env
21
+ global-exclude credentials_storage.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuse-io
3
- Version: 0.1.30
3
+ Version: 0.1.49
4
4
  Summary: Fuse - Workflow automation. A powerful local-first automation platform with visual builder, AI integration, and extensive node library.
5
5
  Author-email: Bibek Timilsina <bibektimilsina@example.com>
6
6
  License: MIT
@@ -42,16 +42,12 @@ Requires-Dist: jinja2>=3.1.6
42
42
  Requires-Dist: jsonschema>=4.25.1
43
43
  Requires-Dist: openai>=2.8.1
44
44
  Requires-Dist: passlib>=1.7.4
45
- Requires-Dist: psycopg>=3.3.2
46
- Requires-Dist: psycopg-binary>=3.3.2
47
- Requires-Dist: psycopg2-binary>=2.9.11
48
45
  Requires-Dist: pydantic>=2.11.7
49
46
  Requires-Dist: pydantic-settings>=2.10.1
50
47
  Requires-Dist: pyjwt>=2.10.1
51
48
  Requires-Dist: python-multipart>=0.0.20
52
49
  Requires-Dist: redis>=7.1.0
53
50
  Requires-Dist: rich>=14.2.0
54
- Requires-Dist: sentry-sdk>=2.37.0
55
51
  Requires-Dist: sqlmodel>=0.0.24
56
52
  Requires-Dist: tenacity>=9.1.2
57
53
  Requires-Dist: uvicorn[standard]>=0.35.0
@@ -0,0 +1,43 @@
1
+ from typing import Any, Optional
2
+ from fastapi import APIRouter, HTTPException
3
+ import logging
4
+
5
+ from fuse.auth.dependencies import CurrentUser
6
+ from fuse.workflows.schemas import AIWorkflowRequest, AIWorkflowResponse
7
+ from fuse.ai.service import ai_service
8
+ from fuse.credentials.service import get_full_credential_by_id
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ router = APIRouter()
13
+
14
+
15
+ @router.post("/generate", response_model=AIWorkflowResponse)
16
+ async def generate_workflow_with_ai(
17
+ request: AIWorkflowRequest,
18
+ current_user: CurrentUser,
19
+ ) -> Any:
20
+ """
21
+ Generate workflow with AI using either global keys or user-provided credentials.
22
+ """
23
+ try:
24
+ credential_data = None
25
+ if request.credential_id:
26
+ credential_data = get_full_credential_by_id(str(request.credential_id))
27
+ if not credential_data:
28
+ raise HTTPException(status_code=404, detail="Credential not found")
29
+
30
+ result = await ai_service.generate_workflow_from_prompt(
31
+ prompt=request.prompt,
32
+ model=request.model,
33
+ current_nodes=request.current_nodes,
34
+ current_edges=request.current_edges,
35
+ credential_data=credential_data,
36
+ )
37
+
38
+ return result
39
+ except HTTPException:
40
+ raise
41
+ except Exception as e:
42
+ logger.error(f"AI generation failed: {e}")
43
+ raise HTTPException(status_code=500, detail=f"AI generation failed: {str(e)}")
@@ -56,32 +56,44 @@ class AIWorkflowService:
56
56
  model: str = "openrouter",
57
57
  current_nodes: Optional[List[dict]] = None,
58
58
  current_edges: Optional[List[dict]] = None,
59
+ credential_data: Optional[Dict[str, Any]] = None,
59
60
  ) -> Dict[str, Any]:
60
61
  """Generate workflow nodes and edges from natural language prompt"""
61
62
  system_prompt = self._get_system_prompt(current_nodes, current_edges)
62
63
  user_prompt = f"USER REQUEST: {prompt}\n\nPlease generate a workflow JSON based on this request."
63
64
 
65
+ # Override model provider if credential specifies one
66
+ provider = model
67
+ if credential_data:
68
+ provider = credential_data.get("provider", model)
69
+
64
70
  try:
65
- if model == "gemini":
71
+ if provider == "gemini":
66
72
  response_text = await self._generate_with_gemini(
67
- f"{system_prompt}\n\n{user_prompt}"
73
+ f"{system_prompt}\n\n{user_prompt}", credential_data=credential_data
68
74
  )
69
- elif model == "openai":
75
+ elif provider == "openai":
70
76
  response_text = await self._generate_with_openai(
71
- f"{system_prompt}\n\n{user_prompt}"
77
+ f"{system_prompt}\n\n{user_prompt}",
78
+ credential_data=credential_data,
79
+ model_name=model if model != "openai" else "gpt-4",
72
80
  )
73
- elif model == "anthropic":
81
+ elif provider == "anthropic":
74
82
  response_text = await self._generate_with_anthropic(
75
- f"{system_prompt}\n\n{user_prompt}"
83
+ f"{system_prompt}\n\n{user_prompt}", credential_data=credential_data
76
84
  )
77
- elif model == "openrouter":
85
+ elif provider == "openrouter":
78
86
  response_text = await self._generate_with_openrouter(
79
- f"{system_prompt}\n\n{user_prompt}"
87
+ f"{system_prompt}\n\n{user_prompt}",
88
+ model=model if model != "openrouter" else "deepseek/deepseek-r1",
89
+ credential_data=credential_data,
80
90
  )
81
91
  else:
82
92
  # Default to openrouter if model is unknown
83
93
  response_text = await self._generate_with_openrouter(
84
- f"{system_prompt}\n\n{user_prompt}", model=model
94
+ f"{system_prompt}\n\n{user_prompt}",
95
+ model=model,
96
+ credential_data=credential_data,
85
97
  )
86
98
 
87
99
  return self._parse_ai_response(response_text, current_nodes)
@@ -229,27 +241,47 @@ Do NOT explain anything
229
241
  Generate one complete workflow JSON that fully satisfies the user request and strictly follows this schema.
230
242
  """
231
243
 
232
- async def _generate_with_gemini(self, prompt: str) -> str:
244
+ async def _generate_with_gemini(
245
+ self, prompt: str, credential_data: Optional[Dict] = None
246
+ ) -> str:
233
247
  """Generate using Google Gemini"""
234
- if not self.gemini_client:
248
+ client = self.gemini_client
249
+ if credential_data:
250
+ api_key = credential_data.get("data", {}).get("api_key")
251
+ if api_key:
252
+ client = genai.Client(api_key=api_key)
253
+
254
+ if not client:
235
255
  raise ValueError(
236
- "Gemini API key not configured. Please set GOOGLE_AI_API_KEY environment variable."
256
+ "Gemini API key not configured. Please set GOOGLE_AI_API_KEY environment variable or provide a credential."
237
257
  )
238
258
  async with CircuitBreakers.google():
239
- response = self.gemini_client.models.generate_content(
259
+ response = client.models.generate_content(
240
260
  model="gemini-2.0-flash", contents=prompt
241
261
  )
242
262
  return response.text
243
263
 
244
- async def _generate_with_openai(self, prompt: str) -> str:
264
+ async def _generate_with_openai(
265
+ self,
266
+ prompt: str,
267
+ credential_data: Optional[Dict] = None,
268
+ model_name: str = "gpt-4",
269
+ ) -> str:
245
270
  """Generate using OpenAI GPT"""
246
- if not self.openai_client:
271
+ client = self.openai_client
272
+ if credential_data:
273
+ api_key = credential_data.get("data", {}).get("api_key")
274
+ base_url = credential_data.get("data", {}).get("base_url")
275
+ if api_key:
276
+ client = OpenAI(api_key=api_key, base_url=base_url)
277
+
278
+ if not client:
247
279
  raise ValueError(
248
- "OpenAI API key not configured. Please set OPENAI_API_KEY environment variable."
280
+ "OpenAI API key not configured. Please set OPENAI_API_KEY environment variable or provide a credential."
249
281
  )
250
282
  async with CircuitBreakers.openai():
251
- response = self.openai_client.chat.completions.create(
252
- model="gpt-4",
283
+ response = client.chat.completions.create(
284
+ model=model_name,
253
285
  messages=[
254
286
  {
255
287
  "role": "system",
@@ -261,14 +293,22 @@ Generate one complete workflow JSON that fully satisfies the user request and st
261
293
  )
262
294
  return response.choices[0].message.content
263
295
 
264
- async def _generate_with_anthropic(self, prompt: str) -> str:
296
+ async def _generate_with_anthropic(
297
+ self, prompt: str, credential_data: Optional[Dict] = None
298
+ ) -> str:
265
299
  """Generate using Anthropic Claude"""
266
- if not self.anthropic_client:
300
+ client = self.anthropic_client
301
+ if credential_data:
302
+ api_key = credential_data.get("data", {}).get("api_key")
303
+ if api_key:
304
+ client = Anthropic(api_key=api_key)
305
+
306
+ if not client:
267
307
  raise ValueError(
268
- "Anthropic API key not configured. Please set ANTHROPIC_API_KEY environment variable."
308
+ "Anthropic API key not configured. Please set ANTHROPIC_API_KEY environment variable or provide a credential."
269
309
  )
270
310
  async with CircuitBreakers.anthropic():
271
- response = self.anthropic_client.messages.create(
311
+ response = client.messages.create(
272
312
  model="claude-3-sonnet-20240229",
273
313
  max_tokens=2048,
274
314
  messages=[{"role": "user", "content": prompt}],
@@ -276,17 +316,29 @@ Generate one complete workflow JSON that fully satisfies the user request and st
276
316
  return response.content[0].text
277
317
 
278
318
  async def _generate_with_openrouter(
279
- self, prompt: str, model: str = "deepseek/deepseek-r1"
319
+ self,
320
+ prompt: str,
321
+ model: str = "deepseek/deepseek-r1",
322
+ credential_data: Optional[Dict] = None,
280
323
  ) -> str:
281
324
  """Generate using OpenRouter"""
282
- if not self.openrouter_client:
325
+ client = self.openrouter_client
326
+ if credential_data:
327
+ api_key = credential_data.get("data", {}).get("api_key")
328
+ if api_key:
329
+ client = OpenAI(
330
+ base_url="https://openrouter.ai/api/v1",
331
+ api_key=api_key,
332
+ )
333
+
334
+ if not client:
283
335
  raise ValueError(
284
- "OpenRouter API key not configured. Please set OPENROUTER_API_KEY environment variable."
336
+ "OpenRouter API key not configured. Please set OPENROUTER_API_KEY environment variable or provide a credential."
285
337
  )
286
338
 
287
339
  # Use a generic HTTP circuit breaker for OpenRouter
288
340
  async with CircuitBreakers.http("openrouter-api"):
289
- response = self.openrouter_client.chat.completions.create(
341
+ response = client.chat.completions.create(
290
342
  model=model,
291
343
  messages=[
292
344
  {
@@ -0,0 +1,33 @@
1
+ """remove_superuser
2
+
3
+ Revision ID: 7a0fd1dc4902
4
+ Revises: 7edc63646d26
5
+ Create Date: 2026-01-19 17:12:48.459465
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlmodel.sql.sqltypes
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = '7a0fd1dc4902'
15
+ down_revision = '7edc63646d26'
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ with op.batch_alter_table('user', schema=None) as batch_op:
23
+ batch_op.drop_column('is_superuser')
24
+
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade():
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ with op.batch_alter_table('user', schema=None) as batch_op:
31
+ batch_op.add_column(sa.Column('is_superuser', sa.BOOLEAN(), nullable=False))
32
+
33
+ # ### end Alembic commands ###
@@ -0,0 +1,41 @@
1
+ """add_credentials_table
2
+
3
+ Revision ID: 7edc63646d26
4
+ Revises: debec76c1cf8
5
+ Create Date: 2026-01-18 21:17:35.367597
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlmodel.sql.sqltypes
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = '7edc63646d26'
15
+ down_revision = 'debec76c1cf8'
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table('credentials',
23
+ sa.Column('id', sa.Uuid(), nullable=False),
24
+ sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
25
+ sa.Column('type', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False),
26
+ sa.Column('data', sa.JSON(), nullable=False),
27
+ sa.Column('owner_id', sa.Uuid(), nullable=False),
28
+ sa.Column('created_at', sa.DateTime(), nullable=False),
29
+ sa.Column('updated_at', sa.DateTime(), nullable=False),
30
+ sa.Column('description', sa.Text(), nullable=True),
31
+ sa.Column('last_used_at', sa.DateTime(), nullable=True),
32
+ sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
33
+ sa.PrimaryKeyConstraint('id')
34
+ )
35
+ # ### end Alembic commands ###
36
+
37
+
38
+ def downgrade():
39
+ # ### commands auto generated by Alembic - please adjust! ###
40
+ op.drop_table('credentials')
41
+ # ### end Alembic commands ###
@@ -0,0 +1,114 @@
1
+ """Initial migration
2
+
3
+ Revision ID: debec76c1cf8
4
+ Revises:
5
+ Create Date: 2026-01-18 13:58:08.905854
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sqlmodel.sql.sqltypes
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = 'debec76c1cf8'
15
+ down_revision = None
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table('user',
23
+ sa.Column('email', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
24
+ sa.Column('is_active', sa.Boolean(), nullable=False),
25
+ sa.Column('is_superuser', sa.Boolean(), nullable=False),
26
+ sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
27
+ sa.Column('id', sa.Uuid(), nullable=False),
28
+ sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
29
+ sa.PrimaryKeyConstraint('id')
30
+ )
31
+ with op.batch_alter_table('user', schema=None) as batch_op:
32
+ batch_op.create_index(batch_op.f('ix_user_email'), ['email'], unique=True)
33
+
34
+ op.create_table('workflow',
35
+ sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
36
+ sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True),
37
+ sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
38
+ sa.Column('tags', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
39
+ sa.Column('id', sa.Uuid(), nullable=False),
40
+ sa.Column('owner_id', sa.Uuid(), nullable=False),
41
+ sa.Column('created_at', sa.DateTime(), nullable=False),
42
+ sa.Column('updated_at', sa.DateTime(), nullable=False),
43
+ sa.Column('execution_config', sa.JSON(), nullable=False),
44
+ sa.Column('observability_config', sa.JSON(), nullable=False),
45
+ sa.Column('ai_metadata', sa.JSON(), nullable=False),
46
+ sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
47
+ sa.PrimaryKeyConstraint('id')
48
+ )
49
+ op.create_table('workflow_edges',
50
+ sa.Column('id', sa.Uuid(), nullable=False),
51
+ sa.Column('workflow_id', sa.Uuid(), nullable=False),
52
+ sa.Column('edge_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
53
+ sa.Column('source', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
54
+ sa.Column('target', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
55
+ sa.Column('label', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
56
+ sa.Column('source_handle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
57
+ sa.Column('target_handle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
58
+ sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
59
+ sa.PrimaryKeyConstraint('id')
60
+ )
61
+ op.create_table('workflow_execution',
62
+ sa.Column('id', sa.Uuid(), nullable=False),
63
+ sa.Column('workflow_id', sa.Uuid(), nullable=False),
64
+ sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
65
+ sa.Column('started_at', sa.DateTime(), nullable=False),
66
+ sa.Column('completed_at', sa.DateTime(), nullable=True),
67
+ sa.Column('error', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
68
+ sa.Column('trigger_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
69
+ sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
70
+ sa.PrimaryKeyConstraint('id')
71
+ )
72
+ op.create_table('workflow_nodes',
73
+ sa.Column('id', sa.Uuid(), nullable=False),
74
+ sa.Column('workflow_id', sa.Uuid(), nullable=False),
75
+ sa.Column('node_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
76
+ sa.Column('node_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
77
+ sa.Column('position_x', sa.Float(), nullable=False),
78
+ sa.Column('position_y', sa.Float(), nullable=False),
79
+ sa.Column('label', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
80
+ sa.Column('subtitle', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
81
+ sa.Column('icon', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
82
+ sa.Column('spec', sa.JSON(), nullable=False),
83
+ sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ondelete='CASCADE'),
84
+ sa.PrimaryKeyConstraint('id')
85
+ )
86
+ op.create_table('node_execution',
87
+ sa.Column('id', sa.Uuid(), nullable=False),
88
+ sa.Column('workflow_execution_id', sa.Uuid(), nullable=False),
89
+ sa.Column('node_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
90
+ sa.Column('node_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
91
+ sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
92
+ sa.Column('started_at', sa.DateTime(), nullable=False),
93
+ sa.Column('completed_at', sa.DateTime(), nullable=True),
94
+ sa.Column('input_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
95
+ sa.Column('output_data', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
96
+ sa.Column('error', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
97
+ sa.ForeignKeyConstraint(['workflow_execution_id'], ['workflow_execution.id'], ondelete='CASCADE'),
98
+ sa.PrimaryKeyConstraint('id')
99
+ )
100
+ # ### end Alembic commands ###
101
+
102
+
103
+ def downgrade():
104
+ # ### commands auto generated by Alembic - please adjust! ###
105
+ op.drop_table('node_execution')
106
+ op.drop_table('workflow_nodes')
107
+ op.drop_table('workflow_execution')
108
+ op.drop_table('workflow_edges')
109
+ op.drop_table('workflow')
110
+ with op.batch_alter_table('user', schema=None) as batch_op:
111
+ batch_op.drop_index(batch_op.f('ix_user_email'))
112
+
113
+ op.drop_table('user')
114
+ # ### end Alembic commands ###
@@ -2,14 +2,12 @@ from fastapi import APIRouter
2
2
 
3
3
  from fuse.auth.router import router as auth_router
4
4
  from fuse.users.router import router as users_router
5
- from fuse.items.router import router as items_router
6
5
  from fuse.workflows.router import router as workflows_router
7
6
  from fuse.ai.router import router as ai_router
8
7
 
9
8
  api_router = APIRouter()
10
9
  api_router.include_router(auth_router, tags=["auth"])
11
10
  api_router.include_router(users_router, prefix="/users", tags=["users"])
12
- api_router.include_router(items_router, prefix="/items", tags=["items"])
13
11
  api_router.include_router(ai_router, prefix="/workflows/ai", tags=["ai"])
14
12
  api_router.include_router(workflows_router, prefix="/workflows", tags=["workflows"])
15
13
 
@@ -56,8 +56,6 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
56
56
  def is_active(self, user: User) -> bool:
57
57
  return user.is_active
58
58
 
59
- def is_superuser(self, user: User) -> bool:
60
- return user.is_superuser
61
59
 
62
60
 
63
61
  user = CRUDUser(User)
@@ -61,9 +61,3 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
61
61
  CurrentUser = Annotated[User, Depends(get_current_user)]
62
62
 
63
63
 
64
- def get_current_active_superuser(current_user: CurrentUser) -> User:
65
- if not current_user.is_superuser:
66
- raise HTTPException(
67
- status_code=403, detail="The user doesn't have enough privileges"
68
- )
69
- return current_user
@@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException
5
5
  from fastapi.responses import HTMLResponse
6
6
  from fastapi.security import OAuth2PasswordRequestForm
7
7
 
8
- from fuse.auth.dependencies import CurrentUser, SessionDep, get_current_active_superuser
8
+ from fuse.auth.dependencies import CurrentUser, SessionDep
9
9
  from fuse.auth import utils as security
10
10
  from fuse.config import settings
11
11
  from fuse.auth.utils import get_password_hash
@@ -121,27 +121,3 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
121
121
  return Message(message="Password updated successfully")
122
122
 
123
123
 
124
- @router.post(
125
- "/password-recovery-html-content/{email}",
126
- dependencies=[Depends(get_current_active_superuser)],
127
- response_class=HTMLResponse,
128
- )
129
- def recover_password_html_content(email: str, session: SessionDep) -> Any:
130
- """
131
- HTML Content for Password Recovery
132
- """
133
- user = user_service.get_user_by_email(session=session, email=email)
134
-
135
- if not user:
136
- raise HTTPException(
137
- status_code=404,
138
- detail="The user with this username does not exist in the system.",
139
- )
140
- password_reset_token = generate_password_reset_token(email=email)
141
- email_data = generate_reset_password_email(
142
- email_to=user.email, email=email, token=password_reset_token
143
- )
144
-
145
- return HTMLResponse(
146
- content=email_data.html_content, headers={"subject:": email_data.subject}
147
- )
@@ -26,7 +26,6 @@ from sqlmodel import Field, SQLModel
26
26
  class UserBase(SQLModel):
27
27
  email: EmailStr = Field(unique=True, index=True, max_length=255)
28
28
  is_active: bool = True
29
- is_superuser: bool = False
30
29
  full_name: Optional[str] = Field(default=None, max_length=255)
31
30
 
32
31
 
@@ -39,9 +39,6 @@ class UserService:
39
39
  def is_active(user: User) -> bool:
40
40
  return crud.user.is_active(user)
41
41
 
42
- @staticmethod
43
- def is_superuser(user: User) -> bool:
44
- return crud.user.is_superuser(user)
45
42
 
46
43
  @staticmethod
47
44
  def count_users(session: Session) -> int:
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
17
  @click.group()
18
- @click.version_option(version="0.1.30")
18
+ @click.version_option(version="0.1.49")
19
19
  def main():
20
20
  """
21
21
  Fuse - Workflow automation.
@@ -28,10 +28,18 @@ def main():
28
28
 
29
29
  def setup_db():
30
30
  """Run migrations and seed initial data if needed."""
31
+ # Suppress verbose migration logging and specific config warnings
32
+ import logging
33
+ import warnings
34
+ logging.getLogger("alembic").setLevel(logging.WARNING)
35
+ warnings.filterwarnings("ignore", message=".*FIRST_SUPERUSER_PASSWORD.*")
36
+
31
37
  try:
32
38
  from alembic.config import Config
33
39
  from alembic import command
34
40
  from pathlib import Path
41
+ import shutil
42
+ from fuse.config import settings
35
43
  import fuse
36
44
 
37
45
  # 1. Discover paths
@@ -57,6 +65,19 @@ def setup_db():
57
65
  console.print(f"[yellow]⚠ Migration directory not found at {script_location}, skipping[/yellow]")
58
66
  return
59
67
 
68
+ # Pre-check: Optimize first run by copying template DB if available
69
+ if settings.SQLALCHEMY_DATABASE_URI.startswith("sqlite"):
70
+ target_db = Path(settings.SQLITE_DB_PATH).resolve()
71
+ if not target_db.exists():
72
+ template_db = package_dir / "initial_fuse.db"
73
+ if template_db.exists():
74
+ console.print(f"[cyan]📦 Initializing database from shipped template...[/cyan]")
75
+ try:
76
+ shutil.copy2(template_db, target_db)
77
+ console.print(f"[green]✓ Pre-migrated database ready.[/green]")
78
+ except Exception as e:
79
+ console.print(f"[yellow]⚠ Failed to copy template database: {e}[/yellow]")
80
+
60
81
  console.print("[cyan]⚙ Checking database and running migrations...[/cyan]")
61
82
 
62
83
  # 3. Create Alembic config and run upgrade
@@ -223,8 +244,8 @@ DATABASE_URL=sqlite:///./fuse.db
223
244
  SECRET_KEY=dev-secret-key-12345
224
245
 
225
246
  # Initial User Data
226
- FIRST_SUPERUSER_EMAIL=admin@fuse.io
227
- FIRST_SUPERUSER_PASSWORD=changethis
247
+ FIRST_USER_EMAIL=admin@fuse.io
248
+ FIRST_USER_PASSWORD=changethis
228
249
 
229
250
  # AI API Keys (Optional - for AI nodes)
230
251
  # OPENAI_API_KEY=
@@ -253,7 +274,7 @@ def version():
253
274
 
254
275
  table = Table(title="Version Information", show_header=False)
255
276
  table.add_row("Package", "fuse-io")
256
- table.add_row("Version", "0.1.30")
277
+ table.add_row("Version", "0.1.49")
257
278
  table.add_row(
258
279
  "Python",
259
280
  f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",