@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.
- package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
- package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
- package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
- package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
- package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
- package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
- package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
- package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
- package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
- package/dist/assets/WorkspaceInspector-BnfDLf9a.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
- package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
- package/dist/assets/channel-Duu1CuH9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
- package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
- package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
- package/dist/assets/{esm-oa69b5u4.js → esm-sdsK6gaB.js} +2 -2
- package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
- package/dist/assets/icons-2D18YbSI.js +1 -0
- package/dist/assets/index-1ei6nhs-.css +3 -0
- package/dist/assets/index-BivLuZvX.js +65 -0
- package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
- package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
- package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
- package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BEV4_TXC.js} +2 -2
- package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
- package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
- package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
- package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
- package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
- package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
- package/dist/assets/{monaco-BVtCspYZ.js → monaco-DvXErxp2.js} +1 -1
- package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
- package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
- package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
- package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
- package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-3UaA-C9W.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
- package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BTJ7mzk_.js} +3 -3
- package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
- package/dist/assets/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +11 -10
- package/dist/sw.js +17 -0
- package/package.json +4 -1
- package/plugins/{openai-documents → documents}/plugin.json +2 -2
- package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
- package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
- package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
- package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
- package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
- package/runtime-assets/agent/linux-x64/qf-agent +0 -0
- package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
- package/server/access-policy.mjs +11 -0
- package/server/acp/server.mjs +36 -26
- package/server/agent-manager.mjs +474 -71
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +28 -9
- package/server/ai-http-logger.mjs +98 -12
- package/server/auto-compaction.mjs +35 -8
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +99 -0
- package/server/cloud/config.mjs +54 -0
- package/server/cloud/credential-store.mjs +200 -0
- package/server/cloud/identity.mjs +312 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/qf-agent-process.mjs +571 -0
- package/server/cloud/runtime.mjs +135 -0
- package/server/cloud/service-config.mjs +81 -0
- package/server/index.mjs +124 -48
- package/server/mcp/registry.mjs +8 -7
- package/server/message-converters.mjs +40 -19
- package/server/model-catalog.mjs +269 -0
- package/server/opencode-acp-agent.mjs +1151 -0
- package/server/plugins/registry.mjs +29 -5
- package/server/public-api.mjs +11 -5
- package/server/routes/agent-profiles.mjs +46 -9
- package/server/routes/agent.mjs +60 -11
- package/server/routes/backup.mjs +8 -1
- package/server/routes/cloud.mjs +246 -0
- package/server/routes/models.mjs +7 -1
- package/server/routes/scheduled-tasks.mjs +38 -8
- package/server/routes/shared-conversation.mjs +62 -58
- package/server/routes/shares.mjs +75 -1
- package/server/routes/storage.mjs +9 -1
- package/server/routes/system.mjs +7 -4
- package/server/routes/workspace.mjs +6 -4
- package/server/share-store.mjs +168 -13
- package/server/skills.mjs +30 -3
- package/server/storage.mjs +7 -1
- package/server/subagents.mjs +3 -3
- package/server/tools/definitions.mjs +1 -1
- package/server/tools/index.mjs +2 -2
- package/server/utils/network.mjs +9 -0
- package/server/utils/process-tree.mjs +78 -0
- package/server/utils/response.mjs +8 -1
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/UPSTREAM.md +6 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
- package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
- package/dist/assets/PluginsPage-D2862wul.js +0 -1
- package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
- package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +0 -1628
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
- package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
- package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
- package/dist/assets/channel-DToA8d-4.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
- package/dist/assets/icons-cTkqD_Gi.js +0 -1
- package/dist/assets/index-B48prgLL.js +0 -65
- package/dist/assets/index-DQhwHHer.css +0 -3
- package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
- package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
- package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
- package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
- /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
- /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
- /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
- /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
- /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
- /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
- /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
- /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
- /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
- /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
- /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
|