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