@shawnstack/quickforge 1.7.4 → 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 (239) hide show
  1. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  2. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  3. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  4. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  5. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  6. package/dist/assets/{SettingsWorkspacePage-B53_eDf2.js → SettingsWorkspacePage-Cb54Ic91.js} +389 -378
  7. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  8. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  9. package/dist/assets/{TerminalDock-Dgg-_ZmU.js → TerminalDock-D4zJBJc4.js} +2 -2
  10. package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  12. package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  16. package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  17. package/dist/assets/channel-Duu1CuH9.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  19. package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  20. package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  21. package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  22. package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  23. package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  24. package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  26. package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  27. package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  29. package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  30. package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  31. package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  32. package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  33. package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  34. package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  35. package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  36. package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  37. package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  38. package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  39. package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  41. package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  42. package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  43. package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  44. package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  45. package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  46. package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  47. package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  48. package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  49. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  50. package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  51. package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  52. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  53. package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  54. package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  55. package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  56. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  57. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  58. package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  59. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  60. package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  61. package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
  62. package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  68. package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  69. package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  70. package/dist/assets/{esm-oa69b5u4.js → esm-Df4edDpj.js} +2 -2
  71. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  72. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  73. package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  74. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  75. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  76. package/dist/assets/icons-CXPP1fFT.js +1 -0
  77. package/dist/assets/index-_ZhK6bvp.js +65 -0
  78. package/dist/assets/index-iVV2bP_f.css +3 -0
  79. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  80. package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  81. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  82. package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  83. package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  84. package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
  85. package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
  86. package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
  87. package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  88. package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  89. package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  90. package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
  91. package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  92. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  93. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  94. package/dist/assets/{monaco-BVtCspYZ.js → monaco-D6OhXgjg.js} +1 -1
  95. package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
  96. package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
  97. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  98. package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  99. package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
  100. package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
  101. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  102. package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  103. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  104. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  105. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  106. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  107. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  108. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  109. package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  110. package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-1KC5PyRx.js} +1 -1
  111. package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  112. package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  113. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  114. package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  115. package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BThnIUBs.js} +3 -3
  116. package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
  117. package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  118. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  119. package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  120. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  121. package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  122. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  123. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  124. package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  125. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  126. package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  127. package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  128. package/dist/index.html +9 -8
  129. package/package.json +3 -1
  130. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  131. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  132. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  133. package/server/acp/server.mjs +36 -26
  134. package/server/agent-manager.mjs +130 -28
  135. package/server/agent-profile-files.mjs +8 -4
  136. package/server/agent-profile-schema.mjs +47 -23
  137. package/server/agent-profiles.mjs +1 -1
  138. package/server/ai-http-logger.mjs +98 -12
  139. package/server/cloud/chat-idempotency-store.mjs +96 -0
  140. package/server/cloud/client.mjs +102 -0
  141. package/server/cloud/config.mjs +53 -0
  142. package/server/cloud/credential-store.mjs +193 -0
  143. package/server/cloud/identity.mjs +355 -0
  144. package/server/cloud/index.mjs +7 -0
  145. package/server/cloud/models.mjs +80 -0
  146. package/server/cloud/runtime.mjs +129 -0
  147. package/server/cloud/service-config.mjs +77 -0
  148. package/server/index.mjs +28 -11
  149. package/server/model-catalog.mjs +262 -0
  150. package/server/plugins/registry.mjs +29 -5
  151. package/server/routes/agent-profiles.mjs +41 -7
  152. package/server/routes/agent.mjs +20 -11
  153. package/server/routes/backup.mjs +7 -1
  154. package/server/routes/cloud.mjs +227 -0
  155. package/server/routes/models.mjs +7 -1
  156. package/server/routes/scheduled-tasks.mjs +38 -8
  157. package/server/routes/shared-conversation.mjs +62 -58
  158. package/server/routes/shares.mjs +76 -1
  159. package/server/routes/storage.mjs +24 -1
  160. package/server/routes/workspace.mjs +6 -4
  161. package/server/share-store.mjs +168 -13
  162. package/server/skills.mjs +30 -3
  163. package/server/storage.mjs +7 -1
  164. package/server/subagents.mjs +3 -3
  165. package/server/tools/definitions.mjs +1 -1
  166. package/server/tools/index.mjs +2 -2
  167. package/server/utils/network.mjs +9 -0
  168. package/server/utils/response.mjs +8 -1
  169. package/skills/skill-creator/LICENSE.txt +202 -0
  170. package/skills/skill-creator/SKILL.md +485 -0
  171. package/skills/skill-creator/UPSTREAM.md +6 -0
  172. package/skills/skill-creator/agents/analyzer.md +274 -0
  173. package/skills/skill-creator/agents/comparator.md +202 -0
  174. package/skills/skill-creator/agents/grader.md +223 -0
  175. package/skills/skill-creator/assets/eval_review.html +146 -0
  176. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  177. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  178. package/skills/skill-creator/references/schemas.md +430 -0
  179. package/skills/skill-creator/scripts/__init__.py +0 -0
  180. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  181. package/skills/skill-creator/scripts/generate_report.py +326 -0
  182. package/skills/skill-creator/scripts/improve_description.py +247 -0
  183. package/skills/skill-creator/scripts/package_skill.py +136 -0
  184. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  185. package/skills/skill-creator/scripts/run_eval.py +310 -0
  186. package/skills/skill-creator/scripts/run_loop.py +328 -0
  187. package/skills/skill-creator/scripts/utils.py +47 -0
  188. package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
  189. package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
  190. package/dist/assets/PluginsPage-D2862wul.js +0 -1
  191. package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
  192. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
  193. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
  194. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
  195. package/dist/assets/channel-DToA8d-4.js +0 -1
  196. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
  197. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
  198. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
  199. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
  200. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
  201. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
  202. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
  203. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
  204. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
  205. package/dist/assets/icons-cTkqD_Gi.js +0 -1
  206. package/dist/assets/index-B48prgLL.js +0 -65
  207. package/dist/assets/index-DQhwHHer.css +0 -3
  208. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
  209. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
  210. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
  211. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
  212. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
  213. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
  214. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
  215. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
  216. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
  217. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
  218. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
  219. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
  220. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
  221. /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  222. /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  223. /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  224. /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
  225. /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  226. /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
  227. /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
  228. /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  229. /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  230. /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
  231. /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
  232. /package/dist/assets/{web-RgX7H-OH.js → web-DoV8MSMn.js} +0 -0
  233. /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
  234. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  235. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  236. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  237. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  238. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  239. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -3,6 +3,8 @@ import path from 'node:path'
