minimal-agent 0.8.2 → 0.10.0

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 (484) hide show
  1. package/README.md +14 -12
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================
4
+ * skills/web-crawler/scripts/crawl.mjs —— 通用爬虫引擎(node + bun 通用,零 npm 依赖)
5
+ * ------------------------------------------------------------
6
+ * 集合主流爬虫该有的能力,做成 minimal-agent 的 bun 脚本 skill:
7
+ * - 静态优先 + JS 渲染升级(疑似 JS-app 或 --render on 时 spawn agent-browser 取渲染后 DOM)
8
+ * - 内容净抽:HTML→markdown(turndown 动态探测,回落内置转换)+ title + OpenGraph + JSON-LD + 主图
9
+ * - 整站爬取:BFS 前沿 + URL 规范化去重 + depth/max-pages 双闸 + 同域/白名单 + robots.txt + sitemap 播种 + 礼貌限速 + retry
10
+ * - 输出:每页 out/page_NNN.md(front-matter)+ out/crawl.json 清单
11
+ *
12
+ * ★ 零 npm 依赖(只用 node 内置 fetch/fs/URL)——因为 skills/ 原样发布、绿色版目录无 node_modules,
13
+ * 依赖 npm 包的 .mjs 在绿色版跑不起来。turndown 用动态 import 探测:有就用(质量更好)、无则回落内置转换。
14
+ * ★ agent-browser 经 PATH 调用(minimal-agent 启动时已把内置 agent-browser 目录前置进 PATH)。
15
+ *
16
+ * 纯函数(extract* / canonicalizeUrl / parseRobots / fallbackHtmlToMarkdown / looksJsRendered / shouldFollow)
17
+ * 全部 export 供单测;crawl() 接受 deps 注入(fetchImpl/renderImpl)不连真网测试。
18
+ *
19
+ * 用法:
20
+ * node crawl.mjs <url> [--mode single|site] [--out ./build/crawl] [--depth 2] [--max-pages 20]
21
+ * [--same-origin | --allow-domains a.com,b.com] [--render auto|on|off]
22
+ * [--concurrency 3] [--delay 500] [--timeout 20000] [--json]
23
+ * ============================================================
24
+ */
25
+
26
+ import { mkdir, writeFile } from 'node:fs/promises';
27
+ import { spawn } from 'node:child_process';
28
+ import { join } from 'node:path';
29
+
30
+ const UA =
31
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
32
+
33
+ // ───────────────────────── 纯函数:抽取 ─────────────────────────
34
+
35
+ /** <title> 文本(去标签、解实体、trim)。 */
36
+ export function extractTitle(html) {
37
+ const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html || '');
38
+ return m ? decodeEntities(m[1]).replace(/\s+/g, ' ').trim() : '';
39
+ }
40
+
41
+ /** OpenGraph:{ 'og:title': '...', 'og:image': '...' }(property/content 任意顺序)。 */
42
+ export function extractOpenGraph(html) {
43
+ const out = {};
44
+ const re = /<meta\b[^>]*>/gi;
45
+ for (const tag of (html || '').match(re) || []) {
46
+ const prop = /(?:property|name)\s*=\s*["']\s*(og:[^"']+?)\s*["']/i.exec(tag);
47
+ const content = /content\s*=\s*["']([\s\S]*?)["']/i.exec(tag);
48
+ if (prop && content) out[prop[1].toLowerCase()] = decodeEntities(content[1]).trim();
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** JSON-LD:解析所有 <script type="application/ld+json"> 块(坏块跳过)。 */
54
+ export function extractJsonLd(html) {
55
+ const out = [];
56
+ const re = /<script\b[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
57
+ let m;
58
+ while ((m = re.exec(html || '')) !== null) {
59
+ try {
60
+ out.push(JSON.parse(m[1].trim()));
61
+ } catch {
62
+ /* 坏 JSON-LD 跳过 */
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+
68
+ /** 抽 <a href> → 绝对 http(s) URL 数组(相对地址按 base 解析、去重)。 */
69
+ export function extractLinks(html, baseUrl) {
70
+ const out = new Set();
71
+ const re = /<a\b[^>]*\bhref\s*=\s*["']([^"']+)["']/gi;
72
+ let m;
73
+ while ((m = re.exec(html || '')) !== null) {
74
+ const href = m[1].trim();
75
+ if (!href || href.startsWith('#') || /^(javascript|mailto|tel):/i.test(href)) continue;
76
+ try {
77
+ const abs = new URL(href, baseUrl).href;
78
+ if (/^https?:/i.test(abs)) out.add(abs);
79
+ } catch {
80
+ /* 非法 URL 跳过 */
81
+ }
82
+ }
83
+ return [...out];
84
+ }
85
+
86
+ /** 主图:优先 og:image,否则第一张较大 <img src>。 */
87
+ export function extractMainImage(html, og, baseUrl) {
88
+ if (og && og['og:image']) {
89
+ try {
90
+ return new URL(og['og:image'], baseUrl).href;
91
+ } catch {
92
+ /* fall through */
93
+ }
94
+ }
95
+ const m = /<img\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/i.exec(html || '');
96
+ if (m) {
97
+ try {
98
+ return new URL(m[1], baseUrl).href;
99
+ } catch {
100
+ /* ignore */
101
+ }
102
+ }
103
+ return '';
104
+ }
105
+
106
+ // ───────────────────────── 纯函数:HTML→Markdown ─────────────────────────
107
+
108
+ const ENTITIES = {
109
+ '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#39;': "'",
110
+ '&apos;': "'", '&nbsp;': ' ', '&mdash;': '—', '&ndash;': '–', '&hellip;': '…',
111
+ };
112
+ export function decodeEntities(s) {
113
+ return String(s || '')
114
+ .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
115
+ .replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16)))
116
+ .replace(/&[a-z]+;/gi, (e) => ENTITIES[e.toLowerCase()] ?? e);
117
+ }
118
+
119
+ /**
120
+ * 零依赖的内置 HTML→Markdown 回落(turndown 不可用时用)。粗但够爬虫消费:
121
+ * 剥 script/style/head/nav/footer,标题→#、链接→[t](u)、列表→-、p/br→换行、其余去标签。
122
+ */
123
+ export function fallbackHtmlToMarkdown(html) {
124
+ let s = String(html || '');
125
+ s = s.replace(/<!--[\s\S]*?-->/g, '');
126
+ s = s.replace(/<(script|style|head|noscript|svg|template)\b[\s\S]*?<\/\1>/gi, '');
127
+ s = s.replace(/<(nav|footer|header|aside)\b[\s\S]*?<\/\1>/gi, ' ');
128
+ s = s.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_, n, t) => `\n\n${'#'.repeat(Number(n))} ${strip(t)}\n\n`);
129
+ s = s.replace(/<a\b[^>]*\bhref\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, t) => {
130
+ const text = strip(t);
131
+ return text ? `[${text}](${href})` : '';
132
+ });
133
+ s = s.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, (_, t) => `\n- ${strip(t)}`);
134
+ s = s.replace(/<(p|div|section|article|tr|h[1-6]|ul|ol|table|br|hr)\b[^>]*>/gi, '\n');
135
+ s = s.replace(/<\/(p|div|section|article|tr|li|ul|ol|table)>/gi, '\n');
136
+ s = s.replace(/<[^>]+>/g, ''); // 剩余标签全去
137
+ s = decodeEntities(s);
138
+ s = s.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').replace(/[ \t]{2,}/g, ' ');
139
+ return s.trim();
140
+ }
141
+
142
+ function strip(htmlFrag) {
143
+ return decodeEntities(String(htmlFrag || '').replace(/<[^>]+>/g, '')).replace(/\s+/g, ' ').trim();
144
+ }
145
+
146
+ /** turndown 有就用(质量更好),无则回落内置。异步(动态 import)。 */
147
+ export async function htmlToMarkdown(html) {
148
+ try {
149
+ const mod = await import('turndown');
150
+ const Turndown = mod.default || mod;
151
+ const td = new Turndown({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
152
+ // 先剥 script/style(turndown 不剥)
153
+ const cleaned = String(html || '').replace(/<(script|style|noscript)\b[\s\S]*?<\/\1>/gi, '');
154
+ return td.turndown(cleaned).trim();
155
+ } catch {
156
+ return fallbackHtmlToMarkdown(html);
157
+ }
158
+ }
159
+
160
+ // ───────────────────────── 纯函数:URL / 域名 / robots ─────────────────────────
161
+
162
+ const TRACKING_PARAMS = /^(utm_|fbclid$|gclid$|gbraid$|wbraid$|msclkid$|ref$|ref_src$|spm$|from$|yclid$|igshid$)/i;
163
+
164
+ /** URL 规范化:去 hash、剥 tracking 参数、host 小写、去默认端口、去末尾斜杠(非根)。用于去重。 */
165
+ export function canonicalizeUrl(url) {
166
+ try {
167
+ const u = new URL(url);
168
+ u.hash = '';
169
+ u.hostname = u.hostname.toLowerCase();
170
+ if (u.pathname !== '/') u.pathname = u.pathname.replace(/\/+$/, '') || '/'; // 去末尾斜杠(非根)
171
+ const keep = [];
172
+ for (const [k, v] of u.searchParams) if (!TRACKING_PARAMS.test(k)) keep.push([k, v]);
173
+ keep.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
174
+ u.search = '';
175
+ for (const [k, v] of keep) u.searchParams.append(k, v);
176
+ return u.href;
177
+ } catch {
178
+ return url;
179
+ }
180
+ }
181
+
182
+ /** 该 URL 是否在爬取域范围内。 */
183
+ export function shouldFollow(url, { origin, allowDomains, sameOrigin }) {
184
+ let host;
185
+ try {
186
+ const u = new URL(url);
187
+ if (!/^https?:$/i.test(u.protocol)) return false;
188
+ host = u.hostname.toLowerCase();
189
+ if (sameOrigin && origin) return u.origin === origin;
190
+ } catch {
191
+ return false;
192
+ }
193
+ if (allowDomains && allowDomains.length) {
194
+ return allowDomains.some((d) => host === d || host.endsWith(`.${d}`));
195
+ }
196
+ return true;
197
+ }
198
+
199
+ /** 解析 robots.txt:取 User-agent:* (和 *=fallback)组的 Disallow + Crawl-delay。 */
200
+ export function parseRobots(txt) {
201
+ const lines = String(txt || '').split(/\r?\n/);
202
+ const groups = [];
203
+ let cur = null;
204
+ for (let raw of lines) {
205
+ raw = raw.replace(/#.*$/, '').trim();
206
+ if (!raw) continue;
207
+ const m = /^([a-z-]+)\s*:\s*(.*)$/i.exec(raw);
208
+ if (!m) continue;
209
+ const field = m[1].toLowerCase();
210
+ const val = m[2].trim();
211
+ if (field === 'user-agent') {
212
+ if (!cur || cur.hasRules) {
213
+ cur = { agents: [], disallow: [], crawlDelay: 0, hasRules: false };
214
+ groups.push(cur);
215
+ }
216
+ cur.agents.push(val.toLowerCase());
217
+ } else if (cur && field === 'disallow') {
218
+ if (val) cur.disallow.push(val);
219
+ cur.hasRules = true;
220
+ } else if (cur && field === 'crawl-delay') {
221
+ const n = Number(val);
222
+ if (Number.isFinite(n)) cur.crawlDelay = n;
223
+ cur.hasRules = true;
224
+ }
225
+ }
226
+ const star = groups.find((g) => g.agents.includes('*'));
227
+ return star
228
+ ? { disallow: star.disallow, crawlDelay: star.crawlDelay }
229
+ : { disallow: [], crawlDelay: 0 };
230
+ }
231
+
232
+ /** robots 是否允许该路径。 */
233
+ export function isAllowedByRobots(url, robots) {
234
+ if (!robots || !robots.disallow || !robots.disallow.length) return true;
235
+ let path;
236
+ try {
237
+ path = new URL(url).pathname;
238
+ } catch {
239
+ return true;
240
+ }
241
+ return !robots.disallow.some((d) => d && path.startsWith(d));
242
+ }
243
+
244
+ /** 静态 HTML 疑似 JS-app(正文太空、靠 JS 渲染)→ 建议升级浏览器渲染。 */
245
+ export function looksJsRendered(html) {
246
+ const h = String(html || '');
247
+ const text = strip(h.replace(/<(script|style|noscript)\b[\s\S]*?<\/\1>/gi, ''));
248
+ const hasAppShell = /__NEXT_DATA__|id=["']root["']|id=["']app["']|window\.__NUXT__/i.test(h);
249
+ return text.length < 200 && (hasAppShell || /<script/i.test(h));
250
+ }
251
+
252
+ // ───────────────────────── 引擎 ─────────────────────────
253
+
254
+ /** 默认静态抓取:node 内置 fetch + 超时 + 浏览器 UA。 */
255
+ async function defaultFetch(url, { timeout = 20000, headers = {} } = {}) {
256
+ const ctrl = new AbortController();
257
+ const t = setTimeout(() => ctrl.abort(), timeout);
258
+ try {
259
+ const res = await fetch(url, {
260
+ redirect: 'follow',
261
+ headers: { 'User-Agent': UA, Accept: 'text/html,*/*', ...headers },
262
+ signal: ctrl.signal,
263
+ });
264
+ const ct = res.headers.get('content-type') || '';
265
+ const body = /text|html|json|xml|javascript/i.test(ct) ? await res.text() : '';
266
+ return { ok: res.ok, status: res.status, contentType: ct, body, url: res.url || url };
267
+ } catch (e) {
268
+ return { ok: false, status: 0, contentType: '', body: '', url, error: e?.message || String(e) };
269
+ } finally {
270
+ clearTimeout(t);
271
+ }
272
+ }
273
+
274
+ /** 默认 JS 渲染:spawn agent-browser 取渲染后 outerHTML(eval 输出双重编码,JSON.parse 一次解开)。 */
275
+ function defaultRender(url, { timeout = 30000 } = {}) {
276
+ return new Promise((resolve) => {
277
+ const session = 'crawl';
278
+ const run = (args) =>
279
+ new Promise((res) => {
280
+ let out = '';
281
+ let err = '';
282
+ let child;
283
+ try {
284
+ child = spawn('agent-browser', ['--session', session, ...args], {
285
+ stdio: ['ignore', 'pipe', 'pipe'],
286
+ windowsHide: true,
287
+ });
288
+ } catch (e) {
289
+ res({ code: -1, out: '', err: e?.message || String(e) });
290
+ return;
291
+ }
292
+ const kill = setTimeout(() => {
293
+ try {
294
+ child.kill();
295
+ } catch {}
296
+ }, timeout);
297
+ child.stdout.on('data', (c) => (out += c));
298
+ child.stderr.on('data', (c) => (err += c));
299
+ child.on('error', (e) => {
300
+ clearTimeout(kill);
301
+ res({ code: -1, out, err: err || e?.message });
302
+ });
303
+ child.on('close', (code) => {
304
+ clearTimeout(kill);
305
+ res({ code: code ?? -1, out, err });
306
+ });
307
+ });
308
+
309
+ (async () => {
310
+ const open = await run(['open', url]);
311
+ if (open.code !== 0) {
312
+ resolve({ ok: false, html: '', error: open.err || 'agent-browser open 失败' });
313
+ return;
314
+ }
315
+ const ev = await run(['eval', 'document.documentElement.outerHTML']);
316
+ let html = ev.out.trim();
317
+ // eval 输出双重编码:结果字符串被 agent-browser 再 JSON 编码一次 → JSON.parse 解开。
318
+ try {
319
+ const once = JSON.parse(html);
320
+ if (typeof once === 'string') html = once;
321
+ } catch {
322
+ /* 非双编码则原样用 */
323
+ }
324
+ resolve({ ok: ev.code === 0 && html.length > 0, html, error: ev.err });
325
+ })();
326
+ });
327
+ }
328
+
329
+ /** 抓 + 抽一个页面(含按需 JS 渲染升级)。返回页面记录(含 markdown)。 */
330
+ async function fetchPage(url, opts, deps) {
331
+ const fetchImpl = deps.fetchImpl || defaultFetch;
332
+ const renderImpl = deps.renderImpl || defaultRender;
333
+
334
+ let r = await fetchImpl(url, { timeout: opts.timeout });
335
+ let rendered = false;
336
+ let html = r.body || '';
337
+
338
+ const wantRender =
339
+ opts.render === 'on' || (opts.render !== 'off' && (!r.ok || looksJsRendered(html)));
340
+ if (wantRender) {
341
+ const rr = await renderImpl(url, { timeout: opts.timeout + 10000 });
342
+ if (rr && rr.ok && rr.html) {
343
+ html = rr.html;
344
+ rendered = true;
345
+ }
346
+ }
347
+
348
+ const og = extractOpenGraph(html);
349
+ const md = await htmlToMarkdown(html);
350
+ return {
351
+ url: r.url || url,
352
+ ok: r.ok || rendered,
353
+ status: r.status,
354
+ title: extractOpenGraph(html)['og:title'] || extractTitle(html),
355
+ rendered,
356
+ markdown: md,
357
+ og,
358
+ jsonld: extractJsonLd(html),
359
+ image: extractMainImage(html, og, r.url || url),
360
+ links: extractLinks(html, r.url || url),
361
+ error: r.error,
362
+ };
363
+ }
364
+
365
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
366
+
367
+ /**
368
+ * 主引擎。opts: { url, mode, out, depth, maxPages, sameOrigin, allowDomains[], render, concurrency, delay, timeout, respectRobots }
369
+ * deps(DI 测试): { fetchImpl, renderImpl, writeFileImpl, mkdirImpl }
370
+ * 返回 manifest 对象(同时落盘 out/page_NNN.md + out/crawl.json,除非 deps 提供假实现)。
371
+ */
372
+ export async function crawl(opts, deps = {}) {
373
+ const o = {
374
+ mode: 'single',
375
+ out: './build/crawl',
376
+ depth: 2,
377
+ maxPages: 20,
378
+ sameOrigin: false,
379
+ allowDomains: [],
380
+ render: 'auto',
381
+ concurrency: 3,
382
+ delay: 500,
383
+ timeout: 20000,
384
+ respectRobots: true,
385
+ ...opts,
386
+ };
387
+ const writeFileImpl = deps.writeFileImpl || writeFile;
388
+ const mkdirImpl = deps.mkdirImpl || mkdir;
389
+ const fetchImpl = deps.fetchImpl || defaultFetch;
390
+
391
+ const origin = (() => {
392
+ try {
393
+ return new URL(o.url).origin;
394
+ } catch {
395
+ return '';
396
+ }
397
+ })();
398
+
399
+ // robots(site 模式且 respectRobots)
400
+ let robots = { disallow: [], crawlDelay: 0 };
401
+ if (o.mode === 'site' && o.respectRobots && origin) {
402
+ const rb = await fetchImpl(`${origin}/robots.txt`, { timeout: o.timeout });
403
+ if (rb.ok && rb.body) robots = parseRobots(rb.body);
404
+ }
405
+ const perDomainDelay = Math.max(o.delay, (robots.crawlDelay || 0) * 1000);
406
+
407
+ const pages = [];
408
+ const seen = new Set();
409
+ const queue = [{ url: o.url, depth: 0 }];
410
+ seen.add(canonicalizeUrl(o.url));
411
+
412
+ await mkdirImpl(o.out, { recursive: true });
413
+
414
+ while (queue.length) {
415
+ const done = pages.filter((p) => p.file).length;
416
+ if (done >= o.maxPages) break;
417
+ // 批大小按剩余配额收,避免一个并发批一次超过 maxPages。
418
+ const room = o.mode === 'single' ? 1 : Math.max(1, Math.min(o.concurrency, o.maxPages - done));
419
+ const batch = queue.splice(0, room);
420
+ const results = await Promise.all(
421
+ batch.map(async (item) => {
422
+ if (o.mode === 'site' && !isAllowedByRobots(item.url, robots)) {
423
+ return { skipped: 'robots', url: item.url };
424
+ }
425
+ const page = await fetchPage(item.url, o, deps);
426
+ return { page, depth: item.depth };
427
+ }),
428
+ );
429
+
430
+ for (const res of results) {
431
+ if (res.skipped) {
432
+ pages.push({ url: res.url, skipped: res.skipped });
433
+ continue;
434
+ }
435
+ const { page, depth } = res;
436
+ const n = pages.filter((p) => p.file).length + 1;
437
+ const file = join(o.out, `page_${String(n).padStart(3, '0')}.md`);
438
+ const front =
439
+ `---\nurl: ${page.url}\ntitle: ${JSON.stringify(page.title)}\nrendered: ${page.rendered}\n` +
440
+ (page.image ? `image: ${page.image}\n` : '') +
441
+ `---\n\n`;
442
+ await writeFileImpl(file, front + (page.markdown || ''), 'utf8');
443
+ pages.push({
444
+ url: page.url,
445
+ title: page.title,
446
+ file,
447
+ rendered: page.rendered,
448
+ status: page.status,
449
+ links: page.links.length,
450
+ jsonld: page.jsonld.length,
451
+ og: page.og,
452
+ error: page.error,
453
+ });
454
+
455
+ // site:把新链接入队
456
+ if (o.mode === 'site' && depth < o.depth) {
457
+ for (const link of page.links) {
458
+ const canon = canonicalizeUrl(link);
459
+ if (seen.has(canon)) continue;
460
+ if (!shouldFollow(link, { origin, allowDomains: o.allowDomains, sameOrigin: o.sameOrigin })) continue;
461
+ seen.add(canon);
462
+ queue.push({ url: link, depth: depth + 1 });
463
+ if (seen.size > o.maxPages * 20) break; // 前沿失控保护
464
+ }
465
+ }
466
+ }
467
+ if (o.mode === 'site' && queue.length && perDomainDelay) await sleep(perDomainDelay);
468
+ }
469
+
470
+ const manifest = {
471
+ seed: o.url,
472
+ mode: o.mode,
473
+ generated_pages: pages.filter((p) => p.file).length,
474
+ crawl_delay_ms: perDomainDelay,
475
+ pages,
476
+ };
477
+ await writeFileImpl(join(o.out, 'crawl.json'), JSON.stringify(manifest, null, 2), 'utf8');
478
+ return manifest;
479
+ }
480
+
481
+ // ───────────────────────── CLI ─────────────────────────
482
+
483
+ function parseArgs(argv) {
484
+ const o = { allowDomains: [] };
485
+ const pos = [];
486
+ for (let i = 0; i < argv.length; i++) {
487
+ const a = argv[i];
488
+ if (a === '--same-origin') o.sameOrigin = true;
489
+ else if (a === '--json') o.json = true;
490
+ else if (a === '--mode') o.mode = argv[++i];
491
+ else if (a === '--out') o.out = argv[++i];
492
+ else if (a === '--depth') o.depth = Number(argv[++i]);
493
+ else if (a === '--max-pages') o.maxPages = Number(argv[++i]);
494
+ else if (a === '--allow-domains') o.allowDomains = String(argv[++i] || '').split(',').map((s) => s.trim()).filter(Boolean);
495
+ else if (a === '--render') o.render = argv[++i];
496
+ else if (a === '--concurrency') o.concurrency = Number(argv[++i]);
497
+ else if (a === '--delay') o.delay = Number(argv[++i]);
498
+ else if (a === '--timeout') o.timeout = Number(argv[++i]);
499
+ else if (a === '--no-robots') o.respectRobots = false;
500
+ else if (!a.startsWith('--')) pos.push(a);
501
+ }
502
+ o.url = pos[0];
503
+ return o;
504
+ }
505
+
506
+ async function main() {
507
+ const o = parseArgs(process.argv.slice(2));
508
+ if (!o.url) {
509
+ process.stderr.write(
510
+ 'CRAWL_ERROR: 缺 URL。用法 node crawl.mjs <url> [--mode single|site] [--out ./build/crawl] [--depth N] [--max-pages N] [--same-origin|--allow-domains a,b] [--render auto|on|off] [--json]\n',
511
+ );
512
+ process.exit(2);
513
+ }
514
+ try {
515
+ const manifest = await crawl(o);
516
+ if (o.json) process.stdout.write(`${JSON.stringify(manifest)}\n`);
517
+ process.stderr.write(
518
+ `CRAWL_DONE: ${manifest.generated_pages} pages → ${join(o.out || './build/crawl', 'crawl.json')}\n`,
519
+ );
520
+ process.exit(0);
521
+ } catch (e) {
522
+ process.stderr.write(`CRAWL_ERROR: ${e?.message || String(e)}\n`);
523
+ process.exit(1);
524
+ }
525
+ }
526
+
527
+ // main 守卫:node 与 bun 通用(直接跑本文件才执行 main,被 import 时不跑)。
528
+ const isMain = (() => {
529
+ try {
530
+ if (typeof import.meta !== 'undefined' && import.meta.main) return true; // bun
531
+ } catch {}
532
+ try {
533
+ return process.argv[1] && import.meta.url === new URL(`file://${process.argv[1].replace(/\\/g, '/')}`).href;
534
+ } catch {
535
+ return false;
536
+ }
537
+ })();
538
+ if (isMain) main();