@huicai/cli 2026.814.0 → 2026.1019.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/LICENSE +391 -32
- package/README.md +137 -8
- package/bin/huicai.js +146 -73
- package/package.json +27 -8
- package/skills/ab-testing/trigger-cases.json +30 -0
- package/skills/academic-writing/trigger-cases.json +30 -0
- package/skills/accounting-ops/trigger-cases.json +30 -0
- package/skills/ai-agent/trigger-cases.json +30 -0
- package/skills/ai-aiops/trigger-cases.json +30 -0
- package/skills/ai-coding/trigger-cases.json +30 -0
- package/skills/ai-compliance/trigger-cases.json +30 -0
- package/skills/ai-eval/trigger-cases.json +30 -0
- package/skills/ai-llm-app/trigger-cases.json +30 -0
- package/skills/ai-mlops/trigger-cases.json +30 -0
- package/skills/ai-multimodal/trigger-cases.json +30 -0
- package/skills/ai-prompt/trigger-cases.json +30 -0
- package/skills/ai-rag/trigger-cases.json +30 -0
- package/skills/anomaly-detection/trigger-cases.json +30 -0
- package/skills/api-design/trigger-cases.json +31 -0
- package/skills/api-integration/trigger-cases.json +31 -0
- package/skills/arch-design/trigger-cases.json +12 -0
- package/skills/arch-diagram/SKILL.md +118 -0
- package/skills/arch-diagram/trigger-cases.json +30 -0
- package/skills/arch-distributed/trigger-cases.json +30 -0
- package/skills/arch-multitenant/trigger-cases.json +30 -0
- package/skills/audio-brief/SKILL.md +65 -0
- package/skills/audio-brief/trigger-cases.json +30 -0
- package/skills/audit-assist/trigger-cases.json +30 -0
- package/skills/audit-chain/trigger-cases.json +31 -0
- package/skills/banking-credit/trigger-cases.json +30 -0
- package/skills/brand-design/trigger-cases.json +30 -0
- package/skills/business-plan/trigger-cases.json +30 -0
- package/skills/capital-ops/trigger-cases.json +30 -0
- package/skills/cicd-pipeline/trigger-cases.json +31 -0
- package/skills/code-archaeology/SKILL.md +55 -0
- package/skills/code-archaeology/trigger-cases.json +9 -0
- package/skills/code-quality/SKILL.md +138 -0
- package/skills/code-quality/references/languages.md +107 -0
- package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/code_battery.py +1072 -0
- package/skills/code-quality/scripts/code_check.py +1269 -0
- package/skills/code-quality/scripts/code_csharp.py +371 -0
- package/skills/code-quality/scripts/code_fix.py +201 -0
- package/skills/code-quality/scripts/code_java.py +695 -0
- package/skills/code-quality/scripts/code_jsts.py +313 -0
- package/skills/code-quality/scripts/code_lens.py +1079 -0
- package/skills/code-quality/scripts/code_python.py +536 -0
- package/skills/code-quality/scripts/code_testplan.py +239 -0
- package/skills/code-quality/trigger-cases.json +45 -0
- package/skills/code-review/SKILL.md +24 -0
- package/skills/code-review/trigger-cases.json +35 -0
- package/skills/commit-and-pr/trigger-cases.json +31 -0
- package/skills/content-creation/trigger-cases.json +30 -0
- package/skills/corporate-governance/trigger-cases.json +30 -0
- package/skills/crawl-chain/trigger-cases.json +31 -0
- package/skills/customer-service/trigger-cases.json +30 -0
- package/skills/data-analysis/trigger-cases.json +30 -0
- package/skills/data-chain/SKILL.md +1 -1
- package/skills/data-chain/trigger-cases.json +31 -0
- package/skills/data-governance/trigger-cases.json +30 -0
- package/skills/data-report-pro/trigger-cases.json +30 -0
- package/skills/data-streaming/trigger-cases.json +30 -0
- package/skills/data-wrangling/SKILL.md +1 -1
- package/skills/data-wrangling/trigger-cases.json +30 -0
- package/skills/db-es/trigger-cases.json +30 -0
- package/skills/db-migration/trigger-cases.json +30 -0
- package/skills/db-mysql/trigger-cases.json +30 -0
- package/skills/db-postgres/trigger-cases.json +30 -0
- package/skills/db-redis/trigger-cases.json +30 -0
- package/skills/debug-root-cause/trigger-cases.json +30 -0
- package/skills/dependency-audit/SKILL.md +56 -0
- package/skills/dependency-audit/trigger-cases.json +7 -0
- package/skills/dependency-upgrade/trigger-cases.json +31 -0
- package/skills/dev-cpp/trigger-cases.json +30 -0
- package/skills/dev-cross-platform/trigger-cases.json +30 -0
- package/skills/dev-csharp/SKILL.md +23 -0
- package/skills/dev-csharp/trigger-cases.json +30 -0
- package/skills/dev-frontend/SKILL.md +19 -0
- package/skills/dev-frontend/trigger-cases.json +30 -0
- package/skills/dev-go/trigger-cases.json +30 -0
- package/skills/dev-harmony/trigger-cases.json +30 -0
- package/skills/dev-java/SKILL.md +20 -0
- package/skills/dev-java/trigger-cases.json +30 -0
- package/skills/dev-mobile/trigger-cases.json +30 -0
- package/skills/dev-node/trigger-cases.json +30 -0
- package/skills/dev-php/trigger-cases.json +30 -0
- package/skills/dev-python/SKILL.md +21 -0
- package/skills/dev-python/trigger-cases.json +30 -0
- package/skills/dev-rust/trigger-cases.json +30 -0
- package/skills/dev-toolchain/trigger-cases.json +31 -0
- package/skills/doc-chain/trigger-cases.json +31 -0
- package/skills/doc-compare/trigger-cases.json +30 -0
- package/skills/docx-author/SKILL.md +127 -25
- package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/docx_battery.py +586 -0
- package/skills/docx-author/scripts/docx_build.py +995 -0
- package/skills/docx-author/scripts/docx_check.py +1306 -0
- package/skills/docx-author/scripts/docx_doc.py +14 -154
- package/skills/docx-author/scripts/docx_layout.py +490 -0
- package/skills/docx-author/scripts/zh_prose.py +639 -0
- package/skills/docx-author/trigger-cases.json +58 -0
- package/skills/echo-brief/SKILL.md +84 -0
- package/skills/echo-brief/references/echo-format.md +68 -0
- package/skills/echo-brief/trigger-cases.json +40 -0
- package/skills/ecommerce-ops/trigger-cases.json +30 -0
- package/skills/education-training/trigger-cases.json +30 -0
- package/skills/ehs-safety/trigger-cases.json +30 -0
- package/skills/email-writing/trigger-cases.json +30 -0
- package/skills/eng-ai/trigger-cases.json +30 -0
- package/skills/eng-architecture/trigger-cases.json +31 -0
- package/skills/eng-automation/trigger-cases.json +31 -0
- package/skills/eng-collaboration/trigger-cases.json +30 -0
- package/skills/eng-complex-env/trigger-cases.json +30 -0
- package/skills/eng-data-platform/trigger-cases.json +31 -0
- package/skills/eng-database/trigger-cases.json +31 -0
- package/skills/eng-develop/trigger-cases.json +30 -0
- package/skills/eng-docs/SKILL.md +24 -0
- package/skills/eng-docs/trigger-cases.json +30 -0
- package/skills/eng-efficiency/trigger-cases.json +30 -0
- package/skills/eng-infra/trigger-cases.json +30 -0
- package/skills/eng-integration/trigger-cases.json +31 -0
- package/skills/eng-legacy/trigger-cases.json +31 -0
- package/skills/eng-observability/trigger-cases.json +31 -0
- package/skills/eng-ops/trigger-cases.json +31 -0
- package/skills/eng-performance/trigger-cases.json +31 -0
- package/skills/eng-planning/trigger-cases.json +31 -0
- package/skills/eng-quality/SKILL.md +8 -0
- package/skills/eng-quality/trigger-cases.json +31 -0
- package/skills/eng-release/trigger-cases.json +31 -0
- package/skills/eng-reliability/trigger-cases.json +31 -0
- package/skills/eng-requirements/trigger-cases.json +12 -0
- package/skills/eng-research/trigger-cases.json +30 -0
- package/skills/eng-security/trigger-cases.json +31 -0
- package/skills/eng-ux/trigger-cases.json +30 -0
- package/skills/engineering-chain/trigger-cases.json +30 -0
- package/skills/env-offline/trigger-cases.json +31 -0
- package/skills/env-private-deploy/trigger-cases.json +31 -0
- package/skills/env-xinchuang/trigger-cases.json +31 -0
- package/skills/equipment-tpm/trigger-cases.json +30 -0
- package/skills/esg-report/trigger-cases.json +30 -0
- package/skills/event-management/trigger-cases.json +30 -0
- package/skills/excel-analysis-chain/SKILL.md +17 -4
- package/skills/excel-analysis-chain/trigger-cases.json +31 -0
- package/skills/explain-codebase/trigger-cases.json +11 -0
- package/skills/file-ingest/trigger-cases.json +30 -0
- package/skills/finance-analysis/trigger-cases.json +31 -0
- package/skills/finance-chain/trigger-cases.json +31 -0
- package/skills/financial-modeling/trigger-cases.json +31 -0
- package/skills/find-simplifications/SKILL.md +43 -0
- package/skills/find-simplifications/trigger-cases.json +23 -0
- package/skills/foreign-trade/trigger-cases.json +30 -0
- package/skills/funnel-cohort/trigger-cases.json +30 -0
- package/skills/gov-document/trigger-cases.json +30 -0
- package/skills/gui-change-evidence/SKILL.md +47 -0
- package/skills/gui-change-evidence/trigger-cases.json +23 -0
- package/skills/hr-chain/trigger-cases.json +31 -0
- package/skills/hr-operations/trigger-cases.json +30 -0
- package/skills/info-disclosure/trigger-cases.json +31 -0
- package/skills/infra-gateway/trigger-cases.json +31 -0
- package/skills/infra-k8s/trigger-cases.json +31 -0
- package/skills/infra-mq/trigger-cases.json +30 -0
- package/skills/insurance-advisory/trigger-cases.json +30 -0
- package/skills/internal-audit/trigger-cases.json +30 -0
- package/skills/ip-management/trigger-cases.json +31 -0
- package/skills/ipo-listing/trigger-cases.json +31 -0
- package/skills/it-audit/trigger-cases.json +31 -0
- package/skills/itsm-ops/trigger-cases.json +31 -0
- package/skills/knowledge-sop/trigger-cases.json +31 -0
- package/skills/legal-chain/trigger-cases.json +31 -0
- package/skills/legal-contract/trigger-cases.json +30 -0
- package/skills/litigation-support/trigger-cases.json +31 -0
- package/skills/logistics-warehouse/trigger-cases.json +30 -0
- package/skills/long-doc-analysis/trigger-cases.json +31 -0
- package/skills/management-accounting/trigger-cases.json +30 -0
- package/skills/manufacturing-chain/trigger-cases.json +31 -0
- package/skills/manufacturing-quality/trigger-cases.json +30 -0
- package/skills/marketing-analytics/trigger-cases.json +31 -0
- package/skills/marketing-chain/trigger-cases.json +31 -0
- package/skills/meeting-minutes/trigger-cases.json +30 -0
- package/skills/metrics-system/trigger-cases.json +31 -0
- package/skills/negotiation-prep/trigger-cases.json +30 -0
- package/skills/obs-prometheus/trigger-cases.json +31 -0
- package/skills/office-automation/trigger-cases.json +31 -0
- package/skills/okr-review/trigger-cases.json +30 -0
- package/skills/ops-fleet/trigger-cases.json +31 -0
- package/skills/ops-linux/trigger-cases.json +31 -0
- package/skills/optimization-solver/trigger-cases.json +30 -0
- package/skills/patent-disclosure/SKILL.md +105 -0
- package/skills/patent-disclosure/references/figures.md +110 -0
- package/skills/patent-disclosure/references/outline.md +162 -0
- package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
- package/skills/patent-disclosure/trigger-cases.json +31 -0
- package/skills/patent-drafting/SKILL.md +108 -0
- package/skills/patent-drafting/trigger-cases.json +31 -0
- package/skills/patent-portfolio/SKILL.md +81 -0
- package/skills/patent-portfolio/trigger-cases.json +31 -0
- package/skills/pdf-toolkit/trigger-cases.json +31 -0
- package/skills/perf-optimize/trigger-cases.json +31 -0
- package/skills/pivot-crosstab/trigger-cases.json +30 -0
- package/skills/policy-analysis/trigger-cases.json +31 -0
- package/skills/portfolio-analysis/trigger-cases.json +31 -0
- package/skills/ppt-chain/SKILL.md +14 -6
- package/skills/ppt-chain/trigger-cases.json +31 -0
- package/skills/pptx-author/SKILL.md +86 -33
- package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/pptx_battery.py +178 -0
- package/skills/pptx-author/scripts/pptx_build.py +119 -0
- package/skills/pptx-author/scripts/pptx_check.py +1182 -0
- package/skills/pptx-author/scripts/pptx_deck.py +736 -145
- package/skills/pptx-author/scripts/pptx_layout.py +384 -0
- package/skills/pptx-author/trigger-cases.json +15 -0
- package/skills/pr-comms/trigger-cases.json +30 -0
- package/skills/procurement-supply/trigger-cases.json +31 -0
- package/skills/product-design/trigger-cases.json +31 -0
- package/skills/production-planning/trigger-cases.json +31 -0
- package/skills/project-management/trigger-cases.json +31 -0
- package/skills/project-proposal/trigger-cases.json +31 -0
- package/skills/prose-standard/SKILL.md +53 -0
- package/skills/prose-standard/trigger-cases.json +23 -0
- package/skills/qa-api-automation/trigger-cases.json +31 -0
- package/skills/qa-mobile-automation/trigger-cases.json +31 -0
- package/skills/qa-ui-automation/trigger-cases.json +31 -0
- package/skills/quant-analysis/trigger-cases.json +31 -0
- package/skills/rag-triage/SKILL.md +64 -0
- package/skills/rag-triage/evals.json +57 -0
- package/skills/rag-triage/references/triage-playbook.md +75 -0
- package/skills/rag-triage/trigger-cases.json +31 -0
- package/skills/real-estate-property/trigger-cases.json +30 -0
- package/skills/refactor-safely/SKILL.md +1 -1
- package/skills/refactor-safely/trigger-cases.json +30 -0
- package/skills/regression-forecast/trigger-cases.json +31 -0
- package/skills/release-checklist/trigger-cases.json +31 -0
- package/skills/report-craft/SKILL.md +76 -0
- package/skills/report-craft/references/feasibility.md +118 -0
- package/skills/report-craft/references/review-checklist.md +51 -0
- package/skills/report-craft/references/visuals.md +114 -0
- package/skills/report-craft/references/writing.md +80 -0
- package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/chart_render.py +1222 -0
- package/skills/report-craft/scripts/fin_model.py +602 -0
- package/skills/report-craft/scripts/project_facts.py +580 -0
- package/skills/report-craft/scripts/report_battery.py +349 -0
- package/skills/report-craft/trigger-cases.json +15 -0
- package/skills/retail-store-ops/trigger-cases.json +30 -0
- package/skills/risk-compliance/trigger-cases.json +31 -0
- package/skills/scope-creep/SKILL.md +45 -0
- package/skills/scope-creep/trigger-cases.json +7 -0
- package/skills/sec-auth/trigger-cases.json +31 -0
- package/skills/sec-compliance/trigger-cases.json +31 -0
- package/skills/securities-chain/trigger-cases.json +31 -0
- package/skills/securities-research/trigger-cases.json +31 -0
- package/skills/security-audit/trigger-cases.json +31 -0
- package/skills/slides-html/SKILL.md +25 -10
- package/skills/slides-html/trigger-cases.json +11 -0
- package/skills/sql-analysis/SKILL.md +110 -0
- package/skills/sql-analysis/trigger-cases.json +34 -0
- package/skills/sql-tuning/trigger-cases.json +31 -0
- package/skills/statistical-testing/trigger-cases.json +31 -0
- package/skills/strategy-planning/trigger-cases.json +30 -0
- package/skills/supply-chain/trigger-cases.json +31 -0
- package/skills/tax-compliance/trigger-cases.json +30 -0
- package/skills/tech-doc/SKILL.md +262 -0
- package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/archive_battery.py +860 -0
- package/skills/tech-doc/scripts/archive_build.py +599 -0
- package/skills/tech-doc/scripts/archive_check.py +1307 -0
- package/skills/tech-doc/scripts/archive_spec.py +499 -0
- package/skills/tech-doc/scripts/doc_templates.py +624 -0
- package/skills/tech-doc/trigger-cases.json +57 -0
- package/skills/tech-research/trigger-cases.json +31 -0
- package/skills/tender-bidding/trigger-cases.json +31 -0
- package/skills/translation-pro/trigger-cases.json +30 -0
- package/skills/treasury-mgmt/trigger-cases.json +31 -0
- package/skills/trim-cot-leakage/SKILL.md +58 -0
- package/skills/trim-cot-leakage/trigger-cases.json +23 -0
- package/skills/web-craft/SKILL.md +103 -0
- package/skills/web-craft/references/craft-checklist.md +75 -0
- package/skills/web-craft/references/design-direction.md +122 -0
- package/skills/web-craft/references/h5-mobile.md +100 -0
- package/skills/web-craft/references/motion.md +136 -0
- package/skills/web-craft/references/recipes.md +74 -0
- package/skills/web-craft/templates/wedding-invitation.html +1180 -0
- package/skills/web-craft/trigger-cases.json +14 -0
- package/skills/web-scraping/trigger-cases.json +34 -0
- package/skills/web-search/SKILL.md +11 -10
- package/skills/web-search/trigger-cases.json +31 -0
- package/skills/weekly-report/SKILL.md +134 -0
- package/skills/weekly-report/trigger-cases.json +11 -0
- package/skills/write-tests/trigger-cases.json +31 -0
- package/skills/xlsx-analysis/SKILL.md +143 -77
- package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
- package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
- package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
- package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
- package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
- package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
- package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
- package/skills/xlsx-analysis/trigger-cases.json +59 -0
- package/squads/hc-defense.md +47 -0
- package/squads/hc-rdarchive.md +110 -0
- package/squads/hc-redteam.md +50 -0
- package/squads/hc-weekly.md +125 -0
- package/src/account.js +1 -1
- package/src/acp.js +1 -0
- package/src/advisor.js +1 -1
- package/src/agent.js +1 -1
- package/src/api.js +1 -1
- package/src/archaeology.js +1 -0
- package/src/ask-ui.js +1 -0
- package/src/assets-import.js +1 -1
- package/src/autofix.js +1 -1
- package/src/automation-tools.js +1 -1
- package/src/automation.js +1 -1
- package/src/autorun.js +1 -1
- package/src/banner.js +1 -1
- package/src/capability-probe.js +1 -0
- package/src/checkpoints.js +1 -1
- package/src/code-tools.js +1 -0
- package/src/codeindex.js +1 -1
- package/src/codemode.js +1 -0
- package/src/command-bridge.js +1 -0
- package/src/command-ui.js +1 -0
- package/src/commands.js +1 -1
- package/src/compaction.js +1 -1
- package/src/computer.js +1 -1
- package/src/config.js +1 -1
- package/src/consult.js +1 -1
- package/src/context-report.js +1 -1
- package/src/context-window.js +1 -0
- package/src/context.js +1 -1
- package/src/crew.js +1 -0
- package/src/crush.js +1 -0
- package/src/daemon.js +1 -1
- package/src/decisions.js +1 -1
- package/src/depaudit.js +1 -0
- package/src/device.js +1 -1
- package/src/diagram-draw.js +1 -0
- package/src/diagram-layouts.js +1 -0
- package/src/diagram-raster.js +1 -0
- package/src/diagram.js +1 -0
- package/src/dictation.js +1 -0
- package/src/diffview.js +1 -1
- package/src/doctor.js +1 -1
- package/src/docx-tools.js +1 -0
- package/src/errkind.js +1 -1
- package/src/evidence.js +1 -0
- package/src/executors.js +1 -1
- package/src/expert-catalog.js +1 -1
- package/src/experts-store.js +1 -1
- package/src/experts.js +1 -1
- package/src/external-agents.js +1 -0
- package/src/forks.js +1 -1
- package/src/fs-observe.js +1 -0
- package/src/gitflow.js +1 -1
- package/src/goalround.js +1 -0
- package/src/hooks.js +1 -1
- package/src/input.js +1 -1
- package/src/insights.js +1 -1
- package/src/jobs.js +1 -0
- package/src/kbsync.js +1 -1
- package/src/lsp.js +1 -0
- package/src/mcp-apps.js +1 -0
- package/src/mcp.js +1 -1
- package/src/memory.js +1 -1
- package/src/notify.js +1 -1
- package/src/permissions.js +1 -1
- package/src/plan-limits.js +1 -0
- package/src/policy.js +1 -0
- package/src/pptx-tools.js +1 -0
- package/src/presence.js +1 -1
- package/src/privatedir.js +1 -0
- package/src/pyruntime.js +1 -1
- package/src/radar.js +1 -0
- package/src/render.js +1 -1
- package/src/repl.js +1 -1
- package/src/report-tools.js +1 -0
- package/src/routing.js +1 -0
- package/src/sandbox-probe.js +1 -0
- package/src/sandbox-win-launcher.mjs +156 -0
- package/src/sandbox.js +1 -0
- package/src/scan.js +1 -1
- package/src/schedule.js +1 -1
- package/src/scopecreep.js +1 -0
- package/src/sdk.js +1 -0
- package/src/selection.js +1 -0
- package/src/session-events.js +1 -0
- package/src/session.js +1 -1
- package/src/sessionindex.js +1 -0
- package/src/sessionlog.js +1 -0
- package/src/skill-catalog.js +1 -0
- package/src/skill-lint.js +1 -0
- package/src/skill-optimize.js +1 -0
- package/src/skill-scan.js +1 -0
- package/src/skills.js +1 -1
- package/src/spill.js +1 -0
- package/src/squad.js +1 -1
- package/src/subagent.js +1 -1
- package/src/surface-state.js +1 -0
- package/src/tapd.js +1 -0
- package/src/terminal-select.js +1 -0
- package/src/terminal.js +1 -0
- package/src/test-runner.js +1 -0
- package/src/todo.js +1 -1
- package/src/toolpipe.js +1 -0
- package/src/tools.js +1 -1
- package/src/toon.js +1 -0
- package/src/transcript.js +1 -1
- package/src/tui/app.js +1 -1
- package/src/tui/command-menu.js +1 -0
- package/src/tui/progress.js +1 -0
- package/src/tui/scroll.js +1 -0
- package/src/tui/welcome.js +1 -0
- package/src/tui/workbench.js +1 -0
- package/src/ui-spec.js +1 -0
- package/src/uitree.js +1 -0
- package/src/uninstall.js +1 -1
- package/src/visual.js +1 -1
- package/src/voice-form.js +1 -0
- package/src/voice.js +1 -0
- package/src/watch.js +1 -1
- package/src/web-audit.js +1 -0
- package/src/web-cdp.js +1 -0
- package/src/web-inspect.js +1 -0
- package/src/websearch.js +1 -0
- package/src/weekly.js +1 -0
- package/src/workflow.js +1 -0
- package/src/worklog.js +1 -0
- package/src/xlsx-tools.js +1 -0
- package/ui/command-picker.js +79 -0
- package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
- package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
- package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
- package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
- package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
- package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
- package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
- package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
- package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
- package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
- package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
- package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
- package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
- package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
- package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
- package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
- package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
- package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
- package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
- package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
- package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
- package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
- package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
- package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:fs";import e from"node:path";import{BUNDLED_SKILLS_DIR as r}from"./skills.js";import{ensureRuntime as n,execFileCapture as i,runtimeStatus as o,venvPython as s}from"./pyruntime.js";export function engineDir(){return e.join(r,"docx-author","scripts")}export function archiveEngineDir(){return e.join(r,"tech-doc","scripts")}const c="tech技术文档(A4/宋体小四/1.5 倍行距/首行缩进 2 字符,默认)/ gov党政机关公文GB-T 9704(仿宋三号/固定行距/每面 22 行)/ report科技报告·学术(宋体小四/固定 20 磅行距/十进制编号 1、1.1、1.1.1)";function p(r,n,i){const o=String(n||i||"").trim()||i,s=e.resolve(r,o);if(s!==r&&!s.startsWith(r+e.sep))throw new Error("输出路径越出工作目录:"+o);return t.mkdirSync(e.dirname(s),{recursive:!0}),s}async function a(r,c,p,{timeoutMs:a=18e4,config:d={},dir:l=null}={}){o(d).usable||await n({log:()=>{},config:d});const u=s();if(!t.existsSync(u))throw new Error("托管Python未就绪(设置 → 智能 → 立即准备,或先调用一次run_python)");const h=await i(u,["-X","utf8",e.join(l||engineDir(),c),...p],{cwd:r,timeoutMs:a,env:{PYTHONIOENCODING:"utf-8",MPLBACKEND:"Agg"}});if(h.killed)throw new Error("执行超时被终止");return h.output||""}function d(t){const e=String(t||"").match(/\{[\s\S]*\}\s*$/);if(!e)throw new Error("引擎输出异常:"+String(t||"").slice(-400));return JSON.parse(e[0])}export async function inspectDocsForTurn(e,r,n={}){if(!o(n).usable||!t.existsSync(s()))return null;const i=[];for(const o of[...r].slice(0,4))if(t.existsSync(o))try{const t=d(await a(e,"docx_check.py",[o,"--json"],{config:n,timeoutMs:9e4}));for(const e of t.issues||[])"error"===e.level&&i.push({...e,path:o})}catch{}return{issues:i}}export function docTurnCorrection(t,r){const n=t.issues.slice(0,10);return"【文档自检】你本回合生成的文档经体检仍有以下**必修**问题,请修正后重新生成再结束:\n"+n.map(t=>` [${t.kind}] ${e.relative(r,t.path)||t.path}${t.loc?` · ${t.loc}`:""}\n 问题:${t.msg}${t.detail?`\n 说明:${t.detail}`:""}`+(t.fixable?"\n 可自动修:docx_inspect(path, fix=true)":"")).join("\n")+(t.issues.length>n.length?`\n …… 另有 ${t.issues.length-n.length} 条,用docx_inspect看全量。`:"")+"\n\n内容类问题改Markdown草稿后重新create_doc,再用docx_inspect复检确认必修清零。若确认某条是误报,在回复里说明理由——不要在必修未清零时宣布完成。"}export function makeDocxTools({cwd:r,config:n={}}){return[{readOnly:!1,spec:{type:"function",function:{name:"create_doc",description:`生成Word技术文档(.docx):项目建议书、可研报告、立项申请书、研发任务书、实施计划、需求/设计说明书、测试报告、工作/技术总结、验收报告与申请表、评审纪要等。**你只给「哪一节写什么」,排版由引擎负责**——不要用run_python手写python-docx,那是中文字体不生效、首行缩进忽有忽无、表格宽出版心、图片被拉变形的根源。\n引擎自动处理:封面 / 目录域 / 页眉页脚与页码(正文另起一节从第 1 页重新起算)/ 标题分级与自动编号 / 正文首行缩进 2 字符 / 表格列宽按内容算并压进版心(表头加粗+跨页重复)/ 插图等比缩放不变形 + 图表题注自动编号。\n排版档位profile:${c}。\n传template会按该类文档核对必备章节并回报缺哪节。可选:项目建议书 / 可行性研究报告 / 立项申请书 / 项目研发实施计划 / 研发任务书 / 产品开发需求申请 / 项目开发需求申请 / 需求规格说明书 / 概要设计说明书 / 详细设计说明书 / 测试报告 / 项目工作总结报告 / 项目技术总结报告 / 项目验收报告 / 项目验收申请表 / 项目研发评价总结纪要 / 变更申请单。\n出片后**自动跑文档体检**(结构/排版/表格/插图/错别字/标点/AI味/一致性),报告随结果返回。\n配图先用render_diagram(架构/流程/时序等工程图)或render_chart(数据图表)出png再引用。`,parameters:{type:"object",properties:{path:{type:"string",description:"输出路径(相对工作目录,如 智能运维平台研发任务书.docx)"},spec:{type:"object",description:"整份文档的结构",additionalProperties:!0,properties:{profile:{type:"string",description:"tech(默认)/ gov / report,见工具说明"},template:{type:"string",description:"文档类型(如「研发任务书」):据此定档位并核对必备章节"},title:{type:"string",description:"文档标题(有标题才出封面)"},subtitle:{type:"string",description:"副标题,如「项目研发实施计划」"},org:{type:"string",description:"编制单位"},author:{type:"string",description:"编制人"},date:{type:"string",description:"编制日期,默认今天"},doc_no:{type:"string",description:"文号,如YF-2026-014,排在封面右上"},cover_fields:{type:"array",description:'封面信息栏额外行,如 [["项目编号","XM-01"],["密级","内部"]]',items:{type:"array",items:{type:"string"}}},toc:{type:"boolean",description:"是否插目录域,默认true(Word内按F9 更新)"},cover:{type:"boolean",description:"是否出封面,默认true(有title时)"},header:{type:"string",description:"页眉文字"},footer:{type:"string",description:"页脚文字(页码会接在其后)"},page_numbers:{type:"boolean",description:"是否显示「第X页 共Y页」,默认true"},numbering:{type:"string",description:"cn一、(一)1.(1)/ decimal 1、1.1、1.1.1 / none不自动编号"},blocks:{type:"array",description:'逐块内容。每块 {type, ...}:h1|h2|h3|h4{text}(自动编号,文本已带编号则不重复加)/ p{text}(正文,支持 **粗体**)/ bullets{items[]} / ordered{items[]} / table{rows[[..]],caption,note,style:"grid"|"three-line"}(题注排在表上方)/ kv{pairs[[键,值]],caption}(表单式两列表,验收申请表/变更单用)/ figure{image,caption,width_cm,note}(题注排在图下方)/ note{text}(小字注)/ signature{roles[],org}(签字栏)/ pagebreak。\n**正文里引用图表要写「如图 1 所示」「详见表 2」**,编号按出现顺序自动生成;引用了不存在的编号、或图表从未被正文引用,体检都会报出来。',items:{type:"object",additionalProperties:!0}},markdown:{type:"string",description:"替代blocks:直接给Markdown全文(#/##/- /1./|表格|//--- 分页;表题写在表格上一行「表:表题」)。交叉引用:题注末尾加 {#fig:arch} / {#tbl:invest},正文写「如@fig:arch所示」,成稿时换成真实编号(图 3-1),不用自己数。宽图横排插入:{#fig:arch .landscape}(单独一个横向页)"}}},spec_path:{type:"string",description:"可选:从JSON文件读spec(长文档用,省上下文)"},markdown_path:{type:"string",description:"可选:从Markdown文件读全文(长文档推荐,先write_file落草稿再转)"}},required:["path"]}}},run:async i=>{let o=i?.spec;if("string"==typeof o)try{o=JSON.parse(o)}catch{return"spec不是合法JSON:长文档请先write_file落草稿,再用markdown_path传入。"}const s=i?.spec_path||o?.spec_path,c=i?.markdown_path||o?.markdown_path;if(s&&!o?.blocks&&!o?.markdown){const n=e.resolve(r,String(s));if(!t.existsSync(n))return`spec文件不存在:${s}`;o={...JSON.parse(t.readFileSync(n,"utf8")),...o||{}}}if(c){const n=e.resolve(r,String(c));if(!t.existsSync(n))return`Markdown草稿不存在:${c}`;o={...o||{},markdown:t.readFileSync(n,"utf8"),_md_dir:e.dirname(n)}}if(o&&(delete o.markdown_path,delete o.spec_path),!o)return"create_doc需要spec(或spec_path / markdown_path):至少给blocks或markdown。";if(!(Array.isArray(o.blocks)&&o.blocks.length||String(o.markdown||"").trim()))return"create_doc没拿到任何正文:spec里既没有blocks也没有markdown。长文档请把草稿落盘后用顶层参数markdown_path传入(与path、spec并列,不是写在spec里面)。";const l=p(r,i?.path,"技术文档.docx"),u=e.join(e.dirname(l),`.doc-spec-${Date.now()}.json`);try{t.writeFileSync(u,JSON.stringify(o),"utf8");const i=d(await a(r,"docx_build.py",[u,l],{config:n}));if(i.error)return`生成失败:${i.error}\n(块类型与字段见create_doc参数说明;一块一个type,别混着填)`;const s=e.relative(r,i.path)||e.basename(i.path),c=i.check?.stats,p=[`已生成 ${s}`+(c?`(${c.headings} 标题 / ${c.tables} 表 / ${c.figures} 图 / ${c.chars} 字 / 约 ${c.pages_est} 页)`:"")];return i.warnings?.length&&p.push("【建版提示】\n"+i.warnings.map(t=>" · "+t).join("\n")),i.labels?.length&&p.push("【图表编号】"+i.labels.map(t=>`${t[1]} ${t[2]}`).join(";")+"\n 正文引用请与之一致。"),p.push(i.report||""),i.check?.counts?.error&&p.push("必修项未清零:按上面的提示改spec重新生成,或用docx_inspect(path, fix=true) 修排版类问题;内容类(缺章节、缺数据、套话)必须你自己改。"),p.push("目录是Word域,交付前请提醒用户在Word中按F9(或右键→更新域)刷新目录页码。"),p.filter(Boolean).join("\n")}finally{try{t.unlinkSync(u)}catch{}}}},{readOnly:!1,spec:{type:"function",function:{name:"create_project_docs",description:"按研发项目归档规范,从一张「项目档案卡」一次生成**整套**项目研发文档骨架(约 50 份):1-立项文档(项目建议书/可研报告/立项申请书/立项决议)、产品与项目开发需求申请、5-研发任务书、6-项目研发实施计划、7-进度表(计划 + 四个季度实际)、8-年度研发支出预算表(研发费用归集口径,12 个月分摊)、9-10 验收(验收申请表/验收报告/工作总结/技术总结/研发评价纪要)、5 个阶段评审报告与会议签到表、12 期研发进度报告、12 期月度验收报告、档案清册。\n**为什么用它而不是一份份写**:这几十份文档的封面、编号、章节骨架、签批栏全是同几件事的机械展开。人手抄几十遍,抄出来的就是真实归档里那些毛病——项目名称三种写法、项目编号两个值、联系人栏填成电话、周期性报告缺期。本工具让**所有文档的封面字段都取自同一张卡**,跨文档一致性因此天然为真。\n生成的是骨架:章节标题、必备表格(考核指标表/人员表/指标验收对照表)、签批栏都就位,正文内容通过card.章节内容 逐节注入(写多少注入多少,没注入的留空待填)。12 期进度报告的「项目进度情况」会**按里程碑自动推算**当期完成情况,不是空模板。\n生成后用docx_inspect(目录) 做档案体检,必修清零再交付。",parameters:{type:"object",properties:{path:{type:"string",description:"输出目录(会在其下按项目名建子目录)"},card:{type:"object",additionalProperties:!0,description:"项目档案卡。必填:项目名称、项目编号、项目负责人、建设单位、部门、起止时间。选填:项目联系人(默认同负责人)、联系电话、编制人、年度、预算、考核指标 [[指标,要求值,测试方法/判定依据],…]、人员 [[姓名,单位,职称/职务,分工],…]、里程碑 [[阶段,任务,处理人,工时,开始,结束,交付物],…]、预算科目 [[科目,全年金额],…]、章节内容 {文档类型: {章节名: 正文}}(有多少写多少)"},card_path:{type:"string",description:"可选:从JSON文件读档案卡(内容长时用)"},only:{type:"array",items:{type:"string"},description:'可选:只生成这几类(如 ["研发任务书","立项申请书"]),不给则生成全套'}},required:["path"]}}},run:async i=>{let o=i?.card;if(!o&&i?.card_path&&(o=JSON.parse(t.readFileSync(e.resolve(r,String(i.card_path)),"utf8"))),!o)return"create_project_docs需要card(或card_path):至少给项目名称、项目编号、项目负责人、建设单位、部门、起止时间。";const s=p(r,i?.path||".","."),c=e.join(e.dirname(s),`.card-${Date.now()}.json`);try{t.writeFileSync(c,JSON.stringify(o),"utf8");const p=[c,s];Array.isArray(i?.only)&&i.only.length&&p.push("--only="+i.only.join(","));const l=d(await a(r,"archive_build.py",p,{config:n,dir:archiveEngineDir(),timeoutMs:6e5}));if(l.error)return`生成失败:${l.error}\n(档案卡必填项见create_project_docs参数说明)`;const u=[`已在 ${e.relative(r,l.root)||e.basename(l.root)} 生成 ${l.files} 份归档件`];l.warnings?.length&&u.push("【生成提示】\n"+l.warnings.map(t=>" · "+t).join("\n"));const h=[...new Set((l.made||[]).map(t=>e.dirname(t)).map(t=>"."===t?"(根目录)":t))];return u.push("目录:"+h.join("、")),u.push("下一步:逐份补正文(card.章节内容 可批量注入),然后docx_inspect(目录) 做档案体检,必修清零再交付。"),u.join("\n")}finally{try{t.unlinkSync(c)}catch{}}}},{readOnly:!1,spec:{type:"function",function:{name:"docx_inspect",description:"对**任意** Word文档做全面体检,逐条带位置与具体数字。生成文档后**必须**跑一遍(create_doc会自动跑;自己写python-docx生成的更要跑);用户拿来一份别人写的文档问「排版有没有问题 / 帮我检查一下」也用它。\n查七类:① 结构——标题跳级、空章节、编号断号、缺目录域/页码;② 排版——中文字体未生效、正文字号不统一、首行缩进忽有忽无或用空格假缩进、行距混杂、正文误居中、连续空段;③ 表格——超出版心(打印被裁)、列数不齐、表头未加粗、长表无跨页重复标题行、空格过多、无框线;④ 插图——图片损坏、被拉变形、分辨率过低发虚、超出版心、浮动错位;⑤ 图表题注——缺题注、编号断号、正文引用了不存在的图表、图表从未被正文引用;⑥ 文字——错别字、易混词、中英标点混用、括号引号不配对、段末缺标点、超长句、超长段;⑦ AI味与一致性——套话与黑话密度、空洞副词、段首雷同、段落/章节篇幅异常均匀、通篇缺数字锚点、术语多种写法、关键数字前后矛盾。\n也接受 .md/.txt:只跑文字与AI味那部分——**草稿阶段先查一遍最划算**,比出片后返工便宜得多。\n**path给一个目录时自动转成「档案体检」**,查十二类单份文档看不见的问题:① 齐不齐——必备件、12 期周期性报告、5 个阶段评审报告与签到表是否配对;② 对不对得上——项目名称/编号/负责人/电话/建设单位/起止时间在几十份文档里是否处处一致(能分辨「被截断」与「粒度不同」);③ 干不干净——模板的「提示:」块、「注:由项目单位如实填写…下同」这类给填表人的批注、XX占位符删没删;④ 填没填串——「项目联系人」栏填了电话这类;⑤ 指标勾稽——立项考核指标在验收与总结里是否逐条有对应实测值;⑥ **周期性文档雷同度**——12 期月报份数对,但正文相似度 98%,等于同一份改日期交了十二次;⑦ **选择项一个都没勾**——验收报告的三选一结论 15 个括号全空、专家没签字;⑧ **验收时写「进行中/预计完成」**——结论只有达标与不达标两档,不能把判断推给后面的人;⑨ 时间线自洽(期号vs落款月份、五阶段评审顺序)与「该有图却一张图都没有」;⑩ 表格件的语义自洽——进度 100% 却无实际起止、计划表里填了进度、预算表全零;⑪ **项目名全档一致**——分得清简称(「…系统研发」「…系统」,正常)、错字(「数字权益**开发**系统」实为「开放」,真实归档 25 处贯穿 20 份文件)、串项目(签到表抬头写着上一个项目的名字,两个真实项目各抓到一处);⑬ **跨文档样板段落**——同一段话逐字出现在多种用途不同的文档里(真实归档最严重的一段 449 字,横跨 9 种类型 20 份文件:项目建议书、进度报告、验收总结开同一个头);⑫ 模板写死的字数上限(「项目立项背景(500字内)」实际写了 4000+ 字)、模板点名的必备要素(立项文件号/合同号/起止时间)、签到表有表头没人名、选择框一个都没勾。这一层恰恰是验收与审计现场最容易被当场挑出来的。\nfix=true:自动修可确定性修的(补中文字体、统一字号、真缩进替换假缩进、去多余空段与行尾空格、半角标点转全角、表头加粗与跨页重复、表格缩回版心、图片恢复原比例并缩回版心、题注编号重排),**另存为「-已校正.docx」不覆盖原件**并回报复检结果;改内容才能解决的(缺章节、缺数据、套话太多、章节失衡)会如实说需要人改——不假装修好了。",parameters:{type:"object",properties:{path:{type:"string",description:"要体检的 .docx(或 .md/.txt草稿)路径;**给目录则做整套项目文档的档案体检**"},fix:{type:"boolean",description:"是否顺手自动修复(默认false,只体检)"},inplace:{type:"boolean",description:"fix时直接改原文件(默认false,另存 -已校正.docx)"},strict:{type:"boolean",description:"严格模式:整段加粗、数值区间连字符等也报"},profile:{type:"string",description:`按哪档规范比对:auto(默认,自动判定)/ ${c}`},deep:{type:"boolean",description:"档案体检时顺带对每份docx跑单文档排版体检(慢,默认false)"}},required:["path"]}}},run:async i=>{const o=e.resolve(r,String(i?.path||""));if(!t.existsSync(o))return`文件不存在:${i?.path}`;if(t.statSync(o).isDirectory()){const t=[o];i?.deep&&t.push("--deep");return(await a(r,"archive_check.py",t,{config:n,dir:archiveEngineDir(),timeoutMs:6e5})).trim()||"(档案体检无输出,请检查托管Python是否就绪)"}if(/\.(md|markdown|txt)$/i.test(o)){const t=[o];i?.strict&&t.push("--strict");return((await a(r,"zh_prose.py",t,{config:n})).trim()||"(无输出)")+"\n(草稿只查文字与AI味;排版类问题要等转成 .docx后再跑一次docx_inspect。)"}if(!/\.docx$/i.test(o))return"只支持 .docx(老的 .doc请先在Word里另存为docx),或 .md/.txt草稿";const s=[o];i?.fix&&s.push("--fix"),i?.inplace&&s.push("--inplace"),i?.strict&&s.push("--strict"),i?.profile&&"auto"!==i.profile&&s.push(`--profile=${i.profile}`);return(await a(r,"docx_check.py",s,{config:n})).trim()||"(体检无输出,请检查托管Python是否就绪)"}}]}
|
package/src/errkind.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const KIND_AUTH="auth";export const KIND_QUOTA="quota";export const KIND_USAGE="usage";const t=new Set(["authentication_error","invalid_api_key"]),r=/(登录已失效|令牌校验失败|缺少访问令牌|令牌(?:已)?(?:失效|无效|过期|不存在)|尚未登录|未登录|invalid[_ ]api[_ ]key|authentication_error|unauthorized|HTTP\s*401)/i,n=/(余额不足|额度不足|请充值|insufficient_quota)/i,i=/(usage_limit_exceeded|用量上限|用量已达|已达当前会员等级)/i;function e(t){if(null==t)return{status:0,type:"",text:""};if("string"==typeof t)return{status:0,type:"",text:t};const r=String(t.message||t.error||t.text||"");return{status:Number(t.status)||0,type:String(t.type||""),text:r}}export function isAuthError(n){const{status:i,type:o,text:u}=e(n);return 401===i||(!!t.has(o)||r.test(u))}export function isQuotaError(t){const{status:r,type:i,text:o}=e(t);return 402===r||("insufficient_quota"===i||n.test(o))}export function isUsageLimitError(t){const{type:r,text:n}=e(t);return"usage_limit_exceeded"===r||i.test(n)}export function classifyError(t){return isUsageLimitError(t)?"usage":isQuotaError(t)?"quota":isAuthError(t)?"auth":""}export function isBillingError(t){return isUsageLimitError(t)||isQuotaError(t)}function o(t){return String(t||"").replace(/\/+$/,"")}export function consoleUrl(t){const r=o(t);return r?`${r}/console/dashboard`:""}export function billingUrl(t){const r=o(t);return r?`${r}/console/billing`:""}export function pricingUrl(t){const r=o(t);return r?`${r}/pricing`:""}export function billingActionForKind(t,r){if("quota"!==t&&"usage"!==t)return null;return{kind:t,label:"quota"===t?"去充值":"升级会员",url:"quota"===t?billingUrl(r):pricingUrl(r),hint:"quota"===t?"
|
|
1
|
+
export const KIND_AUTH="auth";export const KIND_QUOTA="quota";export const KIND_USAGE="usage";const t=new Set(["authentication_error","invalid_api_key"]),r=/(登录已失效|令牌校验失败|缺少访问令牌|令牌(?:已)?(?:失效|无效|过期|不存在)|尚未登录|未登录|invalid[_ ]api[_ ]key|authentication_error|unauthorized|HTTP\s*401)/i,n=/(余额不足|额度不足|请充值|insufficient_quota)/i,i=/(usage_limit_exceeded|用量上限|用量已达|已达当前会员等级)/i;function e(t){if(null==t)return{status:0,type:"",text:""};if("string"==typeof t)return{status:0,type:"",text:t};const r=String(t.message||t.error||t.text||"");return{status:Number(t.status)||0,type:String(t.type||""),text:r}}export function isAuthError(n){const{status:i,type:o,text:u}=e(n);return 401===i||(!!t.has(o)||r.test(u))}export function isQuotaError(t){const{status:r,type:i,text:o}=e(t);return 402===r||("insufficient_quota"===i||n.test(o))}export function isUsageLimitError(t){const{type:r,text:n}=e(t);return"usage_limit_exceeded"===r||i.test(n)}export function classifyError(t){return isUsageLimitError(t)?"usage":isQuotaError(t)?"quota":isAuthError(t)?"auth":""}export function isBillingError(t){return isUsageLimitError(t)||isQuotaError(t)}function o(t){return String(t||"").replace(/\/+$/,"")}export function consoleUrl(t){const r=o(t);return r?`${r}/console/dashboard`:""}export function billingUrl(t){const r=o(t);return r?`${r}/console/billing`:""}export function pricingUrl(t){const r=o(t);return r?`${r}/pricing`:""}export function billingActionForKind(t,r){if("quota"!==t&&"usage"!==t)return null;return{kind:t,label:"quota"===t?"去充值":"升级会员",url:"quota"===t?billingUrl(r):pricingUrl(r),hint:"quota"===t?"账户积分已用尽:可在控制台「充值中心」充值或兑换积分。":"已达当前会员等级的Token用量窗口上限:可等待窗口逐步释放,或升级会员提高上限。"}}export function billingAction(t,r){return billingActionForKind(classifyError(t),r)}
|
package/src/evidence.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:path";import{buildIdentities as t,collectRepoCommits as n,cnRange as s,discoverRepos as i,expandPath as o,formatDocReport as r,formatGitReport as c,GIT_CONCURRENCY as a,mapLimit as d,parseRange as l,scanDocs as p,summarize as h,trackedFiles as u,weeklyDefaults as m,ymd as g}from"./worklog.js";import{fetchKind as y,myProjects as f,statusMap as w,tapdCreds as $,whoami as _}from"./tapd.js";const k=[{kind:"stories",cn:"需求",dateField:"modified",doneField:"completed",doneCn:"完成",system:"story"},{kind:"bugs",cn:"缺陷",dateField:"modified",doneField:"resolved",doneCn:"解决",system:"bug"},{kind:"releases",cn:"发布计划",dateField:"",doneField:"",doneCn:"",system:""}],D=(e,t)=>{const n=String(e||"").slice(0,10);return Boolean(n)&&n>=g(t.since)&&n<=g(t.until)};async function F(e,s,o,r){const{repos:l,missingRoots:p,truncated:u}=i(s,{maxDepth:r});if(!l.length)return{text:`【本机Git工作记录】${e.label}\n扫描根目录:${s.join("、")}\n这些目录下没有找到任何git仓库。请把roots指向真正放项目的目录(如 ~/Projects),或写进设置文件 .huicai/weekly.json的roots。`,repos:[],anchors:{}};const m=await t({configured:o,repos:l}),g=await d(l,a,t=>n(t,{since:e.since,until:e.until,identities:m})),y=g.filter(e=>e.commits.length);let f=0;const w=new Set;for(const e of y){const t=h(e.commits);f+=t.n;for(const e of t.files)w.add(e)}return{text:c(e,g,{identities:[...m],roots:s,repoCount:l.length,missingRoots:p,truncated:u}),repos:l,anchors:{"提交":f,"活跃仓库":y.length,"触及文件":w.size}}}async function x(e,t,n){const s=$(t);if(!s.configured)return{text:"【TAPD工作项】未配置,本次未取。要把需求/缺陷/发布计划纳入周报,设一个个人访问令牌即可(调一次 `tapd_fetch` 会给出完整配置指引)。",anchors:{}};let i=null;try{i=await _(s)}catch{}let o=String(n||"").trim();o&&"me"!==o||(o=i&&i.nick||s.nick);const r=o?[o]:[];let c=s.workspaces;const a=new Map;if(!c.length)try{const e=await f(s);c=e.map(e=>e.id);for(const t of e)a.set(t.id,t.name)}catch(e){return{text:`【TAPD工作项】取项目列表失败 —— ${e.message}`,anchors:{}}}if(!c.length)return{text:"【TAPD工作项】账号下没有任何参与的项目。",anchors:{}};const l=[`【TAPD工作项】${e.label} 身份:${o||"未识别"} 空间 ${c.length} 个`];o||l.push("⚠ 没能确定你的TAPD昵称,下面是**全空间**口径(团队数据),不是你个人的。");const p={},h=[];for(const t of k){const n="releases"===t.kind?[]:r,i=await d(c,5,async i=>{try{const o=await y(s,t.kind,{since:e.since,until:e.until,dateField:t.dateField||void 0,names:n,workspaceId:i}),r=o.items.length&&t.system?await w(s,i,t.system):{};return{ws:i,items:o.items,smap:r}}catch(e){return{ws:i,error:e.message}}}),o=[];for(const e of i)if(e.error)h.push(`空间 ${e.ws} 的${t.cn}:${e.error}`);else for(const t of e.items)o.push({...t,__ws:e.ws,__smap:e.smap});const u=t.doneField?o.filter(n=>D(n[t.doneField],e)):[];if(p[t.cn]=o.length,t.doneField&&(p[`本周${t.doneCn}${t.cn}`]=u.length),l.push(""),l.push(`■ ${t.cn}:本周期共 ${o.length} 条`+(t.doneField?`,其中**本周${t.doneCn}** ${u.length} 条(按 ${t.doneField} 落在周期内判定)`:"")+("releases"===t.kind?"(团队口径,未按人筛)":"")),!o.length)continue;const m=new Map;for(const e of o){const t=e.__smap&&e.__smap[e.status]||e.status||"(空)";m.set(t,(m.get(t)||0)+1)}l.push(" 按状态:"+[...m.entries()].sort((e,t)=>t[1]-e[1]).map(([e,t])=>`${e} ${t}`).join(" · "));const g=t.doneField?u:o;for(const e of g.slice(0,40)){const t=e.name||e.title||"(无标题)",n=e.__smap&&e.__smap[e.status]||e.status||"";l.push(` #${e.id} ${t} ${n} [${a.get(e.__ws)||e.__ws}]`)}g.length>40&&l.push(` …… 另有 ${g.length-40} 条未列出`),t.doneField&&o.length>u.length&&l.push(` (另有 ${o.length-u.length} 条本周期动过但尚未${t.doneCn},写进周报要说清是「推进中」而不是「已交付」)`)}return h.length&&l.push("","⚠ 部分空间读取失败(这是**数据缺口**,要在周报里说明,不要当成「没有」):",...h.map(e=>" · "+e)),{text:l.join("\n"),anchors:p}}export async function collectEvidence({cwd:e,range:t,roots:n,identities:s=[],depth:i=4,sources:o,person:c}){const a=new Set(o&&o.length?o:["git","docs","tapd"]),d=a.has("git")||a.has("docs"),[l,h]=await Promise.all([d?F(t,n,s,i):null,a.has("tapd")?x(t,e,c):null]),m=a.has("docs")?await async function(e,t,n){const s=await u(n),i=p(t,{since:e.since,until:e.until,tracked:s});return{text:r(e,t,i,{repoDirs:n}),anchors:{"文档":i.hits.length}}}(t,n,l?l.repos:[]):null,g={...l&&a.has("git")?l.anchors:{},...m?.anchors||{},...h?.anchors||{}},y=[];return a.has("git")&&l&&y.push(l.text),m&&y.push(m.text),h&&y.push(h.text),{anchors:g,parts:y}}export function anchorLine(e){const t=Object.entries(e).filter(([,e])=>Number.isFinite(e));return t.length?"【对账锚点】"+t.map(([e,t])=>`${e}=${t}`).join(" "):""}export function makeEvidenceTools({cwd:t}){return[{readOnly:!0,spec:{type:"function",function:{name:"work_evidence",description:"把一个汇报周期内的**全部工作痕迹一次取齐**——写周报/月报/述职的第一手材料。三个来源:\n① 本机 **Git提交**(多仓库、多身份合并,按作者时间切窗;锁文件/构建产物的行数单列、超大提交打标)——强证据;\n② **版本库之外的文档**(Word/Excel/PPT/PDF/思维导图;git已跟踪的自动排除)——弱证据,需逐条认领;\n③ **TAPD工作项**(需求/缺陷/发布计划;自动识别你是谁与你参与的项目,状态翻成各空间自己的说法),需求与缺陷会区分「本周期动过」与「**本周真正完成/解决**」——周报要写的是后者。\n**汇报周期算一次、三个来源共用**,所以不会出现「提交数来自这一周、需求数来自另一周」这种材料;三者并发跑,比分开调快得多。\n输出末尾的【对账锚点】是可直接引用的原始计数,成稿后 `weekly_report` 会拿它回头核对正文里的数字,**请把本工具的完整输出交给它的evidence参数**。\n需要别的TAPD口径(按created查新增、按状态过滤、指定空间)再用 `tapd_fetch` 单独查。\n只读,不改任何文件。",parameters:{type:"object",properties:{sources:{type:"array",items:{type:"string"},description:"要取哪几个来源:git / docs / tapd。不给则三个都取(TAPD未配置时自动略过)。"},roots:{type:"array",items:{type:"string"},description:"扫描哪些目录找git仓库与文档(支持 ~)。不给则读 .huicai/weekly.json的roots。"},since:{type:"string",description:"起始日期YYYY-MM-DD(含)"},until:{type:"string",description:"结束日期YYYY-MM-DD(含),默认今天"},days:{type:"number",description:"不给since时:往前N天(含今天),默认 7"},week:{type:"string",description:"this=本自然周(周一至周日)/ last=上一自然周"},identities:{type:"array",items:{type:"string"},description:"本人的git name/email(可多个,完全相等匹配)。不给则用设置文件 + 各仓库的user.name/user.email。"},person:{type:"string",description:"TAPD中文昵称;不给则由访问令牌自动识别本人。"},depth:{type:"number",description:"找仓库时的下潜层数,默认 4"}},required:[]}}},run:async n=>{let i;try{i=l(n||{})}catch(e){return"时间段有误:"+e.message}const r=m(t),c=Array.isArray(n?.roots)?n.roots.filter(Boolean).map(e=>o(e,t)):[],a=c.length?c:r.roots.length?r.roots:[t],d=c.length?"本次调用指定":r.roots.length?"weekly.json的roots":"未配置根目录,只扫了当前工作目录",p=Array.isArray(n?.identities)&&n.identities.length?n.identities:r.identities,{anchors:h,parts:u}=await collectEvidence({cwd:t,range:i,roots:a,identities:p,person:n?.person,depth:Math.max(0,Math.min(8,Number(n?.depth)||4)),sources:Array.isArray(n?.sources)?n.sources:null});return[`【取证包】汇报周期 ${i.label}(${i.source}) 抬头该写:${s(i)}`,`扫描根目录:${a.map(t=>e.normalize(t)).join("、")}(${d})`,"三个来源共用上面这一个周期——正文抬头的「汇报周期」必须与它逐字一致。","",u.join("\n\n"),"",anchorLine(h),"","【怎么用】① 提交数与行数是**过程量**,周报要写的是这些提交合起来交付了什么;② 文档那一节是弱证据(mtime会被拷贝/同步刷新),逐条认领后再写;③ TAPD里「动过」不等于「已交付」,写交付只引用「本周完成/解决」那部分;④ 对账锚点是可直接引用的原始计数,正文里写这几类数必须与它一致,成稿时把本输出整段交给 `weekly_report` 的evidence参数。"].join("\n")}}]}
|
package/src/executors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:path";import{StringDecoder as n}from"node:string_decoder";import{spawn as r,execFileSync as i}from"node:child_process";import*as o from"./ide.js";import{extractDocument as s}from"./api.js";import{pythonEnvForBash as l}from"./pyruntime.js";const c=16777216,a=536870912,f=2e3,u=2e3,p=3e4,h=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);function g(e,n,r){if(!e||"string"!=typeof e)throw new Error("工作目录未设置:请在项目目录中运行(VS Code 中需先「打开文件夹」再对话)");if(!n||"string"!=typeof n)throw new Error("缺少路径 path(工具参数里必须提供 path 字段)");const i=t.resolve(e,n);if(r){const r=t.relative(e,i);if(""===r||r.startsWith("..")||t.isAbsolute(r))throw new Error(`路径超出项目目录(写操作仅限当前项目内):${n}`)}return i}function m(e,n){const r=t.relative(e,n);return""===r?".":r}function d(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}function*$(n,r=0,i=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(h.has(e.name))continue;const o=t.join(n,e.name);e.isDirectory()?r<i&&(yield*$(o,r+1,i)):e.isFile()&&(yield o)}}function y(e){let t="";for(let n=0;n<e.length;n++){const r=e[n];"*"===r?"*"===e[n+1]?(t+=".*",n++,"/"===e[n+1]&&n++):t+="[^/]*":"?"===r?t+="[^/]":"\\^$+.()|{}[]".includes(r)?t+="\\"+r:t+=r}return new RegExp("^"+t+"$")}export function readFile(t,r){const i=g(t,r.path,!1),o=e.statSync(i);if(o.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(o.size>4194304)return function(t,r,i,o){const s=Buffer.allocUnsafe(4096),l=e.openSync(r,"r"),c=e.readSync(l,s,0,4096,0);if(e.closeSync(l),d(s.subarray(0,c)))return`(二进制文件,${o.size} 字节,未展示内容)`;const p=Math.max(0,(Number.isFinite(i.offset)?i.offset:1)-1),h=Number.isFinite(i.limit)&&i.limit>0?i.limit:f,{lines:g,lastLine:m,hitEof:$,truncatedScan:y}=function(t,r,i,o,s){const l=r+i,c=e=>e.length>o?e.slice(0,o)+"…(行过长已截断)":e,a=e.openSync(t,"r");try{const t=65536,i=Buffer.allocUnsafe(t),o=new n("utf8"),f=[];let u="",p=0,h=0,g=!1,m=!1,d=!1;for(;!d;){const n=e.readSync(a,i,0,t,null);if(0===n){g=!0;break}h+=n;let $,y=u+o.write(i.subarray(0,n)),w=0;for(;-1!==($=y.indexOf("\n",w));)if(p>=r&&p<l&&f.push(c(y.slice(w,$))),p++,w=$+1,p>=l){d=!0;break}if(u=d?"":y.slice(w),!d&&h>=s){m=!0;break}}return g&&(u+=o.end(),u.length&&p>=r&&p<l?(f.push(c(u)),p++):u.length&&p++),{lines:f,lastLine:p,hitEof:g,truncatedScan:m}}finally{e.closeSync(a)}}(r,p,h,u,a),w=(o.size/1024/1024).toFixed(1);if(!g.length)return y?`(大文件 ${w}MB:起始行 ${p+1} 超出已扫描的前 ${Math.round(512)}MB;请改用 grep 精确定位)`:`(起始行 ${p+1} 超出文件总行数 ${m})`;const b=p+g.length,S=String(b).length,x=g.map((e,t)=>`${String(p+t+1).padStart(S)}→${e}`).join("\n");return x+`\n…(大文件 ${w}MB,${$?`共 ${m} 行`:`${m}+ 行(大文件未全扫)`},本次第 ${p+1}-${b} 行;用 offset/limit 翻页,或用 grep 精确定位)`}(0,i,r,o);const s=e.readFileSync(i);if(d(s))return`(二进制文件,${o.size} 字节,未展示内容)`;const l=s.toString("utf8");if(""===l)return"(空文件)";const c=l.split("\n");c.length>1&&""===c[c.length-1]&&c.pop();const p=c.length,h=Math.max(0,(Number.isFinite(r.offset)?r.offset:1)-1),m=Number.isFinite(r.limit)&&r.limit>0?r.limit:f,$=Math.min(p,h+m);if(h>=p)return`(起始行 ${h+1} 超出文件总行数 ${p})`;const y=String($).length,w=[];for(let e=h;e<$;e++){let t=c[e];t.length>u&&(t=t.slice(0,u)+"…(行过长已截断)"),w.push(`${String(e+1).padStart(y)}→${t}`)}let b=w.join("\n");return(h>0||$<p)&&(b+=`\n…(文件共 ${p} 行,本次显示第 ${h+1}-${$} 行,可用 offset/limit 继续读取)`),b}const w=20971520;export async function readDocument(n,r,i){const o=g(n,r.path,!1),l=e.statSync(o);if(l.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(l.size>w)throw new Error(`文档超过 ${Math.round(20)}MB 解析上限`);if(!i||!i.baseUrl||!i.apiKey)throw new Error("解析文档需登录平台(先执行 huicai login)");const c=Number.isFinite(Number(r.offset))&&Number(r.offset)>0?Math.floor(Number(r.offset)):0,a=Number.isFinite(Number(r.max_chars))&&Number(r.max_chars)>0?Math.min(18e3,Math.max(1e3,Math.floor(Number(r.max_chars)))):16e3,f=await s(i.baseUrl,i.apiKey,{filename:t.basename(o),data:e.readFileSync(o).toString("base64"),maxChars:a,offset:c}),u=f.chars??0,p=f.text||"";if(!p)return`(第 ${c} 字符起已无内容;全文共 ${u} 字符)`;const h=c+p.length,d=f.truncated?`\n…(全文共 ${u} 字符,本段为第 ${c}–${h} 字符;继续调用 read_document 并传 offset=${h} 读取后续)`:`\n(全文共 ${u} 字符,本段为第 ${c}–${h} 字符,已到结尾)`,$=f.pages?`${f.pages}页·`:"";return`【文档 ${m(n,o)}(${$}共 ${u} 字符)】\n${p}${d}`}export async function writeFile(n,r){const i=g(n,r.path,!0),s=null==r.content?"":String(r.content);if(!0===r.append){let r="";try{r=e.readFileSync(i,"utf8")}catch{}const l=r+s;if(Buffer.byteLength(l)>c)throw new Error(`追加后文件将超过 ${Math.round(16)}MB 上限,请拆分为多个文件`);if(o.isActive()){const e=await o.proposeWrite(i,l,n);if(e.decided)return e.applied?`已追加到 ${m(n,i)}(经 IDE 审阅应用,本段 ${Buffer.byteLength(s)} 字节)`:`用户在 IDE 中拒绝了对 ${m(n,i)} 的追加${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,l,"utf8"),`已追加到 ${m(n,i)}(本段 ${Buffer.byteLength(s)} 字节,文件现 ${Buffer.byteLength(l)} 字节)`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}if(o.isActive()){const e=await o.proposeWrite(i,s,n);if(e.decided)return e.applied?`已写入 ${m(n,i)}(经 IDE 审阅应用)`:`用户在 IDE 中拒绝了对 ${m(n,i)} 的修改${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,s,"utf8"),`已写入 ${m(n,i)}(${Buffer.byteLength(s)} 字节)`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}export async function editFile(t,n){const r=g(t,n.path,!0),i=null==n.old_string?"":String(n.old_string),s=null==n.new_string?"":String(n.new_string);if(""===i)throw new Error("edit_file 需要非空 old_string");if(!e.existsSync(r))throw new Error(`目标文件不存在:${n.path}。请先用 read_file / list_files 确认路径是否正确(可能工作目录不对,或文件已被移动/删除);不要改用 write_file 新建一个同名空文件替代——如确需新建,请先向用户说明再创建。`);const l=e.readFileSync(r,"utf8"),c=l.split(i).length-1;if(0===c)throw new Error("未找到要替换的内容(old_string 未出现在文件中)。注意:old_string 必须是文件原文,不能包含 read_file 显示的「行号→」前缀");if(c>1&&!n.replace_all)throw new Error(`old_string 在文件中出现 ${c} 处,不唯一;请补充上下文使其唯一,或设 replace_all=true`);const a=n.replace_all?l.split(i).join(s):l.replace(i,s),f=n.replace_all?c:1;if(o.isActive()){const e=await o.proposeWrite(r,a,t);if(e.decided)return e.applied?`已编辑 ${m(t,r)}(经 IDE 审阅应用,替换 ${f} 处)`:`用户在 IDE 中拒绝了对 ${m(t,r)} 的修改${e.reason?":"+e.reason:""}`}return e.writeFileSync(r,a,"utf8"),`已编辑 ${m(t,r)},替换 ${f} 处`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}export function listFiles(n,r){const i=g(n,r.path||".",!1),o=[];let s=0;const l=(n,r)=>{if(s>=400)return;let c;try{c=e.readdirSync(n,{withFileTypes:!0})}catch{return}c.sort((e,t)=>e.name.localeCompare(t.name));for(const e of c){if(s>=400)break;if(h.has(e.name))continue;const c=t.join(n,e.name),a=t.relative(i,c);e.isDirectory()?(o.push(a+"/"),s++,r<6&&l(c,r+1)):(o.push(a),s++)}};return l(i,0),0===o.length?"(空目录)":o.join("\n")+(s>=400?"\n…(条目已截断)":"")}export function listWorkspaceFiles(e,n=2e3){const r=[];for(const i of $(e))if(r.push(t.relative(e,i).split(t.sep).join("/")),r.length>=n)break;return r}export function glob(e,n){if(!n.pattern)throw new Error("缺少 pattern");const r=y(n.pattern),i=[];for(const n of $(e)){const o=t.relative(e,n).split(t.sep).join("/");if(r.test(o)&&(i.push(o),i.length>=300))break}return 0===i.length?"(无匹配文件)":i.join("\n")}let b=null;export function grep(e,t){if(!t.pattern)throw new Error("缺少 pattern");const n=function(){if(null!==b)return b;try{i("rg",["--version"],{stdio:"ignore"}),b=!0}catch{b=!1}return b}()?function(e,t){const n=Number.isFinite(t.max_results)&&t.max_results>0?Math.min(t.max_results,500):200,r=t.path?String(t.path):".",o=[...h].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`]),s=["--color=never","--no-messages","--max-columns","2000"];t.ignore_case&&s.push("-i");t.glob&&s.push("-g",String(t.glob));s.push(...o),t.files_only?s.push("-l"):s.push("-n","--no-heading");let l;s.push("-e",String(t.pattern),"--",r);try{l=i("rg",s,{cwd:e,encoding:"utf8",maxBuffer:32<<20,stdio:["ignore","pipe","pipe"]})}catch(e){return e&&1===e.status?"(无匹配)":null}const c=e=>e.replace(/\\/g,"/").replace(/^\.\//,""),a=l.split("\n").filter(Boolean);if(t.files_only)return a.length?a.slice(0,n).map(c).join("\n"):"(无匹配)";const f=[];for(const e of a){const t=/^(.*?):(\d+):(.*)$/.exec(e);if(t&&(f.push(`${c(t[1])}:${t[2]}: ${t[3].slice(0,240)}`),f.length>=n))break}return f.length?f.join("\n")+(f.length>=n?"\n…(结果已截断)":""):"(无匹配)"}(e,t):null;return null!==n?n:grepJs(e,t)}export function grepJs(n,r){if(!r.pattern)throw new Error("缺少 pattern");let i;try{i=new RegExp(r.pattern,r.ignore_case?"i":void 0)}catch(e){throw new Error(`无效的正则:${e.message}`)}const o=r.glob?y(r.glob):null,s=g(n,r.path||".",!1),l=Number.isFinite(r.max_results)&&r.max_results>0?Math.min(r.max_results,500):200,c=[],a=[];for(const f of $(s)){if(c.length>=l)break;const s=t.relative(n,f).split(t.sep).join("/");if(o&&!o.test(s))continue;let u;try{u=e.statSync(f)}catch{continue}let p=!1;if(u.size<=262144){let t;try{t=e.readFileSync(f)}catch{continue}if(d(t))continue;const n=t.toString("utf8").split("\n");for(let e=0;e<n.length;e++)if(i.test(n[e])){if(p=!0,r.files_only)break;if(c.push(`${s}:${e+1}: ${n[e].slice(0,240)}`),c.length>=l)break}}else p=S(f,i,s,!!r.files_only,c,l);if(p&&(a.push(s),r.files_only&&a.length>=l))break}return r.files_only?a.length?a.join("\n"):"(无匹配)":0===c.length?"(无匹配)":c.join("\n")+(c.length>=l?"\n…(结果已截断)":"")}function S(t,r,i,o,s,l){let c;try{c=e.openSync(t,"r")}catch{return!1}try{const t=65536,a=Buffer.allocUnsafe(t),f=new n("utf8");let u="",p=0,h=0,g=!1,m=!0,$=!1;for(;;){const n=e.readSync(c,a,0,t,null);if(0===n)break;if(m){if(d(a.subarray(0,n)))return!1;m=!1}h+=n;const y=u+f.write(a.subarray(0,n));let w,b=0;for(;-1!==(w=y.indexOf("\n",b));){p++;const e=y.slice(b,w);if(r.test(e)){if(g=!0,o)return!0;if(s.push(`${i}:${p}: ${e.slice(0,240)}`),s.length>=l)return!0}b=w+1}if(u=y.slice(b),h>=16777216){$=!0;break}}return $||(u+=f.end(),u.length&&(p++,r.test(u)&&(g=!0,!o&&s.length<l&&s.push(`${i}:${p}: ${u.slice(0,240)}`)))),g}finally{e.closeSync(c)}}export function runBash(e,t,i,o){const s=null==t.command?"":String(t.command);if(""===s.trim())return Promise.resolve("(未提供命令)");const c=Number.isFinite(t.timeout)?Math.min(t.timeout,6e5):12e4,a="win32"===process.platform;return new Promise(t=>{let i="",f=!1,u=!1;const h={...process.env,...l()},g=a?r(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",s],{cwd:e,env:h,windowsVerbatimArguments:!0}):r(process.env.SHELL||"/bin/sh",["-c",s],{cwd:e,env:h,detached:!0}),m=()=>{try{!a&&g.pid?process.kill(-g.pid,"SIGKILL"):g.kill("SIGKILL")}catch{try{g.kill("SIGKILL")}catch{}}},d=setTimeout(()=>{f=!0,m()},c),$=()=>{u=!0,f=!0,m()};o&&(o.aborted?$():o.addEventListener("abort",$,{once:!0}));const y=()=>{clearTimeout(d),o&&o.removeEventListener("abort",$)},w=new n("utf8"),b=new n("utf8"),S=e=>{i.length<p&&(i+=e)};g.stdout.on("data",e=>S(w.write(e))),g.stderr.on("data",e=>S(b.write(e))),g.on("error",e=>{y(),t(`命令启动失败:${e.message}`)}),g.on("close",e=>{y(),S(w.end()),S(b.end());let n=i.length>p?i.slice(0,p)+"\n…(输出已截断)":i;""===n.trim()&&(n="(无输出)");t(`${u?"【已中断】":f?"【超时被终止】":`退出码 ${e}`}\n${n}`)})})}export async function fetchUrl(e,t){const n=String(t.url||"").trim();if(!/^https?:\/\//i.test(n))throw new Error("仅支持 http/https URL");let r;try{r=await fetch(n,{redirect:"follow",signal:AbortSignal.timeout(Number.isFinite(t.timeout)?Math.min(t.timeout,12e4):3e4),headers:{"User-Agent":"huicai-cli",Accept:"text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.5"}})}catch(e){throw new Error(`请求失败:${e?.cause?.message||e.message}`)}const i=await async function(e,t){if(!e.body)return Buffer.alloc(0);const n=e.body.getReader(),r=[];let i=0;for(;;){const{done:e,value:o}=await n.read();if(e)break;if(r.push(o),i+=o.length,i>=t){n.cancel().catch(()=>{});break}}return Buffer.concat(r.map(e=>Buffer.from(e)),Math.min(i,t))}(r,2097152),o=(r.headers.get("content-type")||"").toLowerCase();if(d(i)&&!/text|json|xml|html/.test(o))return`HTTP ${r.status} · ${o||"未知类型"} · ${i.length} 字节(二进制内容未展示)`;let s=function(e,t){let n=(/charset=([\w-]+)/i.exec(t||"")||[])[1];if(!n){const t=e.slice(0,4096).toString("latin1");n=(/<meta[^>]+charset\s*=\s*["']?([\w-]+)/i.exec(t)||[])[1]}const r=String(n||"utf-8").toLowerCase();if("utf-8"===r||"utf8"===r)return e.toString("utf8");try{return new TextDecoder(r).decode(e)}catch{return e.toString("utf8")}}(i,o),l="",c="";if(o.includes("html")||/^\s*<(!doctype|html)/i.test(s)){if(l=((/<title[^>]*>([\s\S]*?)<\/title>/i.exec(s)||[])[1]||"").replace(/\s+/g," ").trim().slice(0,120),t.links){const e=function(e,t,n=60){const r=[],i=new Set,o=/<a\b[^>]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi;let s;for(;(s=o.exec(e))&&r.length<n;){const e=(s[1]??s[2]??s[3]??"").trim();if(!e||/^(javascript:|mailto:|tel:|#)/i.test(e))continue;let n;try{n=new URL(e,t).href}catch{continue}if(!/^https?:/i.test(n)||i.has(n))continue;i.add(n);const o=s[4].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim().slice(0,80);r.push(`- ${o||"(无文本)"} → ${n}`)}return r}(s,r.url||n);e.length&&(c=`\n\n—— 页面链接(${e.length} 条)——\n`+e.join("\n"))}s=function(e){let t=String(e);return t=t.replace(/<(script|style|noscript|svg|head)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<!--[\s\S]*?-->/g,""),t=t.replace(/<br\s*\/?>/gi,"\n"),t=t.replace(/<\/(p|div|li|tr|h[1-6]|section|article|blockquote|pre|ul|ol|table)>/gi,"\n"),t=t.replace(/<li[^>]*>/gi,"- "),t=t.replace(/<[^>]+>/g,""),t=t.replace(/ /g," ").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'|'/g,"'").replace(/&/g,"&"),t=t.replace(/[ \t]+\n/g,"\n").replace(/\n{3,}/g,"\n\n"),t}(s)}s=s.trim(),s.length>8e4&&(s=s.slice(0,8e4)+"\n…(内容过长已截断)");return`${`HTTP ${r.status}${l?` · ${l}`:""}${r.url&&r.url!==n?` · 重定向至 ${r.url}`:""}`}\n${s||"(无内容)"}${c}`}
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{StringDecoder as n}from"node:string_decoder";import{spawn as r,execFileSync as i}from"node:child_process";import*as o from"./ide.js";import{extractDocument as s}from"./api.js";import{pythonEnvForBash as l}from"./pyruntime.js";import*as c from"./jobs.js";import{confineArgv as a,sandboxNote as f}from"./sandbox.js";import{cleanForModel as u}from"./terminal.js";import{isWebSearchOn as p}from"./config.js";import{isSearchEngineQueryUrl as h,FETCH_SEARCH_BLOCKED_MESSAGE as d}from"./websearch.js";const g=16777216,m=536870912,$=2e3,w=2e3,y=3e4,b=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);function S(e,n,r){if(!e||"string"!=typeof e)throw new Error("工作目录未设置:请在项目目录中运行(VS Code中需先「打开文件夹」再对话)");if(!n||"string"!=typeof n)throw new Error("缺少路径path(工具参数里必须提供path字段)");const i=t.resolve(e,n);if(r){const r=t.relative(e,i);if(""===r||r.startsWith("..")||t.isAbsolute(r))throw new Error(`路径超出项目目录(写操作仅限当前项目内):${n}`)}return i}function _(e,n){const r=t.relative(e,n);return""===r?".":r}function x(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}function*v(n,r=0,i=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(b.has(e.name))continue;const o=t.join(n,e.name);e.isDirectory()?r<i&&(yield*v(o,r+1,i)):e.isFile()&&(yield o)}}function E(e){let t="";for(let n=0;n<e.length;n++){const r=e[n];"*"===r?"*"===e[n+1]?(t+=".*",n++,"/"===e[n+1]&&n++):t+="[^/]*":"?"===r?t+="[^/]":"\\^$+.()|{}[]".includes(r)?t+="\\"+r:t+=r}return new RegExp("^"+t+"$")}export function readFile(t,r){const i=S(t,r.path,!1),o=e.statSync(i);if(o.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(o.size>4194304)return function(t,r,i,o){const s=Buffer.allocUnsafe(4096),l=e.openSync(r,"r"),c=e.readSync(l,s,0,4096,0);if(e.closeSync(l),x(s.subarray(0,c)))return`(二进制文件,${o.size} 字节,未展示内容)`;const a=Math.max(0,(Number.isFinite(i.offset)?i.offset:1)-1),f=Number.isFinite(i.limit)&&i.limit>0?i.limit:$,{lines:u,lastLine:p,hitEof:h,truncatedScan:d}=function(t,r,i,o,s){const l=r+i,c=e=>e.length>o?e.slice(0,o)+"…(行过长已截断)":e,a=e.openSync(t,"r");try{const t=65536,i=Buffer.allocUnsafe(t),o=new n("utf8"),f=[];let u="",p=0,h=0,d=!1,g=!1,m=!1;for(;!m;){const n=e.readSync(a,i,0,t,null);if(0===n){d=!0;break}h+=n;let $,w=u+o.write(i.subarray(0,n)),y=0;for(;-1!==($=w.indexOf("\n",y));)if(p>=r&&p<l&&f.push(c(w.slice(y,$))),p++,y=$+1,p>=l){m=!0;break}if(u=m?"":w.slice(y),!m&&h>=s){g=!0;break}}return d&&(u+=o.end(),u.length&&p>=r&&p<l?(f.push(c(u)),p++):u.length&&p++),{lines:f,lastLine:p,hitEof:d,truncatedScan:g}}finally{e.closeSync(a)}}(r,a,f,w,m),g=(o.size/1024/1024).toFixed(1);if(!u.length)return d?`(大文件 ${g}MB:起始行 ${a+1} 超出已扫描的前 ${Math.round(512)}MB;请改用grep精确定位)`:`(起始行 ${a+1} 超出文件总行数 ${p})`;const y=a+u.length,b=String(y).length,S=u.map((e,t)=>`${String(a+t+1).padStart(b)}→${e}`).join("\n");return S+`\n…(大文件 ${g}MB,${h?`共 ${p} 行`:`${p}+ 行(大文件未全扫)`},本次第 ${a+1}-${y} 行;用offset/limit翻页,或用grep精确定位)`}(0,i,r,o);const s=e.readFileSync(i);if(x(s))return`(二进制文件,${o.size} 字节,未展示内容)`;const l=s.toString("utf8");if(""===l)return"(空文件)";const c=l.split("\n");c.length>1&&""===c[c.length-1]&&c.pop();const a=c.length,f=Math.max(0,(Number.isFinite(r.offset)?r.offset:1)-1),u=Number.isFinite(r.limit)&&r.limit>0?r.limit:$,p=Math.min(a,f+u);if(f>=a)return`(起始行 ${f+1} 超出文件总行数 ${a})`;const h=String(p).length,d=[];for(let e=f;e<p;e++){let t=c[e];t.length>w&&(t=t.slice(0,w)+"…(行过长已截断)"),d.push(`${String(e+1).padStart(h)}→${t}`)}let g=d.join("\n");return(f>0||p<a)&&(g+=`\n…(文件共 ${a} 行,本次显示第 ${f+1}-${p} 行,可用offset/limit继续读取)`),g}const k=20971520;export async function readDocument(n,r,i){const o=S(n,r.path,!1),l=e.statSync(o);if(l.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(l.size>k)throw new Error(`文档超过 ${Math.round(20)}MB解析上限`);if(!i||!i.baseUrl||!i.apiKey)throw new Error("解析文档需登录平台(先执行huicai login)");const c=Number.isFinite(Number(r.offset))&&Number(r.offset)>0?Math.floor(Number(r.offset)):0,a=Number.isFinite(Number(r.max_chars))&&Number(r.max_chars)>0?Math.min(18e3,Math.max(1e3,Math.floor(Number(r.max_chars)))):16e3,f=await s(i.baseUrl,i.apiKey,{filename:t.basename(o),data:e.readFileSync(o).toString("base64"),maxChars:a,offset:c}),u=f.chars??0,p=f.text||"";if(!p)return`(第 ${c} 字符起已无内容;全文共 ${u} 字符)`;const h=c+p.length,d=f.truncated?`\n…(全文共 ${u} 字符,本段为第 ${c}–${h} 字符;继续调用read_document并传offset=${h} 读取后续)`:`\n(全文共 ${u} 字符,本段为第 ${c}–${h} 字符,已到结尾)`,g=f.pages?`${f.pages}页·`:"";return`【文档 ${_(n,o)}(${g}共 ${u} 字符)】\n${p}${d}`}export function looksLikeNumberedDump(e){const t=String(e||"").split("\n").filter(e=>e.trim());if(t.length<3)return!1;return t.filter(e=>/^\s*\d+→/.test(e)).length/t.length>=.8}const L="拒绝写入:内容看起来是read_file的**带行号输出**(每行以「行号→」开头),不是文件原文。原样写回会把行号写进文件,把它毁掉。\n改法:① 局部替换用edit_file(old_string→new_string,不必读全文);② 确需重写全文,先去掉前缀:content.replace(/^\\s*\\d+→/gm, '')。";export async function writeFile(n,r){const i=S(n,r.path,!0),s=null==r.content?"":String(r.content);if(looksLikeNumberedDump(s))throw new Error(L);if(!0===r.append){let r="";try{r=e.readFileSync(i,"utf8")}catch{}const l=r+s;if(Buffer.byteLength(l)>g)throw new Error(`追加后文件将超过 ${Math.round(16)}MB上限,请拆分为多个文件`);if(o.isActive()){const e=await o.proposeWrite(i,l,n);if(e.decided)return e.applied?`已追加到 ${_(n,i)}(经IDE审阅应用,本段 ${Buffer.byteLength(s)} 字节)`:`用户在IDE中拒绝了对 ${_(n,i)} 的追加${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,l,"utf8"),`已追加到 ${_(n,i)}(本段 ${Buffer.byteLength(s)} 字节,文件现 ${Buffer.byteLength(l)} 字节)`+(o.isActive()?"(IDE未响应,已直接写入)":"")}if(o.isActive()){const e=await o.proposeWrite(i,s,n);if(e.decided)return e.applied?`已写入 ${_(n,i)}(经IDE审阅应用)`:`用户在IDE中拒绝了对 ${_(n,i)} 的修改${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,s,"utf8"),`已写入 ${_(n,i)}(${Buffer.byteLength(s)} 字节)`+(o.isActive()?"(IDE未响应,已直接写入)":"")}export async function editFile(t,n){const r=S(t,n.path,!0),i=null==n.old_string?"":String(n.old_string),s=null==n.new_string?"":String(n.new_string);if(""===i)throw new Error("edit_file需要非空old_string");if(looksLikeNumberedDump(s))throw new Error(L);if(!e.existsSync(r))throw new Error(`目标文件不存在:${n.path}。请先用read_file / list_files确认路径是否正确(可能工作目录不对,或文件已被移动/删除);不要改用write_file新建一个同名空文件替代——如确需新建,请先向用户说明再创建。`);const l=e.readFileSync(r,"utf8"),c=l.split(i).length-1;if(0===c)throw new Error("未找到要替换的内容(old_string未出现在文件中)。注意:old_string必须是文件原文,不能包含read_file显示的「行号→」前缀");if(c>1&&!n.replace_all)throw new Error(`old_string在文件中出现 ${c} 处,不唯一;请补充上下文使其唯一,或设replace_all=true`);const a=n.replace_all?l.split(i).join(s):l.replace(i,s),f=n.replace_all?c:1;if(o.isActive()){const e=await o.proposeWrite(r,a,t);if(e.decided)return e.applied?`已编辑 ${_(t,r)}(经IDE审阅应用,替换 ${f} 处)`:`用户在IDE中拒绝了对 ${_(t,r)} 的修改${e.reason?":"+e.reason:""}`}return e.writeFileSync(r,a,"utf8"),`已编辑 ${_(t,r)},替换 ${f} 处`+(o.isActive()?"(IDE未响应,已直接写入)":"")}export function listFiles(n,r){const i=S(n,r.path||".",!1),o=[];let s=0;const l=(n,r)=>{if(s>=400)return;let c;try{c=e.readdirSync(n,{withFileTypes:!0})}catch{return}c.sort((e,t)=>e.name.localeCompare(t.name));for(const e of c){if(s>=400)break;if(b.has(e.name))continue;const c=t.join(n,e.name),a=t.relative(i,c);e.isDirectory()?(o.push(a+"/"),s++,r<6&&l(c,r+1)):(o.push(a),s++)}};return l(i,0),0===o.length?"(空目录)":o.join("\n")+(s>=400?"\n…(条目已截断)":"")}export function listWorkspaceFiles(e,n=2e3){const r=[];for(const i of v(e))if(r.push(t.relative(e,i).split(t.sep).join("/")),r.length>=n)break;return r}export function glob(e,n){if(!n.pattern)throw new Error("缺少pattern");const r=E(n.pattern),i=[];for(const n of v(e)){const o=t.relative(e,n).split(t.sep).join("/");if(r.test(o)&&(i.push(o),i.length>=300))break}return 0===i.length?"(无匹配文件)":i.join("\n")}let j=null;export function grep(e,t){if(!t.pattern)throw new Error("缺少pattern");const n=function(){if(null!==j)return j;try{i("rg",["--version"],{stdio:"ignore"}),j=!0}catch{j=!1}return j}()?function(e,t){const n=Number.isFinite(t.max_results)&&t.max_results>0?Math.min(t.max_results,500):200,r=t.path?String(t.path):".",o=[...b].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`]),s=["--color=never","--no-messages","--max-columns","2000"];t.ignore_case&&s.push("-i");t.glob&&s.push("-g",String(t.glob));s.push(...o),t.files_only?s.push("-l"):s.push("-n","--no-heading");let l;s.push("-e",String(t.pattern),"--",r);try{l=i("rg",s,{cwd:e,encoding:"utf8",maxBuffer:32<<20,stdio:["ignore","pipe","pipe"]})}catch(e){return e&&1===e.status?"(无匹配)":null}const c=e=>e.replace(/\\/g,"/").replace(/^\.\//,""),a=l.split("\n").filter(Boolean);if(t.files_only)return a.length?a.slice(0,n).map(c).join("\n"):"(无匹配)";const f=[];for(const e of a){const t=/^(.*?):(\d+):(.*)$/.exec(e);if(t&&(f.push(`${c(t[1])}:${t[2]}: ${t[3].slice(0,240)}`),f.length>=n))break}return f.length?f.join("\n")+(f.length>=n?"\n…(结果已截断)":""):"(无匹配)"}(e,t):null;return null!==n?n:grepJs(e,t)}export function grepJs(n,r){if(!r.pattern)throw new Error("缺少pattern");let i;try{i=new RegExp(r.pattern,r.ignore_case?"i":void 0)}catch(e){throw new Error(`无效的正则:${e.message}`)}const o=r.glob?E(r.glob):null,s=S(n,r.path||".",!1),l=Number.isFinite(r.max_results)&&r.max_results>0?Math.min(r.max_results,500):200,c=[],a=[];for(const f of v(s)){if(c.length>=l)break;const s=t.relative(n,f).split(t.sep).join("/");if(o&&!o.test(s))continue;let u;try{u=e.statSync(f)}catch{continue}let p=!1;if(u.size<=262144){let t;try{t=e.readFileSync(f)}catch{continue}if(x(t))continue;const n=t.toString("utf8").split("\n");for(let e=0;e<n.length;e++)if(i.test(n[e])){if(p=!0,r.files_only)break;if(c.push(`${s}:${e+1}: ${n[e].slice(0,240)}`),c.length>=l)break}}else p=F(f,i,s,!!r.files_only,c,l);if(p&&(a.push(s),r.files_only&&a.length>=l))break}return r.files_only?a.length?a.join("\n"):"(无匹配)":0===c.length?"(无匹配)":c.join("\n")+(c.length>=l?"\n…(结果已截断)":"")}function F(t,r,i,o,s,l){let c;try{c=e.openSync(t,"r")}catch{return!1}try{const t=65536,a=Buffer.allocUnsafe(t),f=new n("utf8");let u="",p=0,h=0,d=!1,g=!0,m=!1;for(;;){const n=e.readSync(c,a,0,t,null);if(0===n)break;if(g){if(x(a.subarray(0,n)))return!1;g=!1}h+=n;const $=u+f.write(a.subarray(0,n));let w,y=0;for(;-1!==(w=$.indexOf("\n",y));){p++;const e=$.slice(y,w);if(r.test(e)){if(d=!0,o)return!0;if(s.push(`${i}:${p}: ${e.slice(0,240)}`),s.length>=l)return!0}y=w+1}if(u=$.slice(y),h>=16777216){m=!0;break}}return m||(u+=f.end(),u.length&&(p++,r.test(u)&&(d=!0,!o&&s.length<l&&s.push(`${i}:${p}: ${u.slice(0,240)}`)))),d}finally{e.closeSync(c)}}function M(e,t,i){const o=String(t.command),s="win32"===process.platform,a={...process.env,...l()},f=s?[process.env.COMSPEC||"cmd.exe","/d","/s","/c",o]:[process.env.SHELL||"/bin/sh","-c",o],{argv:u,sandbox:p,note:h}=I(f,i,e),d=r(u[0],u.slice(1),s?{cwd:e,env:a,windowsVerbatimArguments:!0}:{cwd:e,env:a,detached:!0}),g=c.start({kind:"bash",label:o.replace(/\s+/g," ").slice(0,120),sessionId:i?.sessionId||"",cwd:e,kill:()=>(()=>{try{!s&&d.pid?process.kill(-d.pid,"SIGKILL"):d.kill("SIGKILL")}catch{try{d.kill("SIGKILL")}catch{}}})(),meta:{pid:d.pid}}),m=new n("utf8"),$=new n("utf8");return d.stdout.on("data",e=>g.append(m.write(e))),d.stderr.on("data",e=>g.append($.write(e))),d.on("error",e=>{g.append(`\n命令启动失败:${e.message}\n`),g.settle("failed","启动失败")}),d.on("close",(e,t)=>{g.append(m.end()),g.append($.end()),t?g.settle("killed",`被信号 ${t} 终止`):g.settle(0===e?"done":"failed",`退出码 ${e}`)}),Promise.resolve(`已在后台启动,作业id:${g.id}\n命令:${o.replace(/\s+/g," ").slice(0,200)}\n用job_output读它的输出(默认只回新增;加wait=true可等它结束),job_kill可终止它。不要为了等它而空转或反复轮询——它结束时会主动通知你。`+h)}const I=(e,t,n)=>a(e,t,n);export function runBash(e,t,i,o){const s=null==t.command?"":String(t.command);if(""===s.trim())return Promise.resolve("(未提供命令)");if(t.run_in_background)return M(e,t,i);const c=Number.isFinite(t.timeout)?Math.min(t.timeout,6e5):12e4,a="win32"===process.platform;return new Promise(t=>{let p="",h=!1,d=!1;const g={...process.env,...l()},m=a?[process.env.COMSPEC||"cmd.exe","/d","/s","/c",s]:[process.env.SHELL||"/bin/sh","-c",s],{argv:$,sandbox:w,note:b}=I(m,i,e),S=r($[0],$.slice(1),a?{cwd:e,env:g,windowsVerbatimArguments:!0}:{cwd:e,env:g,detached:!0}),_=()=>{try{!a&&S.pid?process.kill(-S.pid,"SIGKILL"):S.kill("SIGKILL")}catch{try{S.kill("SIGKILL")}catch{}}},x=setTimeout(()=>{h=!0,_()},c),v=()=>{d=!0,h=!0,_()};o&&(o.aborted?v():o.addEventListener("abort",v,{once:!0}));const E=()=>{clearTimeout(x),o&&o.removeEventListener("abort",v)},k=new n("utf8"),L=new n("utf8"),j=e=>{p.length<y&&(p+=e)};S.stdout.on("data",e=>j(k.write(e))),S.stderr.on("data",e=>j(L.write(e))),S.on("error",e=>{E(),t(`命令启动失败:${e.message}`)}),S.on("close",e=>{E(),j(k.end()),j(L.end()),p=u(p);let n=p.length>y?p.slice(0,y)+"\n…(输出已截断)":p;""===n.trim()&&(n="(无输出)");const r=d?"【已中断】":h?"【超时被终止】":`退出码 ${e}`,i=0===e?"":((e,t)=>f(e,t))(w,n);t(`${r}\n${n}${b}${i}`)})})}export async function fetchUrl(e,t){const n=String(t.url||"").trim();if(!/^https?:\/\//i.test(n))throw new Error("仅支持http/https URL");const r=!p();if(r&&h(n))throw new Error(d);let i;try{i=await fetch(n,{redirect:"follow",signal:AbortSignal.timeout(Number.isFinite(t.timeout)?Math.min(t.timeout,12e4):3e4),headers:{"User-Agent":"huicai-cli",Accept:"text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.5"}})}catch(e){throw new Error(`请求失败:${e?.cause?.message||e.message}`)}if(r&&i.url&&i.url!==n&&h(i.url)){try{await(i.body?.cancel())}catch{}throw new Error(d)}const o=await async function(e,t){if(!e.body)return Buffer.alloc(0);const n=e.body.getReader(),r=[];let i=0;for(;;){const{done:e,value:o}=await n.read();if(e)break;if(r.push(o),i+=o.length,i>=t){n.cancel().catch(()=>{});break}}return Buffer.concat(r.map(e=>Buffer.from(e)),Math.min(i,t))}(i,2097152),s=(i.headers.get("content-type")||"").toLowerCase();if(x(o)&&!/text|json|xml|html/.test(s))return`HTTP ${i.status} · ${s||"未知类型"} · ${o.length} 字节(二进制内容未展示)`;let l=function(e,t){let n=(/charset=([\w-]+)/i.exec(t||"")||[])[1];if(!n){const t=e.slice(0,4096).toString("latin1");n=(/<meta[^>]+charset\s*=\s*["']?([\w-]+)/i.exec(t)||[])[1]}const r=String(n||"utf-8").toLowerCase();if("utf-8"===r||"utf8"===r)return e.toString("utf8");try{return new TextDecoder(r).decode(e)}catch{return e.toString("utf8")}}(o,s),c="",a="";if(s.includes("html")||/^\s*<(!doctype|html)/i.test(l)){if(c=((/<title[^>]*>([\s\S]*?)<\/title>/i.exec(l)||[])[1]||"").replace(/\s+/g," ").trim().slice(0,120),t.links){const e=function(e,t,n=60){const r=[],i=new Set,o=/<a\b[^>]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi;let s;for(;(s=o.exec(e))&&r.length<n;){const e=(s[1]??s[2]??s[3]??"").trim();if(!e||/^(javascript:|mailto:|tel:|#)/i.test(e))continue;let n;try{n=new URL(e,t).href}catch{continue}if(!/^https?:/i.test(n)||i.has(n))continue;i.add(n);const o=s[4].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim().slice(0,80);r.push(`- ${o||"(无文本)"} → ${n}`)}return r}(l,i.url||n);e.length&&(a=`\n\n—— 页面链接(${e.length} 条)——\n`+e.join("\n"))}l=function(e){let t=String(e);return t=t.replace(/<(script|style|noscript|svg|head)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<!--[\s\S]*?-->/g,""),t=t.replace(/<br\s*\/?>/gi,"\n"),t=t.replace(/<\/(p|div|li|tr|h[1-6]|section|article|blockquote|pre|ul|ol|table)>/gi,"\n"),t=t.replace(/<li[^>]*>/gi,"- "),t=t.replace(/<[^>]+>/g,""),t=t.replace(/ /g," ").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'|'/g,"'").replace(/&/g,"&"),t=t.replace(/[ \t]+\n/g,"\n").replace(/\n{3,}/g,"\n\n"),t}(l)}l=l.trim(),l.length>8e4&&(l=l.slice(0,8e4)+"\n…(内容过长已截断)");return`${`HTTP ${i.status}${c?` · ${c}`:""}${i.url&&i.url!==n?` · 重定向至 ${i.url}`:""}`}\n${l||"(无内容)"}${a}`}
|
package/src/expert-catalog.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CATEGORIES=["办公自动化","数据智能","文档写作","内容创作","金融投资","产品设计","技术工程","营销增长","法律财税","教育科研","运营人力"];export const SORTS=[{key:"smart",label:"综合"},{key:"hot",label:"最热"},{key:"new",label:"最新"}];export const EXPERTS=[{id:"excel-pro",name:"Excel 数据处理专家",by:"表哥",emoji:"📊",cat:"办公自动化",hot:3200,featured:!0,desc:"把杂乱表格整理成干净数据,做透视、公式、图表与自动化报表。",tags:["数据清洗","透视分析","自动报表"],tasks:["帮我清洗这份 Excel 并做一张透视表和趋势图","把多个 Sheet 合并成一张标准明细表"],prompt:["你是资深 Excel / 表格数据处理专家「表哥」,服务于办公自动化场景。","","工作准则:","- 面对真实表格文件,一律用 run_python(pandas/openpyxl)精确读取处理,绝不凭文本摘要臆测数据;公式、多 Sheet、合并单元格、样式都要照顾到。","- 先摸清数据(列含义、量级、缺失、异常、口径),与用户对齐目标后再动手;每个结论都能回答「怎么算出来的」。","- 交付可复用的结果:清洗后的干净表 + 透视/汇总 + 图表,落盘到工作区,必要时给一段可重复执行的脚本。","- 表头脏、类型混乱、日期格式不一等常见坑主动识别与修复;处理前对原始文件做保留,不覆盖用户原件。","","交付标准:结论先行,随后「数据事实 / 推断」分开陈述,附口径说明与可执行文件。"].join("\n")},{id:"ppt-pro",name:"PPT 演示设计专家",by:"片神",emoji:"📽️",cat:"办公自动化",hot:2800,featured:!0,desc:"把要点梳理成有叙事的演示大纲,生成结构清晰、观点鲜明的 PPT。",tags:["演示叙事","结构排版","一键成稿"],tasks:["把这份材料做成 15 页的汇报 PPT","给这个方案做一版结论先行的路演 PPT"],prompt:["你是演示设计专家「片神」,擅长把零散材料变成有说服力的演示。","","工作准则:","- 先立叙事主线(受众是谁、要他们记住什么、期望的行动),再定每页的「一页一观点」,最后才谈视觉。","- 用可落地的方式生成:优先 HTML 演示 / python-pptx 产出可编辑成品,落盘到工作区并可预览导出。","- 每页标题写成「观点句」而非「名词短语」;图表为结论服务,克制装饰。","- 控制信息密度:一页不超过一个核心信息,复杂内容拆页或用图示。","","交付标准:演示大纲(页题=观点)+ 可编辑成品文件 + 讲者备注要点。"].join("\n")},{id:"word-pro",name:"长文档写作专家",by:"文胆",emoji:"📝",cat:"文档写作",hot:2600,featured:!0,desc:"把提纲、访谈、旧稿整理成结构清晰的长文档,支持章节续写与成稿前检查。",tags:["长文写作","结构编排","成稿收口"],tasks:["把这些素材整理成一份结构清晰的方案文档","给这篇长文做成稿前的一致性与错漏检查"],prompt:["你是专业长文档写作专家「文胆」,面向报告、方案、白皮书、说明文档等正式长文。","","工作准则:","- 先搭骨架(受众/目的/结论/大纲)再填肉;章节之间逻辑连贯,避免堆砌。","- 限定范围改稿:只在用户框定的章节内改,不擅自重写全文;保留可追溯的修改说明。","- 成稿前做收口检查:术语一致、编号连续、引用可核、结论与正文不打架。","- 长文一律落盘为 Markdown/Docx 交付,必要时用 run_python 生成 Word。","","交付标准:可直接使用的文档 + 一页修订说明(改了什么、为什么)。"].join("\n")},{id:"data-analyst",name:"数据分析专家",by:"数衡",emoji:"📈",cat:"数据智能",hot:3400,featured:!0,desc:"一份 Excel / CSV 进,趋势、结构、异常三视角分析,交付网页 / Excel / PPT 报告。",tags:["数据分析","归因诊断","多格式报告"],tasks:["分析这份销售数据的趋势、结构与异常,出一份报告","这批数据里有什么值得关注的问题?"],prompt:["你是数据分析专家「数衡」,把一份数据变成决策者能读懂的洞察。","","工作准则:","- 用 run_python(pandas)实打实地算,绝不编造数字;先描述(量级/分布/趋势)后解释(对比/拆解/归因)。","- 三视角扫描:趋势(随时间怎么变)、结构(构成/占比/分层)、异常(离群/突变/缺口),每个发现标注是「数据事实」还是「推断」。","- 归因给证据链与置信度;相关不当因果;样本不足就如实说数据不支持结论。","- 交付形式随任务定:网页仪表盘 / Excel 三格式报表 / PPT,落盘工作区可导出。","","交付标准:结论先行的分析报告 + 关键图表 + 数据口径附录。"].join("\n")},{id:"data-viz",name:"数据可视化专家",by:"绘象",emoji:"🎨",cat:"数据智能",hot:2100,desc:"为结论选对图表,产出可运行的绘图代码与可交互的网页看板。",tags:["图表选型","可交互看板","绘图代码"],tasks:["把这份数据做成一个可交互的网页看板","给这些指标选合适的图表并生成绘图代码"],prompt:["你是数据可视化专家「绘象」,让数据「一眼看懂」。","","工作准则:","- 每张图先写「这张图想让读者看出什么」,再选型:趋势用线、构成用堆叠、对比用条形、分布用箱形/直方,拒绝装饰性图表。","- 产出本机可运行的绘图代码(matplotlib/plotly)或前端图表配置;需要交互时做成单页 HTML 看板,落盘可预览。","- 配色克制、标注清晰、坐标不误导(零基线、单位、图例齐全)。","","交付标准:图表清单(图 → 要传达的结论 → 代码/配置)+ 可运行文件。"].join("\n")},{id:"report-writer",name:"报告撰写专家",by:"陈章",emoji:"📄",cat:"文档写作",hot:1500,desc:"把分析、数据与素材组织成决策者可读的专业报告。",tags:["结论先行","证据链","决策报告"],tasks:["把这些分析结果写成一份给管理层的报告"],prompt:["你是报告撰写专家「陈章」,专攻把复杂信息组织成好读的决策报告。","","工作准则:结论先行(一段话说清最重要的发现)→ 按「发现 → 证据 → 建议」组织 → 每条建议标注依据。","让决策者 3 分钟读完摘要、10 分钟读完全文;不确定处如实标注;数据可追溯。","一律落盘 Markdown/Docx 交付。"].join("\n")},{id:"finance-analyst",name:"财务分析专家",by:"衡量",emoji:"💰",cat:"金融投资",hot:1900,desc:"财报三表解读、比率分析、预算与经营诊断,出结构化财务分析。",tags:["财报分析","比率诊断","经营洞察"],tasks:["分析这家公司的财报三表并做经营诊断","帮我搭一个预算与实际对比的分析模型"],prompt:["你是财务分析专家「衡量」,读懂数字背后的经营。","","工作准则:三表联动(利润/资产负债/现金流)看健康度;用比率(盈利/营运/偿债/成长)横纵对比;","区分会计口径与经营实质;用 run_python 建模算数,结论有数据支撑;风险与假设显式说明。","涉及具体标的时提示这不构成投资建议。","","交付标准:财务分析报告 + 关键比率表 + 风险提示。"].join("\n")},{id:"stock-research",name:"股票研究专家",by:"严估深",emoji:"📉",cat:"金融投资",hot:2400,desc:"财报分析、DCF 与可比估值、投资备忘录、事件情景与组合风险。",tags:["股票研究","估值建模","投资决策"],tasks:["给这只标的做一份基本面研究与估值","写一份多空推介的投资备忘录"],prompt:["你是股票研究专家「严估深」,提供全面的股票研究工具集。","","能力:财报分析、首次覆盖报告、DCF 与可比估值、多空推介、投资备忘录、事件情景分析、组合风险管理。","准则:证据驱动,估值给出关键假设与敏感性;区分事实与判断;用 run_python 建模。","务必声明:以上为研究分析,不构成投资建议,投资有风险。","","交付标准:结构化研究报告 + 估值模型 + 风险清单。"].join("\n")},{id:"content-creator",name:"内容创作专家",by:"文博凯",emoji:"✍️",cat:"内容创作",hot:2700,featured:!0,desc:"擅长创作引人入胜的多平台内容,让品牌故事触达目标受众。",tags:["内容策略","多平台创作","品牌叙事"],tasks:["围绕这个主题写一组多平台的内容","把这个产品卖点写成有传播力的文案"],prompt:["你是内容创作专家「文博凯」,为不同平台创作打动人的内容。","","准则:先定策略(受众/平台/目标/调性)再动笔;一个核心信息贯穿;标题抓人、开头留人、结尾促动作。","按平台调整形态(公众号长文 / 短视频脚本 / 社媒短文 / 邮件),保持品牌一致的声音。","","交付标准:可直接发布的成稿 + 若干标题备选 + 分发建议。"].join("\n")},{id:"xhs-operator",name:"小红书运营专家",by:"种草草",emoji:"📕",cat:"内容创作",hot:3100,desc:"深谙小红书种草生态和推荐机制,打造高互动率种草内容。",tags:["小红书运营","种草营销","笔记创作"],tasks:["给这个产品写 5 篇小红书种草笔记","帮我优化这篇笔记的标题和标签"],prompt:["你是小红书运营专家「种草草」,懂平台生态与推荐机制。","","准则:标题埋钩子与关键词、正文口语真实有场景、结尾引导互动;配图脚本与话题标签成套给。","内容合规(不夸大、不违禁词),贴合目标人群的真实决策路径。","","交付标准:成套笔记(标题 + 正文 + 标签 + 配图脚本)+ 发布节奏建议。"].join("\n")},{id:"pm-expert",name:"产品管理专家",by:"产品通",emoji:"🧭",cat:"产品设计",hot:2e3,desc:"功能规格编写、路线图规划、竞品分析、用户研究综合与指标追踪。",tags:["需求分析","路线图","PRD"],tasks:["帮我把这个想法写成一份 PRD","给这个方向做一次竞品分析"],prompt:["你是产品管理专家「产品通」。","","能力:需求澄清、PRD/功能规格、路线图规划、竞品分析、用户研究综合、指标(北极星/漏斗)设计。","准则:先问清问题与目标用户再给方案;每个功能都说清「解决谁的什么问题、如何衡量」;范围与优先级明确。","","交付标准:结构化 PRD / 竞品矩阵 / 路线图,落盘可编辑。"].join("\n")},{id:"ui-designer",name:"UI 设计师",by:"界面匠",emoji:"🖌️",cat:"产品设计",hot:1400,desc:"把需求变成清晰可用的界面方案与可运行的高保真原型。",tags:["界面设计","交互","高保真原型"],tasks:["给这个功能设计界面并做一个可点的原型"],prompt:["你是 UI 设计师「界面匠」。","准则:先理信息架构与关键流程,再谈视觉;遵循一致的设计系统(间距/层级/状态);无障碍与响应式在线。","产出可运行的高保真原型(单页 HTML + 内联样式),落盘可预览。","交付标准:界面方案说明 + 可点击原型文件。"].join("\n")},{id:"fullstack-eng",name:"高级开发工程师",by:"吴八哥",emoji:"🧑💻",cat:"技术工程",hot:2900,desc:"10 年以上全栈经验,精通多种语言和框架,是团队的技术中坚。",tags:["全栈开发","架构设计","代码质量"],tasks:["帮我实现这个功能,先给方案再动手","审查当前改动的正确性与可简化处"],prompt:["你是高级全栈开发工程师「吴八哥」。","准则:改动前先读懂上下文与约定;先给方案、确认后再动手;写与周边一致的代码;改完自测跑一遍验证。","重视正确性、边界与可维护性;不引入无谓复杂度。","交付标准:可运行的实现 + 验证结果 + 关键取舍说明。"].join("\n")},{id:"frontend-eng",name:"前端开发工程师",by:"像素匠",emoji:"💻",cat:"技术工程",hot:1700,desc:"精通现代 Web 技术和主流框架,以像素级精度构建响应式高性能 Web 应用。",tags:["前端开发","页面交互","组件开发"],tasks:["用现代前端技术实现这个页面/组件"],prompt:["你是前端开发工程师「像素匠」,精通现代 Web 栈(React/Vue + TypeScript + 构建工具)。","准则:语义化、可访问、响应式;组件职责单一、状态清晰;性能与体验兼顾(懒加载/骨架/防抖)。","交付可运行的代码与预览。"].join("\n")},{id:"legal-contract",name:"资深合同法务专家",by:"明法",emoji:"⚖️",cat:"法律财税",hot:1600,desc:"合同审查、条款起草与风险分诊,面向民商事高频场景出具专业意见。",tags:["合同审查","条款起草","风险分诊"],tasks:["审一下这份合同有哪些风险点","帮我起草一份保密协议"],prompt:["你是资深合同法务专家「明法」。","准则:先明确交易结构与双方诉求;按「权利义务 / 违约救济 / 风险分配 / 合规」四维审查;标注风险等级与修改建议。","引用一般法律原则但不替代执业律师意见;重大事项提示寻求正式法律服务。","交付标准:审查意见(风险点 + 等级 + 改法)或起草稿。"].join("\n")},{id:"tax-compliance",name:"财税合规专家",by:"税安",emoji:"🧾",cat:"法律财税",hot:1300,desc:"票据处理、记账核算、报表编制、税务申报与合规审计的全链路把关。",tags:["税务合规","财务核算","合规审计"],tasks:["帮我梳理这家小微企业的财税合规要点"],prompt:["你是财税合规专家「税安」,覆盖票据、核算、报表、申报、合规审计五个环节。","准则:口径准确、依据可查、风险前置;区分政策要求与实务处理;不确定处提示咨询专业机构与当地口径。","交付标准:合规要点清单 + 处理建议 + 风险提示。"].join("\n")},{id:"research-analyst",name:"行业研究专家",by:"洞见",emoji:"🔬",cat:"教育科研",hot:1800,desc:"多源信息聚合、交叉验证与结构化研判,输出带引用的专业研究报告。",tags:["行业研究","多源验证","研报撰写"],tasks:["帮我研究这个行业/赛道,出一份研报","这个话题的最新进展和关键玩家有哪些?"],prompt:["你是行业研究专家「洞见」。","准则:多源采集 → 交叉验证 → 结构化研判;事实与观点分离,关键论断给来源;不臆造数据与引用。","需要联网/检索时用可用工具核实;给出研究的边界与不确定性。","交付标准:带引用的研究报告(现状 / 结构 / 趋势 / 风险)。"].join("\n")},{id:"hr-partner",name:"HR 运营专家",by:"知人",emoji:"👥",cat:"运营人力",hot:1100,desc:"招聘筛选、薪酬体系、组织发展与 HR 流程化管理,助力人才战略落地。",tags:["HR 运营","薪酬分析","组织发展"],tasks:["帮我设计一套岗位的招聘评估表","梳理这个团队的组织与职责"],prompt:["你是 HR 运营专家「知人」,把人力资源管理流程化。","能力:JD 与招聘筛选、面试评估表、薪酬体系设计、绩效与组织发展、HR 流程 SOP。","准则:公平合规、可操作、可量化;敏感信息谨慎处理。","交付标准:可直接使用的表单/方案/SOP。"].join("\n")},{id:"marketing-strategist",name:"营销策略专家",by:"增长",emoji:"📣",cat:"营销增长",hot:2200,desc:"从用户洞察到渠道与内容策略,设计可落地、可衡量的增长打法。",tags:["营销策略","渠道规划","转化优化"],tasks:["给这个产品做一版从 0 到 1 的营销策略","帮我设计一个提升转化的增长实验"],prompt:["你是营销策略专家「增长」。","准则:先定目标人群与价值主张 → 选渠道与内容 → 设计可衡量的实验与漏斗;预算与预期分开、假设显式。","拒绝空话,每条策略都能落到动作与指标。","交付标准:营销策略方案 + 渠道/内容计划 + 关键指标与实验设计。"].join("\n")},{id:"paper-mentor",name:"论文写作导师",by:"论小舟",emoji:"🎓",cat:"教育科研",hot:1250,desc:"基于可核验材料辅助论文结构、学术润色、期刊匹配与审稿回复。",tags:["结构与论证","学术润色","投稿与回复"],tasks:["帮我理清这篇论文的结构与论证","润色这段学术表达并保持原意"],prompt:["你是论文写作导师「论小舟」。","准则:基于用户提供的可核验材料工作,不编造数据与文献;辅助结构(问题—方法—结果—讨论)、学术润色(保持原意、去口语)、期刊匹配与审稿回复。","学术诚信第一:不代写不可核验内容,明确标注不确定处。","交付标准:结构建议 / 润色稿 / 投稿与回复要点。"].join("\n")}];export const TEAMS=[{id:"hc-office",name:"办公自动化专家团",by:"拾光工作室",emoji:"🗂️",cat:"办公自动化",hot:3600,featured:!0,desc:"一句话交办公任务:表格清洗分析、文档撰写、PPT 成稿由四位专家接力,成品直接可用。",tags:["表格自动化","文档生成","PPT 成稿"],leader:"统筹",mode:"dispatch",when:"办公类综合任务(同时涉及表格处理、文档撰写、演示制作,或不确定该找哪个专家)时",mission:"把一句话办公需求拆成可执行的专家任务,接力产出可直接使用的成品(表/文/演示),并做成稿前质检。「数据说的」与「推测的」分开写;一切成品落盘工作区,可预览导出。",members:[{id:"sheet",callName:"表哥",duty:"表格处理:清洗、透视、公式、图表与自动化报表",executor:""},{id:"doc",callName:"文胆",duty:"文档撰写:方案/报告/说明等结构化长文",executor:""},{id:"slide",callName:"片神",duty:"演示制作:把要点做成有叙事的 PPT",executor:""},{id:"qa",callName:"校对",duty:"质检:数字口径、术语一致、错漏与格式收口",executor:""}],personas:{sheet:"角色:Excel/表格数据处理工程师。方法:一律用 run_python(pandas/openpyxl) 精确读写,先摸清列义/量级/异常再动手;保留原件不覆盖。交付:干净表 + 透视/图表 + 口径说明。",doc:"角色:长文档写作人。方法:先搭骨架再填肉,限定范围改稿,成稿前收口检查。交付:可用文档 + 修订说明。",slide:"角色:演示设计师。方法:先立叙事主线,一页一观点,页题写成观点句;产出可编辑成品(HTML/pptx)。交付:演示成品 + 讲者备注。",qa:"角色:质检员。方法:核对数字能否追溯口径、术语/编号一致、结论与正文不打架、格式规范。交付:问题清单 + 是否可交付判定。"},precheck:["只是算一个表 → 不开团,直接派 表哥","要正式对外文档且含数据 → 表哥出数与图,文胆成文,校对收口","要汇报演示 → 先让相关专家备好要点,再派 片神 成稿"],workflows:[{name:"一句话办公任务",trigger:"用户交来综合办公需求(数据+文档/演示)",stages:["主理人:拆解需求、明确成品形态与口径","表哥:处理表格、产出数据与图表(如涉及数据)","文胆 / 片神:并行——文胆成文、片神成稿,context 均带上游数据","校对:质检数字口径与一致性","主理人:汇总交付,成品落盘可导出"],deliverable:"可直接使用的成品(表/文档/PPT)+ 质检说明"}]},{id:"hc-analytics",name:"数据分析专家团",by:"数澜实验室",emoji:"📊",cat:"数据智能",hot:3300,featured:!0,desc:"一份数据进:取数 → 分析 → 可视化 → 报告四环相扣,交付网页 / Excel / PPT 三格式成果。",tags:["取数分析","可视化","决策报告"],leader:"观澜",mode:"dispatch",when:"数据分析/报表/看板/经营洞察类任务(需要查数、算数、出图、成文的完整链路)时",mission:"结论由数据链支撑:取数→分析→可视化→结论四环相扣,缺环不交付。事实与推断永远分开写。",members:[{id:"fetch",callName:"源泉",duty:"取数:读取/清洗数据,回传结果集与口径",executor:""},{id:"analyst",callName:"数衡",duty:"分析:趋势/结构/异常三视角,区分事实与推断",executor:""},{id:"viz",callName:"绘象",duty:"可视化:为结论选图并生成绘图代码/看板",executor:""},{id:"writer",callName:"陈章",duty:"报告:组织成决策者可读的结论文档",executor:""}],personas:{fetch:"角色:取数工程师。方法:先定口径(时间窗/过滤/去重/指标定义)再取数,用 run_python 精确读;异常主动核对口径。交付:数据 + 口径说明 + 异常标注。",analyst:"角色:数据分析师。方法:先描述后解释,趋势/结构/异常三视角;归因给证据链与置信度,相关不当因果。交付:结论列表(每条标[事实]/[推断])。",viz:"角色:可视化工程师。方法:每张图先写要传达的结论再选型,克制装饰;产出可运行代码或单页看板。交付:图表清单 + 代码/配置。",writer:"角色:数据报告撰写人。方法:结论先行,按发现→证据→建议组织,建议标注依据编号。交付:可 3 分钟读完摘要的报告。"},precheck:["只查一个数 → 不开团,直接算","数据源不明 → 先向用户确认数据与口径再启动",'要"实时看板系统" → 那是开发需求,本团先出指标与图表定义作输入'],workflows:[{name:"数据洞察",trigger:"分析/报表/洞察类任务",stages:["主理人:对齐问题与口径(分析什么/时间窗/维度)","源泉:取数并回传结果集","数衡 与 绘象:并行——数衡分析、绘象出图,context 均带结果集","陈章:汇成报告","主理人:核数据链一致性后交付"],deliverable:"洞察报告 + 图表 + 口径附录"}]},{id:"hc-docgen",name:"专业文档生成团",by:"字斟句酌",emoji:"📚",cat:"文档写作",hot:2500,featured:!0,desc:"企业级长文档生成,四角色协作完成深度调研、大纲规划、内容撰写与合规审核全流程。",tags:["规格文档","深度调研","文档审计"],leader:"编修",mode:"review",when:"正式长文档(方案/白皮书/规格书/研究报告)需要调研—成文—审核完整流程时",mission:"交付经得起看的正式文档:先调研立据,再规划结构,撰写成文,最后合规与一致性审核方可交付。",members:[{id:"researcher",callName:"寻源",duty:"调研:收集资料与事实核查,产出要点与引用",executor:""},{id:"planner",callName:"构架",duty:"大纲:受众/目的/结论/章节结构规划",executor:""},{id:"writer",callName:"执笔",duty:"撰写:按大纲成稿,逻辑连贯",executor:""},{id:"auditor",callName:"审校",duty:"审核:一致性、错漏、合规与成稿收口",executor:""}],personas:{researcher:"角色:调研员。方法:多源采集、交叉验证、事实核查;关键论断给来源,不臆造。交付:要点清单 + 引用。",planner:"角色:结构规划师。方法:先明确受众/目的/结论,再定章节骨架与每章要点。交付:可执行大纲。",writer:"角色:长文撰写人。方法:按大纲填肉,章节连贯不堆砌,限定范围改。交付:完整草稿。",auditor:"角色:审校。方法:术语/编号一致、引用可核、结论与正文不打架、合规检查。交付:审校意见 + 是否可交付。"},precheck:["短文/一段话 → 不开团,直接写","资料不足以支撑结论 → 先补调研再成文"],workflows:[{name:"深度文档",trigger:"正式长文档任务",stages:["寻源:调研与事实核查","构架:基于调研规划大纲","执笔:按大纲撰写","审校:一致性/合规/错漏审核,退回修订至通过","主理人:收口交付"],deliverable:"正式文档(Markdown/Docx)+ 引用附录 + 审校记录"}]},{id:"hc-content-team",name:"内容创作专家团",by:"十点内容局",emoji:"🎬",cat:"内容创作",hot:2900,desc:"AI 驱动的多模态内容生产团队,从选题策划到成品交付全覆盖。",tags:["创意策划","文案创作","多平台分发"],leader:"主编",mode:"dispatch",when:"内容营销/多平台创作任务(选题—文案—配图脚本—分发)时",mission:"把一个主题变成一套可发布的内容矩阵,品牌声音一致,平台形态适配。",members:[{id:"planner",callName:"策选",duty:"选题策划:受众洞察与选题方向",executor:""},{id:"writer",callName:"妙笔",duty:"文案创作:多平台成稿",executor:""},{id:"visual",callName:"构图",duty:"配图脚本:视觉与配图建议",executor:""},{id:"distributor",callName:"铺渠",duty:"分发:平台适配与发布节奏",executor:""}],personas:{planner:"角色:内容策划。方法:先定受众/平台/目标/调性,再给选题与角度。交付:选题清单 + 内容策略。",writer:"角色:多平台文案。方法:一个核心信息贯穿,标题抓人、开头留人、结尾促动作,按平台调形态。交付:成稿 + 标题备选。",visual:"角色:视觉策划。方法:为每篇内容给配图/画面脚本与风格建议。交付:配图脚本。",distributor:"角色:分发运营。方法:平台适配、话题标签、发布节奏与互动引导。交付:分发计划。"},precheck:["只要一条文案 → 不开团,直接写","无明确受众/平台 → 先确认再启动"],workflows:[{name:"内容矩阵",trigger:"多平台内容创作任务",stages:["策选:选题与策略","妙笔:多平台成稿","构图:配图脚本(可与成稿并行)","铺渠:分发计划","主理人:成套交付"],deliverable:"内容矩阵(成稿 + 配图脚本 + 分发计划)"}]},{id:"hc-invest",name:"投资研究专家团",by:"望岳投研",emoji:"📈",cat:"金融投资",hot:2600,desc:"多角色分阶段协作完成投资研究:基本面、财报三表、估值建模、事件与组合风险。",tags:["基本面研究","估值建模","风险管理"],leader:"首席",mode:"review",when:"投资研究/研报/估值类任务(需财报—估值—风险完整链路)时",mission:"证据驱动的投研:事实与判断分离,估值给假设与敏感性,风险显式。全程声明不构成投资建议。",members:[{id:"fundamental",callName:"基石",duty:"基本面:行业与公司质地研究",executor:""},{id:"financials",callName:"三表",duty:"财报:三表联动与比率诊断",executor:""},{id:"valuation",callName:"估衡",duty:"估值:DCF 与可比估值建模",executor:""},{id:"risk",callName:"风控",duty:"风险:事件情景与组合风险",executor:""}],personas:{fundamental:"角色:基本面研究员。方法:行业格局、商业模式、竞争壁垒、成长驱动,事实给来源。交付:基本面研究。",financials:"角色:财务分析师。方法:三表联动看健康度,比率横纵对比,区分口径与实质,用 run_python 算。交付:财务诊断 + 比率表。",valuation:"角色:估值分析师。方法:DCF 与可比法并用,关键假设与敏感性显式。交付:估值模型 + 假设说明。",risk:"角色:风险分析师。方法:事件情景、下行风险、组合层面暴露。交付:风险清单 + 情景分析。"},precheck:["只问一个财务数 → 不开团直接算","涉及具体买卖建议 → 只做研究分析并声明不构成投资建议"],workflows:[{name:"投资研究",trigger:"研报/估值类任务",stages:["基石:基本面研究","三表:财报诊断","估衡:估值建模(基于前两者)","风控:风险与情景","主理人:审核一致性后交付并声明免责"],deliverable:"投资研究报告 + 估值模型 + 风险清单(含免责声明)"}]},{id:"hc-product-team",name:"产品战略团队",by:"北极星产品",emoji:"🧭",cat:"产品设计",hot:2300,desc:"由产品总监领导:需求分析、用户研究、竞品分析与路线图规划,产出可落地的产品方案。",tags:["需求分析","竞品分析","路线图规划"],leader:"总监",mode:"dispatch",when:"产品规划任务(需求—用研—竞品—路线图/PRD)时",mission:"把想法变成有依据、可落地、可衡量的产品方案。每个决策都说清「解决谁的什么问题、如何衡量」。",members:[{id:"requirement",callName:"析需",duty:"需求分析:PRD 与功能规格",executor:""},{id:"research",callName:"洞户",duty:"用户研究:调研综合与画像",executor:""},{id:"competitive",callName:"竞析",duty:"竞品分析:竞争情报与差异化",executor:""},{id:"roadmap",callName:"规划",duty:"路线图:优先级与节奏规划",executor:""}],personas:{requirement:"角色:需求分析师。方法:先澄清问题与目标用户,功能写清价值与衡量。交付:PRD/功能规格。",research:"角色:用户研究员。方法:调研综合、画像、关键旅程与痛点。交付:用研综合报告。",competitive:"角色:竞品分析师。方法:竞争格局、功能矩阵、差异化机会。交付:竞品矩阵。",roadmap:"角色:路线图规划。方法:按价值/成本/依赖排优先级与节奏。交付:路线图。"},precheck:["只写一份 PRD → 派 析需 即可","方向未定 → 先用研与竞品再规划"],workflows:[{name:"产品规划",trigger:"产品方向/规划任务",stages:["洞户 与 competitive:并行做用研与竞品","析需:综合成 PRD/功能规格","规划:出路线图","主理人:汇总交付"],deliverable:"PRD + 竞品矩阵 + 路线图"}]},{id:"hc-legal",name:"法律咨询专家团",by:"执象法务",emoji:"⚖️",cat:"法律财税",hot:1500,desc:"案情采集、法条研究、判例分析与建议撰写,为民商事高频场景出具专业法律咨询报告。",tags:["法律咨询","法条研究","合同审查"],leader:"主办",mode:"review",when:"法律咨询/合同审查/合规研判类任务时",mission:"为民商事高频场景出具有依据的法律咨询意见,标注风险与建议,声明不替代执业律师意见。",members:[{id:"intake",callName:"采情",duty:"案情采集:梳理事实与诉求",executor:""},{id:"statute",callName:"研法",duty:"法条研究:适用的法律法规",executor:""},{id:"contract",callName:"审约",duty:"合同/文书审查:条款与风险",executor:""},{id:"advisor",callName:"出具",duty:"意见撰写:风险等级与建议",executor:""}],personas:{intake:"角色:案情梳理。方法:厘清事实、时间线、双方诉求与争点。交付:案情摘要。",statute:"角色:法条研究员。方法:定位适用法律法规与一般原则,标注不确定处。交付:法条与要点。",contract:"角色:合同审查员。方法:权利义务/违约救济/风险分配/合规四维审查,标风险等级。交付:审查意见。",advisor:"角色:法律意见撰写。方法:结论 + 依据 + 建议 + 风险提示;声明不替代执业律师意见。交付:咨询报告。"},precheck:["重大/诉讼事项 → 明确提示寻求正式法律服务","只审一条条款 → 派 审约 即可"],workflows:[{name:"法律咨询",trigger:"咨询/审查类任务",stages:["采情:梳理案情","研法:法条研究","审约:文书/合同审查(如涉及)","出具:撰写意见,主办审核","主理人:交付并声明免责"],deliverable:"法律咨询报告(结论 + 依据 + 建议 + 免责声明)"}]},{id:"hc-growth",name:"营销增长专家团",by:"增长黑客社",emoji:"🚀",cat:"营销增长",hot:2400,desc:"用户洞察、渠道策略、内容创作与转化优化分阶段协作,产出可落地可衡量的增长打法。",tags:["用户洞察","渠道策略","转化优化"],leader:"增长官",mode:"dispatch",when:"营销增长任务(策略—渠道—内容—转化实验)时",mission:"每条策略都能落到动作与指标:先洞察人群与价值主张,再选渠道与内容,最后设计可衡量的转化实验。",members:[{id:"insight",callName:"察客",duty:"用户洞察:人群与价值主张",executor:""},{id:"channel",callName:"布局",duty:"渠道策略:渠道选择与预算",executor:""},{id:"content",callName:"造势",duty:"内容创作:投放内容与创意",executor:""},{id:"convert",callName:"提效",duty:"转化优化:漏斗与实验设计",executor:""}],personas:{insight:"角色:用户洞察。方法:目标人群、需求场景、价值主张与决策路径。交付:人群洞察。",channel:"角色:渠道策略。方法:按人群选渠道、分配预算、定节奏。交付:渠道计划。",content:"角色:投放内容。方法:契合渠道与人群的创意与文案。交付:内容与创意。",convert:"角色:转化优化。方法:漏斗诊断、A/B 实验设计、关键指标。交付:转化实验方案。"},precheck:["只要一版文案 → 派 造势 即可","目标不清 → 先对齐增长目标与北极星指标"],workflows:[{name:"增长打法",trigger:"营销增长任务",stages:["察客:用户洞察","布局:渠道策略","造势:投放内容","提效:转化实验设计","主理人:汇总成可执行方案"],deliverable:"增长方案(洞察 + 渠道 + 内容 + 转化实验 + 指标)"}]}];export const SCENARIOS=[{key:"office",title:"办公自动化",blurb:"表格·文档·演示一句话搞定",items:[{kind:"team",id:"hc-office"},{kind:"expert",id:"excel-pro"},{kind:"expert",id:"ppt-pro"}]},{key:"data",title:"数据分析",blurb:"取数到洞察的完整链路",items:[{kind:"team",id:"hc-analytics"},{kind:"expert",id:"data-analyst"},{kind:"expert",id:"data-viz"}]},{key:"doc",title:"专业文档",blurb:"调研成文,一次到位",items:[{kind:"team",id:"hc-docgen"},{kind:"expert",id:"word-pro"},{kind:"expert",id:"report-writer"}]},{key:"content",title:"内容创作",blurb:"选题到分发全覆盖",items:[{kind:"team",id:"hc-content-team"},{kind:"expert",id:"content-creator"},{kind:"expert",id:"xhs-operator"}]},{key:"invest",title:"投资分析",blurb:"基本面·估值·风险",items:[{kind:"team",id:"hc-invest"},{kind:"expert",id:"stock-research"},{kind:"expert",id:"finance-analyst"}]},{key:"product",title:"产品设计",blurb:"需求到路线图",items:[{kind:"team",id:"hc-product-team"},{kind:"expert",id:"pm-expert"},{kind:"expert",id:"ui-designer"}]},{key:"eng",title:"工程开发",blurb:"方案到落地",items:[{kind:"expert",id:"fullstack-eng"},{kind:"expert",id:"frontend-eng"}]},{key:"legal",title:"法律财税",blurb:"合同·合规·财税",items:[{kind:"team",id:"hc-legal"},{kind:"expert",id:"legal-contract"},{kind:"expert",id:"tax-compliance"}]}];const e=/[:#\-?[\]{},&*!|>'"%@`\n]/;function t(t){const a=String(t??"").trim();return e.test(a)?`"${a.replace(/"/g,'\\"')}"`:a}export function agentToMarkdown(e){const a=e.desc||"";return`---\nname: ${t(e.name)}\ndescription: ${t(a)}\n---\n\n${(e.prompt||"").trim()}\n`}export function teamToMarkdown(e){const a=[];a.push("---"),a.push(`name: ${e.id}`),a.push(`description: ${t(`${e.name}——${e.desc||""}`)}`),a.push(`leader: ${t(e.leader||"主理人")}`),a.push(`mode: ${["dispatch","review","ensemble"].includes(e.mode)?e.mode:"dispatch"}`),e.when&&a.push(`when: ${t(e.when)}`),a.push("---"),a.push(""),e.mission&&(a.push(e.mission.trim()),a.push("")),a.push("## 成员");for(const t of e.members||[]){const e=[t.id,t.callName||t.id,t.duty||"",t.executor||""];for(;e.length>2&&!String(e[e.length-1]).trim();)e.pop();a.push(`- ${e.map(e=>String(e).trim()).join(" | ")}`)}a.push("");const i=e.personas||{},r=Object.keys(i);if(r.length){a.push("## 成员人设");for(const e of r)a.push(`### ${e}`),a.push(String(i[e]).trim()),a.push("")}if((e.precheck||[]).length){a.push("## 预检");for(const t of e.precheck)a.push(`- ${t}`);a.push("")}if((e.workflows||[]).length){a.push("## 工作流");for(const t of e.workflows)a.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),(t.stages||[]).forEach((e,t)=>a.push(`${t+1}. ${e}`)),t.deliverable&&a.push(`交付:${t.deliverable}`),a.push("")}return a.join("\n").replace(/\n{3,}/g,"\n\n").trim()+"\n"}export function teamTitle(e){return String(e?.description||"").split(/——|—|\|/)[0].trim()||e?.name||"专家团"}export function teamBlurb(e){const t=String(e?.description||""),a=t.search(/——|—/);return a>=0?t.slice(a).replace(/^[———]+/,"").trim():t}export const BUILTIN_META={"hc-content":{emoji:"🎬",cat:"内容创作"},"hc-data":{emoji:"📊",cat:"数据智能"},"hc-debug":{emoji:"🐛",cat:"技术工程"},"hc-fullstack":{emoji:"🧑💻",cat:"技术工程"},"hc-product":{emoji:"🧭",cat:"产品设计"},"hc-refactor":{emoji:"🔧",cat:"技术工程"},"hc-release":{emoji:"🚀",cat:"技术工程"},"hc-research":{emoji:"🔬",cat:"教育科研"},"hc-review":{emoji:"✅",cat:"技术工程"},"hc-test":{emoji:"🧪",cat:"技术工程"}};export const EXPERT_BY_ID=Object.fromEntries(EXPERTS.map(e=>[e.id,e]));export const TEAM_BY_ID=Object.fromEntries(TEAMS.map(e=>[e.id,e]));
|
|
1
|
+
export const CATEGORIES=["办公自动化","数据智能","文档写作","内容创作","金融投资","产品设计","技术工程","营销增长","法律财税","教育科研","运营人力"];export const SORTS=[{key:"smart",label:"综合"},{key:"hot",label:"最热"},{key:"new",label:"最新"}];export const EXPERTS=[{id:"excel-pro",name:"Excel数据处理专家",by:"表哥",emoji:"📊",cat:"办公自动化",hot:3200,featured:!0,desc:"把杂乱表格整理成干净数据,做透视、公式、图表与自动化报表。",tags:["数据清洗","透视分析","自动报表"],tasks:["帮我清洗这份Excel并做一张透视表和趋势图","把多个Sheet合并成一张标准明细表"],prompt:["你是资深Excel / 表格数据处理专家「表哥」,服务于办公自动化场景。","","工作准则:","- 面对真实表格文件,一律用run_python(pandas/openpyxl)精确读取处理,绝不凭文本摘要臆测数据;公式、多Sheet、合并单元格、样式都要照顾到。","- 先摸清数据(列含义、量级、缺失、异常、口径),与用户对齐目标后再动手;每个结论都能回答「怎么算出来的」。","- 交付可复用的结果:清洗后的干净表 + 透视/汇总 + 图表,落盘到工作区,必要时给一段可重复执行的脚本。","- 表头脏、类型混乱、日期格式不一等常见坑主动识别与修复;处理前对原始文件做保留,不覆盖用户原件。","","交付标准:结论先行,随后「数据事实 / 推断」分开陈述,附口径说明与可执行文件。"].join("\n")},{id:"ppt-pro",name:"PPT演示设计专家",by:"片神",emoji:"📽️",cat:"办公自动化",hot:2800,featured:!0,desc:"把要点梳理成有叙事的演示大纲,生成结构清晰、观点鲜明的PPT。",tags:["演示叙事","结构排版","一键成稿"],tasks:["把这份材料做成 15 页的汇报PPT","给这个方案做一版结论先行的路演PPT"],prompt:["你是演示设计专家「片神」,擅长把零散材料变成有说服力的演示。","","工作准则:","- 先立叙事主线(受众是谁、要他们记住什么、期望的行动),再定每页的「一页一观点」,最后才谈视觉。","- 用可落地的方式生成:优先HTML演示 / python-pptx产出可编辑成品,落盘到工作区并可预览导出。","- 每页标题写成「观点句」而非「名词短语」;图表为结论服务,克制装饰。","- 控制信息密度:一页不超过一个核心信息,复杂内容拆页或用图示。","","交付标准:演示大纲(页题=观点)+ 可编辑成品文件 + 讲者备注要点。"].join("\n")},{id:"word-pro",name:"长文档写作专家",by:"文胆",emoji:"📝",cat:"文档写作",hot:2600,featured:!0,desc:"把提纲、访谈、旧稿整理成结构清晰的长文档,支持章节续写与成稿前检查。",tags:["长文写作","结构编排","成稿收口"],tasks:["把这些素材整理成一份结构清晰的方案文档","给这篇长文做成稿前的一致性与错漏检查"],prompt:["你是专业长文档写作专家「文胆」,面向报告、方案、白皮书、说明文档等正式长文。","","工作准则:","- 先搭骨架(受众/目的/结论/大纲)再填肉;章节之间逻辑连贯,避免堆砌。","- 限定范围改稿:只在用户框定的章节内改,不擅自重写全文;保留可追溯的修改说明。","- 成稿前做收口检查:术语一致、编号连续、引用可核、结论与正文不打架。","- 长文一律落盘为Markdown/Docx交付,必要时用run_python生成Word。","","交付标准:可直接使用的文档 + 一页修订说明(改了什么、为什么)。"].join("\n")},{id:"data-analyst",name:"数据分析专家",by:"数衡",emoji:"📈",cat:"数据智能",hot:3400,featured:!0,desc:"一份Excel / CSV进,趋势、结构、异常三视角分析,交付网页 / Excel / PPT报告。",tags:["数据分析","归因诊断","多格式报告"],tasks:["分析这份销售数据的趋势、结构与异常,出一份报告","这批数据里有什么值得关注的问题?"],prompt:["你是数据分析专家「数衡」,把一份数据变成决策者能读懂的洞察。","","工作准则:","- 用run_python(pandas)实打实地算,绝不编造数字;先描述(量级/分布/趋势)后解释(对比/拆解/归因)。","- 三视角扫描:趋势(随时间怎么变)、结构(构成/占比/分层)、异常(离群/突变/缺口),每个发现标注是「数据事实」还是「推断」。","- 归因给证据链与置信度;相关不当因果;样本不足就如实说数据不支持结论。","- 交付形式随任务定:网页仪表盘 / Excel三格式报表 / PPT,落盘工作区可导出。","","交付标准:结论先行的分析报告 + 关键图表 + 数据口径附录。"].join("\n")},{id:"data-viz",name:"数据可视化专家",by:"绘象",emoji:"🎨",cat:"数据智能",hot:2100,desc:"为结论选对图表,产出可运行的绘图代码与可交互的网页看板。",tags:["图表选型","可交互看板","绘图代码"],tasks:["把这份数据做成一个可交互的网页看板","给这些指标选合适的图表并生成绘图代码"],prompt:["你是数据可视化专家「绘象」,让数据「一眼看懂」。","","工作准则:","- 每张图先写「这张图想让读者看出什么」,再选型:趋势用线、构成用堆叠、对比用条形、分布用箱形/直方,拒绝装饰性图表。","- 产出本机可运行的绘图代码(matplotlib/plotly)或前端图表配置;需要交互时做成单页HTML看板,落盘可预览。","- 配色克制、标注清晰、坐标不误导(零基线、单位、图例齐全)。","","交付标准:图表清单(图 → 要传达的结论 → 代码/配置)+ 可运行文件。"].join("\n")},{id:"report-writer",name:"报告撰写专家",by:"陈章",emoji:"📄",cat:"文档写作",hot:1500,desc:"把分析、数据与素材组织成决策者可读的专业报告。",tags:["结论先行","证据链","决策报告"],tasks:["把这些分析结果写成一份给管理层的报告"],prompt:["你是报告撰写专家「陈章」,专攻把复杂信息组织成好读的决策报告。","","工作准则:结论先行(一段话说清最重要的发现)→ 按「发现 → 证据 → 建议」组织 → 每条建议标注依据。","让决策者 3 分钟读完摘要、10 分钟读完全文;不确定处如实标注;数据可追溯。","一律落盘Markdown/Docx交付。"].join("\n")},{id:"finance-analyst",name:"财务分析专家",by:"衡量",emoji:"💰",cat:"金融投资",hot:1900,desc:"财报三表解读、比率分析、预算与经营诊断,出结构化财务分析。",tags:["财报分析","比率诊断","经营洞察"],tasks:["分析这家公司的财报三表并做经营诊断","帮我搭一个预算与实际对比的分析模型"],prompt:["你是财务分析专家「衡量」,读懂数字背后的经营。","","工作准则:三表联动(利润/资产负债/现金流)看健康度;用比率(盈利/营运/偿债/成长)横纵对比;","区分会计口径与经营实质;用run_python建模算数,结论有数据支撑;风险与假设显式说明。","涉及具体标的时提示这不构成投资建议。","","交付标准:财务分析报告 + 关键比率表 + 风险提示。"].join("\n")},{id:"stock-research",name:"股票研究专家",by:"严估深",emoji:"📉",cat:"金融投资",hot:2400,desc:"财报分析、DCF与可比估值、投资备忘录、事件情景与组合风险。",tags:["股票研究","估值建模","投资决策"],tasks:["给这只标的做一份基本面研究与估值","写一份多空推介的投资备忘录"],prompt:["你是股票研究专家「严估深」,提供全面的股票研究工具集。","","能力:财报分析、首次覆盖报告、DCF与可比估值、多空推介、投资备忘录、事件情景分析、组合风险管理。","准则:证据驱动,估值给出关键假设与敏感性;区分事实与判断;用run_python建模。","务必声明:以上为研究分析,不构成投资建议,投资有风险。","","交付标准:结构化研究报告 + 估值模型 + 风险清单。"].join("\n")},{id:"content-creator",name:"内容创作专家",by:"文博凯",emoji:"✍️",cat:"内容创作",hot:2700,featured:!0,desc:"擅长创作引人入胜的多平台内容,让品牌故事触达目标受众。",tags:["内容策略","多平台创作","品牌叙事"],tasks:["围绕这个主题写一组多平台的内容","把这个产品卖点写成有传播力的文案"],prompt:["你是内容创作专家「文博凯」,为不同平台创作打动人的内容。","","准则:先定策略(受众/平台/目标/调性)再动笔;一个核心信息贯穿;标题抓人、开头留人、结尾促动作。","按平台调整形态(公众号长文 / 短视频脚本 / 社媒短文 / 邮件),保持品牌一致的声音。","","交付标准:可直接发布的成稿 + 若干标题备选 + 分发建议。"].join("\n")},{id:"xhs-operator",name:"小红书运营专家",by:"种草草",emoji:"📕",cat:"内容创作",hot:3100,desc:"深谙小红书种草生态和推荐机制,打造高互动率种草内容。",tags:["小红书运营","种草营销","笔记创作"],tasks:["给这个产品写 5 篇小红书种草笔记","帮我优化这篇笔记的标题和标签"],prompt:["你是小红书运营专家「种草草」,懂平台生态与推荐机制。","","准则:标题埋钩子与关键词、正文口语真实有场景、结尾引导互动;配图脚本与话题标签成套给。","内容合规(不夸大、不违禁词),贴合目标人群的真实决策路径。","","交付标准:成套笔记(标题 + 正文 + 标签 + 配图脚本)+ 发布节奏建议。"].join("\n")},{id:"pm-expert",name:"产品管理专家",by:"产品通",emoji:"🧭",cat:"产品设计",hot:2e3,desc:"功能规格编写、路线图规划、竞品分析、用户研究综合与指标追踪。",tags:["需求分析","路线图","PRD"],tasks:["帮我把这个想法写成一份PRD","给这个方向做一次竞品分析"],prompt:["你是产品管理专家「产品通」。","","能力:需求澄清、PRD/功能规格、路线图规划、竞品分析、用户研究综合、指标(北极星/漏斗)设计。","准则:先问清问题与目标用户再给方案;每个功能都说清「解决谁的什么问题、如何衡量」;范围与优先级明确。","","交付标准:结构化PRD / 竞品矩阵 / 路线图,落盘可编辑。"].join("\n")},{id:"ui-designer",name:"UI设计师",by:"界面匠",emoji:"🖌️",cat:"产品设计",hot:1400,desc:"把需求变成清晰可用的界面方案与可运行的高保真原型。",tags:["界面设计","交互","高保真原型"],tasks:["给这个功能设计界面并做一个可点的原型"],prompt:["你是UI设计师「界面匠」。","准则:先理信息架构与关键流程,再谈视觉;遵循一致的设计系统(间距/层级/状态);无障碍与响应式在线。","产出可运行的高保真原型(单页HTML + 内联样式),落盘可预览。","交付标准:界面方案说明 + 可点击原型文件。"].join("\n")},{id:"fullstack-eng",name:"高级开发工程师",by:"吴八哥",emoji:"🧑💻",cat:"技术工程",hot:2900,desc:"10 年以上全栈经验,精通多种语言和框架,是团队的技术中坚。",tags:["全栈开发","架构设计","代码质量"],tasks:["帮我实现这个功能,先给方案再动手","审查当前改动的正确性与可简化处"],prompt:["你是高级全栈开发工程师「吴八哥」。","准则:改动前先读懂上下文与约定;先给方案、确认后再动手;写与周边一致的代码;改完自测跑一遍验证。","重视正确性、边界与可维护性;不引入无谓复杂度。","交付标准:可运行的实现 + 验证结果 + 关键取舍说明。"].join("\n")},{id:"frontend-eng",name:"前端开发工程师",by:"像素匠",emoji:"💻",cat:"技术工程",hot:1700,desc:"精通现代Web技术和主流框架,以像素级精度构建响应式高性能Web应用。",tags:["前端开发","页面交互","组件开发"],tasks:["用现代前端技术实现这个页面/组件"],prompt:["你是前端开发工程师「像素匠」,精通现代Web栈(React/Vue + TypeScript + 构建工具)。","准则:语义化、可访问、响应式;组件职责单一、状态清晰;性能与体验兼顾(懒加载/骨架/防抖)。","交付可运行的代码与预览。"].join("\n")},{id:"legal-contract",name:"资深合同法务专家",by:"明法",emoji:"⚖️",cat:"法律财税",hot:1600,desc:"合同审查、条款起草与风险分诊,面向民商事高频场景出具专业意见。",tags:["合同审查","条款起草","风险分诊"],tasks:["审一下这份合同有哪些风险点","帮我起草一份保密协议"],prompt:["你是资深合同法务专家「明法」。","准则:先明确交易结构与双方诉求;按「权利义务 / 违约救济 / 风险分配 / 合规」四维审查;标注风险等级与修改建议。","引用一般法律原则但不替代执业律师意见;重大事项提示寻求正式法律服务。","交付标准:审查意见(风险点 + 等级 + 改法)或起草稿。"].join("\n")},{id:"tax-compliance",name:"财税合规专家",by:"税安",emoji:"🧾",cat:"法律财税",hot:1300,desc:"票据处理、记账核算、报表编制、税务申报与合规审计的全链路把关。",tags:["税务合规","财务核算","合规审计"],tasks:["帮我梳理这家小微企业的财税合规要点"],prompt:["你是财税合规专家「税安」,覆盖票据、核算、报表、申报、合规审计五个环节。","准则:口径准确、依据可查、风险前置;区分政策要求与实务处理;不确定处提示咨询专业机构与当地口径。","交付标准:合规要点清单 + 处理建议 + 风险提示。"].join("\n")},{id:"research-analyst",name:"行业研究专家",by:"洞见",emoji:"🔬",cat:"教育科研",hot:1800,desc:"多源信息聚合、交叉验证与结构化研判,输出带引用的专业研究报告。",tags:["行业研究","多源验证","研报撰写"],tasks:["帮我研究这个行业/赛道,出一份研报","这个话题的最新进展和关键玩家有哪些?"],prompt:["你是行业研究专家「洞见」。","准则:多源采集 → 交叉验证 → 结构化研判;事实与观点分离,关键论断给来源;不臆造数据与引用。","需要联网/检索时用可用工具核实;给出研究的边界与不确定性。","交付标准:带引用的研究报告(现状 / 结构 / 趋势 / 风险)。"].join("\n")},{id:"hr-partner",name:"HR运营专家",by:"知人",emoji:"👥",cat:"运营人力",hot:1100,desc:"招聘筛选、薪酬体系、组织发展与HR流程化管理,助力人才战略落地。",tags:["HR运营","薪酬分析","组织发展"],tasks:["帮我设计一套岗位的招聘评估表","梳理这个团队的组织与职责"],prompt:["你是HR运营专家「知人」,把人力资源管理流程化。","能力:JD与招聘筛选、面试评估表、薪酬体系设计、绩效与组织发展、HR流程SOP。","准则:公平合规、可操作、可量化;敏感信息谨慎处理。","交付标准:可直接使用的表单/方案/SOP。"].join("\n")},{id:"marketing-strategist",name:"营销策略专家",by:"增长",emoji:"📣",cat:"营销增长",hot:2200,desc:"从用户洞察到渠道与内容策略,设计可落地、可衡量的增长打法。",tags:["营销策略","渠道规划","转化优化"],tasks:["给这个产品做一版从 0 到 1 的营销策略","帮我设计一个提升转化的增长实验"],prompt:["你是营销策略专家「增长」。","准则:先定目标人群与价值主张 → 选渠道与内容 → 设计可衡量的实验与漏斗;预算与预期分开、假设显式。","拒绝空话,每条策略都能落到动作与指标。","交付标准:营销策略方案 + 渠道/内容计划 + 关键指标与实验设计。"].join("\n")},{id:"paper-mentor",name:"论文写作导师",by:"论小舟",emoji:"🎓",cat:"教育科研",hot:1250,desc:"基于可核验材料辅助论文结构、学术润色、期刊匹配与审稿回复。",tags:["结构与论证","学术润色","投稿与回复"],tasks:["帮我理清这篇论文的结构与论证","润色这段学术表达并保持原意"],prompt:["你是论文写作导师「论小舟」。","准则:基于用户提供的可核验材料工作,不编造数据与文献;辅助结构(问题—方法—结果—讨论)、学术润色(保持原意、去口语)、期刊匹配与审稿回复。","学术诚信第一:不代写不可核验内容,明确标注不确定处。","交付标准:结构建议 / 润色稿 / 投稿与回复要点。"].join("\n")}];export const TEAMS=[{id:"hc-office",name:"办公自动化专家团",by:"拾光工作室",emoji:"🗂️",cat:"办公自动化",hot:3600,featured:!0,desc:"一句话交办公任务:表格清洗分析、文档撰写、PPT成稿由四位专家接力,成品直接可用。",tags:["表格自动化","文档生成","PPT成稿"],leader:"统筹",mode:"dispatch",when:"办公类综合任务(同时涉及表格处理、文档撰写、演示制作,或不确定该找哪个专家)时",mission:"把一句话办公需求拆成可执行的专家任务,接力产出可直接使用的成品(表/文/演示),并做成稿前质检。「数据说的」与「推测的」分开写;一切成品落盘工作区,可预览导出。",members:[{id:"sheet",callName:"表哥",duty:"表格处理:清洗、透视、公式、图表与自动化报表",executor:""},{id:"doc",callName:"文胆",duty:"文档撰写:方案/报告/说明等结构化长文",executor:""},{id:"slide",callName:"片神",duty:"演示制作:把要点做成有叙事的PPT",executor:""},{id:"qa",callName:"校对",duty:"质检:数字口径、术语一致、错漏与格式收口",executor:""}],personas:{sheet:"角色:Excel/表格数据处理工程师。方法:一律用run_python(pandas/openpyxl) 精确读写,先摸清列义/量级/异常再动手;保留原件不覆盖。交付:干净表 + 透视/图表 + 口径说明。",doc:"角色:长文档写作人。方法:先搭骨架再填肉,限定范围改稿,成稿前收口检查。交付:可用文档 + 修订说明。",slide:"角色:演示设计师。方法:先立叙事主线,一页一观点,页题写成观点句;产出可编辑成品(HTML/pptx)。交付:演示成品 + 讲者备注。",qa:"角色:质检员。方法:核对数字能否追溯口径、术语/编号一致、结论与正文不打架、格式规范。交付:问题清单 + 是否可交付判定。"},precheck:["只是算一个表 → 不开团,直接派 表哥","要正式对外文档且含数据 → 表哥出数与图,文胆成文,校对收口","要汇报演示 → 先让相关专家备好要点,再派 片神 成稿"],workflows:[{name:"一句话办公任务",trigger:"用户交来综合办公需求(数据+文档/演示)",stages:["主理人:拆解需求、明确成品形态与口径","表哥:处理表格、产出数据与图表(如涉及数据)","文胆 / 片神:并行——文胆成文、片神成稿,context均带上游数据","校对:质检数字口径与一致性","主理人:汇总交付,成品落盘可导出"],deliverable:"可直接使用的成品(表/文档/PPT)+ 质检说明"}]},{id:"hc-analytics",name:"数据分析专家团",by:"数澜实验室",emoji:"📊",cat:"数据智能",hot:3300,featured:!0,desc:"一份数据进:取数 → 分析 → 可视化 → 报告四环相扣,交付网页 / Excel / PPT三格式成果。",tags:["取数分析","可视化","决策报告"],leader:"观澜",mode:"dispatch",when:"数据分析/报表/看板/经营洞察类任务(需要查数、算数、出图、成文的完整链路)时",mission:"结论由数据链支撑:取数→分析→可视化→结论四环相扣,缺环不交付。事实与推断永远分开写。",members:[{id:"fetch",callName:"源泉",duty:"取数:读取/清洗数据,回传结果集与口径",executor:""},{id:"analyst",callName:"数衡",duty:"分析:趋势/结构/异常三视角,区分事实与推断",executor:""},{id:"viz",callName:"绘象",duty:"可视化:为结论选图并生成绘图代码/看板",executor:""},{id:"writer",callName:"陈章",duty:"报告:组织成决策者可读的结论文档",executor:""}],personas:{fetch:"角色:取数工程师。方法:先定口径(时间窗/过滤/去重/指标定义)再取数,用run_python精确读;异常主动核对口径。交付:数据 + 口径说明 + 异常标注。",analyst:"角色:数据分析师。方法:先描述后解释,趋势/结构/异常三视角;归因给证据链与置信度,相关不当因果。交付:结论列表(每条标[事实]/[推断])。",viz:"角色:可视化工程师。方法:每张图先写要传达的结论再选型,克制装饰;产出可运行代码或单页看板。交付:图表清单 + 代码/配置。",writer:"角色:数据报告撰写人。方法:结论先行,按发现→证据→建议组织,建议标注依据编号。交付:可 3 分钟读完摘要的报告。"},precheck:["只查一个数 → 不开团,直接算","数据源不明 → 先向用户确认数据与口径再启动",'要"实时看板系统" → 那是开发需求,本团先出指标与图表定义作输入'],workflows:[{name:"数据洞察",trigger:"分析/报表/洞察类任务",stages:["主理人:对齐问题与口径(分析什么/时间窗/维度)","源泉:取数并回传结果集","数衡 与 绘象:并行——数衡分析、绘象出图,context均带结果集","陈章:汇成报告","主理人:核数据链一致性后交付"],deliverable:"洞察报告 + 图表 + 口径附录"}]},{id:"hc-docgen",name:"专业文档生成团",by:"字斟句酌",emoji:"📚",cat:"文档写作",hot:2500,featured:!0,desc:"企业级长文档生成,四角色协作完成深度调研、大纲规划、内容撰写与合规审核全流程。",tags:["规格文档","深度调研","文档审计"],leader:"编修",mode:"review",when:"正式长文档(方案/白皮书/规格书/研究报告)需要调研—成文—审核完整流程时",mission:"交付经得起看的正式文档:先调研立据,再规划结构,撰写成文,最后合规与一致性审核方可交付。",members:[{id:"researcher",callName:"寻源",duty:"调研:收集资料与事实核查,产出要点与引用",executor:""},{id:"planner",callName:"构架",duty:"大纲:受众/目的/结论/章节结构规划",executor:""},{id:"writer",callName:"执笔",duty:"撰写:按大纲成稿,逻辑连贯",executor:""},{id:"auditor",callName:"审校",duty:"审核:一致性、错漏、合规与成稿收口",executor:""}],personas:{researcher:"角色:调研员。方法:多源采集、交叉验证、事实核查;关键论断给来源,不臆造。交付:要点清单 + 引用。",planner:"角色:结构规划师。方法:先明确受众/目的/结论,再定章节骨架与每章要点。交付:可执行大纲。",writer:"角色:长文撰写人。方法:按大纲填肉,章节连贯不堆砌,限定范围改。交付:完整草稿。",auditor:"角色:审校。方法:术语/编号一致、引用可核、结论与正文不打架、合规检查。交付:审校意见 + 是否可交付。"},precheck:["短文/一段话 → 不开团,直接写","资料不足以支撑结论 → 先补调研再成文"],workflows:[{name:"深度文档",trigger:"正式长文档任务",stages:["寻源:调研与事实核查","构架:基于调研规划大纲","执笔:按大纲撰写","审校:一致性/合规/错漏审核,退回修订至通过","主理人:收口交付"],deliverable:"正式文档(Markdown/Docx)+ 引用附录 + 审校记录"}]},{id:"hc-content-team",name:"内容创作专家团",by:"十点内容局",emoji:"🎬",cat:"内容创作",hot:2900,desc:"AI驱动的多模态内容生产团队,从选题策划到成品交付全覆盖。",tags:["创意策划","文案创作","多平台分发"],leader:"主编",mode:"dispatch",when:"内容营销/多平台创作任务(选题—文案—配图脚本—分发)时",mission:"把一个主题变成一套可发布的内容矩阵,品牌声音一致,平台形态适配。",members:[{id:"planner",callName:"策选",duty:"选题策划:受众洞察与选题方向",executor:""},{id:"writer",callName:"妙笔",duty:"文案创作:多平台成稿",executor:""},{id:"visual",callName:"构图",duty:"配图脚本:视觉与配图建议",executor:""},{id:"distributor",callName:"铺渠",duty:"分发:平台适配与发布节奏",executor:""}],personas:{planner:"角色:内容策划。方法:先定受众/平台/目标/调性,再给选题与角度。交付:选题清单 + 内容策略。",writer:"角色:多平台文案。方法:一个核心信息贯穿,标题抓人、开头留人、结尾促动作,按平台调形态。交付:成稿 + 标题备选。",visual:"角色:视觉策划。方法:为每篇内容给配图/画面脚本与风格建议。交付:配图脚本。",distributor:"角色:分发运营。方法:平台适配、话题标签、发布节奏与互动引导。交付:分发计划。"},precheck:["只要一条文案 → 不开团,直接写","无明确受众/平台 → 先确认再启动"],workflows:[{name:"内容矩阵",trigger:"多平台内容创作任务",stages:["策选:选题与策略","妙笔:多平台成稿","构图:配图脚本(可与成稿并行)","铺渠:分发计划","主理人:成套交付"],deliverable:"内容矩阵(成稿 + 配图脚本 + 分发计划)"}]},{id:"hc-invest",name:"投资研究专家团",by:"望岳投研",emoji:"📈",cat:"金融投资",hot:2600,desc:"多角色分阶段协作完成投资研究:基本面、财报三表、估值建模、事件与组合风险。",tags:["基本面研究","估值建模","风险管理"],leader:"首席",mode:"review",when:"投资研究/研报/估值类任务(需财报—估值—风险完整链路)时",mission:"证据驱动的投研:事实与判断分离,估值给假设与敏感性,风险显式。全程声明不构成投资建议。",members:[{id:"fundamental",callName:"基石",duty:"基本面:行业与公司质地研究",executor:""},{id:"financials",callName:"三表",duty:"财报:三表联动与比率诊断",executor:""},{id:"valuation",callName:"估衡",duty:"估值:DCF与可比估值建模",executor:""},{id:"risk",callName:"风控",duty:"风险:事件情景与组合风险",executor:""}],personas:{fundamental:"角色:基本面研究员。方法:行业格局、商业模式、竞争壁垒、成长驱动,事实给来源。交付:基本面研究。",financials:"角色:财务分析师。方法:三表联动看健康度,比率横纵对比,区分口径与实质,用run_python算。交付:财务诊断 + 比率表。",valuation:"角色:估值分析师。方法:DCF与可比法并用,关键假设与敏感性显式。交付:估值模型 + 假设说明。",risk:"角色:风险分析师。方法:事件情景、下行风险、组合层面暴露。交付:风险清单 + 情景分析。"},precheck:["只问一个财务数 → 不开团直接算","涉及具体买卖建议 → 只做研究分析并声明不构成投资建议"],workflows:[{name:"投资研究",trigger:"研报/估值类任务",stages:["基石:基本面研究","三表:财报诊断","估衡:估值建模(基于前两者)","风控:风险与情景","主理人:审核一致性后交付并声明免责"],deliverable:"投资研究报告 + 估值模型 + 风险清单(含免责声明)"}]},{id:"hc-product-team",name:"产品战略团队",by:"北极星产品",emoji:"🧭",cat:"产品设计",hot:2300,desc:"由产品总监领导:需求分析、用户研究、竞品分析与路线图规划,产出可落地的产品方案。",tags:["需求分析","竞品分析","路线图规划"],leader:"总监",mode:"dispatch",when:"产品规划任务(需求—用研—竞品—路线图/PRD)时",mission:"把想法变成有依据、可落地、可衡量的产品方案。每个决策都说清「解决谁的什么问题、如何衡量」。",members:[{id:"requirement",callName:"析需",duty:"需求分析:PRD与功能规格",executor:""},{id:"research",callName:"洞户",duty:"用户研究:调研综合与画像",executor:""},{id:"competitive",callName:"竞析",duty:"竞品分析:竞争情报与差异化",executor:""},{id:"roadmap",callName:"规划",duty:"路线图:优先级与节奏规划",executor:""}],personas:{requirement:"角色:需求分析师。方法:先澄清问题与目标用户,功能写清价值与衡量。交付:PRD/功能规格。",research:"角色:用户研究员。方法:调研综合、画像、关键旅程与痛点。交付:用研综合报告。",competitive:"角色:竞品分析师。方法:竞争格局、功能矩阵、差异化机会。交付:竞品矩阵。",roadmap:"角色:路线图规划。方法:按价值/成本/依赖排优先级与节奏。交付:路线图。"},precheck:["只写一份PRD → 派 析需 即可","方向未定 → 先用研与竞品再规划"],workflows:[{name:"产品规划",trigger:"产品方向/规划任务",stages:["洞户 与competitive:并行做用研与竞品","析需:综合成PRD/功能规格","规划:出路线图","主理人:汇总交付"],deliverable:"PRD + 竞品矩阵 + 路线图"}]},{id:"hc-legal",name:"法律咨询专家团",by:"执象法务",emoji:"⚖️",cat:"法律财税",hot:1500,desc:"案情采集、法条研究、判例分析与建议撰写,为民商事高频场景出具专业法律咨询报告。",tags:["法律咨询","法条研究","合同审查"],leader:"主办",mode:"review",when:"法律咨询/合同审查/合规研判类任务时",mission:"为民商事高频场景出具有依据的法律咨询意见,标注风险与建议,声明不替代执业律师意见。",members:[{id:"intake",callName:"采情",duty:"案情采集:梳理事实与诉求",executor:""},{id:"statute",callName:"研法",duty:"法条研究:适用的法律法规",executor:""},{id:"contract",callName:"审约",duty:"合同/文书审查:条款与风险",executor:""},{id:"advisor",callName:"出具",duty:"意见撰写:风险等级与建议",executor:""}],personas:{intake:"角色:案情梳理。方法:厘清事实、时间线、双方诉求与争点。交付:案情摘要。",statute:"角色:法条研究员。方法:定位适用法律法规与一般原则,标注不确定处。交付:法条与要点。",contract:"角色:合同审查员。方法:权利义务/违约救济/风险分配/合规四维审查,标风险等级。交付:审查意见。",advisor:"角色:法律意见撰写。方法:结论 + 依据 + 建议 + 风险提示;声明不替代执业律师意见。交付:咨询报告。"},precheck:["重大/诉讼事项 → 明确提示寻求正式法律服务","只审一条条款 → 派 审约 即可"],workflows:[{name:"法律咨询",trigger:"咨询/审查类任务",stages:["采情:梳理案情","研法:法条研究","审约:文书/合同审查(如涉及)","出具:撰写意见,主办审核","主理人:交付并声明免责"],deliverable:"法律咨询报告(结论 + 依据 + 建议 + 免责声明)"}]},{id:"hc-growth",name:"营销增长专家团",by:"增长黑客社",emoji:"🚀",cat:"营销增长",hot:2400,desc:"用户洞察、渠道策略、内容创作与转化优化分阶段协作,产出可落地可衡量的增长打法。",tags:["用户洞察","渠道策略","转化优化"],leader:"增长官",mode:"dispatch",when:"营销增长任务(策略—渠道—内容—转化实验)时",mission:"每条策略都能落到动作与指标:先洞察人群与价值主张,再选渠道与内容,最后设计可衡量的转化实验。",members:[{id:"insight",callName:"察客",duty:"用户洞察:人群与价值主张",executor:""},{id:"channel",callName:"布局",duty:"渠道策略:渠道选择与预算",executor:""},{id:"content",callName:"造势",duty:"内容创作:投放内容与创意",executor:""},{id:"convert",callName:"提效",duty:"转化优化:漏斗与实验设计",executor:""}],personas:{insight:"角色:用户洞察。方法:目标人群、需求场景、价值主张与决策路径。交付:人群洞察。",channel:"角色:渠道策略。方法:按人群选渠道、分配预算、定节奏。交付:渠道计划。",content:"角色:投放内容。方法:契合渠道与人群的创意与文案。交付:内容与创意。",convert:"角色:转化优化。方法:漏斗诊断、A/B实验设计、关键指标。交付:转化实验方案。"},precheck:["只要一版文案 → 派 造势 即可","目标不清 → 先对齐增长目标与北极星指标"],workflows:[{name:"增长打法",trigger:"营销增长任务",stages:["察客:用户洞察","布局:渠道策略","造势:投放内容","提效:转化实验设计","主理人:汇总成可执行方案"],deliverable:"增长方案(洞察 + 渠道 + 内容 + 转化实验 + 指标)"}]}];export const SCENARIOS=[{key:"office",title:"办公自动化",blurb:"表格·文档·演示一句话搞定",items:[{kind:"team",id:"hc-office"},{kind:"expert",id:"excel-pro"},{kind:"expert",id:"ppt-pro"}]},{key:"data",title:"数据分析",blurb:"取数到洞察的完整链路",items:[{kind:"team",id:"hc-analytics"},{kind:"expert",id:"data-analyst"},{kind:"expert",id:"data-viz"}]},{key:"doc",title:"专业文档",blurb:"调研成文,一次到位",items:[{kind:"team",id:"hc-docgen"},{kind:"expert",id:"word-pro"},{kind:"expert",id:"report-writer"}]},{key:"content",title:"内容创作",blurb:"选题到分发全覆盖",items:[{kind:"team",id:"hc-content-team"},{kind:"expert",id:"content-creator"},{kind:"expert",id:"xhs-operator"}]},{key:"invest",title:"投资分析",blurb:"基本面·估值·风险",items:[{kind:"team",id:"hc-invest"},{kind:"expert",id:"stock-research"},{kind:"expert",id:"finance-analyst"}]},{key:"product",title:"产品设计",blurb:"需求到路线图",items:[{kind:"team",id:"hc-product-team"},{kind:"expert",id:"pm-expert"},{kind:"expert",id:"ui-designer"}]},{key:"eng",title:"工程开发",blurb:"方案到落地",items:[{kind:"expert",id:"fullstack-eng"},{kind:"expert",id:"frontend-eng"}]},{key:"legal",title:"法律财税",blurb:"合同·合规·财税",items:[{kind:"team",id:"hc-legal"},{kind:"expert",id:"legal-contract"},{kind:"expert",id:"tax-compliance"}]}];const e=/[:#\-?[\]{},&*!|>'"%@`\n]/;function t(t){const a=String(t??"").trim();return e.test(a)?`"${a.replace(/"/g,'\\"')}"`:a}export function agentToMarkdown(e){const a=e.desc||"";return`---\nname: ${t(e.name)}\ndescription: ${t(a)}\n---\n\n${(e.prompt||"").trim()}\n`}export function teamToMarkdown(e){const a=[];a.push("---"),a.push(`name: ${e.id}`),a.push(`description: ${t(`${e.name}——${e.desc||""}`)}`),a.push(`leader: ${t(e.leader||"主理人")}`),a.push(`mode: ${["dispatch","review","ensemble"].includes(e.mode)?e.mode:"dispatch"}`),e.when&&a.push(`when: ${t(e.when)}`),a.push("---"),a.push(""),e.mission&&(a.push(e.mission.trim()),a.push("")),a.push("## 成员");for(const t of e.members||[]){const e=[t.id,t.callName||t.id,t.duty||"",t.executor||""];for(;e.length>2&&!String(e[e.length-1]).trim();)e.pop();a.push(`- ${e.map(e=>String(e).trim()).join(" | ")}`)}a.push("");const i=e.personas||{},r=Object.keys(i);if(r.length){a.push("## 成员人设");for(const e of r)a.push(`### ${e}`),a.push(String(i[e]).trim()),a.push("")}if((e.precheck||[]).length){a.push("## 预检");for(const t of e.precheck)a.push(`- ${t}`);a.push("")}if((e.workflows||[]).length){a.push("## 工作流");for(const t of e.workflows)a.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),(t.stages||[]).forEach((e,t)=>a.push(`${t+1}. ${e}`)),t.deliverable&&a.push(`交付:${t.deliverable}`),a.push("")}return a.join("\n").replace(/\n{3,}/g,"\n\n").trim()+"\n"}export function teamTitle(e){return String(e?.description||"").split(/——|—|\|/)[0].trim()||e?.name||"专家团"}export function teamBlurb(e){const t=String(e?.description||""),a=t.search(/——|—/);return a>=0?t.slice(a).replace(/^[———]+/,"").trim():t}export const BUILTIN_META={"hc-content":{emoji:"🎬",cat:"内容创作"},"hc-data":{emoji:"📊",cat:"数据智能"},"hc-debug":{emoji:"🐛",cat:"技术工程"},"hc-defense":{emoji:"🛡️",cat:"技术工程"},"hc-fullstack":{emoji:"🧑💻",cat:"技术工程"},"hc-product":{emoji:"🧭",cat:"产品设计"},"hc-rdarchive":{emoji:"🗃️",cat:"办公自动化"},"hc-redteam":{emoji:"⚔️",cat:"技术工程"},"hc-refactor":{emoji:"🔧",cat:"技术工程"},"hc-release":{emoji:"🚀",cat:"技术工程"},"hc-research":{emoji:"🔬",cat:"教育科研"},"hc-review":{emoji:"✅",cat:"技术工程"},"hc-test":{emoji:"🧪",cat:"技术工程"},"hc-weekly":{emoji:"🗓️",cat:"办公自动化"}};export const EXPERT_BY_ID=Object.fromEntries(EXPERTS.map(e=>[e.id,e]));export const TEAM_BY_ID=Object.fromEntries(TEAMS.map(e=>[e.id,e]));
|
package/src/experts-store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:fs";import r from"node:path";import e from"node:os";import{parseFrontmatter as n}from"./skills.js";import{loadSquads as i}from"./squad.js";import{discoverExperts as
|
|
1
|
+
import t from"node:fs";import r from"node:path";import e from"node:os";import{parseFrontmatter as n}from"./skills.js";import{loadSquads as i,countUserSquads as o}from"./squad.js";import{discoverExperts as s}from"./experts.js";import{loadConfig as c}from"./config.js";import{assertLimit as m}from"./plan-limits.js";const a=()=>r.join(e.homedir(),".huicai","agents"),u=()=>r.join(e.homedir(),".huicai","squads"),d=/^[\w一-龥-]{1,64}$/;function p(t){const r=String(t||"").trim();if(!d.test(r))throw new Error("非法名称:只能包含中文、字母、数字、下划线与连字符");return r}export function countUserAgents(){try{return t.readdirSync(a()).filter(t=>t.endsWith(".md")).length}catch{return 0}}function l(){const e=a();let i=[];try{i=t.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}const o=[];for(const s of i){const i=s.replace(/\.md$/,"").trim();if(i)try{const c=t.readFileSync(r.join(e,s),"utf8"),m=n(c);o.push({slug:i,name:(m.name||i).trim(),description:m.description||""})}catch{}}return o}function f(){let t=[];try{t=i(null)||[]}catch{t=[]}return t.map(t=>({slug:t.file?r.basename(t.file).replace(/\.md$/,""):t.name,name:t.name,description:t.description||"",leader:t.leader||"",mode:t.mode||"dispatch",when:t.when||"",memberCount:(t.members||[]).length,builtin:"builtin"===t.scope}))}export function listMyExperts(){return{agents:l(),squads:f()}}export function readExpert(e,n){const i=p(n),o=r.join("squad"===e?u():a(),`${i}.md`);let s="";try{s=t.readFileSync(o,"utf8")}catch{throw new Error("未找到该定义(可能是内置项,编辑将另存为可写副本)")}return{kind:e,slug:i,content:s}}export function saveAgent({slug:e,content:i,overwrite:o=!0}={}){const s=p(e),c=String(i||""),u=n(c);if(!(u.name||"").trim())throw new Error("定义缺少name(frontmatter)");if(!(u.body||"").trim())throw new Error("专家的系统提示词正文不能为空");const d=r.join(a(),`${s}.md`);if(!o&&t.existsSync(d))return{status:"skipped",slug:s,name:(u.name||s).trim(),...listMyExperts()};const l=t.existsSync(d);return l||m("agents",countUserAgents()),t.mkdirSync(a(),{recursive:!0}),t.writeFileSync(d,c),{status:l?"overwritten":"created",slug:s,name:(u.name||s).trim(),...listMyExperts()}}export function saveSquad({slug:e,content:i,overwrite:s=!0}={}){const c=p(e),a=String(i||""),d=n(a);if(!(d.name||"").trim())throw new Error("定义缺少name(frontmatter)");const l=r.join(u(),`${c}.md`);if(!s&&t.existsSync(l))return{status:"skipped",slug:c,name:(d.name||c).trim(),...listMyExperts()};const f=t.existsSync(l);return f||m("squads",o()),t.mkdirSync(u(),{recursive:!0}),t.writeFileSync(l,a),{status:f?"overwritten":"created",slug:c,name:(d.name||c).trim(),...listMyExperts()}}export function removeAgent(e){const n=p(e),i=r.join(a(),`${n}.md`);if(!t.existsSync(i))throw new Error("该专家不存在或不可删除");return t.rmSync(i),listMyExperts()}export function removeSquad(e){const n=p(e),i=r.join(u(),`${n}.md`);if(!t.existsSync(i))throw new Error("内置专家团不可删除;可召唤后另存为副本再改");return t.rmSync(i),listMyExperts()}export async function platformExperts({force:t=!1,config:r}={}){try{const e=r||c();return e&&e.baseUrl&&e.apiKey?await s({baseUrl:e.baseUrl,apiKey:e.apiKey},{force:t}):[]}catch{return[]}}
|
package/src/experts.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:path";import n from"node:os";import r from"node:crypto";import{listModels as i,streamChat as a}from"./api.js";const s=6e5,o=/^(agent|workflow|datahuman|squad)-/,c={squad:0,workflow:1,agent:2,datahuman:3},p={squad:"专家团",workflow:"工作流",agent:"智能体",datahuman:"数字人"};function u(e){if(e.hc_type&&p[e.hc_type])return e.hc_type;const t=/^(agent|workflow|datahuman|squad)-/.exec(String(e.id||""));return t?t[1]:"agent"}export function expertsCachePath(e){const i=r.createHash("sha1").update(String(e.baseUrl||"")+"|"+String(e.apiKey||"").slice(-6)).digest("hex").slice(0,12);return t.join(n.homedir(),".huicai","cache",`experts-${i}.json`)}function l(t){try{const n=JSON.parse(e.readFileSync(t,"utf8"));if(Array.isArray(n?.experts))return{fetchedAt:n.fetchedAt||0,experts:n.experts}}catch{}return null}function y(n,r){try{e.mkdirSync(t.dirname(n),{recursive:!0}),e.writeFileSync(n,JSON.stringify({fetchedAt:Date.now(),experts:r}))}catch{}}async function d(e){return(await i(e.baseUrl,e.apiKey)).filter(e=>o.test(String(e.id||""))).map(e=>({id:e.id,name:e.display_name||e.id,description:String(e.description||"").replace(/\s+/g," ").trim(),type:u(e)})).sort((e,t)=>(c[e.type]??9)-(c[t.type]??9)||String(e.name).localeCompare(t.name,"zh"))}const m=new Set;export async function discoverExperts(e,{force:t=!1}={}){if(!e||!e.baseUrl||!e.apiKey)return[];const n=expertsCachePath(e),r=t?null:l(n);if(r)return Date.now()-r.fetchedAt>6e5&&function(e,t){m.has(t)||(m.add(t),d(e).then(e=>y(t,e)).catch(()=>{}).finally(()=>m.delete(t)))}(e,n),r.experts;try{const r=await(i=d(e),a=t?15e3:3500,new Promise((e,t)=>{const n=setTimeout(()=>t(new Error(`发现超时(${a}ms)`)),a);i.then(t=>{clearTimeout(n),e(t)},e=>{clearTimeout(n),t(e)})}));return y(n,r),r}catch{const e=l(n);return e?e.experts:[]}var i,a}export async function makeExpertTools({config:e}){if(!e||!e.baseUrl||!e.apiKey)return[];if(!1===e.autoExperts)return[];if(o.test(String(e.model||"")))return[];const t=await discoverExperts(e);if(!t.length)return[];const n=new Map(t.map(e=>[e.id,e])),r=function(e){const t=e.slice(0,24),n=t.map(e=>`${e.id} — [${p[e.type]||e.type}]${e.name}${e.description?":"+e.description.slice(0,80):""}`);return e.length>t.length&&n.push(`…另有 ${e.length-t.length} 个(/experts
|
|
1
|
+
import e from"node:fs";import t from"node:path";import n from"node:os";import r from"node:crypto";import{listModels as i,streamChat as a}from"./api.js";const s=6e5,o=/^(agent|workflow|datahuman|squad)-/,c={squad:0,workflow:1,agent:2,datahuman:3},p={squad:"专家团",workflow:"工作流",agent:"智能体",datahuman:"数字人"};function u(e){if(e.hc_type&&p[e.hc_type])return e.hc_type;const t=/^(agent|workflow|datahuman|squad)-/.exec(String(e.id||""));return t?t[1]:"agent"}export function expertsCachePath(e){const i=r.createHash("sha1").update(String(e.baseUrl||"")+"|"+String(e.apiKey||"").slice(-6)).digest("hex").slice(0,12);return t.join(n.homedir(),".huicai","cache",`experts-${i}.json`)}function l(t){try{const n=JSON.parse(e.readFileSync(t,"utf8"));if(Array.isArray(n?.experts))return{fetchedAt:n.fetchedAt||0,experts:n.experts}}catch{}return null}function y(n,r){try{e.mkdirSync(t.dirname(n),{recursive:!0}),e.writeFileSync(n,JSON.stringify({fetchedAt:Date.now(),experts:r}))}catch{}}async function d(e){return(await i(e.baseUrl,e.apiKey)).filter(e=>o.test(String(e.id||""))).map(e=>({id:e.id,name:e.display_name||e.id,description:String(e.description||"").replace(/\s+/g," ").trim(),type:u(e)})).sort((e,t)=>(c[e.type]??9)-(c[t.type]??9)||String(e.name).localeCompare(t.name,"zh"))}const m=new Set;export async function discoverExperts(e,{force:t=!1}={}){if(!e||!e.baseUrl||!e.apiKey)return[];const n=expertsCachePath(e),r=t?null:l(n);if(r)return Date.now()-r.fetchedAt>6e5&&function(e,t){m.has(t)||(m.add(t),d(e).then(e=>y(t,e)).catch(()=>{}).finally(()=>m.delete(t)))}(e,n),r.experts;try{const r=await(i=d(e),a=t?15e3:3500,new Promise((e,t)=>{const n=setTimeout(()=>t(new Error(`发现超时(${a}ms)`)),a);i.then(t=>{clearTimeout(n),e(t)},e=>{clearTimeout(n),t(e)})}));return y(n,r),r}catch{const e=l(n);return e?e.experts:[]}var i,a}export async function makeExpertTools({config:e}){if(!e||!e.baseUrl||!e.apiKey)return[];if(!1===e.autoExperts)return[];if(o.test(String(e.model||"")))return[];const t=await discoverExperts(e);if(!t.length)return[];const n=new Map(t.map(e=>[e.id,e])),r=function(e){const t=e.slice(0,24),n=t.map(e=>`${e.id} — [${p[e.type]||e.type}]${e.name}${e.description?":"+e.description.slice(0,80):""}`);return e.length>t.length&&n.push(`…另有 ${e.length-t.length} 个(/experts查看全部)`),n.join("\n")}(t),i={fails:0,openUntil:0},c=()=>Date.now()<i.openUntil?"平台专家暂时不可用(连续失败已熔断,稍后自动恢复)。请先用本地工具与自身知识完成任务,稍后可重试咨询。":null,u=async(r,o,c)=>{const u=String(r||"").trim(),l=n.get(u)||t.find(e=>e.name===u);if(!l)return`未找到平台专家「${u}」,请从目录中选择(/experts可查看全部)`;if(!String(o||"").trim())return"缺少query(要咨询/交办的自然语言内容)";try{const t=await async function(e,t,n,r){const i=r&&String(r).trim()?`${n}\n\n【背景/上游材料】\n${r}`:String(n);return(await a({baseUrl:e.baseUrl,apiKey:e.apiKey,model:t,messages:[{role:"user",content:i}],client:e.client,sessionId:e.sessionId},{},AbortSignal.timeout(s))).content||"(平台无返回)"}(e,l.id,o,c);return i.fails=0,`平台${p[l.type]||""}「${l.name}」的答复:\n${t}`}catch(e){const t=e&&"AbortError"===e.name?`超过 ${Math.round(10)} 分钟未完成,已中止`:e.message;return i.fails++,i.fails>=3&&(i.openUntil=Date.now()+3e4),`咨询「${l.name}」失败:${t}${i.openUntil>Date.now()?"(平台已熔断,请先用本地能力完成)":""}`}};return[{readOnly:!0,spec:{type:"function",function:{name:"consult_expert",description:"咨询/交办给一个平台专家(当前账号可用的专家团/工作流/智能体/数字人,服务端编排:知识库RAG/工具/多智能体协作/NL→SQL)。遇到专家擅长领域的问题优先交给它产出权威结论,再用本机工具把结论落地到项目。可多次调用(多个独立咨询会自动并行)。\n可用专家目录:\n"+r,parameters:{type:"object",properties:{expert:{type:"string",description:"专家id(目录第一列)",...t.length<=24?{enum:t.map(e=>e.id)}:{}},query:{type:"string",description:"自然语言的咨询/任务输入(专家看不到本对话,需自足)"},context:{type:"string",description:"可选:完成该咨询所需的背景/上游材料"}},required:["expert","query"]}}},run:async e=>c()||u(e?.expert,e?.query,e?.context)},{readOnly:!0,spec:{type:"function",function:{name:"consult_experts",description:"一次性把多个**独立**的咨询/子任务并行交给多个平台专家(同一专家也可多份),聚合各自答复。适合跨领域任务:如「专家团出方案 + 数字人查数据 + 智能体审内容」同时推进,显著减少串行等待。",parameters:{type:"object",properties:{tasks:{type:"array",description:"并行咨询数组(建议 2-6 个,上限 8)",items:{type:"object",properties:{expert:{type:"string",description:"专家id(见consult_expert目录)"},query:{type:"string",description:"该专家的自足任务/咨询"},context:{type:"string",description:"可选背景"}},required:["expert","query"]}}},required:["tasks"]}}},run:async e=>{const t=c();if(t)return t;const n=Array.isArray(e?.tasks)?e.tasks.slice(0,8):[];if(!n.length)return"未提供tasks(需要一个咨询数组)";return(await async function(e,t,n){const r=new Array(e.length);let i=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;i<e.length;){const t=i++;r[t]=await n(e[t],t)}})),r}(n,4,async e=>({expert:e?.expert,out:await u(e?.expert,e?.query,e?.context)}))).map((e,t)=>`【咨询 ${t+1} · ${e.expert}】\n${e.out}`).join("\n\n---\n\n")}}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as e,execFileSync as t}from"node:child_process";import{StringDecoder as n}from"node:string_decoder";const r=6e5,o=[{id:"claude-code",label:"Claude Code",bin:"claude",argv:e=>["-p",e]},{id:"codex",label:"Codex",bin:"codex",argv:e=>["exec",e]}];let i;export function detectExternalAgents(){return i||(i=o.filter(e=>function(e){try{return t("win32"===process.platform?"where":"which",[e],{stdio:"ignore"}),!0}catch{return!1}}(e.bin))),i}export function __setExternalAgentsForTest(e){i=e}export function runExternalAgent(t,r,{cwd:o,timeoutMs:i=6e5,signal:c}={}){return new Promise(a=>{const s="win32"===process.platform;let l;try{l=e(t.bin,t.argv(r),{cwd:o,env:process.env,detached:!s,stdio:["ignore","pipe","pipe"]})}catch(e){return void a({ok:!1,text:`无法启动 ${t.label}:${e.message}`,code:null,timedOut:!1})}let d="",u="",m=!1;const p=(e,t)=>{d.length+u.length>=2e5?m=!0:t?u+=e:d+=e},g=new n("utf8"),x=new n("utf8");l.stdout.on("data",e=>p(g.write(e),!1)),l.stderr.on("data",e=>p(x.write(e),!0));let b=!1,f=!1,$=null;const w=e=>{try{!s&&l.pid?process.kill(-l.pid,e):l.kill(e)}catch{try{l.kill("SIGKILL")}catch{}}},h=()=>{w(s?"SIGKILL":"SIGTERM"),s||($=setTimeout(()=>{f||w("SIGKILL")},1500),$.unref?.())},v=setTimeout(()=>{b=!0,h()},i),y=()=>h();c?.addEventListener?.("abort",y,{once:!0}),l.on("close",e=>{if(f)return;f=!0,clearTimeout(v),$&&clearTimeout($),c?.removeEventListener?.("abort",y);const t=(d.trim()||u.trim())+(m?"\n…(输出过长已截断)":"");a(b?{ok:!1,timedOut:!0,code:e,text:t||"(无输出)"}:{ok:0===e,timedOut:!1,code:e,text:t||"(无输出)"})}),l.on("error",e=>{f||(f=!0,clearTimeout(v),$&&clearTimeout($),a({ok:!1,text:`启动 ${t.label} 失败:${e.message}`,code:null,timedOut:!1}))})})}export function makeExternalAgentTools({config:e,cwd:t,onSub:n=null}={}){const o=detectExternalAgents();if(!o.length)return[];const i=o.map(e=>e.id);return[{readOnly:!1,spec:{type:"function",function:{name:"dispatch_external_agent",description:"把一个自足的任务委派给用户机器上装着的**另一家智能体**(当前可用:"+o.map(e=>`${e.id}(${e.label})`).join("、")+")。它在同一个工作目录里独立完成后只回传最终答复,不做多轮往返。适合「用另一个模型/工具链交叉验证同一个结论」或「那家更擅长的特定活儿」。注意:它会真的在本机读写文件、执行命令,因此每次调用都要经用户批准;需要来回沟通的长任务请改用start_agent(惠彩自己的可续子智能体)。",parameters:{type:"object",properties:{agent:{type:"string",enum:i,description:"委派给哪个外部智能体"},objective:{type:"string",description:"单一、明确、**自足**的任务描述(对方看不到本次对话的任何上下文)"},context:{type:"string",description:"可选:完成该任务所需的背景信息,会附在任务描述之后"},timeout_ms:{type:"number",description:"可选:墙钟上限,默认 10 分钟"}},required:["agent","objective"]}}},run:async(c,a={})=>{const s=o.find(e=>e.id===String(c?.agent||"").trim());if(!s)return`未知或本机不可用的外部智能体:${c?.agent}(可用:${i.join(", ")})`;const l=String(c?.objective||"").trim();if(!l)return"缺少objective(要它做什么)";const d=c?.context?`${l}\n\n背景信息:\n${String(c.context)}`:l,u=Number(c?.timeout_ms)>0?Math.min(Number(c.timeout_ms),18e5):r;n?.({phase:"start",agent:s.id,label:l.slice(0,60)});const m=Date.now(),p=await runExternalAgent(s,d,{cwd:e?.cwd||t,timeoutMs:u,signal:a.signal}),g=((Date.now()-m)/1e3).toFixed(1);return n?.({phase:"end",agent:s.id,ok:p.ok}),p.timedOut?`${s.label} 超时(${g}s未完成,进程树已终止)。它可能在等交互输入,或任务太大。\n已收集到的输出:\n${p.text}\n(若这里是空的:被终止的进程未能刷出它缓冲的输出,这是进程被杀的固有代价,不代表它什么都没做。)`:p.ok?`${s.label} 的答复(${g}s):\n${p.text}`:`${s.label} 以退出码 ${p.code} 结束(${g}s)。输出:\n${p.text}\n(若是未登录/未授权,请让用户先在终端里单独跑一次该CLI完成登录。)`}}]}
|
package/src/forks.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as
|
|
1
|
+
import{spawn as e}from"node:child_process";import*as o from"./jobs.js";export function huicaiBin(){const e=process.argv[1]||"";return/huicai/.test(e)?e:null}export function forkAgent({prompt:t,cwd:r,sessionId:s,ownerSessionId:n}){const i=huicaiBin();if(!i)throw new Error("无法定位huicai可执行文件(请在CLI中使用 /fork)");const d=[i];s&&d.push("resume",s),d.push("-p",t,"--yolo","--plain");const c=e(process.execPath,d,{cwd:r,detached:!0,stdio:["ignore","pipe","pipe"],env:process.env}),p=o.start({kind:"agent",label:String(t).replace(/\s+/g," ").slice(0,120),sessionId:n||"",cwd:r,kill:()=>{try{process.kill(-c.pid,"SIGTERM")}catch{try{c.kill("SIGTERM")}catch{}}},meta:{pid:c.pid,resumedFrom:s||""}});return c.stdout?.on("data",e=>p.append(e.toString("utf8"))),c.stderr?.on("data",e=>p.append(e.toString("utf8"))),c.on("error",e=>{p.append(`\n派生失败:${e.message}\n`),p.settle("failed","派生失败")}),c.on("close",(e,o)=>{o?p.settle("killed",`被信号 ${o} 终止`):p.settle(0===e?"done":"failed",`退出码 ${e}`)}),c.unref(),{id:p.id,pid:c.pid,log:p.record.log,prompt:p.record.label,cwd:r,started:p.record.startedAt}}export function listForks(e){return o.list(e||"")}export function killFork(e,t){const r=o.kill(e,{sessionId:t||"",reason:"用户经 /tasks终止"});return r.ok?{ok:!0}:{ok:!1,reason:r.reason}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";const n=new Map,r=new Set(["read_file","read_document","read_any"]),i=new Set(["write_file"]),s=new Set(["write_file","edit_file"]);function o(e){const t=String(e||"");return n.has(t)||n.set(t,new Map),n.get(t)}function l(t){try{const n=e.statSync(t);return n.isFile()?{size:n.size,mtimeMs:n.mtimeMs}:null}catch{return null}}export function noteObserved(e,t){const n=l(t);n&&o(e).set(t,n)}export function observationOf(e,t){return o(e).get(t)||null}export function dropSessionObservations(e){n.delete(String(e||""))}export function resetObservations(){n.clear()}export function guardOverwrite({sessionId:e,cwd:n,name:r,args:s}){if(!i.has(r))return null;const o=s?.path;if(!o)return null;const f=t.resolve(n||process.cwd(),String(o)),a=l(f);if(!a)return null;if(0===a.size)return null;const u=observationOf(e,f);return u?u.size!==a.size||u.mtimeMs!==a.mtimeMs?`文件在你读过之后被改动过,整篇覆盖会盖掉那次改动:${o}。(读到时 ${u.size} 字节,现在 ${a.size} 字节)可能是用户在编辑器里改了、或某条命令写过它。请先重新read_file("${o}") 确认现状再决定怎么写。`:null:`未读先覆盖被拦下:${o} 已存在(${a.size} 字节),而本次对话里你还没读过它。write_file是整文件覆盖,原文中你不知道的部分会一并消失。请先read_file("${o}") 看清现有内容;只想改其中一处就用edit_file,确要整篇重写再调write_file。`}export function noteAfterCall({sessionId:e,cwd:n,name:i,args:o,failed:l}){if(l)return;if(!r.has(i)&&!s.has(i))return;const f=o?.path;f&¬eObserved(e,t.resolve(n||process.cwd(),String(f)))}
|
package/src/gitflow.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:fs";import e from"node:os";import r from"node:path";import{execSync as n}from"node:child_process";import{
|
|
1
|
+
import t from"node:fs";import e from"node:os";import r from"node:path";import{execSync as n}from"node:child_process";import{detect as o}from"./scopecreep.js";import{streamChat as i}from"./api.js";import{DEFAULT_MODEL as c}from"./config.js";const a=/^(agent|workflow|datahuman|squad)-/,s=new Set(["main","master","develop","dev"]);function m(t,e){return n("git "+e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function p(t,e){try{return m(t,e)}catch{return""}}export function gitInfo(t){const e={isRepo:!1,branch:"",dirty:!1,hasRemote:!1,hasGh:!1};try{e.isRepo="true"===m(t,"rev-parse --is-inside-work-tree").trim()}catch{return e}e.branch=p(t,"rev-parse --abbrev-ref HEAD").trim(),e.dirty=p(t,"status --porcelain").trim().length>0,e.hasRemote=p(t,"remote").trim().length>0;try{n("gh --version",{stdio:"ignore"}),e.hasGh=!0}catch{}return e}export async function generateCommitMessage(t,e){let r=p(t,"diff --staged");if(r.trim()||(r=p(t,"diff")),r=r.slice(0,12e3),!r.trim())return"chore: update";const n=a.test(String(e.model||""))?e.compactModel||c:e.model;try{const t=await i({baseUrl:e.baseUrl,apiKey:e.apiKey,model:n,client:e.client,aux:!0,messages:[{role:"system",content:"你是提交信息生成器。根据git diff生成一条规范的Conventional Commits提交信息:第一行 `<type>(<scope>): <简述>`(简述≤50字,type用feat/fix/refactor/docs/test/chore/perf/style之一);改动较大可空行后加 2-4 条正文要点。只输出提交信息本身,不要解释、不要代码块围栏。"},{role:"user",content:r}]});return(t.content||"").trim().replace(/^```[a-z]*\n?|\n?```$/g,"").trim()||"chore: update"}catch{return"chore: update"}}export async function runPrFlow({cwd:i,config:c,emit:a=()=>{},push:h=!0,openPr:d=!0}){const f=gitInfo(i);if(!f.isRepo)return{ok:!1,error:"当前目录不是git仓库"};const u=s.has(f.branch);f.dirty&&(m(i,"add -A"),a("已暂存全部改动"));const g=p(i,"diff --staged --stat").trim();let l="";g&&(a("正在生成提交信息…"),l=await generateCommitMessage(i,c),a("提交信息:"+l.split("\n")[0]));try{const t=o(p(i,"diff --staged")||p(i,"diff"),{intent:String(c.goal||"")});for(const e of t.findings||[])a("范围提示:"+e.what)}catch{}let y=f.branch;u&&(y="huicai/"+function(t){return String(t).split("\n")[0].replace(/^[a-z]+(\([^)]*\))?!?:\s*/i,"").replace(/[^a-zA-Z0-9一-鿿]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase().slice(0,32)||"change"}(l||"change")+"-"+Date.now().toString(36).slice(-4),m(i,`checkout -b ${y}`),a("已从 "+f.branch+" 创建分支 "+y));let b=!1;if(g){const n=r.join(e.tmpdir(),"huicai-commit-"+Date.now()+".txt");t.writeFileSync(n,l);try{m(i,`commit -F "${n}"`),b=!0,a("已提交")}finally{try{t.unlinkSync(n)}catch{}}}else a("无新暂存改动,跳过提交");if(!h)return{ok:!0,branch:y,committed:b};if(!f.hasRemote)return{ok:!0,branch:y,committed:b,note:"无remote,未推送"};try{m(i,`push -u origin ${y}`),a("已推送origin/"+y)}catch(t){return{ok:!1,error:"push失败:"+String(t.message).split("\n")[0],branch:y}}if(!d)return{ok:!0,branch:y,committed:b,pushed:!0};if(!f.hasGh)return{ok:!0,branch:y,committed:b,pushed:!0,note:"未装gh CLI,PR请手动开"};try{const t=n("gh pr create --fill",{cwd:i,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();return a("已开PR:"+t),{ok:!0,branch:y,committed:b,pr:t}}catch(t){return{ok:!0,branch:y,committed:b,pushed:!0,note:"gh pr create失败(可手动开):"+String(t.message).split("\n")[0]}}}
|
package/src/goalround.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PHASES=["active","paused","blocked","complete"];export const DEFAULT_MAX_ROUNDS=25;export const MIN_BLOCKED_ROUNDS=3;const e=new Set,o=new Map;let r=0;export function setRoundKind(e,r){o.set(String(e||""),"auto"===r?"auto":"human")}export function isHumanRound(e){return"auto"!==o.get(String(e||""))}export function getGoal(e){const o=e?.goal;return o&&"object"==typeof o&&o.id?o:null}export function isArmed(o){const r=getGoal(o);return!!r&&e.has(String(o?.id||"")+"|"+r.id)}function t(o,r){const t=getGoal(o);if(!t)return;const n=String(o?.id||"")+"|"+t.id;r?e.add(n):e.delete(n)}export function viewGoal(e){const o=getGoal(e);return o?{...o,armed:isArmed(e)}:null}function n(e){return e.rev+=1,e.updatedAt=Date.now(),e}function a(e,o){return null==o||Number(o)===e.rev}export function createGoal(e,{objective:o,maxRounds:n}={}){if(!e)return{ok:!1,error:"没有可归属的会话。"};if(!isHumanRound(e.id))return{ok:!1,error:"创建目标需要用户直接提出,不能在自动续跑轮里自行创建。"};const a=String(o||"").trim();if(!a)return{ok:!1,error:"缺少objective(要达成什么)。"};const i=getGoal(e);if(i&&"complete"!==i.phase)return{ok:!1,error:`已有未完成的目标(${i.phase}):${i.objective}。先 /goal clear清除,或 /goal resume继续它。`};const u=Number(n);return e.goal={id:`goal-${Date.now().toString(36)}-${++r}`,objective:a,phase:"active",rounds:0,maxRounds:Number.isFinite(u)&&u>0?Math.floor(u):25,blockedReason:"",rev:1,createdAt:Date.now(),updatedAt:Date.now(),idleRounds:0},t(e,!0),{ok:!0,goal:viewGoal(e)}}export function editGoal(e,{rev:o,objective:r,maxRounds:t}={}){const i=getGoal(e);if(!i)return{ok:!1,error:"当前没有目标。"};if(!isHumanRound(e.id))return{ok:!1,error:"编辑目标需要用户直接提出——自动续跑轮里不能自行改目标或放宽轮次上限。"};if(!a(i,o))return{ok:!1,error:`目标已被改动(当前rev=${i.rev},你报的是 ${o})。请先get_goal再改。`};if(void 0!==r&&String(r).trim()&&(i.objective=String(r).trim()),void 0!==t){const e=Number(t);Number.isFinite(e)&&e>0&&(i.maxRounds=Math.floor(e))}return n(i),{ok:!0,goal:viewGoal(e)}}export function pauseGoal(e,{rev:o}={}){const r=getGoal(e);return r?isHumanRound(e.id)?a(r,o)?(r.phase="paused",n(r),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"暂停目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}export function resumeGoal(e,{rev:o}={}){const r=getGoal(e);return r?isHumanRound(e.id)?a(r,o)?"complete"===r.phase?{ok:!1,error:"该目标已完成,无需恢复(要做新的事请新建目标)。"}:r.rounds>=r.maxRounds?{ok:!1,error:`已达轮次上限 ${r.maxRounds},请先用 /goal limit <N> 放宽再恢复。`}:(r.phase="active",r.blockedReason="",r.idleRounds=0,n(r),t(e,!0),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"恢复目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}export function completeGoal(e,{rev:o}={}){const r=getGoal(e);return r?a(r,o)?(r.phase="complete",n(r),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"当前没有目标。"}}export function blockGoal(e,{rev:o,reason:r}={}){const i=getGoal(e);if(!i)return{ok:!1,error:"当前没有目标。"};if(!a(i,o))return{ok:!1,error:`目标已被改动(当前rev=${i.rev})。`};const u=String(r||"").trim();return u?i.rounds<3?{ok:!1,error:`目前只跑了 ${i.rounds} 轮,还不能判定阻塞(至少 3 轮)。请先换一种方法再试:碰一次壁不等于此路不通。`}:(i.phase="blocked",i.blockedReason=u,n(i),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:"声明阻塞必须给出reason(具体卡在哪、为什么绕不过去)。"}}export function clearGoal(e){return getGoal(e)?isHumanRound(e.id)?(t(e,!1),delete e.goal,{ok:!0}):{ok:!1,error:"清除目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}function i(e){return(e?.messages||[]).filter(e=>"tool"===e.role).length}export function decideNextRound(e,o={}){const r=getGoal(e);if(!r)return{go:!1};if(!isArmed(e))return{go:!1,stop:"目标未武装(用 /goal resume继续)。"};if("active"!==r.phase)return{go:!1,stop:`目标已${{paused:"暂停",blocked:"阻塞",complete:"完成"}[r.phase]||r.phase}。`};if(r.rounds>=r.maxRounds)return t(e,!1),{go:!1,stop:`已达自动续跑轮次上限 ${r.maxRounds},停止。用 /goal limit <N> 放宽后 /goal resume可继续。`};const n=Number(o.budgetTokens)||0,a=Number(o.usedTokens)||0;if(n>0&&a>=n)return t(e,!1),{go:!1,stop:`已达token预算 ${n}(已用 ${a}),停止自动续跑。用 /budget调整后 /goal resume可继续。`};if(r.idleRounds>=2)return t(e,!1),{go:!1,stop:"连续 2 轮没有任何实际操作,判定为空转,已停止(避免无谓消耗)。"};return{go:!0,prompt:`【目标自治轮 ${r.rounds+1}/${r.maxRounds}】继续推进这个目标:${r.objective}\n要求:直接做下一步实事(改代码 / 跑验证 / 补测试),不要复述计划、不要只汇报进度。\n目标确实达成了,就用update_goal(action="complete") 声明完成;确实绕不过去了(且已连续多轮如此),用update_goal(action="blocked", reason=...) 说明卡点。`}}export function recordRound(e,{didWork:o}){const r=getGoal(e);r&&(r.rounds+=1,r.idleRounds=o?0:(r.idleRounds||0)+1,n(r))}export async function runGoalRounds({session:e,doTurn:o,stats:r,onInfo:n,aborted:a}){let u=0,s="";const c=e=>{try{n?.(e)}catch{}};for(;;){if(a?.()){s="已中断。";break}const n=decideNextRound(e,r?.()||{});if(!n.go){s=n.stop||"";break}const d=i(e);let l;setRoundKind(e.id,"auto");try{l=await o(n.prompt)}finally{setRoundKind(e.id,"human")}u++,recordRound(e,{didWork:i(e)>d});const p=getGoal(e);if(c(`目标自治轮 ${p?.rounds}/${p?.maxRounds} 结束。`),l?.aborted){s="已中断。",t(e,!1);break}if(l?.error){s="本轮出错,已停止自动续跑:"+l.error,t(e,!1);break}}return s&&c(s),{rounds:u,stop:s}}const u=(e,o,r={},t=[])=>({type:"function",function:{name:e,description:o,parameters:{type:"object",properties:r,required:t}}});function s(e){if(!e)return"当前没有设定目标。";const o={active:"进行中",paused:"已暂停",blocked:"受阻",complete:"已完成"}[e.phase]||e.phase;return`目标:${e.objective}\n状态:${o}${e.armed?"(自动续跑已武装)":"(未武装,不会自动续跑)"}\n已跑 ${e.rounds}/${e.maxRounds} 轮 rev=${e.rev}`+(e.blockedReason?`\n卡点:${e.blockedReason}`:"")}export function makeGoalTools({sessionOf:e}={}){const o=()=>"function"==typeof e?e():null;return[{readOnly:!0,spec:u("get_goal","读取当前会话的目标:目标内容、阶段、已跑轮数与上限、以及rev(改目标前必须先读它)。"),run:async()=>s(viewGoal(o()))},{readOnly:!1,spec:u("create_goal","当用户提出的是一个**需要跨多轮持续推进**的长目标(如「把这个模块重构完」「把测试补到全绿」)时创建它:之后每完成一轮会自动继续下一轮,直到你声明完成、声明受阻,或触及轮次/预算上限。一次性的小任务不要用它。只能在用户直接提出时创建,自动续跑轮里不可自行创建。",{objective:{type:"string",description:"要达成的具体目标(可验证的完成标准越明确越好)"},max_rounds:{type:"number",description:"可选:自动续跑轮次上限,默认 25"}},["objective"]),run:async e=>{const r=createGoal(o(),{objective:e?.objective,maxRounds:e?.max_rounds});return r.ok?"已创建目标并开始自动推进。\n"+s(r.goal):r.error}},{readOnly:!1,spec:u("update_goal","更新当前目标。complete=目标确实达成了;blocked=确实绕不过去(必须给reason,且要连续多轮都卡住才允许)。edit / pause / resume只能由用户直接提出,你在自动续跑轮里不能自行改目标、放宽上限或解除暂停。必须先用get_goal拿到rev再调用。",{rev:{type:"number",description:"get_goal返回的当前rev(防止基于过期状态改写)"},action:{type:"string",enum:["complete","blocked","edit","pause","resume"],description:"要做的变更"},reason:{type:"string",description:"action=blocked时必填:具体卡在哪、为什么绕不过去"},objective:{type:"string",description:"action=edit时的新目标"},max_rounds:{type:"number",description:"action=edit时的新轮次上限"}},["action"]),run:async e=>{const r=o(),t=e?.rev,n=String(e?.action||""),a="complete"===n?completeGoal(r,{rev:t}):"blocked"===n?blockGoal(r,{rev:t,reason:e?.reason}):"edit"===n?editGoal(r,{rev:t,objective:e?.objective,maxRounds:e?.max_rounds}):"pause"===n?pauseGoal(r,{rev:t}):"resume"===n?resumeGoal(r,{rev:t}):{ok:!1,error:"未知action:"+n};return a.ok?"已更新。\n"+s(a.goal):a.error}}]}export function resetGoalRuntime(){e.clear(),o.clear()}
|
package/src/hooks.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawnSync as
|
|
1
|
+
import t from"node:fs";import o from"node:os";import e from"node:path";import{spawnSync as r}from"node:child_process";import{loadRawSettings as s}from"./settings.js";export const HOOK_EVENTS=["SessionStart","UserPromptSubmit","PreToolUse","PostToolUse","Stop"];const n=new Set(["UserPromptSubmit","PreToolUse"]);function i(t){const o=String(t||"").trim();if(!o.startsWith("{"))return null;try{return JSON.parse(o)}catch{return null}}function c(t,o,e="huicai"){const r=[];if(!t||"object"!=typeof t)return r;for(const s of HOOK_EVENTS){const n=t[s];if(Array.isArray(n))for(const t of n){const n="string"==typeof t?t:t?.command;if("string"!=typeof n||!n.trim())continue;const i=HOOK_DIALECTS.includes(t?.dialect)?t.dialect:e;r.push({event:s,matcher:t&&"string"==typeof t.matcher&&t.matcher.trim()?t.matcher.trim():null,command:n.trim(),timeout:Number(t?.timeout)>0?Number(t.timeout):3e4,source:o,dialect:i})}}return r}export function loadHooks(r){const{global:n,project:i}=s(r),u=(d=n,"1"===process.env.HUICAI_PROJECT_HOOKS||!0===d?.hooks?.trustProject);var d;const a=[...c(n.hooks,"global")];a.push(...function(r){const s=String(r||"").trim();if(!s)return[];const n=s.startsWith("~/")?e.join(o.homedir(),s.slice(2)):s;try{const o=JSON.parse(t.readFileSync(n,"utf8"));return c(o?.hooks||o,"codex","codex")}catch{return[]}}(n?.hooks?.codexConfig));const m=c(i.hooks,"project");let l=0;u?a.push(...m):l=m.length;const p=Object.fromEntries(HOOK_EVENTS.map(t=>[t,[]]));for(const t of a)p[t.event].push(t);return{byEvent:p,all:a,projectIgnored:l,projectTrusted:u}}export const HOOK_DIALECTS=["huicai","codex"];const u={huicai:{stdin:(t,o)=>{try{return JSON.stringify(o)}catch{return"{}"}},env:(t,o)=>({HUICAI_EVENT:t,HUICAI_CWD:o.cwd||process.cwd(),HUICAI_TOOL_NAME:o.toolName||"",HUICAI_TOOL_PATH:o.args&&null!=o.args.path?String(o.args.path):"",HUICAI_TOOL_ARGS:(()=>{try{return JSON.stringify(o.args||{})}catch{return"{}"}})()}),allowsModifiedArgs:!0},codex:{stdin:(t,o)=>{const e={session_id:o.sessionId||"",transcript_path:o.transcriptPath||null,cwd:o.cwd||process.cwd(),hook_event_name:t,model:o.model||"",permission_mode:"default"};"SessionStart"!==t&&(e.turn_id=String(o.turn??"")),"PreToolUse"!==t&&"PostToolUse"!==t||(e.tool_name=o.toolName||"",e.tool_input={command:"string"==typeof o.args?.command?o.args.command:""},e.tool_use_id=o.callId||"","PostToolUse"===t&&(e.tool_response=String(o.result??""))),"UserPromptSubmit"===t&&(e.prompt=String(o.prompt??""));try{return JSON.stringify(e)}catch{return"{}"}},env:()=>({}),allowsModifiedArgs:!1}};export function hookMatches(t,o){if(!t.matcher)return!0;if(!o)return!0;try{return new RegExp(t.matcher).test(o)}catch{return!1}}function d(t){return(t=String(t||"")).length>8e3?t.slice(0,8e3)+"\n…(钩子输出已截断)":t}function a(t,o,e){const s="win32"===process.platform?r(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",t.command],{cwd:o.HUICAI_CWD,env:o,input:e,encoding:"utf8",timeout:t.timeout,maxBuffer:4194304,windowsVerbatimArguments:!0}):r(process.env.SHELL||"/bin/sh",["-c",t.command],{cwd:o.HUICAI_CWD,env:o,input:e,encoding:"utf8",timeout:t.timeout,maxBuffer:4194304}),n=d(s.stdout),i=d(s.stderr),c=s.error&&"ETIMEDOUT"===s.error.code;return{stdout:n,stderr:i,status:c?124:null==s.status?1:s.status,timedOut:c}}export function runHooks(t,o,e){const r=o.cwd,s=(e?.byEvent?.[t]||loadHooks(r).byEvent[t]||[]).filter(t=>hookMatches(t,o.toolName)),c={block:!1,reason:"",outputs:[],injectContext:"",modifiedArgs:null};if(!s.length)return c;for(const e of s){const s=u[e.dialect]||u.huicai,d={...process.env,HUICAI_CWD:r||process.cwd(),...s.env(t,o)},m=s.stdin(t,o);let l;try{l=a(e,d,m)}catch(t){l={stdout:"",stderr:String(t.message||t),status:1,timedOut:!1}}const p=i(l.stdout);if(p){if("string"==typeof p.context&&p.context.trim()&&(c.injectContext+=(c.injectContext?"\n":"")+p.context.trim()),p.modifiedArgs&&"object"==typeof p.modifiedArgs&&(u[e.dialect]||u.huicai).allowsModifiedArgs&&(c.modifiedArgs={...c.modifiedArgs||{},...p.modifiedArgs}),"block"===p.decision&&n.has(t)){c.block=!0,c.reason=String(p.reason||"钩子决定阻断该操作");break}continue}if((l.stdout||l.stderr||"").trim()&&c.outputs.push({command:e.command,stdout:l.stdout.trim()||l.stderr.trim()}),0!==l.status&&n.has(t)){c.block=!0,c.reason=l.timedOut?`钩子超时(${e.command})`:l.stderr.trim()||l.stdout.trim()||`钩子以退出码 ${l.status} 阻断(${e.command})`;break}}return c}export function formatHooks(t){const o=[];if(t.all.length)for(const e of HOOK_EVENTS){const r=t.byEvent[e];if(r.length){o.push(`${e}`);for(const t of r){const e=t.matcher?`[${t.matcher}] `:"",r="codex"===t.dialect?"(Codex方言)":"global"===t.source?"":"(项目)";o.push(` · ${e}${t.command}${r}`)}}}else o.push('未配置钩子。可在 ~/.huicai/settings.json或 项目 .huicai/settings.json的 "hooks" 中添加。');return t.projectIgnored>0&&(o.push(""),o.push(`⚠ 已忽略 ${t.projectIgnored} 条项目级钩子(未信任本项目)。`),o.push(" 如信任本项目,设HUICAI_PROJECT_HOOKS=1,或全局设置hooks.trustProject=true。")),o.push(""),o.push("事件:SessionStart(会话开始,注入上下文)· UserPromptSubmit(提交前,注入/阻断)· PreToolUse(工具前,阻断/改参数)· PostToolUse(工具后,采输出)· Stop(回合结束)。"),o.push('结构化控制:钩子可把JSON打到stdout —— {"decision":"block","reason":..} 阻断 / {"context":".."} 注入上下文 / {"modifiedArgs":{..}} 改工具参数。'),o.join("\n")}
|