3
3
  import { AsyncLocalStorage } from 'node:async_hooks'
4
4
  import { randomUUID } from 'node:crypto'
5
5
  import { streamSimple } from '@earendil-works/pi-ai/compat'
6
+ import { resolveManagedCloudProvider } from './cloud/runtime.mjs'
7
+ import { ensureCloudChatIdempotencyKey } from './cloud/chat-idempotency-store.mjs'
6
8
  import {
7
9
  DEFAULT_AI_STREAM_DEADLINE_MS,
8
10
  withDefaultAiProviderOptions,
@@ -45,14 +47,27 @@ function writeAiHttpRecord(record) {
45
47
  })
46
48
  }
47
49
 
48
- function headersToRecord(headers) {
50
+ const SENSITIVE_HEADER_NAMES = new Set([
51
+ 'authorization',
52
+ 'proxy-authorization',
53
+ 'x-api-key',
54
+ 'api-key',
55
+ 'idempotency-key',
56
+ 'cookie',
57
+ 'set-cookie',
58
+ ])
59
+
60
+ export function sanitizeHttpHeaders(headers) {
49
61
  const result = {}
50
62
  if (!headers) return result
51
63
 
52
64
  try {
53
65
  const iterable = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers)
54
66
  for (const [key, value] of iterable) {
55
- result[String(key)] = Array.isArray(value) ? value.join(', ') : String(value)
67
+ const name = String(key)
68
+ result[name] = SENSITIVE_HEADER_NAMES.has(name.toLowerCase())
69
+ ? '[REDACTED]'
70
+ : Array.isArray(value) ? value.join(', ') : String(value)
56
71
  }
57
72
  } catch {
58
73
  // ignore malformed headers
@@ -61,6 +76,10 @@ function headersToRecord(headers) {
61
76
  return result
62
77
  }
63
78
 
79
+ function headersToRecord(headers) {
80
+ return sanitizeHttpHeaders(headers)
81
+ }
82
+
64
83
  function isRequest(value) {
65
84
  return typeof Request !== 'undefined' && value instanceof Request
66
85
  }
@@ -253,6 +272,63 @@ function wrapStreamWithDeadline(stream, deadlineController, deadlineMs) {
253
272
  }
254
273
  }
255
274
 
275
+ function lazyStream(streamPromise) {
276
+ return {
277
+ result: () => streamPromise.then((stream) => stream.result()),
278
+ [Symbol.asyncIterator]() {
279
+ let iteratorPromise
280
+ const iterator = () => {
281
+ iteratorPromise ||= streamPromise.then((stream) => stream[Symbol.asyncIterator]())
282
+ return iteratorPromise
283
+ }
284
+ return {
285
+ next: async () => (await iterator()).next(),
286
+ return: async () => {
287
+ const resolved = await iterator()
288
+ if (typeof resolved.return === 'function') return resolved.return()
289
+ return { value: undefined, done: true }
290
+ },
291
+ }
292
+ },
293
+ }
294
+ }
295
+
296
+ function createProviderStream(model, context, options) {
297
+ return aiHttpLogEnabled
298
+ ? aiHttpContext.run({
299
+ traceId: randomUUID(),
300
+ sessionId: options.sessionId,
301
+ purpose: options.metadata?.quickforgePurpose || 'chat',
302
+ provider: model?.provider,
303
+ api: model?.api,
304
+ model: model?.id,
305
+ }, () => streamSimple(model, context, options))
306
+ : streamSimple(model, context, options)
307
+ }
308
+
309
+ const CLIENT_MESSAGE_ID_FIELD = 'quickforgeClientMessageId'
310
+
311
+ function logicalMessageIdFromContext(context) {
312
+ const messages = Array.isArray(context?.messages) ? context.messages : []
313
+ for (let index = messages.length - 1; index >= 0; index--) {
314
+ const message = messages[index]
315
+ if (message?.role !== 'user' && message?.role !== 'user-with-attachments') continue
316
+ const metadata = message.metadata
317
+ const messageId = metadata && typeof metadata === 'object' && !Array.isArray(metadata)
318
+ ? metadata[CLIENT_MESSAGE_ID_FIELD]
319
+ : undefined
320
+ return typeof messageId === 'string' && messageId ? messageId : undefined
321
+ }
322
+ return undefined
323
+ }
324
+
325
+ async function managedCloudIdempotencyKey(context, options) {
326
+ const messageId = logicalMessageIdFromContext(context)
327
+ return options.sessionId && messageId
328
+ ? ensureCloudChatIdempotencyKey(options.sessionId, messageId)
329
+ : randomUUID()
330
+ }
331
+
256
332
  export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
257
333
  const providerOptions = withDefaultAiProviderOptions(options)
258
334
  const deadlineMs = Number.isFinite(providerOptions.deadlineMs)
@@ -265,16 +341,26 @@ export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
265
341
  }
