@shawnstack/quickforge 1.7.3 → 1.7.5

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 (267) hide show
  1. package/README.md +1 -1
  2. package/bin/quickforge.mjs +0 -21
  3. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  4. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  5. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  6. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  7. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  8. package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +1639 -0
  9. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  10. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  11. package/dist/assets/TerminalDock-D4zJBJc4.js +2 -0
  12. package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
  13. package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  14. package/dist/assets/{arc-Bhg4c0ht.js → arc-w21O0TLn.js} +1 -1
  15. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  16. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  17. package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  18. package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  19. package/dist/assets/channel-Duu1CuH9.js +1 -0
  20. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  21. package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  22. package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  23. package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  24. package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  25. package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  26. package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  27. package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  28. package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  29. package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  30. package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  31. package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  32. package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  33. package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  34. package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  35. package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  36. package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  37. package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  38. package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  39. package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  40. package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  41. package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  42. package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  43. package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  44. package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  45. package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  46. package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  47. package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  48. package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  49. package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  50. package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  51. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  52. package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  53. package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  54. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  55. package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  56. package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  57. package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  58. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  59. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  60. package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  61. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  62. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  63. package/dist/assets/{dagre-CY3D84Cl.js → dagre-DmHb3Ctt.js} +1 -1
  64. package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  65. package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  70. package/dist/assets/dist-DLKC_odd.js +1 -0
  71. package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  72. package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  73. package/dist/assets/esm-Df4edDpj.js +2 -0
  74. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  75. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  76. package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  77. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  79. package/dist/assets/icons-CXPP1fFT.js +1 -0
  80. package/dist/assets/index-_ZhK6bvp.js +65 -0
  81. package/dist/assets/index-iVV2bP_f.css +3 -0
  82. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  83. package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  84. package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  85. package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  86. package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  87. package/dist/assets/{line-ikKkQiFp.js → line-B4DxX7tC.js} +1 -1
  88. package/dist/assets/{linear-GqPyxQkN.js → linear-U-nKUhyW.js} +1 -1
  89. package/dist/assets/{map-CJwjCvwr.js → map-BneTM-ri.js} +1 -1
  90. package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  91. package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  92. package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  93. package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DWpShXCs.js} +3 -3
  94. package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  95. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  96. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  97. package/dist/assets/{monaco-BTsVCDWS.js → monaco-D6OhXgjg.js} +1 -1
  98. package/dist/assets/{ordinal-CibZU3PM.js → ordinal-B2r5L1v4.js} +1 -1
  99. package/dist/assets/{otel-BNGM50C-.js → otel-BFnphzjt.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  102. package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-Cr2ee4a0.js} +2 -2
  103. package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-DV5jKjSg.js} +1 -1
  104. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  105. package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  106. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  107. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  108. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  109. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  110. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  111. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  113. package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-1KC5PyRx.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  118. package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-BThnIUBs.js} +3 -1
  119. package/dist/assets/{src-B4Tvg3gT.js → src-PYsDme0X.js} +1 -1
  120. package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  121. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  122. package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  123. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  124. package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  125. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  126. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  127. package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  128. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  129. package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  130. package/dist/assets/web-DoV8MSMn.js +1 -0
  131. package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  132. package/dist/index.html +13 -11
  133. package/dist/mobile-error.html +24 -0
  134. package/package.json +6 -1
  135. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  136. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  137. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  138. package/server/acp/server.mjs +50 -35
  139. package/server/agent-manager.mjs +343 -46
  140. package/server/agent-profile-files.mjs +8 -4
  141. package/server/agent-profile-schema.mjs +47 -23
  142. package/server/agent-profiles.mjs +1 -1
  143. package/server/ai-http-logger.mjs +156 -13
  144. package/server/ai-provider-options.mjs +3 -0
  145. package/server/channels/channel-logs.mjs +13 -0
  146. package/server/channels/event-relay.mjs +33 -0
  147. package/server/channels/process-channel.mjs +29 -0
  148. package/server/channels/providers/wechat.mjs +9 -3
  149. package/server/channels/registry.mjs +5 -1
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +102 -0
  152. package/server/cloud/config.mjs +53 -0
  153. package/server/cloud/credential-store.mjs +193 -0
  154. package/server/cloud/identity.mjs +355 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/runtime.mjs +129 -0
  158. package/server/cloud/service-config.mjs +77 -0
  159. package/server/index.mjs +63 -33
  160. package/server/lan-access-store.mjs +93 -3
  161. package/server/model-catalog.mjs +262 -0
  162. package/server/network-proxy.mjs +68 -3
  163. package/server/plugins/registry.mjs +93 -9
  164. package/server/public-api.mjs +6 -2
  165. package/server/routes/agent-profiles.mjs +43 -9
  166. package/server/routes/agent.mjs +48 -11
  167. package/server/routes/backup.mjs +7 -1
  168. package/server/routes/channels.mjs +47 -0
  169. package/server/routes/cloud.mjs +227 -0
  170. package/server/routes/lan-access.mjs +246 -21
  171. package/server/routes/models.mjs +13 -4
  172. package/server/routes/project.mjs +19 -1
  173. package/server/routes/scheduled-tasks.mjs +40 -10
  174. package/server/routes/shared-conversation.mjs +62 -58
  175. package/server/routes/shares.mjs +76 -1
  176. package/server/routes/static.mjs +26 -1
  177. package/server/routes/storage.mjs +24 -1
  178. package/server/routes/system.mjs +11 -0
  179. package/server/routes/terminal.mjs +7 -2
  180. package/server/routes/workspace.mjs +66 -13
  181. package/server/session-utils.mjs +2 -2
  182. package/server/share-store.mjs +168 -13
  183. package/server/skills.mjs +30 -3
  184. package/server/storage.mjs +7 -1
  185. package/server/subagents.mjs +4 -4
  186. package/server/terminal/terminal-manager.mjs +2 -4
  187. package/server/tools/definitions.mjs +3 -3
  188. package/server/tools/index.mjs +7 -5
  189. package/server/utils/network.mjs +9 -0
  190. package/server/utils/package-update.mjs +70 -47
  191. package/server/utils/process-env.mjs +7 -0
  192. package/server/utils/response.mjs +10 -3
  193. package/skills/skill-creator/LICENSE.txt +202 -0
  194. package/skills/skill-creator/SKILL.md +485 -0
  195. package/skills/skill-creator/UPSTREAM.md +6 -0
  196. package/skills/skill-creator/agents/analyzer.md +274 -0
  197. package/skills/skill-creator/agents/comparator.md +202 -0
  198. package/skills/skill-creator/agents/grader.md +223 -0
  199. package/skills/skill-creator/assets/eval_review.html +146 -0
  200. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  201. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  202. package/skills/skill-creator/references/schemas.md +430 -0
  203. package/skills/skill-creator/scripts/__init__.py +0 -0
  204. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  205. package/skills/skill-creator/scripts/generate_report.py +326 -0
  206. package/skills/skill-creator/scripts/improve_description.py +247 -0
  207. package/skills/skill-creator/scripts/package_skill.py +136 -0
  208. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  209. package/skills/skill-creator/scripts/run_eval.py +310 -0
  210. package/skills/skill-creator/scripts/run_loop.py +328 -0
  211. package/skills/skill-creator/scripts/utils.py +47 -0
  212. package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
  213. package/dist/assets/ChatPanelHost-wfGCaCzD.js +0 -288
  214. package/dist/assets/PluginsPage-NAGroBm3.js +0 -1
  215. package/dist/assets/ScheduledTasksPage-Bp0MNfQD.js +0 -2
  216. package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
  217. package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
  218. package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
  219. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
  220. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
  221. package/dist/assets/channel-Rz20dKTK.js +0 -1
  222. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
  223. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
  224. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
  225. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
  226. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
  227. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
  228. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
  229. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
  230. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
  231. package/dist/assets/icons-BP8YOS-Z.js +0 -1
  232. package/dist/assets/index-C6k5taeb.js +0 -66
  233. package/dist/assets/index-CzN8NSKC.css +0 -3
  234. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
  235. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
  236. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
  237. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
  238. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
  239. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
  240. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
  241. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
  242. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
  243. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
  244. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
  245. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
  246. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
  247. /package/dist/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
  248. /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  249. /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  250. /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
  251. /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-DyfqweF2.js} +0 -0
  252. /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/assets/{dist-DelZBZpK.js → dist-CLg13NtV.js} +0 -0
  254. /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-DJkmx0Cq.js} +0 -0
  255. /package/dist/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
  256. /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
  257. /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
  258. /package/dist/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
  259. /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-Uu2xAB4d.js} +0 -0
  260. /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
  261. /package/dist/assets/{xterm-CGiQzDiR.js → xterm-Bm_IbozX.js} +0 -0
  262. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  263. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  264. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  265. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  266. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  267. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
