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.
- package/README.md +14 -12
- package/package.json +34 -1
- package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
- package/plugins/cron-runner/commands/cron.md +26 -0
- package/plugins/cron-runner/commands/crons.md +7 -0
- package/plugins/cron-runner/plugin.js +34 -0
- package/plugins/cron-runner/src/commands.js +348 -0
- package/plugins/cron-runner/src/cronTime.js +148 -0
- package/plugins/cron-runner/src/lock.js +68 -0
- package/plugins/cron-runner/src/runs.js +89 -0
- package/plugins/cron-runner/src/scheduleEngine.js +72 -0
- package/plugins/cron-runner/src/scheduler.js +355 -0
- package/plugins/cron-runner/src/schema.js +140 -0
- package/plugins/cron-runner/src/service.js +167 -0
- package/plugins/cron-runner/src/store.js +298 -0
- package/plugins/cron-runner/src/types.js +1 -0
- package/plugins/workflow-runner/commands/workflow.md +4 -4
- package/plugins/workflow-runner/commands/workflows.md +1 -1
- package/plugins/workflow-runner/plugin.js +3 -1
- package/plugins/workflow-runner/src/agentCommon.js +249 -51
- package/plugins/workflow-runner/src/dslManifest.js +72 -0
- package/plugins/workflow-runner/src/expressions.js +198 -15
- package/plugins/workflow-runner/src/index.js +40 -8
- package/plugins/workflow-runner/src/jsonPayload.js +1 -1
- package/plugins/workflow-runner/src/loader.js +286 -72
- package/plugins/workflow-runner/src/multimodal.js +37 -0
- package/plugins/workflow-runner/src/recovery.js +49 -39
- package/plugins/workflow-runner/src/runner.js +802 -287
- package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
- package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
- package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
- package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
- package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
- package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
- package/plugins/workflow-runner/src/taskIdentity.js +296 -0
- package/plugins/workflow-runner/src/types.js +19 -9
- package/plugins/workflow-runner/src/workflowState.js +516 -33
- package/skills/adversarial-review/SKILL.md +0 -1
- package/skills/ai-image/SKILL.md +89 -0
- package/skills/ai-image/scripts/generate.mjs +497 -0
- package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
- package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
- package/skills/ai-image/scripts/generate_test.mjs +74 -0
- package/skills/browser-automation/SKILL.md +47 -0
- package/skills/docx/SKILL.md +25 -3
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/docx/scripts/opc_check.py +253 -0
- package/skills/image-gen-openrouter/SKILL.md +10 -2
- package/skills/image-stats/SKILL.md +65 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/image_stats.py +368 -0
- package/skills/image-stats/scripts/image_stats_test.py +181 -0
- package/skills/pptx/SKILL.md +123 -95
- package/skills/pptx/design.md +82 -0
- package/skills/pptx/examples/README.md +29 -0
- package/skills/pptx/examples/hybrid_example.mjs +107 -0
- package/skills/pptx/examples/hybrid_example.pptx +0 -0
- package/skills/pptx/pptxgenjs.md +41 -0
- package/skills/pptx/scripts/deck_helpers.mjs +224 -0
- package/skills/pptx/scripts/layout_lint.py +293 -0
- package/skills/pptx/scripts/layout_lint_test.py +160 -0
- package/skills/pptx/scripts/opc_check.py +339 -0
- package/skills/pptx/scripts/svg_fallback.mjs +145 -0
- package/skills/pptx/svg.md +67 -0
- package/skills/tts-zh/SKILL.md +19 -2
- package/skills/ui-design/SKILL.md +158 -0
- package/skills/ui-design/claude_design.md +309 -0
- package/skills/ui-design/evals/evals.json +254 -0
- package/skills/ui-design/references/aesthetic-direction.md +83 -0
- package/skills/ui-design/references/claude-design-protocol.md +36 -0
- package/skills/ui-design/references/design-context.md +53 -0
- package/skills/ui-design/references/design-execution-playbook.md +82 -0
- package/skills/ui-design/references/export-pptx.md +124 -0
- package/skills/ui-design/references/layout-composition.md +654 -0
- package/skills/ui-design/references/preview-feedback.md +37 -0
- package/skills/ui-design/references/source-image-layout.md +189 -0
- package/skills/ui-design/references/starter-components.md +78 -0
- package/skills/ui-design/references/vi-aegis-vector.md +144 -0
- package/skills/ui-design/references/vi-amber-prism.md +133 -0
- package/skills/ui-design/references/vi-aqua-glass.md +200 -0
- package/skills/ui-design/references/vi-comfort-vision.md +147 -0
- package/skills/ui-design/references/vi-ember-prism.md +140 -0
- package/skills/ui-design/references/vi-nebula-console.md +151 -0
- package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
- package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
- package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
- package/skills/ui-design/references/vi-precision-horizon.md +215 -0
- package/skills/ui-design/references/vi-solar-relay.md +130 -0
- package/skills/ui-design/references/vi-template-workflow.md +48 -0
- package/skills/ui-design/references/vi-verdant-grid.md +141 -0
- package/skills/ui-design/references/vi-violet-meridian.md +172 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/assemble_material.py +217 -0
- package/skills/ui-design/scripts/assemble_material_test.py +149 -0
- package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
- package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
- package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
- package/skills/ui-design/scripts/check_language_policy.py +351 -0
- package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
- package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
- package/skills/ui-design/scripts/deck_pages_check.py +158 -0
- package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
- package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
- package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
- package/skills/ui-design/scripts/design_budget_check.py +271 -0
- package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
- package/skills/ui-design/scripts/design_repair_hints.py +162 -0
- package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
- package/skills/ui-design/scripts/design_style_check.py +397 -0
- package/skills/ui-design/scripts/design_style_check_test.py +233 -0
- package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
- package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
- package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
- package/skills/ui-design/scripts/extract_source_assets.py +607 -0
- package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
- package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
- package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
- package/skills/ui-design/scripts/image_plan_lint.py +571 -0
- package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
- package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
- package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
- package/skills/ui-design/scripts/layout_balance.py +881 -0
- package/skills/ui-design/scripts/layout_balance_test.py +927 -0
- package/skills/ui-design/scripts/normalize_design_json.py +272 -0
- package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
- package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
- package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
- package/skills/ui-design/scripts/resolve_source_input.py +270 -0
- package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
- package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
- package/skills/ui-design/scripts/source_usage_check.py +581 -0
- package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
- package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
- package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
- package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
- package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
- package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
- package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
- package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
- package/skills/ui-design/tests/e2e/rubric.json +54 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
- package/skills/video-compose/SKILL.md +8 -1
- package/skills/web-crawler/SKILL.md +70 -0
- package/skills/web-crawler/scripts/crawl.mjs +538 -0
- package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
- package/skills/xlsx/SKILL.md +12 -0
- package/skills/xlsx/scripts/opc_check.py +253 -0
- package/src/advisorState.js +22 -0
- package/src/askBridge.js +97 -3
- package/src/bootstrap/workingDir.js +12 -1
- package/src/cli/args.js +17 -1
- package/src/cli/configMenu.js +236 -0
- package/src/cli/configWizard.js +7 -5
- package/src/cli/print.js +37 -4
- package/src/cli/streamJson.js +77 -24
- package/src/cli/wizards/advisorWizard.js +147 -0
- package/src/cli/wizards/fieldForm.js +149 -0
- package/src/cli/wizards/imageWizard.js +60 -0
- package/src/cli/wizards/openRouterWizard.js +23 -0
- package/src/cli/wizards/providerPresets.js +95 -0
- package/src/cli/wizards/providerWizard.js +219 -0
- package/src/cli/wizards/searchWizard.js +23 -0
- package/src/cli/wizards/ttsWizard.js +34 -0
- package/src/config/configFile.js +441 -83
- package/src/config/configService.js +446 -0
- package/src/config.js +24 -64
- package/src/context/agentHome.js +9 -0
- package/src/context/archive.js +7 -2
- package/src/context/compact.js +8 -5
- package/src/context/contextLock.js +81 -0
- package/src/context/persistContext.js +30 -11
- package/src/context/reactiveCompact.js +4 -2
- package/src/context/recentDirs.js +21 -61
- package/src/context/sessionPath.js +97 -23
- package/src/context/sessionRegistry.js +303 -24
- package/src/context/tokenCounter.js +18 -0
- package/src/context/workspaceIndex.js +731 -0
- package/src/infra/verification.js +28 -15
- package/src/llm/client.js +394 -159
- package/src/llm/providers/anthropicFormat.js +241 -0
- package/src/llm/providers/openaiFormat.js +190 -0
- package/src/loop.js +219 -32
- package/src/main.js +61 -11
- package/src/messageQueue.js +97 -0
- package/src/multimodal.js +203 -0
- package/src/plugin-sdk.js +15 -0
- package/src/plugins/commandRouter.js +3 -0
- package/src/plugins/hookEngine.js +5 -1
- package/src/plugins/pluginLifecycle.js +28 -0
- package/src/plugins/pluginLoader.js +2 -1
- package/src/plugins/pluginRunner.js +49 -21
- package/src/plugins/transcript.js +4 -3
- package/src/prompts/pluginList.js +5 -5
- package/src/prompts/projectInstructions.js +3 -3
- package/src/prompts/promptAssembly.js +133 -144
- package/src/prompts/skillList.js +12 -22
- package/src/prompts/system.js +0 -1
- package/src/session/runAgentTurn.js +70 -0
- package/src/session/runTurn.js +41 -6
- package/src/toolGate.js +17 -0
- package/src/tools/advisor/advisor.js +379 -0
- package/src/tools/agent/agent.js +555 -0
- package/src/tools/agent/agentTypes.js +59 -0
- package/src/tools/agent/coordinator.js +93 -0
- package/src/tools/agent/worktree.js +270 -0
- package/src/tools/ask/ask.js +7 -2
- package/src/tools/bash/bash.js +112 -18
- package/src/tools/download/download.js +144 -0
- package/src/tools/edit/edit.js +1 -0
- package/src/tools/edit/multi-edit.js +1 -0
- package/src/tools/genimage/genimage.js +242 -0
- package/src/tools/genspeech/genspeech.js +67 -0
- package/src/tools/glob/glob.js +1 -0
- package/src/tools/grep/grep.js +3 -0
- package/src/tools/grep/rgPath.js +2 -0
- package/src/tools/index.js +25 -2
- package/src/tools/read/read.js +1 -0
- package/src/tools/shared/killTree.js +52 -0
- package/src/tools/toolProgressBridge.js +48 -0
- package/src/tools/webbrowser/agentBrowserPath.js +258 -0
- package/src/tools/webbrowser/browser.js +59 -42
- package/src/tools/webbrowser/browserControl.js +118 -0
- package/src/tools/webbrowser/browserDaemon.js +97 -0
- package/src/tools/webbrowser/browserService.js +27 -0
- package/src/tools/webbrowser/browserStream.js +119 -0
- package/src/tools/webbrowser/webbrowser.js +699 -130
- package/src/tools/webfetch/webfetch.js +1 -0
- package/src/tools/websearch/websearch.js +134 -51
- package/src/tools/write/write.js +1 -0
- package/src/ui/App.js +249 -22
- package/src/ui/AskQuestion.js +29 -10
- package/src/ui/InputBox.js +104 -12
- package/src/ui/LiveArea.js +112 -19
- package/src/ui/MessageList.js +100 -14
- package/src/ui/ModelPicker.js +56 -0
- package/src/ui/QueuedMessagesBanner.js +14 -0
- package/src/ui/Root.js +18 -10
- package/src/ui/SessionPicker.js +102 -0
- package/src/ui/StatusLine.js +13 -4
- package/src/ui/WorkspacePicker.js +55 -0
- package/src/ui/figures.js +44 -0
- package/src/ui/hooks/askSlot.js +82 -0
- package/src/ui/hooks/useChat.js +358 -64
- package/src/ui/hooks/usePasteHandler.js +11 -1
- package/src/ui/hooks/useTextBuffer.js +1 -3
- package/src/ui/liveViewport.js +5 -1
- package/src/ui/markdown.js +206 -0
- package/src/ui/textBuffer.js +0 -7
- package/src/ui/theme.js +32 -0
- package/src/utils/debugLog.js +0 -4
- package/src/utils/resourcePaths.js +2 -2
- package/src/utils/stripThink.js +50 -0
- package/src/web/browserBridge.js +447 -0
- package/src/web/protocol.js +19 -0
- package/workflows/adaptive-research.yaml +13 -5
- package/workflows/book-review-short.yaml +36 -10
- package/workflows/e2e-write-greet.yaml +6 -2
- package/workflows/illustrate.yaml +227 -0
- package/workflows/plan.yaml +460 -179
- package/workflows/ppt.yaml +1659 -0
- package/workflows/ralph-loop.yaml +199 -0
- package/workflows/schema.json +81 -23
- package/workflows/webppt.yaml +496 -0
- package/workflows/word.yaml +710 -0
- package/workflows/youtube-shorts.yaml +46 -15
- package/MINIMAL-TRACE.md +0 -77
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
- package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
- package/plugins/ralph-wiggum/README.md +0 -197
- package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
- package/plugins/ralph-wiggum/commands/help.md +0 -137
- package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
- package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
- package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
- package/plugins/ralph-wiggum/plugin.js +0 -280
- package/plugins/ralph-wiggum/src/goalState.js +0 -258
- package/plugins/ralph-wiggum/src/sentinels.js +0 -76
- package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
- package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
- package/plugins/workflow-runner/src/miniReAct.js +0 -223
- package/plugins/workflow-runner/src/voteHelpers.js +0 -110
- package/skills/config/SKILL.md +0 -123
- package/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/frontend-design/SKILL.md +0 -42
- package/src/ui/ProgressPanel.js +0 -54
- package/src/ui/StreamingBlock.js +0 -11
- package/src/ui/ToolStatus.js +0 -11
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
opc_check.py —— OOXML(pptx/docx/xlsx 本质都是 OPC zip 包)的「忠实合规校验 + 重打包」工具。
|
|
5
|
+
|
|
6
|
+
为什么需要这个脚本?
|
|
7
|
+
====================
|
|
8
|
+
pptx/docx/xlsx 文件本质是一个遵循 OPC(Open Packaging Conventions, ECMA-376 第 2 部分)
|
|
9
|
+
规范的 zip 包。PowerPoint / Word / Excel 打开文件时会做**严格的 OPC 校验**,任何一处不合规
|
|
10
|
+
都可能被判「需要修复」、修复后内容丢失。
|
|
11
|
+
|
|
12
|
+
但是——「某个库能 load/import ≠ 目标程序(PowerPoint/Word/Excel)能干净打开」。
|
|
13
|
+
python-pptx / python-docx / openpyxl 这类库非常宽容:它们会忽略 zip 里的目录条目、不在乎
|
|
14
|
+
[Content_Types].xml 是不是第一个条目,照样 load 成功。于是「用 python-pptx 重新打开来验证」
|
|
15
|
+
会**误报通过**,让损坏的包溜过验收闸门(这正是本仓库踩过的坑:PptxGenJS 在 Bun 下用 JSZip
|
|
16
|
+
产出含 19 个目录条目 + STORE 无压缩的非合规包,python-pptx 照常加载,PowerPoint 却判损坏)。
|
|
17
|
+
|
|
18
|
+
因此本脚本**绝不依赖任何宽容的高层库**,只用标准库(zipfile / xml.etree / posixpath / re),
|
|
19
|
+
直接从 zip 字节层面 + OPC 规范层面做校验,忠实于最终消费者(Office)。
|
|
20
|
+
|
|
21
|
+
校验项(任一不合规 → 非零退出码 + 打印具体问题)
|
|
22
|
+
================================================
|
|
23
|
+
1. zip 里没有目录条目(条目名以 "/" 结尾的应为 0 个)。
|
|
24
|
+
—— Office 严格 OPC 校验不接受目录条目;JSZip/Bun 常错误地写入它们。
|
|
25
|
+
2. [Content_Types].xml 是 zip 的第一个条目。
|
|
26
|
+
—— OPC 规范虽未强制顺序,但 Office 的快速解析依赖它在前;不在首位常触发「需要修复」。
|
|
27
|
+
3. 所有 .rels 里的内部 Target(TargetMode != External)都能解析到真实存在的部件。
|
|
28
|
+
—— 悬空关系(指向不存在的部件)是最常见的「文件损坏」根因。
|
|
29
|
+
4. 没有 0 字节的媒体文件(ppt/media/、word/media/、xl/media/ 下)。
|
|
30
|
+
—— 空图片 = 渲染失败 / 修复时被丢弃。
|
|
31
|
+
5. 所有 .xml / .rels 都是良构(well-formed)XML。
|
|
32
|
+
—— 坏 XML 直接导致 Office 拒绝加载该部件。
|
|
33
|
+
|
|
34
|
+
6. [Content_Types].xml 里的 <Override PartName="..."> 都能解析到真实存在的部件。
|
|
35
|
+
—— 「孤儿 Override」是「opc_check 报 PASSED 但 PowerPoint 判修复」的最常见根因:
|
|
36
|
+
OPC 规范允许这种孤儿,但 PowerPoint 严格对账「声明 vs 实物」时把数量严重不符的包判损坏。
|
|
37
|
+
典型成因:生成端把另一种 part 的数量当成这种 part 的数量
|
|
38
|
+
(例如 18 张 slide 却声明了 18 个 slideMaster{n}.xml,实际只有 1 个母版)。
|
|
39
|
+
|
|
40
|
+
用法
|
|
41
|
+
====
|
|
42
|
+
python opc_check.py <file> # 校验,合规退出 0,不合规退出 1
|
|
43
|
+
python opc_check.py <file> --verbose # 同上,额外打印每项 PASSED 明细
|
|
44
|
+
python opc_check.py --repack <in> <out> # OPC 重打包:把不合规包修回合规
|
|
45
|
+
|
|
46
|
+
重打包(--repack)做了什么?
|
|
47
|
+
===========================
|
|
48
|
+
读入源包里所有**非目录**条目,按「[Content_Types].xml 第一、_rels/.rels 第二、其余其后」的
|
|
49
|
+
顺序,用 DEFLATE 压缩重写一个干净 zip,并自动删除 [Content_Types].xml 里的孤儿 Override。
|
|
50
|
+
这样即便生成器(如 Bun 下的 PptxGenJS)产出不合规包,也能一步修回合规——重打包后再跑一次校验即应全绿。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
import argparse
|
|
54
|
+
import posixpath
|
|
55
|
+
import re
|
|
56
|
+
import sys
|
|
57
|
+
import zipfile
|
|
58
|
+
import xml.etree.ElementTree as ET
|
|
59
|
+
|
|
60
|
+
# OPC 关系命名空间:.rels 文件里 <Relationship> 元素所在的命名空间,固定不变。
|
|
61
|
+
RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
|
|
62
|
+
CONTENT_TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types"
|
|
63
|
+
|
|
64
|
+
# 三大 OOXML 家族放媒体(图片等)的目录前缀,用于「0 字节媒体」检查。
|
|
65
|
+
MEDIA_DIRS = ("ppt/media/", "word/media/", "xl/media/")
|
|
66
|
+
|
|
67
|
+
CONTENT_TYPES = "[Content_Types].xml"
|
|
68
|
+
|
|
69
|
+
# 避免清理 [Content_Types].xml 后 ElementTree 写出 ns0 前缀。
|
|
70
|
+
ET.register_namespace("", CONTENT_TYPES_NS)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _normalize_part_target(rels_name: str, target: str) -> str:
|
|
74
|
+
"""
|
|
75
|
+
把一条关系里的 Target 解析成「包内绝对部件名」(不带前导 /)。
|
|
76
|
+
|
|
77
|
+
OPC 里 .rels 的 Target 是相对于「该 .rels 所描述部件所在目录」的相对路径:
|
|
78
|
+
- 根关系 _rels/.rels 描述的是包根 → 基准目录是包根 ""。
|
|
79
|
+
- 部件关系 <dir>/_rels/<part>.xml.rels 描述的是 <dir>/<part>.xml → 基准目录是 <dir>。
|
|
80
|
+
Target 也可能以 "/" 开头表示「相对包根的绝对路径」。
|
|
81
|
+
返回归一化后的 posix 路径(用 posixpath,因为 zip 内部一律用正斜杠)。
|
|
82
|
+
"""
|
|
83
|
+
if target.startswith("/"):
|
|
84
|
+
# 绝对路径:直接去掉前导斜杠即为部件名。
|
|
85
|
+
return target.lstrip("/")
|
|
86
|
+
|
|
87
|
+
# 求 .rels 的基准目录。rels_name 形如 "_rels/.rels" 或 "ppt/_rels/presentation.xml.rels"。
|
|
88
|
+
# 基准目录 = .rels 所在目录的父目录(因为 _rels 子目录描述的是其上一级里的部件)。
|
|
89
|
+
rels_dir = posixpath.dirname(rels_name) # 如 "ppt/_rels"
|
|
90
|
+
base_dir = posixpath.dirname(rels_dir) # 如 "ppt"(根关系时为 "")
|
|
91
|
+
joined = posixpath.join(base_dir, target) if base_dir else target
|
|
92
|
+
# posixpath.normpath 处理 "../" 等相对段,得到干净的部件名。
|
|
93
|
+
return posixpath.normpath(joined)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _content_type_override_parts(root: ET.Element) -> set[str]:
|
|
97
|
+
"""返回 [Content_Types].xml 声明的 Override PartName 集合(不带前导 /)。"""
|
|
98
|
+
return {
|
|
99
|
+
child.get("PartName", "").lstrip("/")
|
|
100
|
+
for child in list(root)
|
|
101
|
+
if child.tag == f"{{{CONTENT_TYPES_NS}}}Override" and child.get("PartName")
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _orphan_content_type_overrides(content_types_xml: bytes, present_parts: set[str]) -> list[str]:
|
|
106
|
+
"""找出 [Content_Types].xml 中声明了、但 zip 里不存在的 Override 部件。"""
|
|
107
|
+
root = ET.fromstring(content_types_xml)
|
|
108
|
+
return sorted(_content_type_override_parts(root) - present_parts)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _clean_content_type_overrides(content_types_xml: bytes, present_parts: set[str]) -> bytes:
|
|
112
|
+
"""
|
|
113
|
+
删除 [Content_Types].xml 中指向不存在部件的 Override。
|
|
114
|
+
|
|
115
|
+
若 XML 不良构,故意让 ET.ParseError 冒泡;调用方应保留原有失败语义,
|
|
116
|
+
不能用清理掩盖坏 XML。
|
|
117
|
+
"""
|
|
118
|
+
root = ET.fromstring(content_types_xml)
|
|
119
|
+
removed = False
|
|
120
|
+
|
|
121
|
+
for child in list(root):
|
|
122
|
+
if child.tag != f"{{{CONTENT_TYPES_NS}}}Override":
|
|
123
|
+
continue
|
|
124
|
+
part_name = child.get("PartName", "").lstrip("/")
|
|
125
|
+
if part_name and part_name not in present_parts:
|
|
126
|
+
root.remove(child)
|
|
127
|
+
removed = True
|
|
128
|
+
|
|
129
|
+
if not removed:
|
|
130
|
+
return content_types_xml
|
|
131
|
+
|
|
132
|
+
return ET.tostring(root, encoding="utf-8", xml_declaration=True)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _clean_content_type_overrides_for_repack(content_types_xml: bytes, present_parts: set[str]) -> bytes:
|
|
136
|
+
"""重打包路径的容错包装:坏 XML 保持原样,交给最终 check_compliance 报错。"""
|
|
137
|
+
try:
|
|
138
|
+
return _clean_content_type_overrides(content_types_xml, present_parts)
|
|
139
|
+
except ET.ParseError:
|
|
140
|
+
return content_types_xml
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def check_compliance(path: str, verbose: bool = False) -> list:
|
|
144
|
+
"""
|
|
145
|
+
对 OOXML 文件做忠实 OPC 合规校验,返回「问题列表」(空列表 = 合规)。
|
|
146
|
+
每个问题是一段人话描述的字符串,方便直接打印给用户/喂回 LLM 排障。
|
|
147
|
+
"""
|
|
148
|
+
problems = []
|
|
149
|
+
|
|
150
|
+
# 先确认它确实是个 zip(OOXML 包损坏到连 zip 都不是时,这里就拦下)。
|
|
151
|
+
if not zipfile.is_zipfile(path):
|
|
152
|
+
return [f"不是合法的 zip 包(OOXML 文件本质必须是 zip):{path}"]
|
|
153
|
+
|
|
154
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
155
|
+
infos = zf.infolist()
|
|
156
|
+
# namelist 保持 zip 物理顺序,用于「第一个条目」判定。
|
|
157
|
+
names = [i.filename for i in infos]
|
|
158
|
+
|
|
159
|
+
# 现存的所有「真实部件」名集合(排除目录条目),供关系 Target 解析时核对存在性。
|
|
160
|
+
present_parts = {i.filename for i in infos if not i.filename.endswith("/")}
|
|
161
|
+
|
|
162
|
+
# ---- 校验项 1:没有目录条目 ----
|
|
163
|
+
dir_entries = [n for n in names if n.endswith("/")]
|
|
164
|
+
if dir_entries:
|
|
165
|
+
problems.append(
|
|
166
|
+
"zip 含目录条目(OPC 不允许,Office 会判损坏):"
|
|
167
|
+
+ ", ".join(dir_entries)
|
|
168
|
+
)
|
|
169
|
+
elif verbose:
|
|
170
|
+
print("PASSED [1] 无目录条目")
|
|
171
|
+
|
|
172
|
+
# ---- 校验项 2:[Content_Types].xml 是第一个条目 ----
|
|
173
|
+
if CONTENT_TYPES not in present_parts:
|
|
174
|
+
problems.append(f"缺少 {CONTENT_TYPES}(OPC 必备部件)")
|
|
175
|
+
elif names and names[0] != CONTENT_TYPES:
|
|
176
|
+
problems.append(
|
|
177
|
+
f"{CONTENT_TYPES} 不是 zip 的第一个条目(首位是 '{names[0]}');"
|
|
178
|
+
"Office 严格解析依赖它在最前,可用 --repack 修复"
|
|
179
|
+
)
|
|
180
|
+
elif verbose:
|
|
181
|
+
print(f"PASSED [2] {CONTENT_TYPES} 位于首位")
|
|
182
|
+
|
|
183
|
+
# ---- 校验项 5(先做,因为 3 要解析 .rels XML):所有 .xml/.rels 良构 ----
|
|
184
|
+
# 把良构检查提前,能在解析 .rels 之前就把坏 XML 的部件挑出来,避免下面重复报错。
|
|
185
|
+
malformed = set()
|
|
186
|
+
for info in infos:
|
|
187
|
+
n = info.filename
|
|
188
|
+
if n.endswith("/"):
|
|
189
|
+
continue
|
|
190
|
+
if n.lower().endswith(".xml") or n.lower().endswith(".rels"):
|
|
191
|
+
try:
|
|
192
|
+
ET.fromstring(zf.read(n))
|
|
193
|
+
except ET.ParseError as e:
|
|
194
|
+
malformed.add(n)
|
|
195
|
+
problems.append(f"XML 不良构(Office 无法加载该部件):{n} —— {e}")
|
|
196
|
+
if verbose and not malformed:
|
|
197
|
+
print("PASSED [5] 所有 .xml/.rels 均为良构 XML")
|
|
198
|
+
|
|
199
|
+
# ---- 校验项 3:所有 .rels 内部 Target 都能解析到真实部件 ----
|
|
200
|
+
dangling = []
|
|
201
|
+
for info in infos:
|
|
202
|
+
n = info.filename
|
|
203
|
+
if not n.lower().endswith(".rels") or n in malformed:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
root = ET.fromstring(zf.read(n))
|
|
207
|
+
except ET.ParseError:
|
|
208
|
+
continue # 已在校验项 5 报过
|
|
209
|
+
for rel in root.iter("{%s}Relationship" % RELATIONSHIPS_NS):
|
|
210
|
+
# 外部关系(TargetMode="External",如 URL/超链接)不要求包内存在,跳过。
|
|
211
|
+
if rel.get("TargetMode") == "External":
|
|
212
|
+
continue
|
|
213
|
+
target = rel.get("Target")
|
|
214
|
+
if not target:
|
|
215
|
+
continue
|
|
216
|
+
part = _normalize_part_target(n, target)
|
|
217
|
+
if part not in present_parts:
|
|
218
|
+
dangling.append(f"{n} → Target '{target}'(解析为部件 '{part}',不存在)")
|
|
219
|
+
if dangling:
|
|
220
|
+
problems.append(
|
|
221
|
+
"存在悬空关系(.rels 指向不存在的部件,Office 会判损坏):\n "
|
|
222
|
+
+ "\n ".join(dangling)
|
|
223
|
+
)
|
|
224
|
+
elif verbose:
|
|
225
|
+
print("PASSED [3] 所有内部关系 Target 均指向存在的部件")
|
|
226
|
+
|
|
227
|
+
# ---- 校验项 6:[Content_Types].xml 的 <Override> 必须指向真实部件 ----
|
|
228
|
+
# 与校验项 3 对称:3 校验「关系指向」,6 校验「声明指向」。
|
|
229
|
+
# 依赖校验项 5([Content_Types].xml 良构):若已记入 malformed 则跳过。
|
|
230
|
+
if CONTENT_TYPES in present_parts and CONTENT_TYPES not in malformed:
|
|
231
|
+
try:
|
|
232
|
+
orphan = _orphan_content_type_overrides(zf.read(CONTENT_TYPES), present_parts)
|
|
233
|
+
except ET.ParseError:
|
|
234
|
+
pass
|
|
235
|
+
else:
|
|
236
|
+
if orphan:
|
|
237
|
+
problems.append(
|
|
238
|
+
"[Content_Types].xml 声明了 zip 里不存在的部件"
|
|
239
|
+
"(PowerPoint 打开时常判「需要修复」):\n "
|
|
240
|
+
+ "\n ".join(orphan)
|
|
241
|
+
)
|
|
242
|
+
elif verbose:
|
|
243
|
+
print("PASSED [6] Content_Types Override 全部指向存在的部件")
|
|
244
|
+
|
|
245
|
+
# ---- 校验项 4:没有 0 字节媒体文件 ----
|
|
246
|
+
empty_media = [
|
|
247
|
+
i.filename
|
|
248
|
+
for i in infos
|
|
249
|
+
if (not i.filename.endswith("/"))
|
|
250
|
+
and i.file_size == 0
|
|
251
|
+
and any(i.filename.startswith(d) for d in MEDIA_DIRS)
|
|
252
|
+
]
|
|
253
|
+
if empty_media:
|
|
254
|
+
problems.append(
|
|
255
|
+
"存在 0 字节媒体文件(空图片,渲染/修复时会被丢弃):"
|
|
256
|
+
+ ", ".join(empty_media)
|
|
257
|
+
)
|
|
258
|
+
elif verbose:
|
|
259
|
+
print("PASSED [4] 媒体文件均非空")
|
|
260
|
+
|
|
261
|
+
return problems
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def repack(src: str, dst: str) -> None:
|
|
265
|
+
"""
|
|
266
|
+
OPC 重打包:读入源包所有非目录条目,清理孤儿 Override,再用 DEFLATE 重写干净 zip。
|
|
267
|
+
顺序约定:[Content_Types].xml 第一 → _rels/.rels 第二 → 其余其后(保持源相对顺序)。
|
|
268
|
+
"""
|
|
269
|
+
with zipfile.ZipFile(src, "r") as zin:
|
|
270
|
+
# 只取真实部件,丢弃所有目录条目(这正是修复非合规包的关键一步)。
|
|
271
|
+
parts = [(i.filename, zin.read(i.filename)) for i in zin.infolist()
|
|
272
|
+
if not i.filename.endswith("/")]
|
|
273
|
+
|
|
274
|
+
present_parts = {name for name, _ in parts}
|
|
275
|
+
parts = [
|
|
276
|
+
(
|
|
277
|
+
name,
|
|
278
|
+
_clean_content_type_overrides_for_repack(data, present_parts)
|
|
279
|
+
if name == CONTENT_TYPES
|
|
280
|
+
else data,
|
|
281
|
+
)
|
|
282
|
+
for name, data in parts
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
def sort_key(item):
|
|
286
|
+
name = item[0]
|
|
287
|
+
if name == CONTENT_TYPES:
|
|
288
|
+
return (0, name) # Content_Types 永远第一
|
|
289
|
+
if name == "_rels/.rels":
|
|
290
|
+
return (1, name) # 根关系第二
|
|
291
|
+
return (2, name) # 其余按名次序其后
|
|
292
|
+
|
|
293
|
+
parts.sort(key=sort_key)
|
|
294
|
+
|
|
295
|
+
with zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED) as zout:
|
|
296
|
+
for name, data in parts:
|
|
297
|
+
# 显式指定 DEFLATE,杜绝源包里 STORE(无压缩)残留——Office 对 STORE 的兼容性更差。
|
|
298
|
+
zout.writestr(name, data, compress_type=zipfile.ZIP_DEFLATED)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def main() -> int:
|
|
302
|
+
parser = argparse.ArgumentParser(
|
|
303
|
+
description="OOXML(pptx/docx/xlsx)忠实 OPC 合规校验 + 重打包工具(仅依赖标准库)"
|
|
304
|
+
)
|
|
305
|
+
parser.add_argument("file", help="要校验的 OOXML 文件(或 --repack 模式下的输入文件)")
|
|
306
|
+
parser.add_argument(
|
|
307
|
+
"--repack",
|
|
308
|
+
metavar="OUT",
|
|
309
|
+
help="重打包模式:把 file 修回合规并写到 OUT(例:--repack fixed.pptx)",
|
|
310
|
+
)
|
|
311
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="打印每项 PASSED 明细")
|
|
312
|
+
args = parser.parse_args()
|
|
313
|
+
|
|
314
|
+
if args.repack:
|
|
315
|
+
repack(args.file, args.repack)
|
|
316
|
+
# 重打包后立刻自检,确保产物确实合规,否则别假装成功。
|
|
317
|
+
problems = check_compliance(args.repack, verbose=args.verbose)
|
|
318
|
+
if problems:
|
|
319
|
+
print(f"重打包到 {args.repack} 后仍存在 {len(problems)} 个问题:")
|
|
320
|
+
for p in problems:
|
|
321
|
+
print(" - " + p)
|
|
322
|
+
return 1
|
|
323
|
+
print(f"重打包完成且合规:{args.repack}")
|
|
324
|
+
return 0
|
|
325
|
+
|
|
326
|
+
problems = check_compliance(args.file, verbose=args.verbose)
|
|
327
|
+
if problems:
|
|
328
|
+
print(f"OPC 合规校验 FAILED —— {args.file} 存在 {len(problems)} 类问题:")
|
|
329
|
+
for p in problems:
|
|
330
|
+
print(" - " + p)
|
|
331
|
+
print("提示:可用 `python opc_check.py <in> --repack <out>` 一步修回合规。")
|
|
332
|
+
return 1
|
|
333
|
+
|
|
334
|
+
print(f"OPC 合规校验 PASSED —— {args.file} 是干净的 OPC 包,可被 Office 安全打开。")
|
|
335
|
+
return 0
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
sys.exit(main())
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// skills/pptx/scripts/svg_fallback.mjs
|
|
5
|
+
// ----------------------------------------------------------------------------
|
|
6
|
+
// 「真 SVG 配图」落 pptx 的兜底修复管线(Node + sharp)。生成 deck 后跑一次。
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// node skills/pptx/scripts/svg_fallback.mjs deck.pptx # 原地修复(自动备份 .bak)
|
|
10
|
+
// node skills/pptx/scripts/svg_fallback.mjs deck.pptx -o out.pptx # 写到新文件
|
|
11
|
+
//
|
|
12
|
+
// 为什么需要这一步?(实测确诊,见 svg.md)
|
|
13
|
+
// ========================================
|
|
14
|
+
// PptxGenJS 的 addImage({path:'x.svg'}) 在 **Node** 下会写出 PowerPoint 原生 SVG 结构
|
|
15
|
+
// (slide xml 里 <a:blip r:embed=PNG> + <asvg:svgBlip r:embed=SVG>,{96DAC541-...} 扩展),
|
|
16
|
+
// 其中 .svg 部件是真 SVG —— 所以现代 PowerPoint 能渲染、能「转换为形状」取消组合编辑。
|
|
17
|
+
// **但** PptxGenJS 在 Node 下不栅格化 SVG 的 PNG 兜底(官方 issue #401,源码直接
|
|
18
|
+
// rel.data = IMG_BROKEN / 或把 SVG 字节原样写进 .png)。后果:凡是读 PNG 兜底的消费者
|
|
19
|
+
// (LibreOffice 视觉 QA、旧版 PowerPoint、缩略图)全破图。
|
|
20
|
+
//
|
|
21
|
+
// 本脚本做两件事,一步交付一个「现代 + 旧版都不破图、且 OPC 合规」的 deck:
|
|
22
|
+
// 1. 对每个 ppt/media/*.svg,用 sharp 渲染一张**真** PNG,覆盖它配对的兜底 .png;
|
|
23
|
+
// 2. 调 opc_check.py --repack 做合规重打包(去目录条目 / [Content_Types].xml 置首 /
|
|
24
|
+
// DEFLATE / 删孤儿 Override),再 opc_check 自校验。
|
|
25
|
+
// —— 注:PptxGenJS 产出的包即便不含 SVG 也常需重打包,故本脚本对任何 deck 都安全幂等。
|
|
26
|
+
//
|
|
27
|
+
// 配对方式:解析每张 slide 的 blipFill —— <a:blip r:embed=A> 内嵌 <asvg:svgBlip r:embed=B>,
|
|
28
|
+
// 经该 slide 的 .rels 把 A→兜底 .png、B→真 .svg 解析成具体部件路径(既覆盖 path 形态,
|
|
29
|
+
// 也覆盖 base64 data 形态的 IMG_BROKEN 兜底)。
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
import { spawnSync } from 'node:child_process';
|
|
33
|
+
import fs from 'node:fs';
|
|
34
|
+
import path from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import JSZip from 'jszip';
|
|
37
|
+
import sharp from 'sharp';
|
|
38
|
+
|
|
39
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
const OPC_CHECK = path.join(__dirname, 'opc_check.py');
|
|
41
|
+
const PNG_MAX_WIDTH = 1600; // 兜底 PNG 够清晰即可(真正显示走 svgBlip);上限防超大
|
|
42
|
+
|
|
43
|
+
// ---- args -----------------------------------------------------------------
|
|
44
|
+
const argv = process.argv.slice(2);
|
|
45
|
+
const inPath = argv.find((a) => !a.startsWith('-'));
|
|
46
|
+
const outIdx = argv.findIndex((a) => a === '-o' || a === '--out');
|
|
47
|
+
const outPath = outIdx >= 0 ? argv[outIdx + 1] : inPath;
|
|
48
|
+
if (!inPath) {
|
|
49
|
+
console.error('usage: node svg_fallback.mjs <deck.pptx> [-o <out.pptx>]');
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---- 把 slide 同级目录 + rels Target 解析成部件绝对路径 --------------------
|
|
54
|
+
function resolvePart(fromDir, target) {
|
|
55
|
+
// target 形如 "../media/image-1-2.svg",相对 slide 的 _rels 上一级(即 slide 同级目录)
|
|
56
|
+
return path.posix.normalize(path.posix.join(fromDir, target));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
const buf = fs.readFileSync(inPath);
|
|
61
|
+
const zip = await JSZip.loadAsync(buf);
|
|
62
|
+
|
|
63
|
+
// 收集所有 slide xml
|
|
64
|
+
const slidePaths = Object.keys(zip.files).filter(
|
|
65
|
+
(n) => /^ppt\/slides\/slide\d+\.xml$/.test(n),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const pairs = []; // { pngPart, svgPart }
|
|
69
|
+
for (const sp of slidePaths) {
|
|
70
|
+
const xml = await zip.file(sp).async('string');
|
|
71
|
+
const dir = path.posix.dirname(sp); // ppt/slides
|
|
72
|
+
const relsPath = `${dir}/_rels/${path.posix.basename(sp)}.rels`;
|
|
73
|
+
const relsFile = zip.file(relsPath);
|
|
74
|
+
if (!relsFile) continue;
|
|
75
|
+
const relsXml = await relsFile.async('string');
|
|
76
|
+
const rid2target = {};
|
|
77
|
+
for (const m of relsXml.matchAll(/<Relationship\b[^>]*\bId="([^"]+)"[^>]*\bTarget="([^"]+)"[^>]*\/?>/g)) {
|
|
78
|
+
rid2target[m[1]] = m[2];
|
|
79
|
+
}
|
|
80
|
+
// blipFill 里 a:blip(r:embed=PNG) 包住 asvg:svgBlip(r:embed=SVG)
|
|
81
|
+
for (const m of xml.matchAll(/<a:blip\b[^>]*\br:embed="([^"]+)"[^>]*>[\s\S]*?<asvg:svgBlip\b[^>]*\br:embed="([^"]+)"/g)) {
|
|
82
|
+
const pngTgt = rid2target[m[1]];
|
|
83
|
+
const svgTgt = rid2target[m[2]];
|
|
84
|
+
if (!pngTgt || !svgTgt) continue;
|
|
85
|
+
pairs.push({
|
|
86
|
+
pngPart: resolvePart(dir, pngTgt),
|
|
87
|
+
svgPart: resolvePart(dir, svgTgt),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (pairs.length === 0) {
|
|
93
|
+
console.log('[svg_fallback] 未发现 SVG 配图(asvg:svgBlip)—— 仅做 OPC 合规重打包。');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 逐对:sharp 渲染真 PNG,覆盖兜底
|
|
97
|
+
let fixed = 0;
|
|
98
|
+
for (const { pngPart, svgPart } of pairs) {
|
|
99
|
+
const svgFile = zip.file(svgPart);
|
|
100
|
+
const pngFile = zip.file(pngPart);
|
|
101
|
+
if (!svgFile || !pngFile) {
|
|
102
|
+
console.warn(`[svg_fallback] 跳过:缺部件 ${svgPart} 或 ${pngPart}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const svgBuf = await svgFile.async('nodebuffer');
|
|
106
|
+
const png = await sharp(svgBuf, { density: 220 })
|
|
107
|
+
.resize({ width: PNG_MAX_WIDTH, withoutEnlargement: false })
|
|
108
|
+
.png()
|
|
109
|
+
.toBuffer();
|
|
110
|
+
zip.file(pngPart, png); // 覆盖
|
|
111
|
+
fixed++;
|
|
112
|
+
console.log(`[svg_fallback] 兜底已渲染:${pngPart} (${png.length} bytes, 真 PNG)`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 生成中间包(jszip 可能带目录条目/顺序问题 —— 交给 opc_check --repack 归一化)
|
|
116
|
+
const tmp = path.join(
|
|
117
|
+
path.dirname(path.resolve(outPath)),
|
|
118
|
+
`.svgfix-${path.basename(outPath)}.tmp.pptx`,
|
|
119
|
+
);
|
|
120
|
+
const outBuf = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
|
|
121
|
+
fs.writeFileSync(tmp, outBuf);
|
|
122
|
+
|
|
123
|
+
// 备份原文件(原地写时)
|
|
124
|
+
if (path.resolve(outPath) === path.resolve(inPath)) {
|
|
125
|
+
fs.copyFileSync(inPath, `${inPath}.bak`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// opc_check --repack 归一化到最终路径
|
|
129
|
+
const rp = spawnSync('python', [OPC_CHECK, tmp, '--repack', outPath], { stdio: 'inherit' });
|
|
130
|
+
fs.rmSync(tmp, { force: true });
|
|
131
|
+
if (rp.status !== 0) {
|
|
132
|
+
console.error('[svg_fallback] 重打包失败');
|
|
133
|
+
process.exit(rp.status ?? 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 自校验
|
|
137
|
+
const vr = spawnSync('python', [OPC_CHECK, outPath, '-v'], { stdio: 'inherit' });
|
|
138
|
+
console.log(`[svg_fallback] 完成:修复 ${fixed} 张 SVG 兜底 → ${outPath}`);
|
|
139
|
+
process.exit(vr.status ?? 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main().catch((e) => {
|
|
143
|
+
console.error('[svg_fallback] ERROR:', e.message);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SVG 配图(可编辑矢量美术)
|
|
2
|
+
|
|
3
|
+
PPT 的可编辑性命脉是「图形是矢量、可逐元素改」。本指南讲**美术配图**怎么用 SVG 做到,
|
|
4
|
+
以及 SVG 落 pptx 那一步的**已知坑 + 兜底管线**(实测确诊,下文给数据)。
|
|
5
|
+
|
|
6
|
+
> 结构图(框图/流程图/色块)不一定要 SVG —— 见下「图形决策规则」。
|
|
7
|
+
|
|
8
|
+
## 图形决策规则(先选对路线,再动手)
|
|
9
|
+
|
|
10
|
+
| 内容 | 用什么 | 为什么 |
|
|
11
|
+
|------|--------|--------|
|
|
12
|
+
| 标题 / 正文 / 项目符号 / 表格 —— **任何文字** | **原生 `addText`** | 可编辑、可重排、可改字号;**绝不**把文字烤进图片 |
|
|
13
|
+
| 框图 / 流程图 / 时序 / 色块布局(元素需独立可编辑、常含活文字) | **原生 `addShape`** | PptxGenJS 原生对象,点一下就选中改色/移动/改字,**连「取消组合」都不用**。复用 `scripts/deck_helpers.mjs` |
|
|
14
|
+
| 背景 / 渐变 / 插画 / 图标 / 曲线 / Logo —— addShape 表现不了的美术 | **SVG**(本指南) | 矢量、丰富(渐变/路径/曲线),PowerPoint 365 可「转换为形状」取消组合编辑 |
|
|
15
|
+
| 真实照片 / 已有位图素材 | `addImage(png/jpg)` | 本就是位图;封面/封底装饰 ≤2 张为宜 |
|
|
16
|
+
|
|
17
|
+
一句话:**文字 addText,结构 addShape,美术 SVG,照片 addImage。** 拿不准就退到 addShape(最稳)。
|
|
18
|
+
|
|
19
|
+
## 写「可取消组合」的 SVG
|
|
20
|
+
|
|
21
|
+
目标:用户在 PowerPoint 365 里右键 SVG →「图形格式 → 转换为形状」→「取消组合」后,得到一组**干净、独立、可改色**的形状。为此:
|
|
22
|
+
|
|
23
|
+
- **一个语义元素一个 `<g>`**(背景、色块、圆、流线各自分组)——转换为形状后就是一个个独立对象。
|
|
24
|
+
- **`viewBox` 用目标显示比例**:封面满铺用 `viewBox="0 0 1330 750"`(对齐 13.3"×7.5");图标用 `0 0 100 100`。比例对了,放进 slide 不变形。
|
|
25
|
+
- **避免栅格化滤镜**(`feGaussianBlur`/`feImage` 等)——转换为形状时会塌成位图,失去可编辑性。要阴影用 PptxGenJS 的 `shadow`,要发光用渐变近似。
|
|
26
|
+
- **文字尽量别写进 SVG `<text>`**:放回 pptx 原生文字层(`addText`),既可编辑又能被 markitdown 抽取做内容 QA。SVG 里只放纯图形。
|
|
27
|
+
- 渐变/不透明度 SVG 原生支持,放心用(`<linearGradient>`/`<radialGradient>`/`opacity`)——这正是 addShape 给不了的表现力。
|
|
28
|
+
|
|
29
|
+
参考 `examples/assets/cover-bg.svg`(满铺背景)和 `examples/assets/icon-spark.svg`(图标)。
|
|
30
|
+
|
|
31
|
+
## 落 pptx:嵌入 + 兜底管线(关键,别跳)
|
|
32
|
+
|
|
33
|
+
### 怎么嵌
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// Node 跑(不要 Bun)。把 SVG 当图片加,PptxGenJS 会写出 PowerPoint 原生 SVG 结构。
|
|
37
|
+
slide.addImage({ path: 'assets/cover-bg.svg', x: 0, y: 0, w: 13.3, h: 7.5 });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 为什么必须再跑一步 `svg_fallback.mjs`(实测确诊)
|
|
41
|
+
|
|
42
|
+
PptxGenJS 在 **Node** 下嵌 SVG 时:
|
|
43
|
+
- ✅ slide xml 会写出 PowerPoint 原生 SVG 结构:`<a:blip r:embed=PNG>` 内嵌 `<asvg:svgBlip r:embed=SVG>`(`{96DAC541-...}` 扩展),`.svg` 部件是**真 SVG** → 现代 PowerPoint 能渲染、能「转换为形状」。
|
|
44
|
+
- ❌ 但 **PNG 兜底不会被栅格化**(官方 [issue #401](https://github.com/gitbrent/PptxGenJS/issues/401)):那个 `.png` 部件实际装的是 SVG 字节(或 IMG_BROKEN)。凡是读 PNG 兜底的消费者——**LibreOffice 视觉 QA、旧版 PowerPoint、缩略图**——全破图。
|
|
45
|
+
- ❌ 且 PptxGenJS 产出的 zip 常含目录条目 / `[Content_Types].xml` 不在首位,本就需要合规重打包。
|
|
46
|
+
|
|
47
|
+
所以**生成 deck 后必跑**:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
node skills/pptx/scripts/svg_fallback.mjs deck.pptx # 原地修(留 deck.pptx.bak)
|
|
51
|
+
node skills/pptx/scripts/svg_fallback.mjs deck.pptx -o out.pptx
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
它做两件事,一步交付「现代 + 旧版都不破图、且 OPC 合规」的 deck:
|
|
55
|
+
1. 用 `sharp` 把每个 `ppt/media/*.svg` 渲染成**真** PNG,覆盖配对的兜底 `.png`;
|
|
56
|
+
2. 调 `opc_check.py --repack` 合规重打包(去目录条目 / Content_Types 置首 / DEFLATE / 删孤儿 Override)+ 自校验。
|
|
57
|
+
|
|
58
|
+
对不含 SVG 的 deck 也安全幂等(只做合规重打包)。依赖:`sharp`(npm)+ `python`。
|
|
59
|
+
|
|
60
|
+
### 用户侧验证可编辑性
|
|
61
|
+
|
|
62
|
+
把 deck 在 **PowerPoint 365 / 2019+** 打开 → 点 SVG 图 → 右键「图形格式」→「转换为形状」→ 再右键「组合 → 取消组合」→ 每个 `<g>` 成为可单独改色/移动的形状。旧版 PowerPoint 看到的是 `svg_fallback` 渲染的清晰 PNG(不破图,但不可拆)。
|
|
63
|
+
|
|
64
|
+
## 完整跑通的范例
|
|
65
|
+
|
|
66
|
+
`examples/hybrid_example.mjs` —— 3 页演示文字/结构/美术三路混用,末尾自动跑 `svg_fallback.mjs`。
|
|
67
|
+
直接 `node skills/pptx/examples/hybrid_example.mjs`,产物 OPC 校验全绿、含 2 张可转换为形状的 SVG。
|
package/skills/tts-zh/SKILL.md
CHANGED
|
@@ -3,7 +3,18 @@ name: tts-zh
|
|
|
3
3
|
description: 当用户说"中文配音"、"中文 TTS"、"语音合成"、"文字转语音"、"tts-zh",或在 workflow 里要把中文文本转成 mp3 时使用。优先用 MiniMax T2A API,失败时 fallback 到 edge-tts 离线方案,输出 mp3 到指定路径。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# tts-zh —— 中文 TTS
|
|
6
|
+
# tts-zh —— 中文 TTS
|
|
7
|
+
|
|
8
|
+
优先调用内核 `GenerateSpeech` 工具。该工具从统一 ConfigService 安全读取 MiniMax
|
|
9
|
+
凭据;**不要**用 Bash、`echo`、`env` 或 curl 获取/传递 API Key。
|
|
10
|
+
|
|
11
|
+
## 环境变量
|
|
12
|
+
|
|
13
|
+
| 变量 | 必需性 | 说明 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `MINIMAX_API_KEY` | minimax 路径必需 | MiniMax API key;缺则直接走 edge-tts fallback |
|
|
16
|
+
| `MINIMAX_GROUP_ID` | minimax 路径必需 | MiniMax GroupId(T2A 接口 query 参数);缺则直接走 edge-tts fallback |
|
|
17
|
+
| `MINIMAX_BASE_URL` | 可选 | 自定义网关地址,默认 `https://api.minimax.chat` |
|
|
7
18
|
|
|
8
19
|
## 输入约定
|
|
9
20
|
|
|
@@ -25,7 +36,13 @@ text=今天天气真好 output=./assets/audio_0.mp3 voice=female-shaonv speed=1.
|
|
|
25
36
|
|
|
26
37
|
仔细取 text / output / voice / speed。text 可能包含空格甚至标点,按"剩余串"策略提取。
|
|
27
38
|
|
|
28
|
-
### 2.
|
|
39
|
+
### 2. 调用受控工具
|
|
40
|
+
|
|
41
|
+
调用 `GenerateSpeech`,传入 `text`、`output`、`voice`、`speed`。成功后直接返回产物。
|
|
42
|
+
|
|
43
|
+
下列 shell 示例仅用于历史说明,不应由 Agent 执行。
|
|
44
|
+
|
|
45
|
+
### 3. 旧版 MiniMax T2A 说明
|
|
29
46
|
|
|
30
47
|
#### 2.1 取 key + group id
|
|
31
48
|
|