266
342
  delete effectiveOptions.deadlineMs
267
343
 
268
- const stream = aiHttpLogEnabled
269
- ? aiHttpContext.run({
270
- traceId: randomUUID(),
271
- sessionId: effectiveOptions.sessionId,
272
- purpose: effectiveOptions.metadata?.quickforgePurpose || 'chat',
273
- provider: model?.provider,
274
- api: model?.api,
275
- model: model?.id,
276
- }, () => streamSimple(model, context, effectiveOptions))
277
- : streamSimple(model, context, effectiveOptions)
344
+ const managedCloud = model?.provider === 'quickforge-cloud' && model?.quickforgeModelSource === 'cloud'
345
+ const stream = managedCloud
346
+ ? lazyStream(Promise.all([
347
+ resolveManagedCloudProvider(model, effectiveOptions.signal),
348
+ managedCloudIdempotencyKey(context, effectiveOptions),
349
+ ]).then(([resolved, idempotencyKey]) => createProviderStream(
350
+ {
351
+ ...resolved.model,
352
+ headers: {
353
+ ...(resolved.model.headers || {}),
354
+ 'Idempotency-Key': idempotencyKey,
355
+ },
356
+ },
357
+ context,
358
+ {
359
+ ...effectiveOptions,
360
+ apiKey: resolved.apiKey,
361
+ },
362
+ )))
363
+ : createProviderStream(model, context, effectiveOptions)
278
364
 