package/server/index.mjs CHANGED
@@ -5,7 +5,7 @@ import path from 'node:path'
5
5
  import { randomUUID } from 'node:crypto'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { sendJson, sendError } from './utils/response.mjs'
8
- import { openBrowser } from './utils/platform.mjs'
8
+ import { openBrowser, openPathInFileManager } from './utils/platform.mjs'
9
9
  import { ensureStorage, dataDir, configDir, storageDir, cacheDir, logsDir } from './storage.mjs'
10
10
  import { getNetworkProxyConfig, initializeNetworkProxy, refreshSystemProxy, updateNetworkProxyConfig } from './network-proxy.mjs'
11
11
  import { setDefaultWorkspaceRoot, initializeActiveProject, readProjectConfig, getActiveProject, readTerminalShellSetting, updateTerminalShellSetting, readTerminalShellConfig, updateTerminalShellConfig } from './project-config.mjs'
@@ -17,6 +17,7 @@ import { handleToolApi, handleGetTools } from './routes/tools.mjs'
17
17
  import { handleInstructionsApi } from './routes/instructions.mjs'
18
18
  import { handleMemoryApi } from './routes/memory.mjs'
19
19
  import { handleSkillsApi } from './routes/skills.mjs'
20
+ import { ensureDefaultGlobalSkills } from './skills.mjs'
20
21
  import { handleAgentApi } from './routes/agent.mjs'
21
22
  import { handleAgentProfilesApi } from './routes/agent-profiles.mjs'
