@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
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { CloudClient, CloudApiError } from '../cloud/client.mjs'
|
|
2
|
+
import { parseCloudBaseUrl } from '../cloud/config.mjs'
|
|
3
|
+
import { createCloudCredentialStore } from '../cloud/credential-store.mjs'
|
|
4
|
+
import { getCloudRuntime, invalidateCloudRuntime } from '../cloud/runtime.mjs'
|
|
5
|
+
import {
|
|
6
|
+
publicCloudServiceConfig,
|
|
7
|
+
readCloudServiceConfig,
|
|
8
|
+
saveCloudServiceConfig,
|
|
9
|
+
} from '../cloud/service-config.mjs'
|
|
10
|
+
import { isTailscaleAddress } from '../utils/network.mjs'
|
|
11
|
+
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
12
|
+
|
|
13
|
+
const CLOUD_CONFIG_BODY_MAX_BYTES = 16 * 1024
|
|
14
|
+
const CLOUD_ACTION_HEADER = 'x-quickforge-action'
|
|
15
|
+
const CLOUD_ACTION_VALUE = 'cloud-action'
|
|
16
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
|
|
17
|
+
const JSON_WRITE_ROUTES = new Set([
|
|
18
|
+
'POST /api/cloud/test-connection',
|
|
19
|
+
'PUT /api/cloud/config',
|
|
20
|
+
'POST /api/cloud/identity/reset',
|
|
21
|
+
'POST /api/cloud/device/start',
|
|
22
|
+
'POST /api/cloud/device/poll',
|
|
23
|
+
'POST /api/cloud/device/cancel',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
function routeError(message, statusCode, code) {
|
|
27
|
+
const error = new Error(message)
|
|
28
|
+
error.statusCode = statusCode
|
|
29
|
+
error.errorCode = code
|
|
30
|
+
error.code = code
|
|
31
|
+
return error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mapCloudError(error) {
|
|
35
|
+
if (!(error instanceof CloudApiError)) return error
|
|
36
|
+
const mapped = routeError(error.message, error.status || 503, error.code)
|
|
37
|
+
mapped.retryable = error.retryable
|
|
38
|
+
mapped.details = error.details
|
|
39
|
+
return mapped
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createDefaultRuntime() {
|
|
43
|
+
return getCloudRuntime()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sameCloudUrl(left, right) {
|
|
47
|
+
try {
|
|
48
|
+
return parseCloudBaseUrl(left).href === parseCloudBaseUrl(right).href
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireDangerousConfirmation(body) {
|
|
55
|
+
if (body?.confirm !== 'reset-cloud-identity') {
|
|
56
|
+
throw routeError('Explicit confirmation is required to rebuild the QuickForge Cloud identity.', 400, 'cloud_reset_confirmation_required')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function requireProtectedCloudWrite(req, pathname) {
|
|
61
|
+
const method = String(req.method || 'GET').toUpperCase()
|
|
62
|
+
if (SAFE_METHODS.has(method)) return
|
|
63
|
+
|
|
64
|
+
if (req.headers[CLOUD_ACTION_HEADER] !== CLOUD_ACTION_VALUE) {
|
|
65
|
+
throw routeError('QuickForge Cloud action header is required.', 403, 'cloud_action_header_required')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!JSON_WRITE_ROUTES.has(`${method} ${pathname}`)) return
|
|
69
|
+
const mediaType = String(req.headers['content-type'] || '').split(';', 1)[0].trim().toLowerCase()
|
|
70
|
+
if (mediaType !== 'application/json') {
|
|
71
|
+
throw routeError('QuickForge Cloud JSON requests require Content-Type: application/json.', 415, 'cloud_json_content_type_required')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createCloudRouteHandler({
|
|
76
|
+
runtimeFactory = createDefaultRuntime,
|
|
77
|
+
readServiceConfig = readCloudServiceConfig,
|
|
78
|
+
saveServiceConfig = saveCloudServiceConfig,
|
|
79
|
+
credentialStoreFactory = createCloudCredentialStore,
|
|
80
|
+
invalidateRuntime = invalidateCloudRuntime,
|
|
81
|
+
cloudClientFactory = (config) => new CloudClient(config),
|
|
82
|
+
} = {}) {
|
|
83
|
+
async function runtimeOrError() {
|
|
84
|
+
try {
|
|
85
|
+
return { runtime: await runtimeFactory() }
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return { configurationError: error }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return async function handleCloudApi(req, res, url, {
|
|
92
|
+
isLocalRequest = false,
|
|
93
|
+
remoteAddress,
|
|
94
|
+
remoteAuthorized = false,
|
|
95
|
+
} = {}) {
|
|
96
|
+
const tailscaleAllowed = remoteAuthorized === true
|
|
97
|
+
&& isTailscaleAddress(remoteAddress)
|
|
98
|
+
if (!isLocalRequest && !tailscaleAllowed) {
|
|
99
|
+
throw routeError('QuickForge Cloud is available only on this computer or an authorized Tailscale client.', 403, 'cloud_local_only')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
requireProtectedCloudWrite(req, url.pathname)
|
|
103
|
+
|
|
104
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/config') {
|
|
105
|
+
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/test-connection') {
|
|
110
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
111
|
+
const baseUrl = parseCloudBaseUrl(body?.cloudUrl)
|
|
112
|
+
const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
|
|
113
|
+
const client = cloudClientFactory({
|
|
114
|
+
baseUrl,
|
|
115
|
+
timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
|
|
116
|
+
})
|
|
117
|
+
try {
|
|
118
|
+
const [health, ready] = await Promise.all([client.health(), client.ready()])
|
|
119
|
+
sendJson(res, 200, { ok: true, cloudUrl: baseUrl.href, health, ready })
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw mapCloudError(error)
|
|
122
|
+
}
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (req.method === 'PUT' && url.pathname === '/api/cloud/config') {
|
|
127
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
128
|
+
const nextUrl = parseCloudBaseUrl(body?.cloudUrl).href
|
|
129
|
+
const credentialStore = credentialStoreFactory()
|
|
130
|
+
const identityRecord = await credentialStore.read()
|
|
131
|
+
if (identityRecord.refreshToken || identityRecord.pendingDeviceFlow) {
|
|
132
|
+
const boundUrl = String(identityRecord.pendingDeviceFlow?.sessionCloudUrl || identityRecord.sessionCloudUrl || '').trim()
|
|
133
|
+
const sameUrl = boundUrl ? sameCloudUrl(boundUrl, nextUrl) : false
|
|
134
|
+
if (!sameUrl) {
|
|
135
|
+
throw routeError('A QuickForge Cloud session is active. Sign out first, or explicitly rebuild the identity before switching services.', 409, 'cloud_session_active')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await saveServiceConfig(nextUrl)
|
|
139
|
+
invalidateRuntime()
|
|
140
|
+
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/identity/reset') {
|
|
145
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
146
|
+
requireDangerousConfirmation(body)
|
|
147
|
+
const { runtime } = await runtimeOrError()
|
|
148
|
+
if (runtime?.enabled) await runtime.identity.resetIdentity()
|
|
149
|
+
else {
|
|
150
|
+
const store = credentialStoreFactory()
|
|
151
|
+
await store.rotateInstallation()
|
|
152
|
+
}
|
|
153
|
+
invalidateRuntime()
|
|
154
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { runtime: current, configurationError } = await runtimeOrError()
|
|
159
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/status') {
|
|
160
|
+
if (configurationError) {
|
|
161
|
+
sendJson(res, 200, { configured: false, mode: 'local', configurationError: configurationError.message })
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
if (!current?.enabled) {
|
|
165
|
+
sendJson(res, 200, { configured: false, mode: 'local' })
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
sendJson(res, 200, { configured: true, ...(await current.identity.status()) })
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (configurationError) throw routeError(configurationError.message, 503, 'cloud_configuration_error')
|
|
173
|
+
if (!current?.enabled) throw routeError('QuickForge Cloud is not configured.', 503, 'cloud_not_configured')
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/guest/start') {
|
|
177
|
+
sendJson(res, 201, await current.identity.startGuest())
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/start') {
|
|
181
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
182
|
+
sendJson(res, 201, await current.identity.startDeviceFlow())
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/poll') {
|
|
186
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
187
|
+
sendJson(res, 200, await current.identity.pollDeviceFlow())
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/cancel') {
|
|
191
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
192
|
+
sendJson(res, 200, await current.identity.cancelDeviceFlow())
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/models') {
|
|
196
|
+
sendJson(res, 200, { items: await current.models.list(undefined, { refresh: true }) })
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/usage') {
|
|
200
|
+
sendJson(res, 200, await current.identity.usage())
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/installations') {
|
|
204
|
+
sendJson(res, 200, await current.identity.installations())
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
|
|
208
|
+
await current.identity.logout()
|
|
209
|
+
invalidateRuntime()
|
|
210
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
const revokeMatch = url.pathname.match(/^\/api\/cloud\/installations\/([^/]+)$/)
|
|
214
|
+
if (req.method === 'DELETE' && revokeMatch) {
|
|
215
|
+
await current.identity.revokeInstallation(decodeURIComponent(revokeMatch[1]))
|
|
216
|
+
sendJson(res, 200, { ok: true })
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
throw mapCloudError(error)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw routeError('Not found', 404, 'route_not_found')
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const handleCloudApi = createCloudRouteHandler()
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
2
2
|
import { getLanUrls } from '../utils/network.mjs'
|
|
3
3
|
import { logger } from '../utils/logger.mjs'
|
|
4
|
+
import { parseCookies } from '../share-store.mjs'
|
|
4
5
|
import {
|
|
5
6
|
issueLanAccessToken,
|
|
6
7
|
lanAccessCookieName,
|
|
7
8
|
readLanAccessStatus,
|
|
9
|
+
revokeLanAccessToken,
|
|
10
|
+
revokeLanAccessTokenById,
|
|
8
11
|
revokeLanAccessTokens,
|
|
9
12
|
updateLanAccessSettings,
|
|
10
13
|
} from '../lan-access-store.mjs'
|
|
@@ -102,36 +105,242 @@ export function renderLanUnlockPage(res) {
|
|
|
102
105
|
<head>
|
|
103
106
|
<meta charset="utf-8" />
|
|
104
107
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
108
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
|
109
|
+
<meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" />
|
|
110
|
+
<link rel="icon" href="/favicon.svg" />
|
|
105
111
|
<title>QuickForge 局域网访问</title>
|
|
106
112
|
<style>
|
|
107
|
-
:root {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
:root {
|
|
114
|
+
color-scheme: light dark;
|
|
115
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
116
|
+
"Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
117
|
+
--background: #ffffff;
|
|
118
|
+
--foreground: #171717;
|
|
119
|
+
--muted-foreground: #737373;
|
|
120
|
+
--border: #e5e5e5;
|
|
121
|
+
--input-border: #dedede;
|
|
122
|
+
--surface: #ffffff;
|
|
123
|
+
--surface-muted: #fafafa;
|
|
124
|
+
--primary: #171717;
|
|
125
|
+
--primary-hover: #292929;
|
|
126
|
+
--primary-foreground: #fafafa;
|
|
127
|
+
--danger: #dc2626;
|
|
128
|
+
--shadow: 0 10px 26px -18px rgba(15, 23, 42, .48);
|
|
129
|
+
}
|
|
130
|
+
* { box-sizing: border-box; }
|
|
131
|
+
html, body { margin: 0; min-width: 0; min-height: 100%; }
|
|
132
|
+
body {
|
|
133
|
+
min-height: 100vh;
|
|
134
|
+
min-height: 100dvh;
|
|
135
|
+
display: grid;
|
|
136
|
+
place-items: center;
|
|
137
|
+
padding: 32px 20px;
|
|
138
|
+
background: var(--background);
|
|
139
|
+
color: var(--foreground);
|
|
140
|
+
-webkit-font-smoothing: antialiased;
|
|
141
|
+
}
|
|
142
|
+
main { width: min(100%, 440px); }
|
|
143
|
+
.intro { display: flex; align-items: center; gap: 13px; margin-bottom: 30px; }
|
|
144
|
+
.brand-mark {
|
|
145
|
+
display: grid;
|
|
146
|
+
width: 46px;
|
|
147
|
+
height: 46px;
|
|
148
|
+
flex: 0 0 auto;
|
|
149
|
+
place-items: center;
|
|
150
|
+
border: 1px solid var(--border);
|
|
151
|
+
border-radius: 13px;
|
|
152
|
+
background: var(--surface);
|
|
153
|
+
box-shadow: var(--shadow);
|
|
154
|
+
}
|
|
155
|
+
.brand-mark img { width: 29px; height: 29px; }
|
|
156
|
+
.eyebrow {
|
|
157
|
+
margin: 0 0 2px;
|
|
158
|
+
color: var(--muted-foreground);
|
|
159
|
+
font-size: 11px;
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
letter-spacing: .1em;
|
|
162
|
+
}
|
|
163
|
+
h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: -.02em; line-height: 1.35; }
|
|
164
|
+
.subtitle { margin: 3px 0 0; color: var(--muted-foreground); font-size: 14px; line-height: 1.5; }
|
|
165
|
+
form {
|
|
166
|
+
padding: 22px;
|
|
167
|
+
border: 1px solid var(--border);
|
|
168
|
+
border-radius: 16px;
|
|
169
|
+
background: var(--surface);
|
|
170
|
+
box-shadow: var(--shadow);
|
|
171
|
+
}
|
|
172
|
+
label { display: block; margin-bottom: 9px; font-size: 14px; font-weight: 500; }
|
|
173
|
+
.input-wrap { position: relative; }
|
|
174
|
+
.input-icon {
|
|
175
|
+
position: absolute;
|
|
176
|
+
top: 50%;
|
|
177
|
+
left: 13px;
|
|
178
|
+
width: 17px;
|
|
179
|
+
height: 17px;
|
|
180
|
+
color: var(--muted-foreground);
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
transform: translateY(-50%);
|
|
183
|
+
}
|
|
184
|
+
input {
|
|
185
|
+
width: 100%;
|
|
186
|
+
height: 44px;
|
|
187
|
+
border: 1px solid var(--input-border);
|
|
188
|
+
border-radius: 12px;
|
|
189
|
+
outline: none;
|
|
190
|
+
background: var(--surface);
|
|
191
|
+
color: var(--foreground);
|
|
192
|
+
padding: 0 13px 0 40px;
|
|
193
|
+
font: inherit;
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
transition: border-color 150ms ease, box-shadow 150ms ease;
|
|
196
|
+
}
|
|
197
|
+
input::placeholder { color: var(--muted-foreground); opacity: .7; }
|
|
198
|
+
input:hover { border-color: color-mix(in srgb, var(--foreground) 24%, var(--border)); }
|
|
199
|
+
input:focus {
|
|
200
|
+
border-color: color-mix(in srgb, var(--foreground) 30%, var(--border));
|
|
201
|
+
box-shadow: var(--shadow);
|
|
202
|
+
}
|
|
203
|
+
input[aria-invalid="true"] { border-color: color-mix(in srgb, var(--danger) 60%, var(--border)); }
|
|
204
|
+
button {
|
|
205
|
+
width: 100%;
|
|
206
|
+
height: 44px;
|
|
207
|
+
margin-top: 18px;
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
gap: 8px;
|
|
212
|
+
border: 0;
|
|
213
|
+
border-radius: 12px;
|
|
214
|
+
background: var(--primary);
|
|
215
|
+
color: var(--primary-foreground);
|
|
216
|
+
font: inherit;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
font-weight: 500;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
transition: background-color 150ms ease, opacity 150ms ease;
|
|
221
|
+
}
|
|
222
|
+
button:hover:not(:disabled) { background: var(--primary-hover); }
|
|
223
|
+
button:active:not(:disabled) { opacity: .88; }
|
|
224
|
+
button:focus-visible { outline: 2px solid color-mix(in srgb, var(--foreground) 28%, transparent); outline-offset: 3px; }
|
|
225
|
+
button:disabled { cursor: default; opacity: .62; }
|
|
226
|
+
.button-icon { width: 16px; height: 16px; }
|
|
227
|
+
.spinner { display: none; width: 15px; height: 15px; animation: spin .8s linear infinite; }
|
|
228
|
+
button.is-loading .button-icon { display: none; }
|
|
229
|
+
button.is-loading .spinner { display: block; }
|
|
230
|
+
.error {
|
|
231
|
+
min-height: 20px;
|
|
232
|
+
margin: 10px 0 -2px;
|
|
233
|
+
color: var(--danger);
|
|
234
|
+
font-size: 13px;
|
|
235
|
+
line-height: 20px;
|
|
236
|
+
}
|
|
237
|
+
.note {
|
|
238
|
+
display: flex;
|
|
239
|
+
gap: 9px;
|
|
240
|
+
margin: 17px 3px 0;
|
|
241
|
+
color: var(--muted-foreground);
|
|
242
|
+
font-size: 12px;
|
|
243
|
+
line-height: 1.65;
|
|
244
|
+
}
|
|
245
|
+
.note svg { width: 15px; height: 15px; flex: 0 0 auto; margin-top: 2px; }
|
|
246
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
247
|
+
@media (prefers-color-scheme: dark) {
|
|
248
|
+
:root {
|
|
249
|
+
--background: #171717;
|
|
250
|
+
--foreground: #fafafa;
|
|
251
|
+
--muted-foreground: #a3a3a3;
|
|
252
|
+
--border: #353535;
|
|
253
|
+
--input-border: #404040;
|
|
254
|
+
--surface: #1d1d1d;
|
|
255
|
+
--surface-muted: #262626;
|
|
256
|
+
--primary: #e5e5e5;
|
|
257
|
+
--primary-hover: #fafafa;
|
|
258
|
+
--primary-foreground: #171717;
|
|
259
|
+
--danger: #f87171;
|
|
260
|
+
--shadow: 0 10px 26px -18px rgba(0, 0, 0, .9);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
@media (max-width: 480px) {
|
|
264
|
+
body { place-items: start center; padding-top: max(28px, 8vh); }
|
|
265
|
+
.intro { margin-bottom: 24px; }
|
|
266
|
+
form { padding: 20px; }
|
|
267
|
+
}
|
|
268
|
+
@media (prefers-reduced-motion: reduce) {
|
|
269
|
+
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
|
|
270
|
+
}
|
|
117
271
|
</style>
|
|
118
272
|
</head>
|
|
119
273
|
<body>
|
|
120
274
|
<main>
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
275
|
+
<header class="intro">
|
|
276
|
+
<div class="brand-mark" aria-hidden="true"><img src="/favicon.svg" alt="" /></div>
|
|
277
|
+
<div>
|
|
278
|
+
<p class="eyebrow">QUICKFORGE</p>
|
|
279
|
+
<h1>验证局域网访问</h1>
|
|
280
|
+
<p class="subtitle">输入主机端设置的访问密码,继续进入工作区。</p>
|
|
281
|
+
</div>
|
|
282
|
+
</header>
|
|
283
|
+
|
|
284
|
+
<form id="unlock-form">
|
|
285
|
+
<label for="password">访问密码</label>
|
|
286
|
+
<div class="input-wrap">
|
|
287
|
+
<svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
288
|
+
<rect x="4.5" y="10" width="15" height="10" rx="2.5"></rect>
|
|
289
|
+
<path d="M8 10V7.5a4 4 0 0 1 8 0V10"></path>
|
|
290
|
+
</svg>
|
|
291
|
+
<input
|
|
292
|
+
id="password"
|
|
293
|
+
type="password"
|
|
294
|
+
autocomplete="current-password"
|
|
295
|
+
placeholder="请输入访问密码"
|
|
296
|
+
aria-describedby="error"
|
|
297
|
+
aria-invalid="false"
|
|
298
|
+
autofocus
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
<button id="submit" type="submit">
|
|
302
|
+
<svg class="spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
303
|
+
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-opacity=".25" stroke-width="3"></circle>
|
|
304
|
+
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path>
|
|
305
|
+
</svg>
|
|
306
|
+
<span id="button-label">进入 QuickForge</span>
|
|
307
|
+
<svg class="button-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
308
|
+
<path d="M5 12h14M13 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
309
|
+
</svg>
|
|
310
|
+
</button>
|
|
311
|
+
<p id="error" class="error" role="alert" aria-live="polite"></p>
|
|
312
|
+
</form>
|
|
313
|
+
|
|
314
|
+
<p class="note">
|
|
315
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
316
|
+
<path d="M12 3 5 6v5c0 4.6 2.9 8.4 7 10 4.1-1.6 7-5.4 7-10V6l-7-3Z" stroke-linejoin="round"></path>
|
|
317
|
+
<path d="m9.5 12 1.7 1.7 3.6-3.7" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
318
|
+
</svg>
|
|
319
|
+
<span>密码仅用于验证本次局域网访问,不会保存在此设备。</span>
|
|
320
|
+
</p>
|
|
127
321
|
</main>
|
|
128
322
|
<script>
|
|
323
|
+
const form = document.getElementById('unlock-form')
|
|
129
324
|
const password = document.getElementById('password')
|
|
130
325
|
const button = document.getElementById('submit')
|
|
326
|
+
const buttonLabel = document.getElementById('button-label')
|
|
131
327
|
const error = document.getElementById('error')
|
|
132
|
-
|
|
328
|
+
|
|
329
|
+
password.addEventListener('input', () => {
|
|
330
|
+
error.textContent = ''
|
|
331
|
+
password.setAttribute('aria-invalid', 'false')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
form.addEventListener('submit', async (event) => {
|
|
335
|
+
event.preventDefault()
|
|
336
|
+
if (button.disabled) return
|
|
337
|
+
|
|
133
338
|
error.textContent = ''
|
|
339
|
+
password.setAttribute('aria-invalid', 'false')
|
|
134
340
|
button.disabled = true
|
|
341
|
+
button.classList.add('is-loading')
|
|
342
|
+
buttonLabel.textContent = '正在验证…'
|
|
343
|
+
|
|
135
344
|
try {
|
|
136
345
|
const response = await fetch('/api/lan-access/unlock', {
|
|
137
346
|
method: 'POST',
|
|
@@ -143,12 +352,14 @@ export function renderLanUnlockPage(res) {
|
|
|
143
352
|
window.location.reload()
|
|
144
353
|
} catch (err) {
|
|
145
354
|
error.textContent = err instanceof Error ? err.message : '密码错误'
|
|
355
|
+
password.setAttribute('aria-invalid', 'true')
|
|
356
|
+
password.focus()
|
|
146
357
|
} finally {
|
|
147
358
|
button.disabled = false
|
|
359
|
+
button.classList.remove('is-loading')
|
|
360
|
+
buttonLabel.textContent = '进入 QuickForge'
|
|
148
361
|
}
|
|
149
|
-
}
|
|
150
|
-
button.addEventListener('click', unlock)
|
|
151
|
-
password.addEventListener('keydown', (event) => { if (event.key === 'Enter') unlock() })
|
|
362
|
+
})
|
|
152
363
|
</script>
|
|
153
364
|
</body>
|
|
154
365
|
</html>`
|
|
@@ -186,7 +397,10 @@ export async function handleLanAccessApi(req, res, url, context = {}) {
|
|
|
186
397
|
assertNotLocked(req)
|
|
187
398
|
const body = await readJsonBody(req, 1024)
|
|
188
399
|
try {
|
|
189
|
-
const result = await issueLanAccessToken(body?.password
|
|
400
|
+
const result = await issueLanAccessToken(body?.password, {
|
|
401
|
+
remoteAddress: req.socket.remoteAddress,
|
|
402
|
+
userAgent: req.headers['user-agent'],
|
|
403
|
+
})
|
|
190
404
|
setLanCookie(res, result.token, result.maxAge)
|
|
191
405
|
clearFailures(req)
|
|
192
406
|
logger.info('LAN access unlock succeeded.', { remoteAddress: req.socket.remoteAddress })
|
|
@@ -202,11 +416,22 @@ export async function handleLanAccessApi(req, res, url, context = {}) {
|
|
|
202
416
|
}
|
|
203
417
|
|
|
204
418
|
if (req.method === 'POST' && pathname === '/api/lan-access/logout') {
|
|
419
|
+
const token = parseCookies(req.headers.cookie).get(lanAccessCookieName())
|
|
420
|
+
await revokeLanAccessToken(token)
|
|
205
421
|
clearLanCookie(res)
|
|
206
422
|
sendJson(res, 200, { ok: true })
|
|
207
423
|
return
|
|
208
424
|
}
|
|
209
425
|
|
|
426
|
+
if (req.method === 'POST' && pathname === '/api/lan-access/revoke') {
|
|
427
|
+
requireLocal(context)
|
|
428
|
+
const body = await readJsonBody(req, 1024)
|
|
429
|
+
const status = await revokeLanAccessTokenById(body?.id)
|
|
430
|
+
logger.info('LAN access session revoked.', { sessionId: body?.id })
|
|
431
|
+
sendJson(res, 200, { ok: true, ...status })
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
210
435
|
if (req.method === 'POST' && pathname === '/api/lan-access/revoke-all') {
|
|
211
436
|
requireLocal(context)
|
|
212
437
|
const status = await revokeLanAccessTokens()
|
package/server/routes/models.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
|
|
2
|
+
import { listModelCatalog } from '../model-catalog.mjs'
|
|
2
3
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
3
4
|
import { readStore } from '../storage.mjs'
|
|
4
5
|
import { logger } from '../utils/logger.mjs'
|
|
@@ -22,7 +23,7 @@ async function getApiKey(provider) {
|
|
|
22
23
|
// API key is valid. Returns { ok: true } on success; throws on failure.
|
|
23
24
|
async function probeModelConnection(model, apiKeyOverride) {
|
|
24
25
|
const apiKey = apiKeyOverride || (await getApiKey(model?.provider))
|
|
25
|
-
const stream =
|
|
26
|
+
const stream = streamSimpleWithAiHttpLogging(
|
|
26
27
|
model,
|
|
27
28
|
{
|
|
28
29
|
systemPrompt: 'You are a connectivity test. Reply with a single word.',
|
|
@@ -39,11 +40,19 @@ async function probeModelConnection(model, apiKeyOverride) {
|
|
|
39
40
|
maxRetryDelayMs: 30000,
|
|
40
41
|
},
|
|
41
42
|
)
|
|
42
|
-
await stream.result()
|
|
43
|
+
const message = await stream.result()
|
|
44
|
+
if (message?.stopReason === 'error' || message?.stopReason === 'aborted') {
|
|
45
|
+
throw new Error(message.errorMessage || `Model request ${message.stopReason}`)
|
|
46
|
+
}
|
|
43
47
|
return { ok: true }
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
export async function handleModelsApi(req, res, url) {
|
|
50
|
+
export async function handleModelsApi(req, res, url, context = {}) {
|
|
51
|
+
if (req.method === 'GET' && url.pathname === '/api/models/catalog') {
|
|
52
|
+
sendJson(res, 200, { models: await listModelCatalog({ context, refreshCloud: url.searchParams.get('refresh') === 'true' }) })
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
if (req.method === 'POST' && url.pathname === '/api/models/test-connection') {
|
|
48
57
|
const body = await readJsonBody(req)
|
|
49
58
|
const model = body?.model
|
|
@@ -8,7 +8,25 @@ import { getWorkspaceRoot, setWorkspaceRoot } from '../utils/workspace.mjs'
|
|
|
8
8
|
import { selectDirectoryDialog, openPathInFileManager, openPathInIDEA, openPathInVSCode } from '../utils/platform.mjs'
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function assertLocalExternalAction(context) {
|
|
12
|
+
if (context.isLocalRequest === true) return
|
|
13
|
+
const error = new Error('Opening applications is only allowed from this computer')
|
|
14
|
+
error.statusCode = 403
|
|
15
|
+
throw error
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleProjectApi(req, res, url, context = {}) {
|
|
19
|
+
const externalAction = req.method === 'POST' && (
|
|
20
|
+
url.pathname === '/api/project/select-directory'
|
|
21
|
+
|| (url.pathname.startsWith('/api/project/') && (
|
|
22
|
+
url.pathname.endsWith('/open-in-explorer')
|
|
23
|
+
|| url.pathname.endsWith('/open-in-vscode')
|
|
24
|
+
|| url.pathname.endsWith('/open-in-idea')
|
|
25
|
+
))
|
|
26
|
+
|| url.pathname === '/api/project/open-path'
|
|
27
|
+
)
|
|
28
|
+
if (externalAction) assertLocalExternalAction(context)
|
|
29
|
+
|
|
12
30
|
const config = await readProjectConfig()
|
|
13
31
|
|
|
14
32
|
if (req.method === 'GET' && url.pathname === '/api/project') {
|