279
365
  return wrapStreamWithDeadline(stream, deadlineController, deadlineMs)
280
366
  }
@@ -0,0 +1,96 @@
1
+ import { createHash, randomUUID } from 'node:crypto'
2
+ import { promises as fs } from 'node:fs'
3
+ import path from 'node:path'
4
+ import { ensureStorage, storageDir } from '../storage.mjs'
5
+
6
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
7
+ const MESSAGE_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/
8
+
9
+ function digest(value) {
10
+ return createHash('sha256').update(value).digest('hex')
11
+ }
12
+
13
+ function assertIdentifier(value, label, pattern) {
14
+ const normalized = typeof value === 'string' ? value.trim() : ''
15
+ if (!pattern.test(normalized)) throw new Error(`Invalid ${label}.`)
16
+ return normalized
17
+ }
18
+
19
+ export function createCloudChatIdempotencyStore({
20
+ directory = path.join(storageDir, 'security', 'cloud-chat-idempotency'),
21
+ ensureBaseStorage = ensureStorage,
22
+ } = {}) {
23
+ function entryPath(sessionId, messageId) {
24
+ return path.join(directory, digest(sessionId), `${digest(messageId)}.json`)
25
+ }
26
+
27
+ async function ensureDirectory(file) {
28
+ await ensureBaseStorage()
29
+ await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 })
30
+ }
31
+
32
+ async function readEntry(file, sessionId, messageId) {
33
+ try {
34
+ const raw = await fs.readFile(file, 'utf8')
35
+ const entry = raw.trim() ? JSON.parse(raw) : null
36
+ if (entry?.sessionId !== sessionId || entry?.messageId !== messageId || !UUID_PATTERN.test(entry?.idempotencyKey || '')) {
37
+ throw new Error('Invalid QuickForge Cloud chat idempotency record.')
38
+ }
39
+ return entry
40
+ } catch (error) {
41
+ if (error?.code === 'ENOENT') return null
42
+ throw error
43
+ }
44
+ }
45
+
46
+ async function ensure(sessionIdInput, messageIdInput) {
47
+ const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
48
+ const messageId = assertIdentifier(messageIdInput, 'logical message ID', MESSAGE_ID_PATTERN)
49
+ const file = entryPath(sessionId, messageId)
50
+ await ensureDirectory(file)
51
+
52
+ const existing = await readEntry(file, sessionId, messageId)
53
+ if (existing) return existing.idempotencyKey
54
+
55
+ const entry = {
56
+ schemaVersion: 1,
57
+ sessionId,
58
+ messageId,
59
+ idempotencyKey: randomUUID(),
60
+ createdAt: new Date().toISOString(),
61
+ }
62
+
63
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`
64
+ await fs.writeFile(temporary, `${JSON.stringify(entry, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 })
65
+ try { await fs.chmod(temporary, 0o600) } catch { /* best effort on Windows */ }
66
+ try {
67
+ await fs.link(temporary, file)
68
+ try { await fs.chmod(file, 0o600) } catch { /* best effort on Windows */ }
69
+ return entry.idempotencyKey
70
+ } catch (error) {
71
+ if (error?.code !== 'EEXIST') throw error
72
+ const concurrent = await readEntry(file, sessionId, messageId)
73
+ if (!concurrent) throw error
74
+ return concurrent.idempotencyKey
75
+ } finally {
76
+ await fs.rm(temporary, { force: true })
77
+ }
78
+ }
79
+
80
+ async function removeSession(sessionIdInput) {
81
+ const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
82
+ await fs.rm(path.join(directory, digest(sessionId)), { recursive: true, force: true })
83
+ }
84
+
85
+ return { directory, ensure, removeSession }
86
+ }
87
+
88
+ const defaultStore = createCloudChatIdempotencyStore()
89
+
90
+ export function ensureCloudChatIdempotencyKey(sessionId, messageId) {
91
+ return defaultStore.ensure(sessionId, messageId)
92
+ }
93
+
94
+ export function deleteCloudChatIdempotencySession(sessionId) {
95
+ return defaultStore.removeSession(sessionId)
96
+ }
@@ -0,0 +1,102 @@
1
+ import { cloudEndpoint } from './config.mjs'
2
+
3
+ export class CloudApiError extends Error {
4
+ constructor(message, { status = 0, code = 'cloud_request_failed', retryable = false, details } = {}) {
5
+ super(message)
6
+ this.name = 'CloudApiError'
7
+ this.status = status
8
+ this.code = code
9
+ this.retryable = retryable
10
+ this.details = details
11
+ }
12
+ }
13
+
14
+ async function parseError(response) {
15
+ let payload
16
+ try { payload = await response.json() } catch { payload = undefined }
17
+ const error = payload?.error
18
+ const structured = error && typeof error === 'object' ? error : undefined
19
+ const oauthCode = typeof error === 'string' ? error : undefined
20
+ return new CloudApiError(
21
+ structured?.message || payload?.error_description || `QuickForge Cloud request failed (${response.status}).`,
22
+ {
23
+ status: response.status,
24
+ code: structured?.code || payload?.code || oauthCode || 'cloud_request_failed',
25
+ retryable: Boolean(structured?.retryable || payload?.retryable),
26
+ details: structured?.details || payload?.details,
27
+ },
28
+ )
29
+ }
30
+
31
+ function timeoutSignal(timeoutMs, outerSignal) {
32
+ const timeout = AbortSignal.timeout(timeoutMs)
33
+ return outerSignal ? AbortSignal.any([outerSignal, timeout]) : timeout
34
+ }
35
+
36
+ export class CloudClient {
37
+ constructor({ baseUrl, timeoutMs = 10_000, fetchImpl = fetch } = {}) {
38
+ if (!(baseUrl instanceof URL)) throw new Error('CloudClient requires a configured base URL.')
39
+ this.baseUrl = baseUrl
40
+ this.timeoutMs = timeoutMs
41
+ this.fetch = fetchImpl
42
+ }
43
+
44
+ async request(pathname, { method = 'GET', token, body, headers, signal, raw = false, timeout = true } = {}) {
45
+ const requestHeaders = { Accept: 'application/json', ...headers }
46
+ if (body !== undefined) requestHeaders['Content-Type'] = 'application/json'
47
+ if (token) requestHeaders.Authorization = `Bearer ${token}`
48
+ const response = await this.fetch(cloudEndpoint(this.baseUrl, pathname), {
49
+ method,
50
+ headers: requestHeaders,
51
+ body: body === undefined ? undefined : JSON.stringify(body),
52
+ signal: timeout ? timeoutSignal(this.timeoutMs, signal) : signal,
53
+ redirect: 'error',
54
+ })
55
+ if (!response.ok) throw await parseError(response)
56
+ if (raw) return response
57
+ if (response.status === 204) return undefined
58
+ return response.json()
59
+ }
60
+
61
+ health(signal) { return this.request('healthz', { signal }) }
62
+ ready(signal) { return this.request('readyz', { signal }) }
63
+ registerGuest(input, idempotencyKey, signal) {
64
+ return this.request('v1/guest/installations', {
65
+ method: 'POST', body: input, headers: { 'Idempotency-Key': idempotencyKey }, signal,
66
+ })
67
+ }
68
+ refresh(refreshToken, signal) {
69
+ return this.request('oauth/token', {
70
+ method: 'POST', body: { grantType: 'refresh_token', refreshToken }, signal,
71
+ })
72
+ }
73
+ authorizeDevice(token, installationId, clientId = 'quickforge-desktop', signal) {
74
+ return this.request('oauth/device_authorization', {
75
+ method: 'POST', token, body: { installationId, clientId }, signal,
76
+ })
77
+ }
78
+ exchangeDeviceCode(deviceCode, clientId = 'quickforge-desktop', signal) {
79
+ return this.request('oauth/token', {
80
+ method: 'POST',
81
+ body: {
82
+ grantType: 'urn:ietf:params:oauth:grant-type:device_code',
83
+ deviceCode,
84
+ clientId,
85
+ },
86
+ signal,
87
+ })
88
+ }
89
+ me(token, signal) { return this.request('v1/me', { token, signal }) }
90
+ models(token, signal) { return this.request('v1/models', { token, signal }) }
91
+ usage(token, signal) { return this.request('v1/usage', { token, signal }) }
92
+ installations(token, signal) { return this.request('v1/installations', { token, signal }) }
93
+ revokeInstallation(token, installationId, signal) {
94
+ return this.request(`v1/installations/${encodeURIComponent(installationId)}`, { method: 'DELETE', token, signal })
95
+ }
96
+ chat(token, payload, idempotencyKey, signal) {
97
+ return this.request('v1/chat/completions', {
98
+ method: 'POST', token, body: payload,
99
+ headers: { 'Idempotency-Key': idempotencyKey }, signal, raw: true, timeout: false,
100
+ })
101
+ }
102
+ }
@@ -0,0 +1,53 @@
1
+ import net from 'node:net'
2
+
3
+ const DEFAULT_TIMEOUT_MS = 10_000
4
+
5
+ function isLoopbackHost(hostname) {
6
+ const host = String(hostname || '').toLowerCase()
7
+ if (host === 'localhost' || host.endsWith('.localhost')) return true
8
+ const ip = net.isIP(host)
9
+ if (ip === 4) return host.startsWith('127.')
10
+ if (ip === 6) return host === '::1' || host === '0:0:0:0:0:0:0:1'
11
+ return false
12
+ }
13
+
14
+ export function parseCloudBaseUrl(value, _options = {}) {
15
+ const raw = typeof value === 'string' ? value.trim() : ''
16
+ if (!raw) return undefined
17
+
18
+ let url
19
+ try {
20
+ url = new URL(raw)
21
+ } catch {
22
+ throw new Error('QUICKFORGE_CLOUD_URL must be a valid URL.')
23
+ }
24
+
25
+ if (url.username || url.password || url.search || url.hash) {
26
+ throw new Error('QUICKFORGE_CLOUD_URL must not contain credentials, query parameters, or fragments.')
27
+ }
28
+ if (url.protocol !== 'https:') {
29
+ if (!(url.protocol === 'http:' && isLoopbackHost(url.hostname))) {
30
+ throw new Error('QUICKFORGE_CLOUD_URL must use HTTPS. HTTP is allowed only for loopback addresses.')
31
+ }
32
+ }
33
+ if (url.pathname.includes('..')) throw new Error('QUICKFORGE_CLOUD_URL contains an unsafe path.')
34
+ url.pathname = `${url.pathname.replace(/\/+$/, '')}/`
35
+ return url
36
+ }
37
+
38
+ export function cloudEndpoint(baseUrl, pathname) {
39
+ if (!(baseUrl instanceof URL)) throw new Error('QuickForge Cloud is not configured.')
40
+ const relative = String(pathname || '').replace(/^\/+/, '')
41
+ if (!relative || relative.split('/').includes('..')) throw new Error('Invalid QuickForge Cloud endpoint.')
42
+ return new URL(relative, baseUrl)
43
+ }
44
+
45
+ export function readCloudConfig(env = process.env) {
46
+ const baseUrl = parseCloudBaseUrl(env.QUICKFORGE_CLOUD_URL)
47
+ const timeoutValue = Number(env.QUICKFORGE_CLOUD_TIMEOUT_MS || DEFAULT_TIMEOUT_MS)
48
+ return {
49
+ enabled: Boolean(baseUrl),
50
+ baseUrl,
51
+ timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : DEFAULT_TIMEOUT_MS,
52
+ }
53
+ }
@@ -0,0 +1,193 @@
1
+ import { generateKeyPair } from 'node:crypto'
2
+ import { promises as fs } from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { promisify } from 'node:util'
6
+ import { createRandomToken } from '../utils/password-auth.mjs'
7
+ import { ensureStorage, storageDir } from '../storage.mjs'
8
+
9
+ const generateKeyPairAsync = promisify(generateKeyPair)
10
+ const SCHEMA_VERSION = 1
11
+ const ACCOUNT_FIELDS = ['id', 'email', 'plan']
12
+
13
+ function publicAccount(value) {
14
+ if (!value || typeof value !== 'object') return undefined
15
+ const account = {}
16
+ for (const field of ACCOUNT_FIELDS) {
17
+ const candidate = value[field]
18
+ if (typeof candidate === 'string' && candidate) account[field] = candidate
19
+ }
20
+ return Object.keys(account).length ? account : undefined
21
+ }
22
+
23
+ function publicPendingDeviceFlow(value) {
24
+ if (!value || typeof value !== 'object') return undefined
25
+ const expiresAt = Number(value.expiresAt)
26
+ if (!Number.isFinite(expiresAt)) return undefined
27
+ return {
28
+ userCode: String(value.userCode || ''),
29
+ verificationUri: String(value.verificationUri || ''),
30
+ verificationUriComplete: String(value.verificationUriComplete || ''),
31
+ expiresAt,
32
+ interval: Math.max(1, Number(value.interval) || 5),
33
+ status: String(value.status || 'pending'),
34
+ }
35
+ }
36
+
37
+ function platformName(platform = process.platform) {
38
+ if (platform === 'win32') return 'windows'
39
+ if (platform === 'darwin') return 'macos'
40
+ return 'linux'
41
+ }
42
+
43
+ function defaultRecord({ installationName, platform, clientVersion } = {}) {
44
+ return {
45
+ schemaVersion: SCHEMA_VERSION,
46
+ mode: 'local',
47
+ installationName: installationName || os.hostname() || 'QuickForge',
48
+ platform: platformName(platform),
49
+ clientVersion: clientVersion || 'unknown',
50
+ updatedAt: new Date().toISOString(),
51
+ }
52
+ }
53
+
54
+ function normalizeRecord(value, defaults) {
55
+ const input = value && typeof value === 'object' ? value : {}
56
+ const base = defaultRecord(defaults)
57
+ return {
58
+ ...base,
59
+ ...input,
60
+ schemaVersion: SCHEMA_VERSION,
61
+ mode: ['local', 'guest', 'account'].includes(input.mode) ? input.mode : 'local',
62
+ installationName: String(input.installationName || base.installationName).slice(0, 120),
63
+ platform: ['windows', 'macos', 'linux'].includes(input.platform) ? input.platform : base.platform,
64
+ clientVersion: String(input.clientVersion || base.clientVersion).slice(0, 80),
65
+ }
66
+ }
67
+
68
+ function publicRecord(record) {
69
+ return {
70
+ configured: true,
71
+ mode: record.mode,
72
+ installationId: record.installationId,
73
+ installationName: record.installationName,
74
+ platform: record.platform,
75
+ clientVersion: record.clientVersion,
76
+ hasInstallationKey: Boolean(record.publicKey && record.privateKeyPkcs8),
77
+ hasSession: Boolean(record.refreshToken),
78
+ account: publicAccount(record.account),
79
+ pendingDeviceFlow: publicPendingDeviceFlow(record.pendingDeviceFlow),
80
+ updatedAt: record.updatedAt,
81
+ }
82
+ }
83
+
84
+ async function generateInstallationKeyPair() {
85
+ const { publicKey, privateKey } = await generateKeyPairAsync('ed25519')
86
+ const publicJwk = publicKey.export({ format: 'jwk' })
87
+ return {
88
+ publicKey: publicJwk.x,
89
+ privateKeyPkcs8: privateKey.export({ format: 'der', type: 'pkcs8' }).toString('base64url'),
90
+ }
91
+ }
92
+
93
+ export function createCloudCredentialStore({
94
+ filePath = path.join(storageDir, 'security', 'cloud-identity.json'),
95
+ ensureBaseStorage = ensureStorage,
96
+ installationName,
97
+ platform,
98
+ clientVersion,
99
+ } = {}) {
100
+ let queue = Promise.resolve()
101
+ const defaults = { installationName, platform, clientVersion }
102
+
103
+ async function ensureDirectory() {
104
+ await ensureBaseStorage()
105
+ await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 })
106
+ }
107
+
108
+ async function read() {
109
+ await ensureDirectory()
110
+ try {
111
+ const raw = await fs.readFile(filePath, 'utf8')
112
+ return normalizeRecord(raw.trim() ? JSON.parse(raw) : {}, defaults)
113
+ } catch (error) {
114
+ if (error?.code === 'ENOENT') return defaultRecord(defaults)
115
+ throw error
116
+ }
117
+ }
118
+
119
+ async function writeNow(record) {
120
+ await ensureDirectory()
121
+ const normalized = normalizeRecord({ ...record, updatedAt: new Date().toISOString() }, defaults)
122
+ const temporary = `${filePath}.${process.pid}.${createRandomToken(8)}.tmp`
123
+ await fs.writeFile(temporary, `${JSON.stringify(normalized, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
124
+ try { await fs.chmod(temporary, 0o600) } catch { /* best effort on Windows */ }
125
+ try {
126
+ await fs.rename(temporary, filePath)
127
+ } catch (error) {
128
+ if (!['EEXIST', 'EPERM'].includes(error?.code)) throw error
129
+ await fs.rm(filePath, { force: true })
130
+ await fs.rename(temporary, filePath)
131
+ }
132
+ try { await fs.chmod(filePath, 0o600) } catch { /* best effort on Windows */ }
133
+ return normalized
134
+ }
135
+
136
+ function update(mutator) {
137
+ const next = queue.catch(() => undefined).then(async () => {
138
+ const current = await read()
139
+ const result = await mutator({ ...current })
140
+ return writeNow(result || current)
141
+ })
142
+ queue = next
143
+ return next
144
+ }
145
+
146
+ async function ensureInstallation() {
147
+ const current = await read()
148
+ if (current.publicKey && current.privateKeyPkcs8) return current
149
+ return update(async (record) => ({ ...record, ...(await generateInstallationKeyPair()) }))
150
+ }
151
+
152
+ async function rotateInstallation() {
153
+ const keyPair = await generateInstallationKeyPair()
154
+ return update((record) => ({
155
+ ...record,
156
+ ...keyPair,
157
+ mode: 'local',
158
+ installationId: undefined,
159
+ refreshToken: undefined,
160
+ sessionCloudUrl: undefined,
161
+ pendingRegistrationKey: undefined,
162
+ pendingRegistrationHash: undefined,
163
+ pendingDeviceFlow: undefined,
164
+ account: undefined,
165
+ rotateInstallationBeforeRegistration: undefined,
166
+ }))
167
+ }
168
+
169
+ return {
170
+ filePath,
171
+ read,
172
+ update,
173
+ ensureInstallation,
174
+ rotateInstallation,
175
+ async readPublic() { return publicRecord(await read()) },
176
+ async clearSession({ rotateInstallationBeforeRegistration = false } = {}) {
177
+ return update((record) => ({
178
+ ...record,
179
+ mode: 'local',
180
+ installationId: undefined,
181
+ refreshToken: undefined,
182
+ sessionCloudUrl: undefined,
183
+ pendingRegistrationKey: undefined,
184
+ pendingRegistrationHash: undefined,
185
+ pendingDeviceFlow: undefined,
186
+ account: undefined,
187
+ rotateInstallationBeforeRegistration: rotateInstallationBeforeRegistration || undefined,
188
+ }))
189
+ },
190
+ }
191
+ }
192
+
193
+ export { publicRecord as publicCloudCredentialRecord }