22
23
  import { handleScheduledTasksApi, startScheduledTaskRunner, stopScheduledTaskRunner } from './routes/scheduled-tasks.mjs'
@@ -33,6 +34,7 @@ import { handleWorkspaceApi, handleGitApi } from './routes/workspace.mjs'
33
34
  import { handleTerminalApi, handleTerminalUpgrade } from './routes/terminal.mjs'
34
35
  import { handleChannelsApi } from './routes/channels.mjs'
35
36
  import { handleModelsApi } from './routes/models.mjs'
37
+ import { handleCloudApi } from './routes/cloud.mjs'
36
38
  import { serveStatic } from './routes/static.mjs'
37
39
  import { logger, flushLogger } from './utils/logger.mjs'
38
40
  import { getPackageInfo, checkForUpdates, checkDesktopRelease } from './utils/package-update.mjs'
@@ -44,6 +46,7 @@ import { shutdown as shutdownAgentManager, resetStaleTaskStatuses } from './agen
44
46
  import { initializeChannels, shutdownChannels } from './channels/registry.mjs'
45
47
  import { shutdownMcpConnections } from './mcp/registry.mjs'
46
48
  import { shutdownTerminalSessions } from './terminal/terminal-manager.mjs'
49
+ import { createNodeProcessEnv } from './utils/process-env.mjs'
47
50
 
48
51
  const __filename = fileURLToPath(import.meta.url)
49
52
  const __dirname = path.dirname(__filename)
@@ -71,10 +74,11 @@ function getRestartSupport() {
71
74
  return { supported: true, reason: null }
72
75
  }
73
76
 
