@shawnstack/quickforge 1.7.4 → 1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
  2. package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
  3. package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
  4. package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
  5. package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
  6. package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
  7. package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
  8. package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
  9. package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
  10. package/dist/assets/WorkspaceInspector-BnfDLf9a.js +36 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  12. package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  16. package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  17. package/dist/assets/channel-Duu1CuH9.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  19. package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  20. package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  21. package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  22. package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  23. package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  24. package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  26. package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  27. package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  29. package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  30. package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  31. package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  32. package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  33. package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  34. package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  35. package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  36. package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  37. package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  38. package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  39. package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  41. package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  42. package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  43. package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  44. package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  45. package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  46. package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  47. package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  48. package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  49. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  50. package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  51. package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  52. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  53. package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  54. package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  55. package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  56. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  57. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  58. package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  59. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  60. package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  61. package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
  62. package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  68. package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
  69. package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  70. package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  71. package/dist/assets/{esm-oa69b5u4.js → esm-sdsK6gaB.js} +2 -2
  72. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  73. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  74. package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  75. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  76. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  77. package/dist/assets/icons-2D18YbSI.js +1 -0
  78. package/dist/assets/index-1ei6nhs-.css +3 -0
  79. package/dist/assets/index-BivLuZvX.js +65 -0
  80. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  81. package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  82. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  83. package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  84. package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  85. package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
  86. package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
  87. package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
  88. package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BEV4_TXC.js} +2 -2
  89. package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  90. package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  91. package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
  92. package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  93. package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
  94. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  95. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  96. package/dist/assets/{monaco-BVtCspYZ.js → monaco-DvXErxp2.js} +1 -1
  97. package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
  98. package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
  99. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  100. package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  101. package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
  102. package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  105. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  106. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  107. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  108. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  109. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  110. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  111. package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  112. package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-3UaA-C9W.js} +1 -1
  113. package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  114. package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  115. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  116. package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  117. package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BTJ7mzk_.js} +3 -3
  118. package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
  119. package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  120. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  121. package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  122. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  123. package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  124. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  125. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  126. package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  127. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  128. package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  129. package/dist/assets/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
  130. package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  131. package/dist/index.html +11 -10
  132. package/dist/sw.js +17 -0
  133. package/package.json +4 -1
  134. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  135. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  136. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  137. package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
  138. package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
  139. package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
  140. package/runtime-assets/agent/linux-x64/qf-agent +0 -0
  141. package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
  142. package/server/access-policy.mjs +11 -0
  143. package/server/acp/server.mjs +36 -26
  144. package/server/agent-manager.mjs +474 -71
  145. package/server/agent-profile-files.mjs +8 -4
  146. package/server/agent-profile-schema.mjs +47 -23
  147. package/server/agent-profiles.mjs +28 -9
  148. package/server/ai-http-logger.mjs +98 -12
  149. package/server/auto-compaction.mjs +35 -8
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +99 -0
  152. package/server/cloud/config.mjs +54 -0
  153. package/server/cloud/credential-store.mjs +200 -0
  154. package/server/cloud/identity.mjs +312 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/qf-agent-process.mjs +571 -0
  158. package/server/cloud/runtime.mjs +135 -0
  159. package/server/cloud/service-config.mjs +81 -0
  160. package/server/index.mjs +124 -48
  161. package/server/mcp/registry.mjs +8 -7
  162. package/server/message-converters.mjs +40 -19
  163. package/server/model-catalog.mjs +269 -0
  164. package/server/opencode-acp-agent.mjs +1151 -0
  165. package/server/plugins/registry.mjs +29 -5
  166. package/server/public-api.mjs +11 -5
  167. package/server/routes/agent-profiles.mjs +46 -9
  168. package/server/routes/agent.mjs +60 -11
  169. package/server/routes/backup.mjs +8 -1
  170. package/server/routes/cloud.mjs +246 -0
  171. package/server/routes/models.mjs +7 -1
  172. package/server/routes/scheduled-tasks.mjs +38 -8
  173. package/server/routes/shared-conversation.mjs +62 -58
  174. package/server/routes/shares.mjs +75 -1
  175. package/server/routes/storage.mjs +9 -1
  176. package/server/routes/system.mjs +7 -4
  177. package/server/routes/workspace.mjs +6 -4
  178. package/server/share-store.mjs +168 -13
  179. package/server/skills.mjs +30 -3
  180. package/server/storage.mjs +7 -1
  181. package/server/subagents.mjs +3 -3
  182. package/server/tools/definitions.mjs +1 -1
  183. package/server/tools/index.mjs +2 -2
  184. package/server/utils/network.mjs +9 -0
  185. package/server/utils/process-tree.mjs +78 -0
  186. package/server/utils/response.mjs +8 -1
  187. package/skills/skill-creator/LICENSE.txt +202 -0
  188. package/skills/skill-creator/SKILL.md +485 -0
  189. package/skills/skill-creator/UPSTREAM.md +6 -0
  190. package/skills/skill-creator/agents/analyzer.md +274 -0
  191. package/skills/skill-creator/agents/comparator.md +202 -0
  192. package/skills/skill-creator/agents/grader.md +223 -0
  193. package/skills/skill-creator/assets/eval_review.html +146 -0
  194. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  195. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  196. package/skills/skill-creator/references/schemas.md +430 -0
  197. package/skills/skill-creator/scripts/__init__.py +0 -0
  198. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  199. package/skills/skill-creator/scripts/generate_report.py +326 -0
  200. package/skills/skill-creator/scripts/improve_description.py +247 -0
  201. package/skills/skill-creator/scripts/package_skill.py +136 -0
  202. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  203. package/skills/skill-creator/scripts/run_eval.py +310 -0
  204. package/skills/skill-creator/scripts/run_loop.py +328 -0
  205. package/skills/skill-creator/scripts/utils.py +47 -0
  206. package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
  207. package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
  208. package/dist/assets/PluginsPage-D2862wul.js +0 -1
  209. package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
  210. package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +0 -1628
  211. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
  212. package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
  213. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
  214. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
  215. package/dist/assets/channel-DToA8d-4.js +0 -1
  216. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
  217. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
  218. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
  219. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
  220. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
  221. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
  222. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
  223. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
  224. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
  225. package/dist/assets/icons-cTkqD_Gi.js +0 -1
  226. package/dist/assets/index-B48prgLL.js +0 -65
  227. package/dist/assets/index-DQhwHHer.css +0 -3
  228. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
  229. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
  230. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
  231. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
  232. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
  233. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
  234. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
  235. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
  236. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
  237. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
  238. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
  239. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
  240. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
  241. /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  242. /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  243. /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  244. /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
  245. /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  246. /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
  247. /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
  248. /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  249. /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  250. /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
  251. /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
  252. /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
  253. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  254. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  255. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  256. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  257. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  258. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -0,0 +1,54 @@
