@shawnstack/quickforge 1.7.3 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +1 -1
  2. package/bin/quickforge.mjs +0 -21
  3. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  4. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  5. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  6. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  7. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  8. package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +1639 -0
  9. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  10. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  11. package/dist/assets/TerminalDock-D4zJBJc4.js +2 -0
  12. package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
  13. package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  14. package/dist/assets/{arc-Bhg4c0ht.js → arc-w21O0TLn.js} +1 -1
  15. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  16. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  17. package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  18. package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  19. package/dist/assets/channel-Duu1CuH9.js +1 -0
  20. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  21. package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  22. package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  23. package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  24. package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  25. package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  26. package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  27. package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  28. package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  29. package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  30. package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  31. package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  32. package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  33. package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  34. package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  35. package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  36. package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  37. package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  38. package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  39. package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  40. package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  41. package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  42. package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  43. package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  44. package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  45. package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  46. package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  47. package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  48. package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  49. package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  50. package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  51. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  52. package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  53. package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  54. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  55. package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  56. package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  57. package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  58. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  59. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  60. package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  61. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  62. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  63. package/dist/assets/{dagre-CY3D84Cl.js → dagre-DmHb3Ctt.js} +1 -1
  64. package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  65. package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  70. package/dist/assets/dist-DLKC_odd.js +1 -0
  71. package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  72. package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  73. package/dist/assets/esm-Df4edDpj.js +2 -0
  74. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  75. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  76. package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  77. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  79. package/dist/assets/icons-CXPP1fFT.js +1 -0
  80. package/dist/assets/index-_ZhK6bvp.js +65 -0
  81. package/dist/assets/index-iVV2bP_f.css +3 -0
  82. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  83. package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  84. package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  85. package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  86. package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  87. package/dist/assets/{line-ikKkQiFp.js → line-B4DxX7tC.js} +1 -1
  88. package/dist/assets/{linear-GqPyxQkN.js → linear-U-nKUhyW.js} +1 -1
  89. package/dist/assets/{map-CJwjCvwr.js → map-BneTM-ri.js} +1 -1
  90. package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  91. package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  92. package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  93. package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DWpShXCs.js} +3 -3
  94. package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  95. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  96. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  97. package/dist/assets/{monaco-BTsVCDWS.js → monaco-D6OhXgjg.js} +1 -1
  98. package/dist/assets/{ordinal-CibZU3PM.js → ordinal-B2r5L1v4.js} +1 -1
  99. package/dist/assets/{otel-BNGM50C-.js → otel-BFnphzjt.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  102. package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-Cr2ee4a0.js} +2 -2
  103. package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-DV5jKjSg.js} +1 -1
  104. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  105. package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  106. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  107. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  108. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  109. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  110. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  111. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  113. package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-1KC5PyRx.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  118. package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-BThnIUBs.js} +3 -1
  119. package/dist/assets/{src-B4Tvg3gT.js → src-PYsDme0X.js} +1 -1
  120. package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  121. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  122. package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  123. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  124. package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  125. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  126. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  127. package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  128. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  129. package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  130. package/dist/assets/web-DoV8MSMn.js +1 -0
  131. package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  132. package/dist/index.html +13 -11
  133. package/dist/mobile-error.html +24 -0
  134. package/package.json +6 -1
  135. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  136. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  137. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  138. package/server/acp/server.mjs +50 -35
  139. package/server/agent-manager.mjs +343 -46
  140. package/server/agent-profile-files.mjs +8 -4
  141. package/server/agent-profile-schema.mjs +47 -23
  142. package/server/agent-profiles.mjs +1 -1
  143. package/server/ai-http-logger.mjs +156 -13
  144. package/server/ai-provider-options.mjs +3 -0
  145. package/server/channels/channel-logs.mjs +13 -0
  146. package/server/channels/event-relay.mjs +33 -0
  147. package/server/channels/process-channel.mjs +29 -0
  148. package/server/channels/providers/wechat.mjs +9 -3
  149. package/server/channels/registry.mjs +5 -1
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +102 -0
  152. package/server/cloud/config.mjs +53 -0
  153. package/server/cloud/credential-store.mjs +193 -0
  154. package/server/cloud/identity.mjs +355 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/runtime.mjs +129 -0
  158. package/server/cloud/service-config.mjs +77 -0
  159. package/server/index.mjs +63 -33
  160. package/server/lan-access-store.mjs +93 -3
  161. package/server/model-catalog.mjs +262 -0
  162. package/server/network-proxy.mjs +68 -3
  163. package/server/plugins/registry.mjs +93 -9
  164. package/server/public-api.mjs +6 -2
  165. package/server/routes/agent-profiles.mjs +43 -9
  166. package/server/routes/agent.mjs +48 -11
  167. package/server/routes/backup.mjs +7 -1
  168. package/server/routes/channels.mjs +47 -0
  169. package/server/routes/cloud.mjs +227 -0
  170. package/server/routes/lan-access.mjs +246 -21
  171. package/server/routes/models.mjs +13 -4
  172. package/server/routes/project.mjs +19 -1
  173. package/server/routes/scheduled-tasks.mjs +40 -10
  174. package/server/routes/shared-conversation.mjs +62 -58
  175. package/server/routes/shares.mjs +76 -1
  176. package/server/routes/static.mjs +26 -1
  177. package/server/routes/storage.mjs +24 -1
  178. package/server/routes/system.mjs +11 -0
  179. package/server/routes/terminal.mjs +7 -2
  180. package/server/routes/workspace.mjs +66 -13
  181. package/server/session-utils.mjs +2 -2
  182. package/server/share-store.mjs +168 -13
  183. package/server/skills.mjs +30 -3
  184. package/server/storage.mjs +7 -1
  185. package/server/subagents.mjs +4 -4
  186. package/server/terminal/terminal-manager.mjs +2 -4
  187. package/server/tools/definitions.mjs +3 -3
  188. package/server/tools/index.mjs +7 -5
  189. package/server/utils/network.mjs +9 -0
  190. package/server/utils/package-update.mjs +70 -47
  191. package/server/utils/process-env.mjs +7 -0
  192. package/server/utils/response.mjs +10 -3
  193. package/skills/skill-creator/LICENSE.txt +202 -0
  194. package/skills/skill-creator/SKILL.md +485 -0
  195. package/skills/skill-creator/UPSTREAM.md +6 -0
  196. package/skills/skill-creator/agents/analyzer.md +274 -0
  197. package/skills/skill-creator/agents/comparator.md +202 -0
  198. package/skills/skill-creator/agents/grader.md +223 -0
  199. package/skills/skill-creator/assets/eval_review.html +146 -0
  200. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  201. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  202. package/skills/skill-creator/references/schemas.md +430 -0
  203. package/skills/skill-creator/scripts/__init__.py +0 -0
  204. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  205. package/skills/skill-creator/scripts/generate_report.py +326 -0
  206. package/skills/skill-creator/scripts/improve_description.py +247 -0
  207. package/skills/skill-creator/scripts/package_skill.py +136 -0
  208. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  209. package/skills/skill-creator/scripts/run_eval.py +310 -0
  210. package/skills/skill-creator/scripts/run_loop.py +328 -0
  211. package/skills/skill-creator/scripts/utils.py +47 -0
  212. package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
  213. package/dist/assets/ChatPanelHost-wfGCaCzD.js +0 -288
  214. package/dist/assets/PluginsPage-NAGroBm3.js +0 -1
  215. package/dist/assets/ScheduledTasksPage-Bp0MNfQD.js +0 -2
  216. package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
  217. package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
  218. package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
  219. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
  220. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
  221. package/dist/assets/channel-Rz20dKTK.js +0 -1
  222. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
  223. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
  224. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
  225. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
  226. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
  227. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
  228. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
  229. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
  230. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
  231. package/dist/assets/icons-BP8YOS-Z.js +0 -1
  232. package/dist/assets/index-C6k5taeb.js +0 -66
  233. package/dist/assets/index-CzN8NSKC.css +0 -3
  234. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
  235. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
  236. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
  237. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
  238. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
  239. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
  240. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
  241. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
  242. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
  243. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
  244. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
  245. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
  246. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
  247. /package/dist/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
  248. /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  249. /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  250. /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
  251. /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-DyfqweF2.js} +0 -0
  252. /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/assets/{dist-DelZBZpK.js → dist-CLg13NtV.js} +0 -0
  254. /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-DJkmx0Cq.js} +0 -0
  255. /package/dist/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
  256. /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
  257. /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
  258. /package/dist/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
  259. /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-Uu2xAB4d.js} +0 -0
  260. /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
  261. /package/dist/assets/{xterm-CGiQzDiR.js → xterm-Bm_IbozX.js} +0 -0
  262. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  263. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  264. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  265. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  266. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  267. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -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 { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
108
- body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #0f172a; color: #e5e7eb; }
109
- main { width: min(420px, calc(100vw - 32px)); border: 1px solid rgba(148,163,184,.3); border-radius: 20px; padding: 28px; background: rgba(15,23,42,.92); box-shadow: 0 24px 80px rgba(0,0,0,.35); }
110
- h1 { margin: 0 0 8px; font-size: 22px; }
111
- p { margin: 0 0 18px; color: #94a3b8; line-height: 1.6; }
112
- label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 600; }
113
- input { width: 100%; box-sizing: border-box; height: 42px; border-radius: 10px; border: 1px solid #334155; background: #020617; color: #f8fafc; padding: 0 12px; font-size: 15px; }
114
- button { width: 100%; height: 42px; margin-top: 14px; border: 0; border-radius: 10px; background: #2563eb; color: white; font-weight: 700; cursor: pointer; }
115
- button:disabled { opacity: .6; cursor: default; }
116
- .error { min-height: 20px; margin-top: 12px; color: #fca5a5; font-size: 13px; }
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
- <h1>QuickForge 局域网访问</h1>
122
- <p>请输入本机设置中配置的局域网访问密码。</p>
123
- <label for="password">访问密码</label>
124
- <input id="password" type="password" autocomplete="current-password" autofocus />
125
- <button id="submit" type="button">进入 QuickForge</button>
126
- <div id="error" class="error" role="alert"></div>
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
- async function unlock() {
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()
@@ -1,4 +1,5 @@
1
- import { streamSimple } from '@earendil-works/pi-ai/compat'
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 = streamSimple(
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
- export async function handleProjectApi(req, res, url) {
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') {