74
- async function getSystemStatus() {
77
+ async function getSystemStatus({ isLocalRequest = true } = {}) {
75
78
  const config = await readProjectConfig()
76
79
  const restartSupport = getRestartSupport()
77
80
  const packageInfo = await getPackageInfo(projectRoot)
81
+ const restartSupported = isLocalRequest && restartSupport.supported
78
82
  return {
79
83
  ok: true,
80
84
  mode: isDev ? 'development' : 'production',
@@ -83,8 +87,14 @@ async function getSystemStatus() {
83
87
  startedAt,
84
88
  version: packageInfo.version,
85
89
  package: packageInfo,
86
- restartSupported: restartSupport.supported,
87
- restartUnsupportedReason: restartSupport.reason,
90
+ isLocalRequest,
91
+ capabilities: {
92
+ restart: restartSupported,
93
+ openLocalApps: isLocalRequest,
94
+ terminal: isLocalRequest,
95
+ },
96
+ restartSupported,
97
+ restartUnsupportedReason: isLocalRequest ? restartSupport.reason : 'Restart is only allowed from this computer',
88
98
  dataDir,
89
99
  configDir,
90
100
  storageDir,
@@ -113,10 +123,9 @@ function spawnRestartSupervisor() {
113
123
  stdio: 'ignore',
114
124
  windowsHide: true,
115
125
  shell: false,
116
- env: {
117
- ...process.env,
126
+ env: createNodeProcessEnv(process.env, {
118
127
  QUICKFORGE_NO_OPEN: '1',
119
- },
128
+ }),
120
129
  })
121
130
 
122
131
  child.once('error', reject)
@@ -206,10 +215,9 @@ function spawnUpdateSupervisor(update) {
206
215
  stdio: 'ignore',
207
216
  windowsHide: true,
208
217
  shell: false,
209
- env: {
210
- ...process.env,
218
+ env: createNodeProcessEnv(process.env, {
211
219
  QUICKFORGE_NO_OPEN: '1',
212
- },
220
+ }),
213
221
  })
214
222
 
215
223
  child.once('error', reject)
@@ -277,18 +285,18 @@ async function updateQuickForge() {
277
285
  }
278
286
 
279
287
  // --- Route dispatching ---
280
- async function handleApi(req, res, url) {
288
+ async function handleApi(req, res, url, requestContext = {}) {
281
289
  const pathname = url.pathname
282
290
  const parts = pathname.split('/').filter(Boolean)
283
291
 
284
292
  // Conversation share routes (management + public LAN access)
285
293
  if (pathname === '/api/shares' || pathname.startsWith('/api/shares/')) {
286
- await handleSharesApi(req, res, url, { port })
294
+ await handleSharesApi(req, res, url, { ...requestContext, port })
287
295
  return
288
296
  }
289
297
 
290
298
  if (pathname.startsWith('/api/shared/')) {
291
- await handleSharedConversationApi(req, res, url)
299
+ await handleSharedConversationApi(req, res, url, requestContext)
292
300
  return
293
301
  }
294
302
 
@@ -297,22 +305,22 @@ async function handleApi(req, res, url) {
297
305
  return
298
306
  }
299
307
 
300
- if (pathname === '/api/lan-access/status' || pathname === '/api/lan-access/settings' || pathname === '/api/lan-access/unlock' || pathname === '/api/lan-access/logout' || pathname === '/api/lan-access/revoke-all') {
308
+ if (pathname.startsWith('/api/lan-access/')) {
301
309
  await handleLanAccessApi(req, res, url, {
302
310
  port,
303
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
311
+ isLocalRequest: requestContext.isLocalRequest === true,
304
312
  })
305
313
  return
306
314
  }
307
315
 
308
316
  // Health check
309
317
  if (req.method === 'GET' && pathname === '/api/health') {
310
- sendJson(res, 200, await getSystemStatus())
318
+ sendJson(res, 200, await getSystemStatus(requestContext))
311
319
  return
312
320
  }
313
321
 
314
322
  if (req.method === 'GET' && pathname === '/api/project/commands') {
315
- await handleProjectApi(req, res, url)
323
+ await handleProjectApi(req, res, url, requestContext)
316
324
  return
317
325
  }
318
326
 
@@ -330,13 +338,23 @@ async function handleApi(req, res, url) {
330
338
 
331
339
  // Agent profiles
332
340
  if (pathname === '/api/agent-profiles' || pathname.startsWith('/api/agent-profiles/')) {
333
- await handleAgentProfilesApi(req, res, url)
341
+ await handleAgentProfilesApi(req, res, url, requestContext)
342
+ return
343
+ }
344
+
345
+ // QuickForge Cloud account and managed models (local or authenticated Tailscale requests).
346
+ if (pathname === '/api/cloud' || pathname.startsWith('/api/cloud/')) {
347
+ await handleCloudApi(req, res, url, {
348
+ isLocalRequest: requestContext.isLocalRequest === true,
349
+ remoteAddress: requestContext.remoteAddress,
350
+ remoteAuthorized: requestContext.remoteAuthorized === true,
351
+ })
334
352
  return
335
353
  }
336
354
 
337
355
  // Custom model management (connection test)
338
- if (pathname === '/api/models/test-connection') {
339
- await handleModelsApi(req, res, url)
356
+ if (pathname === '/api/models/catalog' || pathname === '/api/models/test-connection') {
357
+ await handleModelsApi(req, res, url, requestContext)
340
358
  return
341
359
  }
342
360
 
@@ -355,7 +373,9 @@ async function handleApi(req, res, url) {
355
373
  // Channels
356
374
  if (pathname === '/api/channels' || pathname.startsWith('/api/channels/')) {
357
375
  await handleChannelsApi(req, res, url, {
358
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
376
+ isLocalRequest: requestContext.isLocalRequest === true,
377
+ logsDir,
378
+ openPathInFileManager,
359
379
  })
360
380
  return
361
381
  }
@@ -368,18 +388,18 @@ async function handleApi(req, res, url) {
368
388
 
369
389
  // Project routes
370
390
  if (pathname === '/api/project' || pathname.startsWith('/api/project/')) {
371
- await handleProjectApi(req, res, url)
391
+ await handleProjectApi(req, res, url, requestContext)
372
392
  return
373
393
  }
374
394
 
375
395
  // Project workspace inspector routes
376
396
  if (pathname === '/api/workspace/tree' || pathname === '/api/workspace/file' || pathname === '/api/workspace/resolve-path' || pathname === '/api/workspace/open-external' || pathname.startsWith('/api/workspace/preview/')) {
377
- await handleWorkspaceApi(req, res, url)
397
+ await handleWorkspaceApi(req, res, url, requestContext)
378
398
  return
379
399
  }
380
400
 
381
401
  if (pathname.startsWith('/api/git/')) {
382
- await handleGitApi(req, res, url)
402
+ await handleGitApi(req, res, url, requestContext)
383
403
  return
384
404
  }
385
405
 
@@ -403,32 +423,32 @@ async function handleApi(req, res, url) {
403
423
 
404
424
  // Agent routes
405
425
  if (parts[0] === 'api' && parts[1] === 'agents') {
406
- await handleAgentApi(req, res, url)
426
+ await handleAgentApi(req, res, url, requestContext)
407
427
  return
408
428
  }
409
429
 
410
430
  // Scheduled task routes
411
431
  if (pathname === '/api/scheduled-tasks' || pathname.startsWith('/api/scheduled-tasks/')) {
412
- await handleScheduledTasksApi(req, res, url)
432
+ await handleScheduledTasksApi(req, res, url, requestContext)
413
433
  return
414
434
  }
415
435
 
416
436
  // Backup / import-export routes
417
437
  if (pathname === '/api/backup/export' || pathname === '/api/backup/import' || pathname === '/api/backup/inspect' || pathname === '/api/backup/inspect-file') {
418
- await handleBackupApi(req, res, url)
438
+ await handleBackupApi(req, res, url, requestContext)
419
439
  return
420
440
  }
421
441
 
422
442
  // System routes
423
443
  if (pathname === '/api/system/status' || pathname === '/api/system/restart' || pathname === '/api/system/network' || pathname === '/api/system/network-proxy' || pathname === '/api/system/network-proxy/refresh' || pathname === '/api/system/terminal-shell' || pathname === '/api/system/about' || pathname === '/api/system/update/check' || pathname === '/api/system/update/desktop' || pathname === '/api/system/update') {
424
444
  await handleSystemApi(req, res, url, {
425
- getSystemStatus,
445
+ getSystemStatus: () => getSystemStatus(requestContext),
426
446
  requestRestart,
427
447
  getPackageInfo: () => getPackageInfo(projectRoot),
428
448
  checkForUpdates: () => checkForUpdates(projectRoot),
429
449
  checkDesktopRelease: () => checkDesktopRelease(projectRoot),
430
450
  updateQuickForge,
431
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
451
+ isLocalRequest: requestContext.isLocalRequest === true,
432
452
  getTerminalShellSetting: readTerminalShellSetting,
433
453
  updateTerminalShellSetting,
434
454
  getTerminalShellConfig: readTerminalShellConfig,
@@ -446,14 +466,14 @@ async function handleApi(req, res, url) {
446
466
  // Terminal routes (local-only; real shell access)
447
467
  if (pathname === '/api/terminal/capabilities' || pathname === '/api/terminal/sessions' || pathname.startsWith('/api/terminal/sessions/')) {
448
468
  await handleTerminalApi(req, res, url, {
449
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
469
+ isLocalRequest: requestContext.isLocalRequest === true,
450
470
  })
451
471
  return
452
472
  }
453
473
 
454
474
  // Storage routes (catch-all)
455
475
  if (parts[0] === 'api' && parts[1] === 'storage') {
456
- await handleStorageApi(req, res, url)
476
+ await handleStorageApi(req, res, url, requestContext)
457
477
  return
458
478
  }
459
479
 
@@ -532,6 +552,7 @@ function isLanAccessBootstrapPath(pathname) {
532
552
 
533
553
  function isStaticAssetPath(pathname) {
534
554
  return pathname.startsWith('/assets/')
555
+ || pathname.startsWith('/downloads/')
535
556
  || pathname === '/favicon.svg'
536
557
  || pathname === '/vite.svg'
537
558
  || pathname === '/manifest.webmanifest'
@@ -610,7 +631,11 @@ const server = createServer(async (req, res) => {
610
631
  }
611
632
 
612
633
  if (url.pathname.startsWith('/api/')) {
613
- await handleApi(req, res, url)
634
+ await handleApi(req, res, url, {
635
+ isLocalRequest: !isRemoteRequest,
636
+ remoteAddress,
637
+ remoteAuthorized,
638
+ })
614
639
  return
615
640
  }
616
641
 
@@ -672,9 +697,14 @@ server.on('upgrade', (req, socket, head) => {
672
697
  })
673
698
 
674
699
  await ensureStorage()
700
+ await ensureDefaultGlobalSkills()
675
701
  await initializeNetworkProxy()
676
702
  installAiHttpLogger()
677
- initializeChannels({ projectRoot })
703
+ initializeChannels({
704
+ projectRoot,
705
+ channelEventsUrl: `http://127.0.0.1:${port}/api/channels/events`,
706
+ logsDir,
707
+ })
678
708
  await resetStaleTaskStatuses()
679
709
  await initializeActiveProject()
680
710
  setActiveWorkspaceRootForFilesystem(getWorkspaceRoot())
@@ -80,13 +80,41 @@ async function writeLanAccessFile(config) {
80
80
  await fs.writeFile(LAN_ACCESS_FILE, `${JSON.stringify(normalizeConfig(config), null, 2)}\n`, 'utf8')
81
81
  }
82
82
 
83
+ function normalizeText(value, maxLength) {
84
+ if (typeof value !== 'string') return undefined
85
+ const text = value.trim()
86
+ return text ? text.slice(0, maxLength) : undefined
87
+ }
88
+
89
+ function normalizeRemoteAddress(value) {
90
+ const address = normalizeText(value, 128)
91
+ return address?.startsWith('::ffff:') ? address.slice(7) : address
92
+ }
93
+
94
+ function tokenRecordId(tokenRecord) {
95
+ const id = normalizeText(tokenRecord?.id, 128)
96
+ return id || `legacy_${sha256Base64Url(tokenRecord?.tokenHash || '').slice(0, 24)}`
97
+ }
98
+
99
+ function publicTokenRecord(tokenRecord) {
100
+ return {
101
+ id: tokenRecordId(tokenRecord),
102
+ address: normalizeRemoteAddress(tokenRecord.remoteAddress),
103
+ userAgent: normalizeText(tokenRecord.userAgent, 300),
104
+ issuedAt: tokenRecord.issuedAt,
105
+ expiresAt: tokenRecord.expiresAt,
106
+ }
107
+ }
108
+
83
109
  function publicStatus(config) {
110
+ const activeTokens = pruneTokens(config.tokens)
84
111
  return {
85
112
  enabled: Boolean(config.enabled),
86
113
  hasPassword: Boolean(config.passwordHash),
87
114
  sessionTtlHours: config.sessionTtlHours,
88
115
  authVersion: config.authVersion || 1,
89
- activeTokenCount: pruneTokens(config.tokens).length,
116
+ activeTokenCount: activeTokens.length,
117
+ activeDevices: activeTokens.map(publicTokenRecord),
90
118
  updatedAt: config.updatedAt,
91
119
  }
92
120
  }
@@ -98,6 +126,12 @@ function pruneTokens(tokens, now = Date.now()) {
98
126
  if (!tokenRecord.expiresAt) return true
99
127
  return Date.parse(tokenRecord.expiresAt) > now
100
128
  })
129
+ .map((tokenRecord) => ({
130
+ ...tokenRecord,
131
+ id: tokenRecordId(tokenRecord),
132
+ remoteAddress: normalizeRemoteAddress(tokenRecord.remoteAddress),
133
+ userAgent: normalizeText(tokenRecord.userAgent, 300),
134
+ }))
101
135
  .slice(-LAN_TOKEN_MAX_COUNT)
102
136
  }
103
137
 
@@ -162,11 +196,58 @@ export async function revokeLanAccessTokens() {
162
196
  })
163
197
  }
164
198
 
199
+ export async function revokeLanAccessTokenById(id) {
200
+ const normalizedId = normalizeText(id, 128)
201
+ if (!normalizedId) {
202
+ const error = new Error('LAN access session id is required.')
203
+ error.statusCode = 400
204
+ throw error
205
+ }
206
+ return enqueueWrite(writeQueueName, async () => {
207
+ const current = await readLanAccessFile()
208
+ const tokens = pruneTokens(current.tokens)
209
+ const nextTokens = tokens.filter((tokenRecord) => tokenRecordId(tokenRecord) !== normalizedId)
210
+ if (nextTokens.length === tokens.length) {
211
+ const error = new Error('LAN access session not found.')
212
+ error.statusCode = 404
213
+ throw error
214
+ }
215
+ const next = normalizeConfig({
216
+ ...current,
217
+ tokens: nextTokens,
218
+ updatedAt: new Date().toISOString(),
219
+ })
220
+ await writeLanAccessFile(next)
221
+ return publicStatus(next)
222
+ })
223
+ }
224
+
225
+ export async function revokeLanAccessToken(token) {
226
+ if (!token || typeof token !== 'string') return false
227
+ const [versionText, secret] = token.split('.')
228
+ if (!secret) return false
229
+ const actualHash = sha256Base64Url(secret)
230
+ return enqueueWrite(writeQueueName, async () => {
231
+ const current = await readLanAccessFile()
232
+ if (Number(versionText) !== (current.authVersion || 1)) return false
233
+ const tokens = pruneTokens(current.tokens)
234
+ const nextTokens = tokens.filter((tokenRecord) => !safeHashEqual(tokenRecord.tokenHash, actualHash))
235
+ if (nextTokens.length === tokens.length) return false
236
+ const next = normalizeConfig({
237
+ ...current,
238
+ tokens: nextTokens,
239
+ updatedAt: new Date().toISOString(),
240
+ })
241
+ await writeLanAccessFile(next)
242
+ return true
243
+ })
244
+ }
245
+
165
246
  export function lanAccessCookieName() {
166
247
  return 'qf_lan_access'
167
248
  }
168
249
 
169
- export async function issueLanAccessToken(password) {
250
+ export async function issueLanAccessToken(password, metadata = {}) {
170
251
  return enqueueWrite(writeQueueName, async () => {
171
252
  const current = await readLanAccessFile()
172
253
  if (!current.enabled || !current.passwordHash) {
@@ -185,11 +266,20 @@ export async function issueLanAccessToken(password) {
185
266
  const issuedAt = new Date().toISOString()
186
267
  const ttlMs = normalizeSessionTtlHours(current.sessionTtlHours) * 60 * 60 * 1000
187
268
  const expiresAt = new Date(Date.now() + ttlMs).toISOString()
269
+ const tokenRecord = {
270
+ id: createRandomToken(18),
271
+ tokenHash,
272
+ issuedAt,
273
+ expiresAt,
274
+ authVersion: current.authVersion || 1,
275
+ remoteAddress: normalizeRemoteAddress(metadata.remoteAddress),
276
+ userAgent: normalizeText(metadata.userAgent, 300),
277
+ }
188
278
  const next = normalizeConfig({
189
279
  ...current,
190
280
  tokens: [
191
281
  ...pruneTokens(current.tokens),
192
- { tokenHash, issuedAt, expiresAt, authVersion: current.authVersion || 1 },
282
+ tokenRecord,
193
283
  ].slice(-LAN_TOKEN_MAX_COUNT),
194
284
  updatedAt: issuedAt,
195
285
  })
@@ -0,0 +1,262 @@
1
+ import { getCloudRuntime } from './cloud/runtime.mjs'
2
+ import { isManagedCloudModel } from './cloud/models.mjs'
3
+ import { readStore } from './storage.mjs'
4
+ import { isTailscaleAddress } from './utils/network.mjs'
5
+
6
+ export const MODEL_REFERENCE_VERSION = 1
7
+
8
+ function requestError(message, statusCode = 400, code) {
9
+ const error = new Error(message)
10
+ error.statusCode = statusCode
11
+ if (code) {
12
+ error.code = code
13
+ error.errorCode = code
14
+ }
15
+ return error
16
+ }
17
+
18
+ function normalizedBaseUrl(value) {
19
+ return String(value || '').trim().replace(/\/$/, '')
20
+ }
21
+
22
+ function isUsableModel(model) {
23
+ return Boolean(model && typeof model === 'object' && model.id && model.provider && model.api && model.baseUrl)
24
+ }
25
+
26
+ function sameModel(left, right) {
27
+ return Boolean(left && right
28
+ && left.id === right.id
29
+ && left.provider === right.provider
30
+ && left.api === right.api
31
+ && normalizedBaseUrl(left.baseUrl) === normalizedBaseUrl(right.baseUrl))
32
+ }
33
+
34
+ function publicModel(model, modelRef) {
35
+ const result = {
36
+ ...model,
37
+ quickforgeModelRef: modelRef,
38
+ }
39
+ delete result.apiKey
40
+ delete result.key
41
+ delete result.headers
42
+ return result
43
+ }
44
+
45
+ async function configuredProviders() {
46
+ const store = await readStore('custom-providers').catch(() => ({}))
47
+ return Array.isArray(store) ? store : Object.values(store || {})
48
+ }
49
+
50
+ function configuredEntries(providers) {
51
+ return providers.flatMap((provider) => {
52
+ const providerId = String(provider?.id || '').trim()
53
+ return (Array.isArray(provider?.models) ? provider.models : [])
54
+ .filter(isUsableModel)
55
+ .map((model) => ({ provider, providerId, model }))
56
+ })
57
+ }
58
+
59
+ export function cloudAllowedForContext(context = {}) {
60
+ if (context.allowCloud === false) return false
61
+ if (context.allowCloud === true || context.source === 'acp' || context.source === 'scheduled') return true
62
+ if (context.isLocalRequest !== false) return true
63
+ return context.remoteAuthorized === true && isTailscaleAddress(context.remoteAddress)
64
+ }
65
+
66
+ export function modelReferenceFromSnapshot(model, providers = []) {
67
+ if (!model || typeof model !== 'object') return null
68
+ if (isManagedCloudModel(model)) {
69
+ const catalogId = String(model.quickforgeCatalogId || model.id || '').trim()
70
+ return catalogId ? { version: MODEL_REFERENCE_VERSION, source: 'cloud', catalogId } : null
71
+ }
72
+
73
+ const matched = configuredEntries(providers).find((entry) => sameModel(entry.model, model))
74
+ if (matched?.providerId) {
75
+ return {
76
+ version: MODEL_REFERENCE_VERSION,
77
+ source: 'custom',
78
+ providerId: matched.providerId,
79
+ modelId: matched.model.id,
80
+ }
81
+ }
82
+
83
+ if (model.provider && model.id) {
84
+ return {
85
+ version: MODEL_REFERENCE_VERSION,
86
+ source: 'legacy-custom',
87
+ provider: String(model.provider),
88
+ modelId: String(model.id),
89
+ ...(model.api ? { api: String(model.api) } : {}),
90
+ ...(model.baseUrl ? { baseUrl: String(model.baseUrl) } : {}),
91
+ }
92
+ }
93
+ return null
94
+ }
95
+
96
+ export function normalizeModelReference(value) {
97
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null
98
+ const source = String(value.source || '').trim()
99
+ if (source === 'cloud') {
100
+ const catalogId = String(value.catalogId || value.quickforgeCatalogId || value.modelId || '').trim()
101
+ return catalogId ? { version: MODEL_REFERENCE_VERSION, source: 'cloud', catalogId } : null
102
+ }
103
+ if (source === 'custom') {
104
+ const providerId = String(value.providerId || '').trim()
105
+ const modelId = String(value.modelId || value.id || '').trim()
106
+ return providerId && modelId
107
+ ? { version: MODEL_REFERENCE_VERSION, source: 'custom', providerId, modelId }
108
+ : null
109
+ }
110
+ if (source === 'legacy-custom') {
111
+ const provider = String(value.provider || '').trim()
112
+ const modelId = String(value.modelId || value.id || '').trim()
113
+ return provider && modelId
114
+ ? {
115
+ version: MODEL_REFERENCE_VERSION,
116
+ source: 'legacy-custom',
117
+ provider,
118
+ modelId,
119
+ ...(value.api ? { api: String(value.api) } : {}),
120
+ ...(value.baseUrl ? { baseUrl: String(value.baseUrl) } : {}),
121
+ }
122
+ : null
123
+ }
124
+ return null
125
+ }
126
+
127
+ async function listCloudModels(context, { refresh = false } = {}) {
128
+ if (!cloudAllowedForContext(context)) return []
129
+ try {
130
+ const runtime = await getCloudRuntime()
131
+ if (!runtime?.enabled) return []
132
+ const models = await runtime.models.list(undefined, { refresh })
133
+ return models.filter((model) => model?.quickforgeCatalogId)
134
+ } catch {
135
+ return []
136
+ }
137
+ }
138
+
139
+ export async function listModelCatalog({
140
+ context = {},
141
+ includeHidden = false,
142
+ currentModel = null,
143
+ refreshCloud = false,
144
+ } = {}) {
145
+ const providers = await configuredProviders()
146
+ const custom = configuredEntries(providers)
147
+ .filter(({ model }) => includeHidden || model.quickforgeHidden !== true || sameModel(model, currentModel))
148
+ .map(({ providerId, model }) => publicModel(model, {
149
+ version: MODEL_REFERENCE_VERSION,
150
+ source: 'custom',
151
+ providerId,
152
+ modelId: model.id,
153
+ }))
154
+ if (currentModel) {
155
+ const currentIndex = custom.findIndex((model) => sameModel(model, currentModel))
156
+ if (currentIndex > 0 && custom[currentIndex].quickforgeHidden === true) {
157
+ custom.unshift(custom.splice(currentIndex, 1)[0])
158
+ }
159
+ }
160
+ const cloud = (await listCloudModels(context, { refresh: refreshCloud }))
161
+ .map((model) => publicModel(model, {
162
+ version: MODEL_REFERENCE_VERSION,
163
+ source: 'cloud',
164
+ catalogId: model.quickforgeCatalogId,
165
+ }))
166
+
167
+ if (currentModel && ![...custom, ...cloud].some((model) => sameModel(model, currentModel))) {
168
+ const ref = modelReferenceFromSnapshot(currentModel, providers)
169
+ if (ref) return [publicModel(currentModel, ref), ...custom, ...cloud]
170
+ }
171
+ return [...custom, ...cloud]
172
+ }
173
+
174
+ async function resolveCloud(ref, context) {
175
+ if (!cloudAllowedForContext(context)) {
176
+ throw requestError('QuickForge Cloud is not available from this client.', 403, 'cloud_access_denied')
177
+ }
178
+ const runtime = await getCloudRuntime()
179
+ if (!runtime?.enabled) throw requestError('QuickForge Cloud is not configured.', 503, 'cloud_not_configured')
180
+ return (await runtime.models.resolve({
181
+ provider: 'quickforge-cloud',
182
+ quickforgeModelSource: 'cloud',
183
+ quickforgeCatalogId: ref.catalogId,
184
+ })).publicModel
185
+ }
186
+
187
+ function findCustomByReference(ref, entries) {
188
+ if (ref.source === 'custom') {
189
+ return entries.find(({ providerId, model }) => providerId === ref.providerId && model.id === ref.modelId)?.model
190
+ }
191
+ if (ref.source === 'legacy-custom') {
192
+ return entries.find(({ model }) => model.provider === ref.provider
193
+ && model.id === ref.modelId
194
+ && (!ref.api || model.api === ref.api)
195
+ && (!ref.baseUrl || normalizedBaseUrl(model.baseUrl) === normalizedBaseUrl(ref.baseUrl)))?.model
196
+ }
197
+ return null
198
+ }
199
+
200
+ export async function resolveModelBinding(input, {
201
+ context = {},
202
+ currentModel = null,
203
+ allowCurrentHidden = false,
204
+ forExecution = false,
205
+ legacySnapshot = null,
206
+ } = {}) {
207
+ const providers = await configuredProviders()
208
+ const entries = configuredEntries(providers)
209
+ const explicitRef = normalizeModelReference(input?.modelRef || input?.quickforgeModelRef || input)
210
+ const snapshot = input?.model && typeof input.model === 'object'
211
+ ? input.model
212
+ : (!explicitRef && input && typeof input === 'object' && input.id ? input : legacySnapshot)
213
+ const ref = explicitRef || modelReferenceFromSnapshot(snapshot, providers)
214
+ if (!ref) throw requestError('A valid model reference is required.', 400, 'invalid_model_reference')
215
+
216
+ if (ref.source === 'cloud') {
217
+ const model = await resolveCloud(ref, context)
218
+ return { model, modelRef: ref }
219
+ }
220
+
221
+ const configured = findCustomByReference(ref, entries)
222
+ if (configured) {
223
+ const hidden = configured.quickforgeHidden === true
224
+ if (hidden && !forExecution && !(allowCurrentHidden && sameModel(configured, currentModel))) {
225
+ throw requestError('Selected model is not available for new selection.', 400, 'model_not_selectable')
226
+ }
227
+ const canonicalRef = modelReferenceFromSnapshot(configured, providers)
228
+ return { model: configured, modelRef: canonicalRef }
229
+ }
230
+
231
+ throw requestError('Selected model is not configured in QuickForge.', 400, 'model_not_configured')
232
+ }
233
+
234
+ export async function resolveImplicitModelPreference(value, context = {}) {
235
+ let stored = value
236
+ if (typeof stored === 'string') {
237
+ try { stored = JSON.parse(stored) } catch { stored = null }
238
+ }
239
+ const ref = normalizeModelReference(stored?.modelRef || stored?.quickforgeModelRef)
240
+ const snapshot = stored?.modelSnapshot || stored?.model || (stored?.id ? stored : null)
241
+ const catalog = await listModelCatalog({ context })
242
+ if (ref) {
243
+ const matched = catalog.find((model) => {
244
+ const candidate = normalizeModelReference(model.quickforgeModelRef)
245
+ if (!candidate || candidate.source !== ref.source) return false
246
+ if (ref.source === 'cloud') return candidate.catalogId === ref.catalogId
247
+ if (ref.source === 'custom') return candidate.providerId === ref.providerId && candidate.modelId === ref.modelId
248
+ return false
249
+ })
250
+ if (matched) return matched
251
+ }
252
+ if (snapshot) {
253
+ const matched = catalog.find((model) => sameModel(model, snapshot))
254
+ if (matched) return matched
255
+ }
256
+ return catalog[0] || null
257
+ }
258
+
259
+ export async function modelBindingFromModel(model) {
260
+ const providers = await configuredProviders()
261
+ return { model, modelRef: modelReferenceFromSnapshot(model, providers) }
262
+ }