1
+ import net from 'node:net'
2
+
3
+ const DEFAULT_TIMEOUT_MS = 10_000
4
+
5
+ function isLoopbackHost(hostname) {
6
+ const host = String(hostname || '').toLowerCase().replace(/^\[|\]$/g, '')
7
+ if (host === 'localhost' || host.endsWith('.localhost')) return true
8
+ const ip = net.isIP(host)
9
+ if (ip === 4) return host.startsWith('127.')
10
+ if (ip === 6) return host === '::1' || host === '0:0:0:0:0:0:0:1'
11
+ return false
12
+ }
13
+
14
+ export function parseCloudBaseUrl(value, _options = {}) {
15
+ const raw = typeof value === 'string' ? value.trim() : ''
16
+ if (!raw) return undefined
17
+
18
+ let url
19
+ try {
20
+ url = new URL(raw)
21
+ } catch {
22
+ throw new Error('QUICKFORGE_CLOUD_URL must be a valid URL.')
23
+ }
24
+
25
+ if (url.username || url.password || url.search || url.hash) {
26
+ throw new Error('QUICKFORGE_CLOUD_URL must not contain credentials, query parameters, or fragments.')
27
+ }
28
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
29
+ throw new Error('QUICKFORGE_CLOUD_URL must use http or https.')
30
+ }
31
+ if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
32
+ throw new Error('QUICKFORGE_CLOUD_URL over HTTP is only allowed for loopback hosts.')
33
+ }
34
+ if (url.pathname.includes('..')) throw new Error('QUICKFORGE_CLOUD_URL contains an unsafe path.')
35
+ url.pathname = `${url.pathname.replace(/\/+$/, '')}/`
36
+ return url
37
+ }
38
+
39
+ export function cloudEndpoint(baseUrl, pathname) {
40
+ if (!(baseUrl instanceof URL)) throw new Error('QuickForge Cloud is not configured.')
41
+ const relative = String(pathname || '').replace(/^\/+/, '')
42
+ if (!relative || relative.split('/').includes('..')) throw new Error('Invalid QuickForge Cloud endpoint.')
43
+ return new URL(relative, baseUrl)
44
+ }
45
+
46
+ export function readCloudConfig(env = process.env) {
47
+ const baseUrl = parseCloudBaseUrl(env.QUICKFORGE_CLOUD_URL)
48
+ const timeoutValue = Number(env.QUICKFORGE_CLOUD_TIMEOUT_MS || DEFAULT_TIMEOUT_MS)
49
+ return {
50
+ enabled: Boolean(baseUrl),
51
+ baseUrl,
52
+ timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : DEFAULT_TIMEOUT_MS,
53
+ }
54
+ }
@@ -0,0 +1,200 @@
1
+ import { generateKeyPair, randomUUID } from 'node:crypto'
2
+ import { promises as fs } from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { promisify } from 'node:util'
6
+ import { createRandomToken } from '../utils/password-auth.mjs'
7
+ import { ensureStorage, storageDir } from '../storage.mjs'
8
+
9
+ const generateKeyPairAsync = promisify(generateKeyPair)
10
+ const SCHEMA_VERSION = 1
11
+ const ACCOUNT_FIELDS = ['id', 'email', 'plan']
12
+
13
+ function publicAccount(value) {
14
+ if (!value || typeof value !== 'object') return undefined
15
+ const account = {}
16
+ for (const field of ACCOUNT_FIELDS) {
17
+ const candidate = value[field]
18
+ if (typeof candidate === 'string' && candidate) account[field] = candidate
19
+ }
20
+ return Object.keys(account).length ? account : undefined
21
+ }
22
+
23
+ function publicPendingDeviceFlow(value) {
24
+ if (!value || typeof value !== 'object') return undefined
25
+ const expiresAt = Number(value.expiresAt)
26
+ if (!Number.isFinite(expiresAt)) return undefined
27
+ return {
28
+ userCode: String(value.userCode || ''),
29
+ verificationUri: String(value.verificationUri || ''),
30
+ verificationUriComplete: String(value.verificationUriComplete || ''),
31
+ expiresAt,
32
+ interval: Math.max(1, Number(value.interval) || 5),
33
+ status: String(value.status || 'pending'),
34
+ }
35
+ }
36
+
37
+ function platformName(platform = process.platform) {
38
+ if (platform === 'win32') return 'windows'
39
+ if (platform === 'darwin') return 'macos'
40
+ return 'linux'
41
+ }
42
+
43
+ function defaultRecord({ installationName, platform, clientVersion } = {}) {
44
+ return {
45
+ schemaVersion: SCHEMA_VERSION,
46
+ mode: 'local',
47
+ installationName: installationName || os.hostname() || 'QuickForge',
48
+ platform: platformName(platform),
49
+ clientVersion: clientVersion || 'unknown',
50
+ updatedAt: new Date().toISOString(),
51
+ }
52
+ }
53
+
54
+ function normalizeRecord(value, defaults) {
55
+ const input = value && typeof value === 'object' ? value : {}
56
+ const base = defaultRecord(defaults)
57
+ return {
58
+ ...base,
59
+ ...input,
60
+ schemaVersion: SCHEMA_VERSION,
61
+ mode: ['local', 'guest', 'account'].includes(input.mode) ? input.mode : 'local',
62
+ installationName: String(input.installationName || base.installationName).slice(0, 120),
63
+ platform: ['windows', 'macos', 'linux'].includes(input.platform) ? input.platform : base.platform,
64
+ clientVersion: String(input.clientVersion || base.clientVersion).slice(0, 80),
65
+ }
66
+ }
67
+
68
+ function publicRecord(record) {
69
+ return {
70
+ configured: true,
71
+ mode: record.mode,
72
+ installationId: record.installationId,
73
+ installationName: record.installationName,
74
+ platform: record.platform,
75
+ clientVersion: record.clientVersion,
76
+ hasInstallationKey: Boolean(record.publicKey && record.privateKeyPkcs8),
77
+ hasSession: Boolean(record.refreshToken),
78
+ account: publicAccount(record.account),
79
+ pendingDeviceFlow: publicPendingDeviceFlow(record.pendingDeviceFlow),
80
+ updatedAt: record.updatedAt,
81
+ }
82
+ }
83
+
84
+ async function generateInstallationKeyPair() {
85
+ const { publicKey, privateKey } = await generateKeyPairAsync('ed25519')
86
+ const publicJwk = publicKey.export({ format: 'jwk' })
87
+ return {
88
+ publicKey: publicJwk.x,
89
+ privateKeyPkcs8: privateKey.export({ format: 'der', type: 'pkcs8' }).toString('base64url'),
90
+ }
91
+ }
92
+
93
+ export function createCloudCredentialStore({
94
+ filePath = path.join(storageDir, 'security', 'cloud-identity.json'),
95
+ ensureBaseStorage = ensureStorage,
96
+ installationName,
97
+ platform,
98
+ clientVersion,
99
+ } = {}) {
100
+ let queue = Promise.resolve()
101
+ const defaults = { installationName, platform, clientVersion }
102
+
103
+ async function ensureDirectory() {
104
+ await ensureBaseStorage()
105
+ await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 })
106
+ }
107
+
108
+ async function read() {
109
+ await ensureDirectory()
110
+ try {
111
+ const raw = await fs.readFile(filePath, 'utf8')
112
+ return normalizeRecord(raw.trim() ? JSON.parse(raw) : {}, defaults)
113
+ } catch (error) {
114
+ if (error?.code === 'ENOENT') return defaultRecord(defaults)
115
+ throw error
116
+ }
117
+ }
118
+
119
+ async function writeNow(record) {
120
+ await ensureDirectory()
121
+ const normalized = normalizeRecord({ ...record, updatedAt: new Date().toISOString() }, defaults)
122
+ const temporary = `${filePath}.${process.pid}.${createRandomToken(8)}.tmp`
123
+ await fs.writeFile(temporary, `${JSON.stringify(normalized, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
124
+ try { await fs.chmod(temporary, 0o600) } catch { /* best effort on Windows */ }
125
+ try {
126
+ await fs.rename(temporary, filePath)
127
+ } catch (error) {
128
+ if (!['EEXIST', 'EPERM'].includes(error?.code)) throw error
129
+ await fs.rm(filePath, { force: true })
130
+ await fs.rename(temporary, filePath)
131
+ }
132
+ try { await fs.chmod(filePath, 0o600) } catch { /* best effort on Windows */ }
133
+ return normalized
134
+ }
135
+
136
+ function update(mutator) {
137
+ const next = queue.catch(() => undefined).then(async () => {
138
+ const current = await read()
139
+ const result = await mutator({ ...current })
140
+ return writeNow(result || current)
141
+ })
142
+ queue = next
143
+ return next
144
+ }
145
+
146
+ async function ensureInstallation() {
147
+ const current = await read()
148
+ if (current.publicKey && current.privateKeyPkcs8 && current.installationId) return current
149
+ return update(async (record) => {
150
+ const keyPair = (record.publicKey && record.privateKeyPkcs8) ? {} : await generateInstallationKeyPair()
151
+ return {
152
+ ...record,
153
+ ...keyPair,
154
+ installationId: record.installationId || randomUUID(),
155
+ }
156
+ })
157
+ }
158
+
159
+ async function rotateInstallation() {
160
+ const keyPair = await generateInstallationKeyPair()
161
+ return update((record) => ({
162
+ ...record,
163
+ ...keyPair,
164
+ mode: 'local',
165
+ installationId: undefined,
166
+ refreshToken: undefined,
167
+ sessionCloudUrl: undefined,
168
+ pendingRegistrationKey: undefined,
169
+ pendingRegistrationHash: undefined,
170
+ pendingDeviceFlow: undefined,
171
+ account: undefined,
172
+ rotateInstallationBeforeRegistration: undefined,
173
+ }))
174
+ }
175
+
176
+ return {
177
+ filePath,
178
+ read,
179
+ update,
180
+ ensureInstallation,
181
+ rotateInstallation,
182
+ async readPublic() { return publicRecord(await read()) },
183
+ async clearSession({ rotateInstallationBeforeRegistration = false } = {}) {
184
+ return update((record) => ({
185
+ ...record,
186
+ mode: 'local',
187
+ installationId: undefined,
188
+ refreshToken: undefined,
189
+ sessionCloudUrl: undefined,
190
+ pendingRegistrationKey: undefined,
191
+ pendingRegistrationHash: undefined,
192
+ pendingDeviceFlow: undefined,
193
+ account: undefined,
194
+ rotateInstallationBeforeRegistration: rotateInstallationBeforeRegistration || undefined,
195
+ }))
196
+ },
197
+ }
198
+ }
199
+
200
+ export { publicRecord as publicCloudCredentialRecord }
@@ -0,0 +1,312 @@
1
+ import { CloudApiError } from './client.mjs'
2
+
3
+ const SESSION_INVALID_CODES = new Set(['refresh_token_reused', 'installation_revoked', 'invalid_refresh_token'])
4
+ const DEVICE_PENDING_CODES = new Set(['authorization_pending', 'pending'])
5
+ const DEVICE_SLOW_DOWN_CODES = new Set(['slow_down'])
6
+ const DEVICE_DENIED_CODES = new Set(['access_denied', 'authorization_declined', 'denied'])
7
+ const DEVICE_EXPIRED_CODES = new Set(['expired_token', 'device_code_expired', 'expired'])
8
+ const DEVICE_NETWORK_CODES = new Set(['cloud_unavailable', 'service_unavailable', 'temporarily_unavailable'])
9
+ const DEVICE_NETWORK_CAUSE_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ENETDOWN', 'ENETUNREACH', 'EHOSTUNREACH', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_SOCKET'])
10
+ const DEVICE_CLIENT_ID = 'quickforge-desktop'
11
+ const MODEL_CACHE_TTL_MS = 60_000
12
+ const MODEL_CACHE_HARD_TTL_MS = 5 * 60_000
13
+
14
+ function tokenExpiry(expiresIn) {
15
+ const seconds = Number(expiresIn)
16
+ return Date.now() + Math.max(1, Number.isFinite(seconds) ? seconds : 300) * 1000
17
+ }
18
+
19
+ function publicAccountSummary(value) {
20
+ if (!value || typeof value !== 'object') return undefined
21
+ const summary = {}
22
+ for (const field of ['id', 'email', 'plan']) {
23
+ const candidate = value[field]
24
+ if (typeof candidate === 'string' && candidate) summary[field] = candidate
25
+ }
26
+ return Object.keys(summary).length ? summary : undefined
27
+ }
28
+
29
+ function isDeviceFlowNetworkError(error) {
30
+ if (error instanceof CloudApiError) {
31
+ return error.retryable === true
32
+ || error.status >= 500
33
+ || DEVICE_NETWORK_CODES.has(error.code)
34
+ }
35
+ const causeCode = error?.cause?.code || error?.code
36
+ return error instanceof TypeError || DEVICE_NETWORK_CAUSE_CODES.has(causeCode)
37
+ }
38
+
39
+
40
+ export class CloudIdentityManager {
41
+ constructor({ client, store, serviceUrl } = {}) {
42
+ if (!client || !store) throw new Error('CloudIdentityManager requires client and store.')
43
+ this.client = client
44
+ this.store = store
45
+ this.serviceUrl = String(serviceUrl || client.baseUrl?.href || '')
46
+ if (!this.serviceUrl) throw new Error('CloudIdentityManager requires a service URL.')
47
+ this.accessToken = undefined
48
+ this.accessExpiresAt = 0
49
+ this.refreshPromise = undefined
50
+ this.devicePollPromise = undefined
51
+ this.modelsCache = undefined
52
+ this.modelsCachedAt = 0
53
+ this.modelsPromise = undefined
54
+ }
55
+
56
+ async status() {
57
+ let record = await this.store.read()
58
+ if (record.pendingDeviceFlow && Number(record.pendingDeviceFlow.expiresAt) <= Date.now()) {
59
+ record = await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
60
+ }
61
+ const publicStatus = await this.store.readPublic()
62
+ const sessionServiceMismatch = Boolean(record.refreshToken && record.sessionCloudUrl !== this.serviceUrl)
63
+ const pendingServiceMismatch = Boolean(record.pendingDeviceFlow && record.pendingDeviceFlow.sessionCloudUrl !== this.serviceUrl)
64
+ return (sessionServiceMismatch || pendingServiceMismatch)
65
+ ? { ...publicStatus, sessionServiceMismatch: true }
66
+ : publicStatus
67
+ }
68
+
69
+ assertSessionService(record) {
70
+ if (record.refreshToken && record.sessionCloudUrl !== this.serviceUrl) {
71
+ throw new CloudApiError('The saved QuickForge Cloud session belongs to a different or unknown service. Rebuild the local Cloud identity before continuing.', {
72
+ status: 409,
73
+ code: 'cloud_session_service_mismatch',
74
+ })
75
+ }
76
+ if (record.pendingDeviceFlow && record.pendingDeviceFlow.sessionCloudUrl !== this.serviceUrl) {
77
+ throw new CloudApiError('The pending QuickForge Cloud sign-in belongs to a different service. Cancel or rebuild the local Cloud identity before continuing.', {
78
+ status: 409,
79
+ code: 'cloud_session_service_mismatch',
80
+ })
81
+ }
82
+ }
83
+
84
+
85
+ async startDeviceFlow({ signal } = {}) {
86
+ let record = await this.store.read()
87
+ this.assertSessionService(record)
88
+ if (record.mode === 'account' && record.refreshToken) {
89
+ throw new CloudApiError('QuickForge Cloud is already connected to an account.', { status: 409, code: 'cloud_account_already_connected' })
90
+ }
91
+ if (record.rotateInstallationBeforeRegistration) {
92
+ record = await this.store.rotateInstallation()
93
+ }
94
+ record = await this.store.ensureInstallation()
95
+ if (!record.installationId || !record.publicKey) {
96
+ throw new CloudApiError('QuickForge Cloud installation identity is incomplete.', { status: 409, code: 'cloud_installation_missing' })
97
+ }
98
+ this.assertSessionService(record)
99
+ const authorization = await this.client.authorizeDevice({
100
+ installationId: record.installationId,
101
+ clientId: DEVICE_CLIENT_ID,
102
+ publicKey: record.publicKey,
103
+ installationName: record.installationName,
104
+ platform: record.platform,
105
+ clientVersion: record.clientVersion,
106
+ signal,
107
+ })
108
+ const expiresIn = Math.max(1, Number(authorization?.expiresIn) || 600)
109
+ const interval = Math.max(1, Number(authorization?.interval) || 5)
110
+ const pendingDeviceFlow = {
111
+ deviceCode: String(authorization?.deviceCode || ''),
112
+ userCode: String(authorization?.userCode || ''),
113
+ verificationUri: String(authorization?.verificationUri || ''),
114
+ verificationUriComplete: String(authorization?.verificationUriComplete || ''),
115
+ expiresAt: Date.now() + expiresIn * 1000,
116
+ interval,
117
+ status: 'pending',
118
+ sessionCloudUrl: this.serviceUrl,
119
+ }
120
+ if (!pendingDeviceFlow.deviceCode || !pendingDeviceFlow.userCode || !pendingDeviceFlow.verificationUri) {
121
+ throw new CloudApiError('QuickForge Cloud returned an invalid device authorization response.', { code: 'invalid_device_authorization_response' })
122
+ }
123
+ await this.store.update((current) => ({ ...current, pendingDeviceFlow }))
124
+ return this.status()
125
+ }
126
+
127
+ async pollDeviceFlow(options = {}) {
128
+ if (this.devicePollPromise) return this.devicePollPromise
129
+ this.devicePollPromise = this.pollDeviceFlowOnce(options)
130
+ .finally(() => { this.devicePollPromise = undefined })
131
+ return this.devicePollPromise
132
+ }
133
+
134
+ async pollDeviceFlowOnce({ signal } = {}) {
135
+ const record = await this.store.read()
136
+ this.assertSessionService(record)
137
+ const pending = record.pendingDeviceFlow
138
+ if (!pending?.deviceCode) {
139
+ throw new CloudApiError('There is no pending QuickForge Cloud sign-in.', { status: 409, code: 'cloud_device_flow_not_pending' })
140
+ }
141
+ if (Number(pending.expiresAt) <= Date.now()) {
142
+ await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
143
+ return { ...(await this.status()), deviceFlowResult: 'expired' }
144
+ }
145
+ try {
146
+ const tokens = await this.client.exchangeDeviceCode(pending.deviceCode, DEVICE_CLIENT_ID, signal)
147
+ if (!tokens?.refreshToken || !tokens?.accessToken) {
148
+ throw new CloudApiError('QuickForge Cloud returned an invalid account token response.', { code: 'invalid_token_response' })
149
+ }
150
+ this.setAccess(tokens)
151
+ this.modelsCache = undefined
152
+ this.modelsCachedAt = 0
153
+ this.modelsPromise = undefined
154
+ await this.store.update((current) => ({
155
+ ...current,
156
+ mode: 'account',
157
+ installationId: tokens.installationId || current.installationId,
158
+ refreshToken: tokens.refreshToken,
159
+ sessionCloudUrl: this.serviceUrl,
160
+ pendingDeviceFlow: undefined,
161
+ account: undefined,
162
+ }))
163
+ return { ...(await this.getStatusWithRemoteSummary(signal)), deviceFlowResult: 'success' }
164
+ } catch (error) {
165
+ if (DEVICE_PENDING_CODES.has(error?.code)) return { ...(await this.status()), deviceFlowResult: 'pending' }
166
+ if (DEVICE_SLOW_DOWN_CODES.has(error?.code)) {
167
+ await this.store.update((current) => ({
168
+ ...current,
169
+ pendingDeviceFlow: current.pendingDeviceFlow
170
+ ? { ...current.pendingDeviceFlow, interval: Math.max(1, Number(current.pendingDeviceFlow.interval) || 5) + 5, status: 'slow_down' }
171
+ : undefined,
172
+ }))
173
+ return { ...(await this.status()), deviceFlowResult: 'slow_down' }
174
+ }
175
+ if (DEVICE_DENIED_CODES.has(error?.code) || DEVICE_EXPIRED_CODES.has(error?.code)) {
176
+ await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
177
+ return { ...(await this.status()), deviceFlowResult: DEVICE_DENIED_CODES.has(error?.code) ? 'denied' : 'expired' }
178
+ }
179
+ if (isDeviceFlowNetworkError(error)) {
180
+ return { ...(await this.status()), deviceFlowResult: 'network' }
181
+ }
182
+ throw error
183
+ }
184
+ }
185
+
186
+ async cancelDeviceFlow() {
187
+ const record = await this.store.read()
188
+ this.assertSessionService(record)
189
+ await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
190
+ return this.status()
191
+ }
192
+
193
+ setAccess(tokens) {
194
+ this.accessToken = tokens?.accessToken
195
+ this.accessExpiresAt = tokenExpiry(tokens?.expiresIn)
196
+ }
197
+
198
+ async clearInvalidSession({ rotateInstallationBeforeRegistration = false } = {}) {
199
+ this.clearMemory()
200
+ await this.store.clearSession({ rotateInstallationBeforeRegistration })
201
+ }
202
+
203
+ clearMemory() {
204
+ this.accessToken = undefined
205
+ this.accessExpiresAt = 0
206
+ this.refreshPromise = undefined
207
+ this.devicePollPromise = undefined
208
+ this.modelsCache = undefined
209
+ this.modelsCachedAt = 0
210
+ this.modelsPromise = undefined
211
+ }
212
+
213
+ async resetIdentity() {
214
+ this.clearMemory()
215
+ await this.store.rotateInstallation()
216
+ }
217
+
218
+ async refresh({ signal } = {}) {
219
+ if (this.refreshPromise) return this.refreshPromise
220
+ this.refreshPromise = (async () => {
221
+ const record = await this.store.read()
222
+ if (!record.refreshToken) throw new CloudApiError('QuickForge Cloud is not connected.', { status: 401, code: 'cloud_not_connected' })
223
+ this.assertSessionService(record)
224
+ try {
225
+ const tokens = await this.client.refresh(record.refreshToken, signal)
226
+ if (!tokens?.refreshToken) throw new CloudApiError('QuickForge Cloud did not rotate the refresh token.', { code: 'invalid_token_response' })
227
+ this.setAccess(tokens)
228
+ await this.store.update((current) => ({
229
+ ...current,
230
+ mode: tokens.identityMode || current.mode || 'account',
231
+ installationId: tokens.installationId || current.installationId,
232
+ refreshToken: tokens.refreshToken,
233
+ sessionCloudUrl: this.serviceUrl,
234
+ }))
235
+ return this.accessToken
236
+ } catch (error) {
237
+ if (SESSION_INVALID_CODES.has(error?.code)) {
238
+ await this.clearInvalidSession({ rotateInstallationBeforeRegistration: true })
239
+ }
240
+ throw error
241
+ }
242
+ })().finally(() => { this.refreshPromise = undefined })
243
+ return this.refreshPromise
244
+ }
245
+
246
+ async access({ signal, forceRefresh = false } = {}) {
247
+ if (!forceRefresh && this.accessToken && this.accessExpiresAt > Date.now() + 30_000) return this.accessToken
248
+ return this.refresh({ signal })
249
+ }
250
+
251
+ async withAccessToken(operation, { signal } = {}) {
252
+ let token = await this.access({ signal })
253
+ try {
254
+ return await operation(token)
255
+ } catch (error) {
256
+ if (!(error instanceof CloudApiError) || error.status !== 401) throw error
257
+ token = await this.access({ signal, forceRefresh: true })
258
+ return operation(token)
259
+ }
260
+ }
261
+
262
+ async getStatusWithRemoteSummary(signal) {
263
+ const local = await this.status()
264
+ if (!local.hasSession) return local
265
+ try {
266
+ const [remoteAccount, usage] = await Promise.all([this.me(signal), this.usage(signal)])
267
+ const account = publicAccountSummary(remoteAccount)
268
+ await this.store.update((record) => ({ ...record, account }))
269
+ return { ...(await this.status()), account, usage, cloudAvailable: true }
270
+ } catch (error) {
271
+ return { ...local, cloudAvailable: false, cloudError: error?.code || 'cloud_unavailable' }
272
+ }
273
+ }
274
+
275
+ me(signal) { return this.withAccessToken((token) => this.client.me(token, signal), { signal }) }
276
+ usage(signal) { return this.withAccessToken((token) => this.client.usage(token, signal), { signal }) }
277
+ installations(signal) { return this.withAccessToken((token) => this.client.installations(token, signal), { signal }) }
278
+ revokeInstallation(id, signal) {
279
+ return this.withAccessToken((token) => this.client.revokeInstallation(token, id, signal), { signal })
280
+ }
281
+ async logout({ signal } = {}) {
282
+ const record = await this.store.read()
283
+ if (!record.refreshToken || !record.installationId) {
284
+ await this.clearInvalidSession()
285
+ return
286
+ }
287
+ this.assertSessionService(record)
288
+ await this.revokeInstallation(record.installationId, signal)
289
+ this.clearMemory()
290
+ await this.store.clearSession({ rotateInstallationBeforeRegistration: true })
291
+ }
292
+ async models(signal, { refresh = false, allowStale = true } = {}) {
293
+ const age = Date.now() - this.modelsCachedAt
294
+ if (!refresh && this.modelsCache && age < MODEL_CACHE_TTL_MS) return this.modelsCache
295
+ if (this.modelsPromise) return this.modelsPromise
296
+ this.modelsPromise = this.withAccessToken((token) => this.client.models(token, signal), { signal })
297
+ .then((response) => {
298
+ this.modelsCache = Array.isArray(response?.data) ? response.data : (Array.isArray(response?.items) ? response.items : [])
299
+ this.modelsCachedAt = Date.now()
300
+ return this.modelsCache
301
+ })
302
+ .catch((error) => {
303
+ if (allowStale && this.modelsCache && age < MODEL_CACHE_HARD_TTL_MS) return this.modelsCache
304
+ throw error
305
+ })
306
+ .finally(() => { this.modelsPromise = undefined })
307
+ return this.modelsPromise
308
+ }
309
+ chat(payload, idempotencyKey, signal) {
310
+ return this.withAccessToken((token) => this.client.chat(token, payload, idempotencyKey, signal), { signal })
311
+ }
312
+ }
@@ -0,0 +1,7 @@
1
+ export { readCloudConfig, parseCloudBaseUrl, cloudEndpoint } from './config.mjs'
2
+ export { readCloudServiceConfig, saveCloudServiceConfig, publicCloudServiceConfig, DEFAULT_CLOUD_URL } from './service-config.mjs'
3
+ export { CloudClient, CloudApiError } from './client.mjs'
4
+ export { createCloudCredentialStore, publicCloudCredentialRecord } from './credential-store.mjs'
5
+ export { CloudIdentityManager } from './identity.mjs'
6
+ export { ManagedCloudModels, isManagedCloudModel, toPublicCloudModel, QUICKFORGE_CLOUD_PROVIDER } from './models.mjs'
7
+ export { createCloudRuntime, getCloudRuntime, invalidateCloudRuntime, resolveManagedCloudProvider } from './runtime.mjs'
@@ -0,0 +1,80 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
3
+ const CLOUD_PROVIDER = 'quickforge-cloud'
4
+ const CLOUD_SOURCE = 'cloud'
5
+
6
+ export function toPublicCloudModel(model) {
7
+ const id = String(model?.id || '').trim()
8
+ if (!id) throw new Error('Cloud model is missing an id.')
9
+ const capabilities = model.capabilities || {}
10
+ return {
11
+ id,
12
+ name: String(model.name || id),
13
+ provider: CLOUD_PROVIDER,
14
+ api: 'openai-completions',
15
+ baseUrl: `quickforge://cloud/${encodeURIComponent(id)}`,
16
+ reasoning: Boolean(capabilities.reasoning),
17
+ input: capabilities.vision ? ['text', 'image'] : ['text'],
18
+ contextWindow: Number(model.contextWindow || 128_000),
19
+ maxTokens: Number(model.maxTokens || 32_768),
20
+ quickforgeModelSource: CLOUD_SOURCE,
21
+ quickforgeCatalogId: id,
22
+ quickforgeCapabilities: {
23
+ tools: Boolean(capabilities.tools),
24
+ vision: Boolean(capabilities.vision),
25
+ reasoning: Boolean(capabilities.reasoning),
26
+ },
27
+ }
28
+ }
29
+
30
+ export function isManagedCloudModel(model) {
31
+ return model?.provider === CLOUD_PROVIDER && model?.quickforgeModelSource === CLOUD_SOURCE && typeof model?.quickforgeCatalogId === 'string'
32
+ }
33
+
34
+ export class ManagedCloudModels {
35
+ constructor({ identity } = {}) {
36
+ if (!identity) throw new Error('ManagedCloudModels requires an identity manager.')
37
+ this.identity = identity
38
+ }
39
+
40
+ async list(signal, options) {
41
+ return (await this.identity.models(signal, options))
42
+ .filter((model) => model?.available !== false)
43
+ .map(toPublicCloudModel)
44
+ }
45
+
46
+ async resolve(model, signal) {
47
+ if (!isManagedCloudModel(model)) return undefined
48
+ const catalogId = model.quickforgeCatalogId
49
+ let available = await this.identity.models(signal)
50
+ let entry = available.find((item) => item?.id === catalogId && item?.available !== false)
51
+ if (!entry) {
52
+ available = await this.identity.models(signal, { refresh: true, allowStale: false })
53
+ entry = available.find((item) => item?.id === catalogId && item?.available !== false)
54
+ }
55
+ if (!entry) {
56
+ const error = new Error(`QuickForge Cloud model is unavailable: ${catalogId}`)
57
+ error.code = 'cloud_model_unavailable'
58
+ throw error
59
+ }
60
+ return {
61
+ publicModel: toPublicCloudModel(entry),
62
+ catalogId,
63
+ }
64
+ }
65
+
66
+ async chat(model, payload, signal) {
67
+ const resolved = await this.resolve(model, signal)
68
+ if (!resolved) throw new Error('Model is not managed by QuickForge Cloud.')
69
+ const request = {
70
+ ...payload,
71
+ model: resolved.catalogId,
72
+ }
73
+ delete request.baseUrl
74
+ delete request.headers
75
+ delete request.apiKey
76
+ return this.identity.chat(request, randomUUID(), signal)
77
+ }
78
+ }
79
+
80
+ export const QUICKFORGE_CLOUD_PROVIDER = CLOUD_PROVIDER