@runweave/server-linux-x64 0.1.8 → 0.1.10

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 (357) hide show
  1. package/manifest.json +1494 -161
  2. package/native.tar.gz +0 -0
  3. package/package.json +1 -1
  4. package/payload/.agents/plugins/marketplace.json +20 -0
  5. package/payload/app-server/build-info.json +4 -4
  6. package/payload/app-server/index.cjs +6 -2
  7. package/payload/app-server/index.cjs.map +1 -1
  8. package/payload/backend/activity-sqlite-worker.cjs +2 -0
  9. package/payload/backend/build-info.json +4 -4
  10. package/payload/backend/dev-resources/control.cjs +5 -1
  11. package/payload/backend/index.cjs +466 -295
  12. package/payload/backend/ios-simulators/pool.py +3 -3
  13. package/payload/backend/scheduled-tasks-sqlite-worker.cjs +2 -0
  14. package/payload/frontend/dist/assets/{_baseUniq-vo4uuhMd.js → _baseUniq--CWv8e7O.js} +1 -1
  15. package/payload/frontend/dist/assets/{access-_jCu7Rgu.js → access-BCF23Qto.js} +1 -1
  16. package/payload/frontend/dist/assets/{advanced-CqOhObYj.js → advanced-D8viCLue.js} +1 -1
  17. package/payload/frontend/dist/assets/{appearance-CrrD5gzC.js → appearance-wPVMx-tB.js} +1 -1
  18. package/payload/frontend/dist/assets/{arc-dbfyHSF0.js → arc-BXffHD7A.js} +1 -1
  19. package/payload/frontend/dist/assets/{architectureDiagram-Q4EWVU46-CntSCAsO.js → architectureDiagram-Q4EWVU46-BXHN9QCj.js} +1 -1
  20. package/payload/frontend/dist/assets/{blockDiagram-DXYQGD6D-Chp0D4qy.js → blockDiagram-DXYQGD6D-DoZjab_0.js} +1 -1
  21. package/payload/frontend/dist/assets/{c4Diagram-AHTNJAMY-CF91pOuW.js → c4Diagram-AHTNJAMY-Ds2E62Z8.js} +1 -1
  22. package/payload/frontend/dist/assets/channel-83CTXkVl.js +1 -0
  23. package/payload/frontend/dist/assets/{chunk-4BX2VUAB-B1gxlVu2.js → chunk-4BX2VUAB-Dk1H2-5_.js} +1 -1
  24. package/payload/frontend/dist/assets/{chunk-4TB4RGXK-Cxt3mj9b.js → chunk-4TB4RGXK-Sg0gzSvg.js} +1 -1
  25. package/payload/frontend/dist/assets/{chunk-55IACEB6-BJT6qNik.js → chunk-55IACEB6-Du_EJG4S.js} +1 -1
  26. package/payload/frontend/dist/assets/{chunk-EDXVE4YY-yI59bCa0.js → chunk-EDXVE4YY-BM1Cs5dU.js} +1 -1
  27. package/payload/frontend/dist/assets/{chunk-FMBD7UC4-CDvncKKJ.js → chunk-FMBD7UC4-BM9thfyY.js} +1 -1
  28. package/payload/frontend/dist/assets/{chunk-OYMX7WX6-BblOIU57.js → chunk-OYMX7WX6-C9Bvn4fo.js} +1 -1
  29. package/payload/frontend/dist/assets/{chunk-QZHKN3VN-rODzNdO0.js → chunk-QZHKN3VN-CaakbFhJ.js} +1 -1
  30. package/payload/frontend/dist/assets/{chunk-YZCP3GAM-B6D4wQwk.js → chunk-YZCP3GAM-DkVNrd40.js} +1 -1
  31. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-oR8IYJwC.js +1 -0
  32. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-oR8IYJwC.js +1 -0
  33. package/payload/frontend/dist/assets/clone-BIDF-bd9.js +1 -0
  34. package/payload/frontend/dist/assets/connection-DIdF_3aa.js +12 -0
  35. package/payload/frontend/dist/assets/{connections-DqDmxS-O.js → connections-DBYC8SFl.js} +1 -1
  36. package/payload/frontend/dist/assets/{cose-bilkent-S5V4N54A-BvwVWp29.js → cose-bilkent-S5V4N54A-B4-fwllY.js} +1 -1
  37. package/payload/frontend/dist/assets/{dagre-KV5264BT-h_K1TH11.js → dagre-KV5264BT-Byu4wqLk.js} +1 -1
  38. package/payload/frontend/dist/assets/{desktop-BQ3kGtKG.js → desktop-aESkh0HL.js} +1 -1
  39. package/payload/frontend/dist/assets/{diagram-5BDNPKRD-B8g2SV6L.js → diagram-5BDNPKRD-DJW8DuCX.js} +1 -1
  40. package/payload/frontend/dist/assets/{diagram-G4DWMVQ6-BOEnrqYm.js → diagram-G4DWMVQ6-DTBvds5J.js} +1 -1
  41. package/payload/frontend/dist/assets/{diagram-MMDJMWI5-DlaN_z71.js → diagram-MMDJMWI5-D42e4xzm.js} +1 -1
  42. package/payload/frontend/dist/assets/{diagram-TYMM5635-C_pSKftn.js → diagram-TYMM5635-BjV43-C_.js} +1 -1
  43. package/payload/frontend/dist/assets/{erDiagram-SMLLAGMA-C2dL-h3I.js → erDiagram-SMLLAGMA-DE47Ibp_.js} +1 -1
  44. package/payload/frontend/dist/assets/{flowDiagram-DWJPFMVM-BmXhKWFj.js → flowDiagram-DWJPFMVM-CjGBlzS4.js} +1 -1
  45. package/payload/frontend/dist/assets/{ganttDiagram-T4ZO3ILL-D_Odnssf.js → ganttDiagram-T4ZO3ILL-vfj40KZi.js} +1 -1
  46. package/payload/frontend/dist/assets/{gitGraphDiagram-UUTBAWPF-DcQdcwKZ.js → gitGraphDiagram-UUTBAWPF-DT5xfRNb.js} +1 -1
  47. package/payload/frontend/dist/assets/{graph-C_XsxefA.js → graph-D-AF6yQT.js} +1 -1
  48. package/payload/frontend/dist/assets/{image-D3hgVr8O.js → image-D9gtSsD9.js} +1 -1
  49. package/payload/frontend/dist/assets/{index-CEUsFFQq.js → index-BPVP2P-2.js} +95 -105
  50. package/payload/frontend/dist/assets/{infoDiagram-42DDH7IO-C6TnRyMi.js → infoDiagram-42DDH7IO-C7ew_uLJ.js} +1 -1
  51. package/payload/frontend/dist/assets/{ishikawaDiagram-UXIWVN3A-BL7rfALJ.js → ishikawaDiagram-UXIWVN3A-B6-YebU9.js} +1 -1
  52. package/payload/frontend/dist/assets/{journeyDiagram-VCZTEJTY-C_6407GW.js → journeyDiagram-VCZTEJTY-BOFgJDA5.js} +1 -1
  53. package/payload/frontend/dist/assets/{kanban-definition-6JOO6SKY-CK61Fd0p.js → kanban-definition-6JOO6SKY-jh06wZYz.js} +1 -1
  54. package/payload/frontend/dist/assets/{layout-DVn6JKbl.js → layout-B4p7V5fB.js} +1 -1
  55. package/payload/frontend/dist/assets/{linear-DyWskq8H.js → linear-D2JWfvdc.js} +1 -1
  56. package/payload/frontend/dist/assets/main-CuRgiPQf.js +706 -0
  57. package/payload/frontend/dist/assets/main-D4_uoZQa.css +33 -0
  58. package/payload/frontend/dist/assets/{min-apbLpSJ3.js → min--jD4Arnm.js} +1 -1
  59. package/payload/frontend/dist/assets/{mindmap-definition-QFDTVHPH-MVHZZbma.js → mindmap-definition-QFDTVHPH-DmzjmSuf.js} +1 -1
  60. package/payload/frontend/dist/assets/{monitoring-BApsVez_.js → monitoring-Tvvk6TfB.js} +1 -1
  61. package/payload/frontend/dist/assets/{notifications-CjxSNLU1.js → notifications-CqP2AQrW.js} +1 -1
  62. package/payload/frontend/dist/assets/{pieDiagram-DEJITSTG-Dhbb0AYU.js → pieDiagram-DEJITSTG-B79qIVe5.js} +1 -1
  63. package/payload/frontend/dist/assets/{quadrantDiagram-34T5L4WZ-Yk6LOF0S.js → quadrantDiagram-34T5L4WZ-DIOj1DhE.js} +1 -1
  64. package/payload/frontend/dist/assets/{record-content-BCJ80KS5.js → record-content-CzGGCbNs.js} +1 -1
  65. package/payload/frontend/dist/assets/{requirementDiagram-MS252O5E-BUhS0PEq.js → requirementDiagram-MS252O5E-C-n1j3tN.js} +1 -1
  66. package/payload/frontend/dist/assets/{sankeyDiagram-XADWPNL6-D2sdxz14.js → sankeyDiagram-XADWPNL6-BVKqWE6C.js} +1 -1
  67. package/payload/frontend/dist/assets/{sequenceDiagram-FGHM5R23-CRere4Fy.js → sequenceDiagram-FGHM5R23-B980Icqs.js} +1 -1
  68. package/payload/frontend/dist/assets/{stateDiagram-FHFEXIEX-_pk-OMlm.js → stateDiagram-FHFEXIEX-C-mJHzhL.js} +1 -1
  69. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-B4L4fK9G.js +1 -0
  70. package/payload/frontend/dist/assets/{status-Dk5rRVQF.js → status-Bv4Czr9-.js} +1 -1
  71. package/payload/frontend/dist/assets/suiji-DyzQqRTE.js +1 -0
  72. package/payload/frontend/dist/assets/{svg-X7YkMMvx.js → svg-C6Aj62ig.js} +1 -1
  73. package/payload/frontend/dist/assets/{tabs-BmKbcPVh.js → tabs-BTUOhdAF.js} +1 -1
  74. package/payload/frontend/dist/assets/{timeline-definition-GMOUNBTQ-BPsI2pMC.js → timeline-definition-GMOUNBTQ-Dr9yRuVq.js} +1 -1
  75. package/payload/frontend/dist/assets/{vennDiagram-DHZGUBPP-BNJkZ1Az.js → vennDiagram-DHZGUBPP-B0Xsg1-Z.js} +1 -1
  76. package/payload/frontend/dist/assets/{wardley-RL74JXVD-CVcYeATS.js → wardley-RL74JXVD-BjIKKaA_.js} +1 -1
  77. package/payload/frontend/dist/assets/{wardleyDiagram-NUSXRM2D-BpLJ0alk.js → wardleyDiagram-NUSXRM2D-DMJ7MEA9.js} +1 -1
  78. package/payload/frontend/dist/assets/{xychartDiagram-5P7HB3ND-BdsYYk4M.js → xychartDiagram-5P7HB3ND-2tvgfdab.js} +1 -1
  79. package/payload/frontend/dist/build-info.json +4 -4
  80. package/payload/frontend/dist/companion.html +1 -1
  81. package/payload/frontend/dist/index.html +3 -3
  82. package/payload/packages/agent-bridge/src/install-pi.mjs +81 -0
  83. package/payload/plugins/pi/dist/SKILL.md +22 -0
  84. package/payload/plugins/pi/dist/bridge/app-server-client.cjs +82 -0
  85. package/payload/plugins/pi/dist/bridge/feishu_stop_notify.sh +169 -0
  86. package/payload/plugins/pi/dist/bridge/runweave-hook-bridge.cjs +583 -0
  87. package/payload/plugins/pi/dist/bridge/runweave-hook-completion.cjs +42 -0
  88. package/payload/plugins/pi/dist/bridge/runweave-hook-payload.cjs +578 -0
  89. package/payload/plugins/pi/dist/bridge/runweave-task-supervision.cjs +167 -0
  90. package/payload/plugins/pi/dist/runweave.js +316 -0
  91. package/payload/plugins/toolkit/.codex-plugin/plugin.json +31 -0
  92. package/payload/plugins/toolkit/README.md +185 -0
  93. package/payload/plugins/toolkit/hooks/AGENTS.md +6 -0
  94. package/payload/plugins/toolkit/hooks/app-server-client.cjs +82 -0
  95. package/payload/plugins/toolkit/hooks/feishu_stop_notify.sh +169 -0
  96. package/payload/plugins/toolkit/hooks/runweave-hook-bridge.cjs +583 -0
  97. package/payload/plugins/toolkit/hooks/runweave-hook-completion.cjs +42 -0
  98. package/payload/plugins/toolkit/hooks/runweave-hook-dispatch.cjs +123 -0
  99. package/payload/plugins/toolkit/hooks/runweave-hook-payload.cjs +578 -0
  100. package/payload/plugins/toolkit/hooks/runweave-task-supervision.cjs +167 -0
  101. package/payload/plugins/toolkit/hooks.json +108 -0
  102. package/payload/plugins/toolkit/skills/acceptance/SKILL.md +70 -0
  103. package/payload/plugins/toolkit/skills/acceptance/agents/openai.yaml +4 -0
  104. package/payload/plugins/toolkit/skills/acceptance/references/capture.md +38 -0
  105. package/payload/plugins/toolkit/skills/acceptance/references/delivery.md +54 -0
  106. package/payload/plugins/toolkit/skills/acceptance/references/gallery-contract.md +84 -0
  107. package/payload/plugins/toolkit/skills/acceptance/references/gallery-copy.md +34 -0
  108. package/payload/plugins/toolkit/skills/acceptance/references/video.md +52 -0
  109. package/payload/plugins/toolkit/skills/acceptance/scripts/capture.py +137 -0
  110. package/payload/plugins/toolkit/skills/acceptance/scripts/common.py +63 -0
  111. package/payload/plugins/toolkit/skills/acceptance/scripts/gallery.py +353 -0
  112. package/payload/plugins/toolkit/skills/acceptance/scripts/preview.py +109 -0
  113. package/payload/plugins/toolkit/skills/acceptance/scripts/video.py +283 -0
  114. package/payload/plugins/toolkit/skills/acceptance/templates/reader.css +576 -0
  115. package/payload/plugins/toolkit/skills/acceptance/templates/reader.html +63 -0
  116. package/payload/plugins/toolkit/skills/acceptance/templates/reader.js +326 -0
  117. package/payload/plugins/toolkit/skills/acceptance/templates/viewer.css +55 -0
  118. package/payload/plugins/toolkit/skills/acceptance-gallery/SKILL.md +8 -0
  119. package/payload/plugins/toolkit/skills/acceptance-gallery/agents/openai.yaml +6 -0
  120. package/payload/plugins/toolkit/skills/acceptance-gallery/references/capture-and-copy.md +3 -0
  121. package/payload/plugins/toolkit/skills/acceptance-gallery/references/content-contract.md +3 -0
  122. package/payload/plugins/toolkit/skills/acceptance-gallery/scripts/gallery.py +9 -0
  123. package/payload/plugins/toolkit/skills/acceptance-video/SKILL.md +8 -0
  124. package/payload/plugins/toolkit/skills/acceptance-video/agents/openai.yaml +6 -0
  125. package/payload/plugins/toolkit/skills/acceptance-video/references/input-and-capture.md +3 -0
  126. package/payload/plugins/toolkit/skills/acceptance-video/references/mobile-review.md +3 -0
  127. package/payload/plugins/toolkit/skills/acceptance-video/scripts/capture.py +9 -0
  128. package/payload/plugins/toolkit/skills/acceptance-video/scripts/video.py +9 -0
  129. package/payload/plugins/toolkit/skills/agent-device/SKILL.md +86 -0
  130. package/payload/plugins/toolkit/skills/agent-device/agents/openai.yaml +4 -0
  131. package/payload/plugins/toolkit/skills/agent-device/references/ios-setup.md +36 -0
  132. package/payload/plugins/toolkit/skills/agent-device/scripts/device.py +398 -0
  133. package/payload/plugins/toolkit/skills/agent-device/scripts/resources.py +248 -0
  134. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/SKILL.md +128 -0
  135. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/agents/openai.yaml +4 -0
  136. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-checklist.md +59 -0
  137. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-guide.md +135 -0
  138. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-handoff-template.md +57 -0
  139. package/payload/plugins/toolkit/skills/ask-first/SKILL.md +81 -0
  140. package/payload/plugins/toolkit/skills/brainstorming/SKILL.md +41 -0
  141. package/payload/plugins/toolkit/skills/browse-record/SKILL.md +35 -0
  142. package/payload/plugins/toolkit/skills/browse-record/references/app-usage.md +19 -0
  143. package/payload/plugins/toolkit/skills/browse-record/references/daily-reporting.md +115 -0
  144. package/payload/plugins/toolkit/skills/browse-record/references/zhihu.md +33 -0
  145. package/payload/plugins/toolkit/skills/browse-record/scripts/app_usage.py +110 -0
  146. package/payload/plugins/toolkit/skills/browse-record/scripts/browse_job.py +385 -0
  147. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu-read.js +126 -0
  148. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu_http.py +273 -0
  149. package/payload/plugins/toolkit/skills/clear-technical-explanations/LICENSE +21 -0
  150. package/payload/plugins/toolkit/skills/clear-technical-explanations/SKILL.md +103 -0
  151. package/payload/plugins/toolkit/skills/code-grounded-requirements/SKILL.md +54 -0
  152. package/payload/plugins/toolkit/skills/daily-doc-maintenance/SKILL.md +151 -0
  153. package/payload/plugins/toolkit/skills/daily-record/SKILL.md +63 -0
  154. package/payload/plugins/toolkit/skills/daily-record/agents/openai.yaml +4 -0
  155. package/payload/plugins/toolkit/skills/daily-record/references/browser-history.md +6 -0
  156. package/payload/plugins/toolkit/skills/daily-record/references/feishu.md +90 -0
  157. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-reporting.md +144 -0
  158. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-task.template.json +15 -0
  159. package/payload/plugins/toolkit/skills/daily-record/references/suiji-work-records.md +57 -0
  160. package/payload/plugins/toolkit/skills/daily-record/scripts/feishu.py +280 -0
  161. package/payload/plugins/toolkit/skills/daily-record/scripts/job.py +416 -0
  162. package/payload/plugins/toolkit/skills/daily-record/scripts/job_browser.py +77 -0
  163. package/payload/plugins/toolkit/skills/daily-record/scripts/job_facts.py +132 -0
  164. package/payload/plugins/toolkit/skills/daily-record/scripts/job_isolation.py +69 -0
  165. package/payload/plugins/toolkit/skills/daily-record/scripts/job_materials.py +154 -0
  166. package/payload/plugins/toolkit/skills/daily-record/scripts/job_screen.py +77 -0
  167. package/payload/plugins/toolkit/skills/daily-record/scripts/job_state.py +309 -0
  168. package/payload/plugins/toolkit/skills/daily-refactor/SKILL.md +101 -0
  169. package/payload/plugins/toolkit/skills/daily-refactor/scripts/compare-architecture-reports.mjs +168 -0
  170. package/payload/plugins/toolkit/skills/debugging-strategies/SKILL.md +38 -0
  171. package/payload/plugins/toolkit/skills/debugging-strategies/resources/implementation-playbook.md +520 -0
  172. package/payload/plugins/toolkit/skills/diagnostic-log-debugging/SKILL.md +79 -0
  173. package/payload/plugins/toolkit/skills/doc-coauthoring/SKILL.md +64 -0
  174. package/payload/plugins/toolkit/skills/eli5/LICENSE +21 -0
  175. package/payload/plugins/toolkit/skills/eli5/SKILL.md +18 -0
  176. package/payload/plugins/toolkit/skills/experience/SKILL.md +71 -0
  177. package/payload/plugins/toolkit/skills/experience/package.json +13 -0
  178. package/payload/plugins/toolkit/skills/git-advanced-workflows/SKILL.md +457 -0
  179. package/payload/plugins/toolkit/skills/github-pr/SKILL.md +109 -0
  180. package/payload/plugins/toolkit/skills/github-pr/scripts/github-access.mjs +292 -0
  181. package/payload/plugins/toolkit/skills/github-pr/scripts/preflight.mjs +191 -0
  182. package/payload/plugins/toolkit/skills/github-pr/scripts/source-worktree.mjs +354 -0
  183. package/payload/plugins/toolkit/skills/grill-with-docs/SKILL.md +66 -0
  184. package/payload/plugins/toolkit/skills/grill-with-docs/agents/openai.yaml +6 -0
  185. package/payload/plugins/toolkit/skills/grilling/SKILL.md +14 -0
  186. package/payload/plugins/toolkit/skills/handoff/SKILL.md +98 -0
  187. package/payload/plugins/toolkit/skills/karpathy-guidelines/SKILL.md +73 -0
  188. package/payload/plugins/toolkit/skills/playwright-cli/SKILL.md +562 -0
  189. package/payload/plugins/toolkit/skills/playwright-cli/references/element-attributes.md +23 -0
  190. package/payload/plugins/toolkit/skills/playwright-cli/references/playwright-tests.md +39 -0
  191. package/payload/plugins/toolkit/skills/playwright-cli/references/request-mocking.md +87 -0
  192. package/payload/plugins/toolkit/skills/playwright-cli/references/running-code.md +240 -0
  193. package/payload/plugins/toolkit/skills/playwright-cli/references/session-management.md +226 -0
  194. package/payload/plugins/toolkit/skills/playwright-cli/references/spec-driven-testing.md +312 -0
  195. package/payload/plugins/toolkit/skills/playwright-cli/references/storage-state.md +275 -0
  196. package/payload/plugins/toolkit/skills/playwright-cli/references/test-generation.md +138 -0
  197. package/payload/plugins/toolkit/skills/playwright-cli/references/tracing.md +142 -0
  198. package/payload/plugins/toolkit/skills/playwright-cli/references/video-recording.md +157 -0
  199. package/payload/plugins/toolkit/skills/playwright-cli/templates/standalone.config.json +7 -0
  200. package/payload/plugins/toolkit/skills/product-exploration/SKILL.md +64 -0
  201. package/payload/plugins/toolkit/skills/product-exploration/agents/openai.yaml +5 -0
  202. package/payload/plugins/toolkit/skills/prototype-first-development/SKILL.md +134 -0
  203. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/README.md +88 -0
  204. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/app.js +145 -0
  205. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/index.html +229 -0
  206. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/mock-state.json +60 -0
  207. package/payload/plugins/toolkit/skills/prototype-first-development/references/implementation-plan-handoff-template.md +61 -0
  208. package/payload/plugins/toolkit/skills/prototype-first-development/references/prototype-workflow-checklist.md +64 -0
  209. package/payload/plugins/toolkit/skills/publish-runweave-server/SKILL.md +69 -0
  210. package/payload/plugins/toolkit/skills/publish-runweave-server/agents/openai.yaml +4 -0
  211. package/payload/plugins/toolkit/skills/react-best-practices/AGENTS.md +3227 -0
  212. package/payload/plugins/toolkit/skills/react-best-practices/README.md +127 -0
  213. package/payload/plugins/toolkit/skills/react-best-practices/SKILL.md +143 -0
  214. package/payload/plugins/toolkit/skills/react-best-practices/metadata.json +15 -0
  215. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/event-handler-refs.md +55 -0
  216. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/init-once.md +42 -0
  217. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/use-latest.md +39 -0
  218. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/api-routes.md +38 -0
  219. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/defer-await.md +80 -0
  220. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/dependencies.md +52 -0
  221. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/parallel.md +28 -0
  222. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/suspense-boundaries.md +99 -0
  223. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/barrel-imports.md +59 -0
  224. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/conditional.md +37 -0
  225. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/defer-third-party.md +49 -0
  226. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/dynamic-imports.md +35 -0
  227. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/preload.md +46 -0
  228. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/event-listeners.md +78 -0
  229. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/localstorage-schema.md +77 -0
  230. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/passive-event-listeners.md +48 -0
  231. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/swr-dedup.md +56 -0
  232. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/batch-dom-css.md +110 -0
  233. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-function-results.md +80 -0
  234. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-property-access.md +28 -0
  235. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-storage.md +70 -0
  236. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/combine-iterations.md +32 -0
  237. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/early-exit.md +50 -0
  238. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/flatmap-filter.md +55 -0
  239. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/hoist-regexp.md +45 -0
  240. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/index-maps.md +37 -0
  241. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/length-check-first.md +50 -0
  242. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/min-max-loop.md +82 -0
  243. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/set-map-lookups.md +24 -0
  244. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/tosorted-immutable.md +57 -0
  245. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/sections.md +46 -0
  246. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/template.md +28 -0
  247. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/activity.md +26 -0
  248. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/animate-svg-wrapper.md +38 -0
  249. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/conditional-render.md +32 -0
  250. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/content-visibility.md +38 -0
  251. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hoist-jsx.md +36 -0
  252. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-no-flicker.md +72 -0
  253. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-suppress-warning.md +26 -0
  254. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/resource-hints.md +89 -0
  255. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/script-defer-async.md +71 -0
  256. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/svg-precision.md +28 -0
  257. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/usetransition-loading.md +75 -0
  258. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/defer-reads.md +39 -0
  259. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/dependencies.md +45 -0
  260. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state-no-effect.md +40 -0
  261. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state.md +29 -0
  262. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/functional-setstate.md +77 -0
  263. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/lazy-state-init.md +58 -0
  264. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo-with-default-value.md +36 -0
  265. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo.md +44 -0
  266. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/move-effect-to-event.md +45 -0
  267. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/no-inline-components.md +83 -0
  268. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/simple-expression-in-memo.md +35 -0
  269. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/transitions.md +40 -0
  270. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/use-ref-transient-values.md +73 -0
  271. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/after-nonblocking.md +74 -0
  272. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/auth-actions.md +96 -0
  273. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-lru.md +41 -0
  274. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-react.md +76 -0
  275. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/dedup-props.md +65 -0
  276. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/hoist-static-io.md +139 -0
  277. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/parallel-fetching.md +83 -0
  278. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/serialization.md +38 -0
  279. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/SKILL.md +8 -0
  280. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/agents/openai.yaml +6 -0
  281. package/payload/plugins/toolkit/skills/reproduce-before-fix/SKILL.md +93 -0
  282. package/payload/plugins/toolkit/skills/reproduce-before-fix/agents/openai.yaml +7 -0
  283. package/payload/plugins/toolkit/skills/review-only/SKILL.md +72 -0
  284. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/SKILL.md +134 -0
  285. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/agents/openai.yaml +7 -0
  286. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/references/contracts.md +114 -0
  287. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/classify-runtime-verdict.mjs +140 -0
  288. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/run-static-review.mjs +389 -0
  289. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/static-review-contract.mjs +243 -0
  290. package/payload/plugins/toolkit/skills/run-test-cases/SKILL.md +63 -0
  291. package/payload/plugins/toolkit/skills/runweave-change-validation/SKILL.md +196 -0
  292. package/payload/plugins/toolkit/skills/runweave-change-validation/agents/openai.yaml +7 -0
  293. package/payload/plugins/toolkit/skills/runweave-change-validation/scripts/assert-dev-session-status.mjs +181 -0
  294. package/payload/plugins/toolkit/skills/runweave-dev-session/SKILL.md +126 -0
  295. package/payload/plugins/toolkit/skills/runweave-dev-session/agents/openai.yaml +7 -0
  296. package/payload/plugins/toolkit/skills/runweave-management/SKILL.md +47 -0
  297. package/payload/plugins/toolkit/skills/scheduled-task/SKILL.md +35 -0
  298. package/payload/plugins/toolkit/skills/shadcn/SKILL.md +245 -0
  299. package/payload/plugins/toolkit/skills/shadcn/agents/openai.yml +5 -0
  300. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn-small.png +0 -0
  301. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn.png +0 -0
  302. package/payload/plugins/toolkit/skills/shadcn/cli.md +276 -0
  303. package/payload/plugins/toolkit/skills/shadcn/customization.md +209 -0
  304. package/payload/plugins/toolkit/skills/shadcn/evals/evals.json +47 -0
  305. package/payload/plugins/toolkit/skills/shadcn/mcp.md +94 -0
  306. package/payload/plugins/toolkit/skills/shadcn/rules/base-vs-radix.md +310 -0
  307. package/payload/plugins/toolkit/skills/shadcn/rules/composition.md +197 -0
  308. package/payload/plugins/toolkit/skills/shadcn/rules/forms.md +198 -0
  309. package/payload/plugins/toolkit/skills/shadcn/rules/icons.md +101 -0
  310. package/payload/plugins/toolkit/skills/shadcn/rules/styling.md +163 -0
  311. package/payload/plugins/toolkit/skills/suiji/SKILL.md +54 -0
  312. package/payload/plugins/toolkit/skills/suiji/references/api.md +72 -0
  313. package/payload/plugins/toolkit/skills/suiji/references/source-adapter.md +40 -0
  314. package/payload/plugins/toolkit/skills/suiji/references/work-records.md +36 -0
  315. package/payload/plugins/toolkit/skills/suiji/scripts/source_adapter.py +46 -0
  316. package/payload/plugins/toolkit/skills/suiji/scripts/suiji.py +358 -0
  317. package/payload/plugins/toolkit/skills/suiji/scripts/work_records.py +328 -0
  318. package/payload/plugins/toolkit/skills/suiji/scripts/work_reports.py +75 -0
  319. package/payload/plugins/toolkit/skills/sync-branch-hard-reset/SKILL.md +59 -0
  320. package/payload/plugins/toolkit/skills/update-runweave-backend/SKILL.md +90 -0
  321. package/payload/plugins/toolkit/skills/update-runweave-backend/agents/openai.yaml +4 -0
  322. package/payload/plugins/toolkit/skills/update-runweave-backend/references/npm-update.md +68 -0
  323. package/payload/plugins/toolkit/skills/update-runweave-backend/references/systemd.md +34 -0
  324. package/payload/plugins/toolkit/skills/update-runweave-desktop/SKILL.md +102 -0
  325. package/payload/plugins/toolkit/skills/update-runweave-ios/SKILL.md +16 -0
  326. package/payload/plugins/toolkit/skills/update-runweave-ios/references/ios-wireless-recovery.md +54 -0
  327. package/payload/plugins/toolkit/skills/using-rw/SKILL.md +265 -0
  328. package/payload/plugins/toolkit/skills/write-plan/SKILL.md +72 -0
  329. package/payload/plugins/toolkit/skills/write-test-cases/SKILL.md +100 -0
  330. package/payload/plugins/toolkit/skills/write-test-cases/assets/test-plan-template.yaml +15 -0
  331. package/payload/plugins/toolkit/skills/write-test-cases/references/coverage-checklist.md +75 -0
  332. package/payload/plugins/toolkit/skills/write-test-cases/references/design-techniques.md +91 -0
  333. package/payload/plugins/toolkit/skills/youtube-reading/SKILL.md +35 -0
  334. package/payload/plugins/toolkit/skills/youtube-reading/agents/openai.yaml +4 -0
  335. package/payload/plugins/toolkit/skills/youtube-reading/assets/runtime-lock.json +92 -0
  336. package/payload/plugins/toolkit/skills/youtube-reading/references/content-contract.md +52 -0
  337. package/payload/plugins/toolkit/skills/youtube-reading/references/setup.md +70 -0
  338. package/payload/plugins/toolkit/skills/youtube-reading/requirements.lock +9 -0
  339. package/payload/plugins/toolkit/skills/youtube-reading/scripts/artifacts.py +206 -0
  340. package/payload/plugins/toolkit/skills/youtube-reading/scripts/runtime.py +424 -0
  341. package/payload/plugins/toolkit/skills/youtube-reading/scripts/youtube_reading.py +314 -0
  342. package/payload/plugins/toolkit-release.json +1 -0
  343. package/payload/pty-host/index.cjs +99 -31
  344. package/payload/scripts/toolkit/codex-state.mjs +75 -0
  345. package/payload/scripts/toolkit/release.mjs +210 -0
  346. package/payload/scripts/toolkit/sync-runtime.mjs +108 -0
  347. package/payload/scripts/toolkit/sync.mjs +585 -0
  348. package/payload/server/index.cjs +5 -1
  349. package/payload/frontend/dist/assets/channel-Bf3IeONI.js +0 -1
  350. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-CFsG2SvG.js +0 -1
  351. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-CFsG2SvG.js +0 -1
  352. package/payload/frontend/dist/assets/clone-CeQXm2YN.js +0 -1
  353. package/payload/frontend/dist/assets/connection-XriXysSc.js +0 -12
  354. package/payload/frontend/dist/assets/main-Cg6Oo1xK.css +0 -33
  355. package/payload/frontend/dist/assets/main-H5R-3_w1.js +0 -696
  356. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-CzpdUl_0.js +0 -1
  357. package/payload/frontend/dist/assets/suiji-CO_L5W1X.js +0 -1
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ """Prepare a Playwright CLI capture wrapper; encode real timestamped frames."""
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+ import sys
7
+ sys.dont_write_bytecode = True
8
+ from common import duration, number, probe, run, write_json
9
+
10
+
11
+ def prepare(args):
12
+ action = args.actions.read_text().strip().rstrip(';')
13
+ if '=>' not in action or not action.startswith('async'):
14
+ raise ValueError('actions must contain one async arrow function, not a module')
15
+ number(args.fps, 1, 10, 'fps')
16
+ clip = None
17
+ if args.clip:
18
+ values = [float(x) for x in args.clip.split(',')]
19
+ if len(values) != 4 or min(values[:2]) < 0 or min(values[2:]) <= 0:
20
+ raise ValueError('clip must be x,y,width,height with positive dimensions')
21
+ for index, value in enumerate(values):
22
+ number(value, 0 if index < 2 else 0.001, 100000, 'clip coordinate')
23
+ clip = dict(zip(('x', 'y', 'width', 'height'), values))
24
+ out = args.output.resolve()
25
+ out.mkdir(parents=True, exist_ok=False)
26
+ (out / 'frames').mkdir()
27
+ write_json(out / 'capture-plan.json', {'actions': str(args.actions.resolve()), 'maxFps': args.fps,
28
+ 'clip': clip, 'requires': 'repository-pinned playwright cli run-code'})
29
+ # This expression is evaluated by the existing Playwright CLI page, never launches a browser itself.
30
+ code = '''async page => {
31
+ const perform = ACTION;
32
+ const frames = [];
33
+ const startPage = {url: await page.url(), title: await page.title(), viewport: page.viewportSize()};
34
+ const captureStartedAt = Date.now();
35
+ const clock = {now: () => (Date.now() - captureStartedAt) / 1000};
36
+ let running = true;
37
+ let captureError;
38
+ const shot = async () => {
39
+ const atSeconds = frames.length ? clock.now() : 0;
40
+ const path = FRAME_ROOT + '/' + String(frames.length).padStart(6, '0') + '.png';
41
+ await page.screenshot({path, fullPage: false, ...CLIP});
42
+ frames.push({path, atSeconds});
43
+ };
44
+ await shot();
45
+ const recorder = (async () => {
46
+ try {
47
+ while (running) {
48
+ await page.waitForTimeout(INTERVAL);
49
+ if (running) await shot();
50
+ }
51
+ } catch (error) {captureError = error; running = false;}
52
+ })();
53
+ let result;
54
+ try {result = await perform(page, clock);}
55
+ finally {running = false; await recorder;}
56
+ if (captureError) throw captureError;
57
+ await shot();
58
+ return {schemaVersion: 1, captureMethod: 'playwright-viewport-screenshot-loop',
59
+ startedAt: new Date(captureStartedAt).toISOString(), endedAt: new Date().toISOString(),
60
+ durationSeconds: clock.now(), viewport: page.viewportSize(), startPage,
61
+ endPage: {url: await page.url(), title: await page.title(), viewport: page.viewportSize()}, clip: CLIP_VALUE,
62
+ maxFps: FPS_VALUE, frames, actions: result ?? null,
63
+ limitation: 'Sampled screenshots, not high-FPS animation or latency evidence'};
64
+ }'''
65
+ # Replace unique markers before injecting the task's actual action code.
66
+ for key, value in [('FRAME_ROOT', json.dumps(str(out / 'frames'))),
67
+ ('CLIP_VALUE', json.dumps(clip)), ('CLIP', json.dumps({'clip': clip} if clip else {})),
68
+ ('INTERVAL', str(1000 / args.fps)), ('FPS_VALUE', str(args.fps))]:
69
+ code = code.replace(key, value)
70
+ code = code.replace('ACTION', '(' + action + ')')
71
+ (out / 'run-code.js').write_text(code)
72
+ print(json.dumps({'script': str(out / 'run-code.js'), 'maxFps': args.fps,
73
+ 'next': 'Run with owned playwright cli session; save --raw result as capture.json'}))
74
+
75
+
76
+ def encode(args):
77
+ data = json.loads(args.input.read_text())
78
+ if data.get('schemaVersion') != 1 or data.get('captureMethod') != 'playwright-viewport-screenshot-loop':
79
+ raise ValueError('Expected successful capture.py result, not CLI errors or a snapshot')
80
+ total = number(data.get('durationSeconds'), 0.001, 86400, 'durationSeconds')
81
+ frames = data.get('frames', [])
82
+ if len(frames) < 2:
83
+ raise ValueError('At least two captured frames are required')
84
+ if args.output.exists():
85
+ raise FileExistsError('Refusing to overwrite an existing recording')
86
+ times, paths = [], []
87
+ for frame in frames:
88
+ at = number(frame.get('atSeconds'), 0, total, 'frame.atSeconds')
89
+ if times and at <= times[-1]:
90
+ raise ValueError('Frame timestamps must increase strictly')
91
+ path = (args.input.resolve().parent / frame['path']).resolve(strict=True)
92
+ if not path.is_file():
93
+ raise ValueError('Frame path must be a file')
94
+ times.append(at)
95
+ paths.append(path)
96
+ if times[0] != 0:
97
+ raise ValueError('First frame must start at zero')
98
+ concat = args.output.with_suffix('.ffconcat')
99
+ lines = ['ffconcat version 1.0']
100
+ for i, path in enumerate(paths):
101
+ quoted = str(path).replace("'", "'\\''")
102
+ end = times[i + 1] if i + 1 < len(times) else total
103
+ lines.extend([f"file '{quoted}'", f'duration {max(end - times[i], 1 / 30):.6f}'])
104
+ lines.append("file '" + str(paths[-1]).replace("'", "'\\''") + "'")
105
+ with concat.open('x') as f:
106
+ f.write('\n'.join(lines) + '\n')
107
+ run(['ffmpeg', '-hide_banner', '-loglevel', 'error', '-n', '-safe', '0', '-i', str(concat),
108
+ '-t', str(total), '-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2,format=yuv420p', '-r', '30',
109
+ '-c:v', 'libx264', '-preset', 'fast', '-crf', '20', '-movflags', '+faststart', str(args.output)])
110
+ actual = duration(probe(args.output))
111
+ if abs(actual - total) > 0.1:
112
+ raise ValueError('Encoded duration does not match captured duration')
113
+ print(json.dumps({'recording': str(args.output.resolve()), 'durationSeconds': actual,
114
+ 'capturedFrames': len(frames), 'timing': 'measured frame timestamps, rounded to 30 FPS'}))
115
+
116
+
117
+ def main():
118
+ parser = argparse.ArgumentParser(description=__doc__)
119
+ sub = parser.add_subparsers(dest='command', required=True)
120
+ make = sub.add_parser('prepare', help='Wrap real actions without starting a browser')
121
+ make.add_argument('--actions', type=Path, required=True)
122
+ make.add_argument('--output', type=Path, required=True)
123
+ make.add_argument('--fps', type=float, default=4)
124
+ make.add_argument('--clip', help='Observed viewport region: x,y,width,height')
125
+ movie = sub.add_parser('encode', help='Encode successful CLI capture result, preserving frame timing')
126
+ movie.add_argument('--input', type=Path, required=True)
127
+ movie.add_argument('--output', type=Path, required=True)
128
+ args = parser.parse_args()
129
+ prepare(args) if args.command == 'prepare' else encode(args)
130
+
131
+
132
+ if __name__ == '__main__':
133
+ try:
134
+ main()
135
+ except Exception as error:
136
+ print(json.dumps({'error': str(error)}, ensure_ascii=False), file=sys.stderr)
137
+ sys.exit(1)
@@ -0,0 +1,63 @@
1
+ """Shared local media and preview helpers; no optional dependencies at import time."""
2
+ import hashlib
3
+ import json
4
+ import math
5
+ import os
6
+ import subprocess
7
+ import urllib.parse
8
+ import urllib.request
9
+
10
+ def write_json(path, value, private=False):
11
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600 if private else 0o666)
12
+ with os.fdopen(fd, 'w', encoding='utf-8') as f:
13
+ json.dump(value, f, ensure_ascii=False, indent=2)
14
+ f.write('\n')
15
+
16
+ def run(command):
17
+ return subprocess.run(command, check=True, capture_output=True, text=True).stdout
18
+
19
+ def probe(path):
20
+ return json.loads(run(['ffprobe', '-v', 'error', '-show_streams',
21
+ '-show_format', '-of', 'json', str(path)]))
22
+
23
+ def duration(info):
24
+ value = float(info['format']['duration'])
25
+ if not math.isfinite(value) or value <= 0:
26
+ raise ValueError('Video duration must be finite and positive')
27
+ if not any(s['codec_type'] == 'video' for s in info['streams']):
28
+ raise ValueError('No video stream')
29
+ return value
30
+
31
+ def digest(path):
32
+ h = hashlib.sha256()
33
+ with path.open('rb') as f:
34
+ for chunk in iter(lambda: f.read(1024 * 1024), b''):
35
+ h.update(chunk)
36
+ return h.hexdigest()
37
+
38
+ def number(value, low, high, label):
39
+ if (isinstance(value, bool) or not isinstance(value, (int, float)) or
40
+ not math.isfinite(value) or not low <= value <= high):
41
+ raise ValueError(f'{label} must be a finite number in [{low}, {high}]')
42
+ return value
43
+
44
+ def text(value, label):
45
+ if not isinstance(value, str) or not value.strip():
46
+ raise ValueError(f'{label} must be nonempty text')
47
+ return value
48
+
49
+ def http(url, headers=None, body=None, method=None):
50
+ request = urllib.request.Request(url, data=body, headers=headers or {}, method=method)
51
+ # Never forward an API token or preview capability through redirects.
52
+ class NoRedirect(urllib.request.HTTPRedirectHandler):
53
+ def redirect_request(self, req, fp, code, msg, hdrs, newurl):
54
+ return None
55
+ with urllib.request.build_opener(NoRedirect).open(request, timeout=20) as response:
56
+ return response.status, response.headers, response.read()
57
+
58
+ def base_url(value):
59
+ parsed = urllib.parse.urlsplit(value)
60
+ if (parsed.scheme not in ('http', 'https') or not parsed.netloc or parsed.username or
61
+ parsed.password or parsed.query or parsed.fragment):
62
+ raise ValueError('Expected HTTP(S) base URL without credentials/query/fragment')
63
+ return value.rstrip('/')
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+ """Build portable, local evidence galleries. Python 3.9+; no third-party packages."""
3
+ import argparse
4
+ import copy
5
+ import hashlib
6
+ import html
7
+ import json
8
+ import os
9
+ from pathlib import Path, PurePosixPath
10
+ import re
11
+ import shutil
12
+ import struct
13
+ import sys
14
+ import tempfile
15
+ import zlib
16
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
17
+ from urllib.parse import unquote, urlsplit
18
+
19
+ TEMPLATE_VERSION = '1.1.0'
20
+ TEMPLATES = Path(__file__).resolve().parent.parent / 'templates'
21
+ ID = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}$')
22
+ MAX_FILE = 32 * 1024 * 1024
23
+ MAX_TEXT = 2 * 1024 * 1024
24
+
25
+
26
+ class Invalid(ValueError):
27
+ pass
28
+
29
+
30
+ def require(ok, where, message):
31
+ if not ok:
32
+ raise Invalid(f'{where}: {message}')
33
+
34
+
35
+ def fields(value, where, required, optional=()):
36
+ require(isinstance(value, dict), where, '需要对象')
37
+ missing = set(required) - value.keys()
38
+ unknown = value.keys() - set(required) - set(optional)
39
+ require(not missing, where, f'缺少字段 {sorted(missing)}')
40
+ require(not unknown, where, f'未知字段 {sorted(unknown)}')
41
+
42
+
43
+ def string(value, where, nullable=False):
44
+ if value is None and nullable:
45
+ return
46
+ require(isinstance(value, str) and bool(value.strip()), where, '需要非空字符串')
47
+
48
+
49
+ def strings(value, where):
50
+ require(isinstance(value, list), where, '需要字符串数组')
51
+ for i, item in enumerate(value):
52
+ string(item, f'{where}[{i}]')
53
+
54
+
55
+ def identifier(value, where):
56
+ require(isinstance(value, str) and ID.fullmatch(value), where, 'ID 仅支持字母、数字、-、_,长度 1–80')
57
+
58
+
59
+ def local_file(root, relative):
60
+ string(relative, 'path')
61
+ parts = PurePosixPath(relative).parts
62
+ require(not relative.startswith('/') and '\\' not in relative and ':' not in relative
63
+ and '..' not in parts, relative, '需要根目录内的相对路径')
64
+ target = (root / relative).resolve(strict=True)
65
+ require(target.is_relative_to(root) and target.is_file(), relative, '文件不存在或超出素材目录')
66
+ return target
67
+
68
+
69
+ def read_file(path, limit):
70
+ require(path.stat().st_size <= limit, path.name, f'超过 {limit} 字节限制')
71
+ with path.open('rb') as stream:
72
+ data = stream.read(limit + 1)
73
+ require(len(data) <= limit, path.name, '读取时文件超过大小限制')
74
+ return data
75
+
76
+
77
+ def image_info(data, suffix, where):
78
+ """Check container structure and dimensions; browser acceptance verifies decoding."""
79
+ if suffix == '.png':
80
+ require(data.startswith(b'\x89PNG\r\n\x1a\n'), where, '不是 PNG')
81
+ pos, size, has_data, ended = 8, None, False, False
82
+ while pos + 12 <= len(data):
83
+ length = struct.unpack('>I', data[pos:pos + 4])[0]
84
+ chunk = data[pos + 4:pos + 8]
85
+ end = pos + 8 + length
86
+ require(end + 4 <= len(data), where, 'PNG chunk 被截断')
87
+ require(zlib.crc32(data[pos + 4:end]) & 0xffffffff == struct.unpack('>I', data[end:end + 4])[0], where, 'PNG CRC 错误')
88
+ if pos == 8:
89
+ require(chunk == b'IHDR' and length == 13, where, '缺少 PNG IHDR')
90
+ size = struct.unpack('>II', data[pos + 8:pos + 16])
91
+ has_data |= chunk == b'IDAT'
92
+ pos = end + 4
93
+ if chunk == b'IEND':
94
+ require(length == 0 and pos == len(data), where, 'PNG 结束块不合法')
95
+ ended = True
96
+ break
97
+ require(ended and has_data, where, 'PNG 缺少数据或结束块')
98
+ else:
99
+ require(data.startswith(b'\xff\xd8') and data.endswith(b'\xff\xd9'), where, '不是完整 JPEG')
100
+ pos, size = 2, None
101
+ while pos < len(data) - 2:
102
+ require(data[pos] == 255, where, 'JPEG marker 不合法')
103
+ while pos < len(data) and data[pos] == 255:
104
+ pos += 1
105
+ require(pos < len(data), where, 'JPEG 被截断')
106
+ marker = data[pos]
107
+ pos += 1
108
+ if marker in (0xda, 0xd9):
109
+ break
110
+ if marker == 1 or 0xd0 <= marker <= 0xd7:
111
+ continue
112
+ require(pos + 2 <= len(data), where, 'JPEG 被截断')
113
+ length = struct.unpack('>H', data[pos:pos + 2])[0]
114
+ require(length >= 2 and pos + length <= len(data), where, 'JPEG segment 被截断')
115
+ if marker in (0xc0, 0xc1, 0xc2):
116
+ require(length >= 8, where, 'JPEG SOF 不合法')
117
+ height, width = struct.unpack('>HH', data[pos + 3:pos + 7])
118
+ size = (width, height)
119
+ pos += length
120
+ require(size is not None, where, '不支持的 JPEG 编码或缺少尺寸')
121
+ require(size and all(0 < n <= 32768 for n in size), where, '图片尺寸不合法或超出 32768px')
122
+ return dict(width=size[0], height=size[1])
123
+
124
+
125
+ def parse_json(data):
126
+ def pairs(items):
127
+ result = {}
128
+ for key, value in items:
129
+ require(key not in result, key, '重复 JSON 字段')
130
+ result[key] = value
131
+ return result
132
+ return json.loads(data.decode('utf-8'), object_pairs_hook=pairs,
133
+ parse_constant=lambda value: (_ for _ in ()).throw(Invalid(f'非法 JSON 数字 {value}')))
134
+
135
+
136
+ def load_task(root, input_name):
137
+ raw = read_file(local_file(root, input_name), MAX_TEXT)
138
+ task = parse_json(raw)
139
+ fields(task, 'root', ['schemaVersion', 'task', 'assets', 'pages'])
140
+ require(type(task['schemaVersion']) is int and task['schemaVersion'] == 1, 'schemaVersion', '仅支持版本 1')
141
+ meta = task['task']
142
+ fields(meta, 'task', ['id', 'title', 'scope', 'materialKind', 'limitations'])
143
+ identifier(meta['id'], 'task.id')
144
+ for key in ('title', 'scope'):
145
+ string(meta[key], f'task.{key}')
146
+ require(meta['materialKind'] in ('current', 'historical'), 'task.materialKind', '需要 current/historical')
147
+ strings(meta['limitations'], 'task.limitations')
148
+ require(isinstance(task['assets'], list), 'assets', '需要数组')
149
+ assets, payloads, image_sizes = {}, {}, {}
150
+ for i, asset in enumerate(task['assets']):
151
+ where = f'assets[{i}]'
152
+ fields(asset, where, ['id', 'kind', 'path', 'label', 'origin'])
153
+ identifier(asset['id'], where + '.id')
154
+ require(asset['id'] not in assets, where, '重复素材 ID')
155
+ require(asset['kind'] in ('image', 'text'), where, 'kind 需要 image/text')
156
+ string(asset['label'], where + '.label')
157
+ fields(asset['origin'], where + '.origin', ['surface', 'capturedAt', 'sourceRef'])
158
+ for key, value in asset['origin'].items():
159
+ string(value, where + '.origin.' + key, nullable=True)
160
+ source = local_file(root, asset['path'])
161
+ suffix = source.suffix.lower()
162
+ data = read_file(source, MAX_FILE if asset['kind'] == 'image' else MAX_TEXT)
163
+ if asset['kind'] == 'image':
164
+ require(suffix in ('.png', '.jpg', '.jpeg'), where, '图片仅支持 PNG/JPEG')
165
+ image_sizes[asset['id']] = image_info(data, suffix, where)
166
+ else:
167
+ require(suffix in ('.txt', '.md', '.json'), where, '文字仅支持 TXT/MD/JSON')
168
+ data.decode('utf-8')
169
+ if suffix == '.json':
170
+ parse_json(data)
171
+ assets[asset['id']] = asset
172
+ payloads[asset['id']] = (data, suffix)
173
+ require(isinstance(task['pages'], list) and len(task['pages']) > 0, 'pages', '至少需要一页')
174
+ seen = set()
175
+ for i, page in enumerate(task['pages']):
176
+ where = f'pages[{i}]'
177
+ fields(page, where, ['id', 'type', 'title', 'description', 'evidenceState', 'items', 'sourceIds'], ['lookAt', 'limitations'])
178
+ identifier(page['id'], where + '.id')
179
+ require(page['id'] not in seen, where, '重复页 ID')
180
+ seen.add(page['id'])
181
+ for key in ('title', 'description', 'lookAt'):
182
+ if key in page:
183
+ string(page[key], where + '.' + key)
184
+ strings(page.get('limitations', []), where + '.limitations')
185
+ require(page['evidenceState'] in ('observed', 'insufficient', 'not-run'), where, '未知 evidenceState')
186
+ require(isinstance(page['type'], str) and page['type'] in ('single', 'compare', 'sequence', 'text'), where, '未知页型')
187
+ items = page['items']
188
+ require(isinstance(items, list), where, 'items 需要数组')
189
+ low, high = {'single': (1, 1), 'compare': (2, 2), 'sequence': (2, 4), 'text': (1, 1)}[page['type']]
190
+ require(low <= len(items) <= high, where, f'当前页型需要 {low}–{high} 个素材项')
191
+ for j, item in enumerate(items):
192
+ iw = where + f'.items[{j}]'
193
+ fields(item, iw, ['assetId', 'label'], ['alt', 'crop'])
194
+ string(item['assetId'], iw + '.assetId')
195
+ string(item['label'], iw + '.label')
196
+ require(item['assetId'] in assets, iw, '素材 ID 不存在')
197
+ asset = assets[item['assetId']]
198
+ kind = 'text' if page['type'] == 'text' else 'image'
199
+ require(asset['kind'] == kind, iw, '素材类型与页型不匹配')
200
+ if kind == 'image':
201
+ string(item.get('alt'), iw + '.alt')
202
+ else:
203
+ require('crop' not in item and 'alt' not in item, iw, '文字项不接受 crop/alt')
204
+ if 'crop' in item:
205
+ crop = item['crop']
206
+ fields(crop, iw + '.crop', ['x', 'y', 'width', 'height'])
207
+ require(all(type(v) is int for v in crop.values()), iw, '取景参数必须为整数')
208
+ size = image_sizes[item['assetId']]
209
+ require(crop['x'] >= 0 and crop['y'] >= 0 and crop['width'] > 0 and crop['height'] > 0
210
+ and crop['x'] + crop['width'] <= size['width']
211
+ and crop['y'] + crop['height'] <= size['height'], iw, '取景超出原图或尺寸不合法')
212
+ require(payloads[item['assetId']][1] == '.png', iw, 'JPEG 取景请先重新采集 PNG;避免 EXIF 方向歧义')
213
+ strings(page['sourceIds'], where + '.sourceIds')
214
+ for source_id in page['sourceIds']:
215
+ require(source_id in assets and assets[source_id]['kind'] == 'text', where, 'sourceIds 只能引用存在的文字素材')
216
+ return task, payloads, image_sizes, hashlib.sha256(raw).hexdigest()
217
+
218
+
219
+ def dump(path, value):
220
+ path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
221
+
222
+
223
+ def build(loaded, output):
224
+ task, payloads, sizes, input_hash = loaded
225
+ output = output.absolute()
226
+ output.parent.mkdir(parents=True, exist_ok=True)
227
+ # Serializes concurrent builds of the same target without replacing old evidence.
228
+ lock = output.parent / ('.' + output.name + '.gallery-lock')
229
+ fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
230
+ os.close(fd)
231
+ stage = None
232
+ try:
233
+ require(not output.exists() and not output.is_symlink(), 'output', '目标已存在,请使用新目录')
234
+ stage = Path(tempfile.mkdtemp(prefix='.gallery-', dir=output.parent))
235
+ (stage / 'assets').mkdir()
236
+ snapshot = copy.deepcopy(task)
237
+ display = copy.deepcopy(task)
238
+ manifest = {'schemaVersion': 1, 'templateVersion': TEMPLATE_VERSION,
239
+ 'inputSha256': input_hash, 'assets': []}
240
+ for index, asset in enumerate(snapshot['assets']):
241
+ data, suffix = payloads[asset['id']]
242
+ # Numeric prefixes also keep distinct IDs safe on case-insensitive disks.
243
+ relative = f'assets/{index + 1:04d}-{asset["id"]}{suffix}'
244
+ (stage / relative).write_bytes(data)
245
+ digest = hashlib.sha256(data).hexdigest()
246
+ asset['path'] = relative
247
+ shown = display['assets'][index]
248
+ shown['path'] = relative
249
+ shown.update(sizes.get(asset['id'], {}))
250
+ if asset['kind'] == 'text':
251
+ shown['text'] = data.decode('utf-8')
252
+ manifest['assets'].append({'id': asset['id'], 'path': relative, 'sha256': digest})
253
+ display['templateVersion'] = TEMPLATE_VERSION
254
+ embedded = json.dumps(display, ensure_ascii=False).replace('&', '\\u0026').replace('<', '\\u003c').replace('>', '\\u003e')
255
+ template = (TEMPLATES / 'reader.html').read_text(encoding='utf-8')
256
+ # Split once so placeholders in user-supplied titles cannot be interpreted.
257
+ before, after = template.split('@@DATA@@')
258
+ before = before.replace('@@TITLE@@', html.escape(task['task']['title']))
259
+ (stage / 'index.html').write_text(before + embedded + after, encoding='utf-8')
260
+ for name in ('reader.css', 'reader.js', 'viewer.css'):
261
+ shutil.copyfile(TEMPLATES / name, stage / name)
262
+ dump(stage / 'task.json', snapshot)
263
+ dump(stage / 'manifest.json', manifest)
264
+ require(not output.exists() and not output.is_symlink(), 'output', '目标已存在')
265
+ stage.rename(output)
266
+ stage = None
267
+ finally:
268
+ if stage is not None:
269
+ shutil.rmtree(stage)
270
+ lock.unlink()
271
+ return {'output': str(output), 'index': str(output / 'index.html'), 'pages': len(task['pages'])}
272
+
273
+
274
+ def serve(directory, port):
275
+ root = directory.resolve(strict=True)
276
+ manifest = parse_json(read_file(root / 'manifest.json', MAX_TEXT))
277
+ require(manifest.get('schemaVersion') == 1 and isinstance(manifest.get('assets'), list), 'directory', '不是生成的成果目录')
278
+ allowed = {'index.html', 'reader.css', 'reader.js', 'task.json', 'manifest.json'}
279
+ if manifest.get('templateVersion') == '1.1.0':
280
+ allowed.add('viewer.css')
281
+ allowed.update(asset['path'] for asset in manifest['assets'])
282
+ for relative in allowed:
283
+ local_file(root, relative)
284
+ mime = {'.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
285
+ '.css': 'text/css; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg',
286
+ '.jpeg': 'image/jpeg', '.json': 'application/json; charset=utf-8'}
287
+
288
+ class Handler(BaseHTTPRequestHandler):
289
+ def do_GET(self):
290
+ self.send_file(False)
291
+
292
+ def do_HEAD(self):
293
+ self.send_file(True)
294
+
295
+ def send_file(self, head):
296
+ relative = unquote(urlsplit(self.path).path).removeprefix('/') or 'index.html'
297
+ try:
298
+ require(relative in allowed, relative, '不在成果清单内')
299
+ target = local_file(root, relative)
300
+ data = read_file(target, MAX_FILE * 8)
301
+ except (Invalid, OSError, ValueError):
302
+ self.send_error(404, 'Not found')
303
+ return
304
+ self.send_response(200)
305
+ self.send_header('Content-Type', mime.get(target.suffix.lower(), 'text/plain; charset=utf-8'))
306
+ self.send_header('Content-Length', str(len(data)))
307
+ self.send_header('X-Content-Type-Options', 'nosniff')
308
+ self.send_header('Cache-Control', 'no-store')
309
+ self.send_header('Referrer-Policy', 'no-referrer')
310
+ self.end_headers()
311
+ if not head:
312
+ self.wfile.write(data)
313
+
314
+ def log_message(self, *_args):
315
+ pass
316
+
317
+ with ThreadingHTTPServer(('127.0.0.1', port), Handler) as server:
318
+ print(json.dumps({'url': f'http://127.0.0.1:{server.server_port}/', 'pid': os.getpid(),
319
+ 'directory': str(root), 'stop': '前台按 Ctrl-C;后台停止前核对 PID 与完整命令'}, ensure_ascii=False), flush=True)
320
+ try:
321
+ server.serve_forever()
322
+ except KeyboardInterrupt:
323
+ pass
324
+
325
+
326
+ def main():
327
+ parser = argparse.ArgumentParser(description='本地图文验收阅读器:validate / build / serve')
328
+ sub = parser.add_subparsers(dest='command', required=True)
329
+ for name in ('validate', 'build'):
330
+ command = sub.add_parser(name)
331
+ command.add_argument('--input-root', type=Path, required=True)
332
+ command.add_argument('--input', default='task.json')
333
+ if name == 'build':
334
+ command.add_argument('--output', type=Path, required=True)
335
+ command = sub.add_parser('serve')
336
+ command.add_argument('--directory', type=Path, required=True)
337
+ command.add_argument('--port', type=int, default=0)
338
+ args = parser.parse_args()
339
+ try:
340
+ if args.command == 'serve':
341
+ require(0 <= args.port <= 65535, 'port', '端口范围 0–65535')
342
+ serve(args.directory, args.port)
343
+ return
344
+ loaded = load_task(args.input_root.resolve(strict=True), args.input)
345
+ result = build(loaded, args.output) if args.command == 'build' else {'valid': True, 'pages': len(loaded[0]['pages']), 'assets': len(loaded[0]['assets'])}
346
+ print(json.dumps(result, ensure_ascii=False))
347
+ except (Invalid, OSError, ValueError, KeyError, TypeError) as error:
348
+ print(f'gallery: {error}', file=sys.stderr)
349
+ sys.exit(1)
350
+
351
+
352
+ if __name__ == '__main__':
353
+ main()
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """Issue scoped Runweave preview links for HTML galleries or MP4 videos."""
3
+ import argparse
4
+ from datetime import datetime, timedelta, timezone
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import sys
9
+ import urllib.parse
10
+ sys.dont_write_bytecode = True
11
+ from common import base_url, http, write_json
12
+
13
+
14
+ def configure_parser(parser, default_kind='auto'):
15
+ parser.add_argument('--file', '--video', dest='file', type=Path, required=True,
16
+ help='Local HTML or MP4; --video is a legacy alias')
17
+ parser.add_argument('--kind', choices=['auto', 'html', 'video'], default=default_kind)
18
+ parser.add_argument('--project-id', required=True)
19
+ parser.add_argument('--viewer-base-url', required=True)
20
+ parser.add_argument('--profile', default='local')
21
+ parser.add_argument('--settings', type=Path, default=Path.home() / '.runweave/settings.yaml')
22
+ parser.add_argument('--token-env', help='Name of an existing API-token environment variable')
23
+ parser.add_argument('--backend', help='Configured Backend URL; required with --token-env')
24
+ parser.add_argument('--output', type=Path, required=True)
25
+
26
+
27
+ def preview(args):
28
+ file = args.file.resolve(strict=True)
29
+ if not file.is_file():
30
+ raise ValueError('Preview input must be a regular file')
31
+ kind = args.kind
32
+ if kind == 'auto':
33
+ kind = 'html' if file.suffix.lower() in ('.html', '.htm') else 'video'
34
+ if ((kind == 'html' and file.suffix.lower() not in ('.html', '.htm')) or
35
+ (kind == 'video' and file.suffix.lower() != '.mp4')):
36
+ raise ValueError('Expected HTML or MP4 input matching preview kind')
37
+ if args.output.exists() or args.output.is_symlink():
38
+ raise FileExistsError('Use a new output path for each preview ticket')
39
+ if not args.output.parent.is_dir():
40
+ raise ValueError('Preview output parent must already exist')
41
+ if args.token_env:
42
+ if not args.backend:
43
+ raise ValueError('--token-env requires --backend')
44
+ token = os.environ[args.token_env]
45
+ local = base_url(args.backend)
46
+ else:
47
+ if args.settings.stat().st_mode & 0o077:
48
+ raise ValueError('Connection settings must be private (0600)')
49
+ try:
50
+ import yaml
51
+ except ImportError as error:
52
+ raise ValueError('Profile preview requires PyYAML; use --token-env with --backend otherwise') from error
53
+ config = yaml.safe_load(args.settings.read_text())['cli']['profiles'][args.profile]
54
+ token = config['accessToken']
55
+ local = base_url(config['baseUrl'])
56
+ viewer = base_url(args.viewer_base_url)
57
+ identity = json.loads(http(local + '/health')[2]).get('serviceInstanceId')
58
+ if not identity or json.loads(http(viewer + '/health')[2]).get('serviceInstanceId') != identity:
59
+ raise ValueError('Viewer and configured Backend service identities differ')
60
+ payload = json.dumps({'path': str(file)}).encode()
61
+ _, _, body = http(local + '/api/terminal/project/' + urllib.parse.quote(args.project_id, safe='') +
62
+ '/preview/' + kind + '-ticket', {'Authorization': 'Bearer ' + token,
63
+ 'Content-Type': 'application/json'}, payload, 'POST')
64
+ ticket = json.loads(body)
65
+ path = ticket['path']
66
+ if not path.startswith('/' + kind + '-preview/') or urllib.parse.urlsplit(path).netloc:
67
+ raise ValueError('Unexpected preview-ticket path')
68
+ url = viewer + path
69
+ status, headers, _ = http(url, method='HEAD')
70
+ mime = 'text/html' if kind == 'html' else 'video/mp4'
71
+ if (status != 200 or headers.get('Content-Type', '').split(';')[0] != mime or
72
+ int(headers.get('Content-Length', '-1')) != file.stat().st_size):
73
+ raise ValueError('Anonymous preview HEAD does not match local input')
74
+ now = datetime.now(timezone.utc)
75
+ expires = int(ticket['expiresIn'])
76
+ receipt = {'url': url, 'file': str(file), 'kind': kind, 'backendServiceInstanceId': identity,
77
+ 'viewerBaseUrl': viewer, 'issuedAt': now.isoformat(), 'expiresIn': expires,
78
+ 'expiresAt': (now + timedelta(seconds=expires)).isoformat(), 'headStatus': 200,
79
+ 'physicalPhoneReachability': 'not-verified',
80
+ 'hostPrerequisites': ['Mac awake', 'Backend running', 'viewer route reachable', 'ticket valid']}
81
+ if kind == 'video':
82
+ status, headers, content = http(url, headers={'Range': 'bytes=0-1023'})
83
+ with file.open('rb') as f:
84
+ expected = f.read(1024)
85
+ if status != 206 or content != expected or headers.get('Accept-Ranges') != 'bytes':
86
+ raise ValueError('Anonymous Range request did not match local bytes')
87
+ receipt.update(video=str(file), rangeStatus=206, rangeMatchesLocal=True)
88
+ else:
89
+ status, _, content = http(url)
90
+ if status != 200 or content != file.read_bytes():
91
+ raise ValueError('Anonymous HTML GET did not match local bytes')
92
+ receipt.update(htmlStatus=200, htmlMatchesLocal=True,
93
+ resources='not-verified; check styles/scripts/images in a real browser')
94
+ write_json(args.output, receipt, private=True)
95
+ print(json.dumps({k: v for k, v in receipt.items() if k != 'url'}, ensure_ascii=False))
96
+
97
+
98
+ def main():
99
+ parser = argparse.ArgumentParser(description=__doc__)
100
+ configure_parser(parser)
101
+ preview(parser.parse_args())
102
+
103
+
104
+ if __name__ == '__main__':
105
+ try:
106
+ main()
107
+ except Exception as error:
108
+ print(json.dumps({'error': str(error), 'automaticRetry': False}, ensure_ascii=False), file=sys.stderr)
109
+ sys.exit(1)