@whyshu/argent-engine 0.0.0-stage → 0.6.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/MANIFEST.json +307 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +377 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +134 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +46 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +260 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1024 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2275 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +194 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent_datadir.py +262 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/run-tests.sh +256 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +136 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +73 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +34 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +706 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# release-argent.sh <版本> — Argent 发布版本流程(KB-082,2026-09-01)
|
|
4
|
+
#
|
|
5
|
+
# 用途:把开发 HEAD 的稳定改动作为正式版本发布——校验 → 打 tag → 同步分发文件
|
|
6
|
+
# 到服务器 dl/ → 验证 → 输出发布报告。替代手动同步(曾发生 dl/ 落后漏同步)。
|
|
7
|
+
#
|
|
8
|
+
# 用法:
|
|
9
|
+
# bash release-argent.sh 0.7.1 # 正式发布(校验 → tag+push → 同步 → 验证)
|
|
10
|
+
# bash release-argent.sh 0.7.1 --dry-run # 演练:只校验+打印计划,不 tag 不同步
|
|
11
|
+
# bash release-argent.sh 0.7.1 --full # 额外同步仓库 skills/、plugins/ 目录
|
|
12
|
+
# bash release-argent.sh --check-dl # 只体检:仓库 vs 服务器 dl/ 是否一致(不改任何东西)
|
|
13
|
+
# bash release-argent.sh --publish-dl # 只同步分发文件(不打 tag、不 push)——应急修 dl 漂移
|
|
14
|
+
#
|
|
15
|
+
# 为什么有 --check-dl:2026-09-20 实测发现服务器 dl/install-argent.mjs 停在 9-07,
|
|
16
|
+
# 而仓库已是 9-15(缺 dsh 0.1.5-rc.1 已验版本 + profile-doctor 收尾门禁)——**整整漂了 8 天**。
|
|
17
|
+
# 官网那条 `curl https://whyshu.com/dl/install-argent.mjs | node` 下发的就是它。
|
|
18
|
+
# dl/ 与仓库没有任何自动连接,只能靠"发布时同步 + 平时可体检"两道:本模式就是体检那道。
|
|
19
|
+
# (默认同步 CORE_FILES 四件安装器
|
|
20
|
+
# install-argent.mjs / install.sh / install-dsh.sh / install.ps1;
|
|
21
|
+
# 服务器 dl/skills、dl/plugins 有独立静态资源
|
|
22
|
+
# (whyshu-ppt-template/argent-account),
|
|
23
|
+
# 与仓库目录不同源,--full 才覆盖,谨慎使用)
|
|
24
|
+
#
|
|
25
|
+
# 前置条件:
|
|
26
|
+
# - 本机可 ssh ubuntu@81.69.222.211(~/.ssh/whyshu.pem)
|
|
27
|
+
# - 发布门槛(docs/release.md):对应改动已在看板 review→done 验收通过(PM 批准)
|
|
28
|
+
# - VERSION 文件已更新为目标版本、CHANGELOG.md 已含对应条目(可先跑 --dry-run 检查)
|
|
29
|
+
#
|
|
30
|
+
# 流程:
|
|
31
|
+
# 1. 校验:VERSION 与参数一致 / git working tree clean / CHANGELOG 含 ## v<版本>
|
|
32
|
+
# (缺 CHANGELOG 条目仅警告)/ tag v<版本> 未存在
|
|
33
|
+
# 2. 打 git tag v<版本> + push origin v<版本>(含 tag)
|
|
34
|
+
# 3. 同步分发文件到 ubuntu@81.69.222.211:/home/whyshu/web/dl/:
|
|
35
|
+
# install-argent.mjs、install.sh、install-dsh.sh、install.ps1(默认);skills/、plugins/(--full)
|
|
36
|
+
# 4. 验证:本地/服务器 md5 一致 + curl -sI https://whyshu.com/dl/install-argent.mjs = 200
|
|
37
|
+
# 5. 输出发布报告(版本/tag/文件清单/md5/验证结果)
|
|
38
|
+
#
|
|
39
|
+
# ⛔ 投递纪律(本仓红线,2026-10-06 定、2026-10-06 收口):**客户机/服务器的任何投递禁用 `scp`**,一律用 `rsync`
|
|
40
|
+
# (或 `cat | ssh`)。原因:scp 的协议/选项面在新旧 OpenSSH 之间不兼容、不支持增量 ⇒ 大包重传、失败静默半截。
|
|
41
|
+
# **本脚本三条投递路径已全部走 rsync**:模式 P 的能力包上传 / CORE_FILES / `--full` 的 DIR_FILES。
|
|
42
|
+
# ⚠️ 且投递后都**显式归一远端模式**(`remote_chmod`):dl/ 是 nginx 静态面,源文件 600(开发树 umask)
|
|
43
|
+
# 被 `rsync -a` 原样带过去 ⇒ **403**,官网那条 `curl …/dl/install-argent.mjs | node` 当场废
|
|
44
|
+
# (2026-10-09 实测命中,当场 chmod 644 修复;体检现在把"HTTP 取不到"也判红)。
|
|
45
|
+
# ⛔ 为什么不用 rsync 的 `--chmod=F644`:macOS 自带的 openrsync("rsync version 2.6.9 compatible")
|
|
46
|
+
# 不认 `F`/`D` 前缀(实测报 `--chmod=F600: invalid argument`)⇒ 走远端 `chmod`,与 rsync 版本无关。
|
|
47
|
+
# `grep -n scp` 只应命中本段注释。⚠️ 清单口径(2026-10-06 修脚枪):模式 P 第 ④ 步的
|
|
48
|
+
# `--emit-server-manifest` **默认 merge**(只刷新注册表里认识的能力,现网独有的条目原样保留、键序不动;
|
|
49
|
+
# 删条目须显式 `--prune`)⇒ **工具已保证不误删**;仍建议发布前 `--dry-run` 复核 diff
|
|
50
|
+
# (脚本已自动先 dry-run 打印、再落盘)。
|
|
51
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
|
|
54
|
+
# ── 常量 ──────────────────────────────────────────────────────────────────────
|
|
55
|
+
SSH_HOST="ubuntu@81.69.222.211"
|
|
56
|
+
SSH_KEY="$HOME/.ssh/whyshu.pem"
|
|
57
|
+
DL_DIR="/home/whyshu/web/dl"
|
|
58
|
+
DL_URL="https://whyshu.com/dl"
|
|
59
|
+
# whyshu-svr 在服务器上的**扁平**部署目录(pm2 cwd;能力包清单就部署在这里)
|
|
60
|
+
SRV_DEPLOY="${SRV_DEPLOY:-/home/whyshu/server}"
|
|
61
|
+
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
62
|
+
# push 目标 remote:默认仓库第一个 remote(本机为 whyshu);可用 ARGENT_RELEASE_REMOTE 覆盖
|
|
63
|
+
REMOTE="${ARGENT_RELEASE_REMOTE:-$(git -C "$REPO_DIR" remote | head -1)}"
|
|
64
|
+
|
|
65
|
+
# 核心分发文件(与服务器 dl/ 直接对应)
|
|
66
|
+
# 2026-09-20:补 install.ps1 —— 官网 Windows PowerShell 一行命令 `irm .../dl/install.ps1 | iex`
|
|
67
|
+
# 直接吃这个文件;漏了它就等于 Windows 用户在下载"没人管"的副本。
|
|
68
|
+
# 2026-10-09:补 install.sh —— 官网 macOS/Linux 一行命令 `curl .../dl/install.sh | bash` 的入口
|
|
69
|
+
# (旧的 `curl .../dl/install-argent.mjs | node` 因模块级相对 import 必然 ERR_MODULE_NOT_FOUND,
|
|
70
|
+
# 已被 install.sh 取代)。`install-dsh.sh` 留在这里只为**存量老命令**仍能取到,是**遗留**件。
|
|
71
|
+
CORE_FILES=("install-argent.mjs" "install.sh" "install-dsh.sh" "install.ps1")
|
|
72
|
+
# 目录级分发(--full 时启用;服务器对应目录有独立静态资源,非同名覆盖不删旧)
|
|
73
|
+
DIR_FILES=("skills" "plugins")
|
|
74
|
+
|
|
75
|
+
# ── 参数 ──────────────────────────────────────────────────────────────────────
|
|
76
|
+
VERSION_ARG=""
|
|
77
|
+
DRY_RUN=0
|
|
78
|
+
FULL=0
|
|
79
|
+
MODE="release" # release | check-dl | publish-dl
|
|
80
|
+
for a in "$@"; do
|
|
81
|
+
case "$a" in
|
|
82
|
+
--dry-run) DRY_RUN=1 ;;
|
|
83
|
+
--full) FULL=1 ;;
|
|
84
|
+
--check-dl) MODE="check-dl" ;;
|
|
85
|
+
--publish-packs) MODE="publish-packs" ;;
|
|
86
|
+
--publish-dl) MODE="publish-dl" ;;
|
|
87
|
+
-h|--help) sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
88
|
+
*) VERSION_ARG="$a" ;;
|
|
89
|
+
esac
|
|
90
|
+
done
|
|
91
|
+
if [[ "$MODE" == "release" && -z "$VERSION_ARG" ]]; then
|
|
92
|
+
echo "✗ 缺少版本参数(用法:bash release-argent.sh <版本> [--dry-run] [--full])"
|
|
93
|
+
echo " 或:bash release-argent.sh --check-dl | --publish-dl | --publish-packs"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
if [[ "$MODE" != "release" ]]; then
|
|
97
|
+
VERSION_ARG="${VERSION_ARG:-current}"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ── md5 / 拉取校验的小工具(本地 / 服务器 / 真正下发的 HTTP 内容)──────────────
|
|
101
|
+
md5_of() { md5 -q "$1" 2>/dev/null || md5sum "$1" | awk '{print $1}'; }
|
|
102
|
+
md5_stdin() { md5 -q 2>/dev/null || md5sum | awk '{print $1}'; }
|
|
103
|
+
remote_md5_of() { ssh -i "$SSH_KEY" -o ConnectTimeout=10 "$SSH_HOST" \
|
|
104
|
+
"md5sum ${DL_DIR}/$1 2>/dev/null | awk '{print \$1}'" 2>/dev/null || echo ''; }
|
|
105
|
+
# 真正"用户会拿到什么":绕过缓存拉一遍内容算 md5(只有 200 + 内容一致才算数)
|
|
106
|
+
# ⚠️ 2026-10-09 实测事故:旧实现是 `curl -fsSL … | md5_stdin` —— curl 失败时下游**照样**算出一个
|
|
107
|
+
# "空内容的 md5"(d41d8cd9…)⇒ 调用方看到的是"内容不同"而不是"取不到";且 `set -e` 下 curl 的
|
|
108
|
+
# 退出码(56)会**直接结束脚本**(现象 = 只打印一行"── dl/ 体检 ──"就没了)。
|
|
109
|
+
# 现在:先探 HTTP 状态码,**非 200 ⇒ 返回空串**(调用方按"HTTP 取不到"报红 + 给 chmod 修法);
|
|
110
|
+
# 状态码 200 才流式取内容(⛔ 不做命令替换:那会剥掉尾换行 ⇒ md5 与实际下发字节不符 ⇒ 恒假红)。
|
|
111
|
+
http_md5_of() {
|
|
112
|
+
local code
|
|
113
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 60 -H 'Cache-Control: no-cache' "${DL_URL}/$1" 2>/dev/null || echo 000)"
|
|
114
|
+
[ "$code" = "200" ] || return 0
|
|
115
|
+
curl -fsSL --max-time 60 -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' "${DL_URL}/$1" 2>/dev/null | md5_stdin || true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# 投递后**归一远端文件模式**(2026-10-09 实测事故,见脚本头"投递纪律"):
|
|
119
|
+
# `rsync -a` 会保留开发树的模式 ⇒ 源文件 600 时 dl/ 上也 600 ⇒ nginx 403 ⇒ 官网一行命令当场废。
|
|
120
|
+
# 用法:remote_chmod <模式> <远端路径…>(模式与路径都交给远端 chmod,路径里的 glob 由远端展开)
|
|
121
|
+
remote_chmod() {
|
|
122
|
+
local mode="$1"; shift
|
|
123
|
+
ssh -i "$SSH_KEY" -o ConnectTimeout=10 "$SSH_HOST" "chmod ${mode} $*" 2>/dev/null || true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ── 解释器解析(两个档)─────────────────────────────────────────────────────────
|
|
127
|
+
# 2026-09-22 重启事故:默认值写死 ~/.dsh/argent/venv ⇒ /tmp 被清后静默空转。
|
|
128
|
+
# 候选(2026-09-22):ARGENT_PY → DSH_HOME/argent/venv → ~/.dsh/argent/venv → 仓库 .venv → PATH
|
|
129
|
+
py_candidates() {
|
|
130
|
+
printf '%s\n' "${ARGENT_PY:-}" "${DSH_HOME:-$HOME/.dsh}/argent/venv/bin/python3" \
|
|
131
|
+
"$HOME/.dsh/argent/venv/bin/python3" "${REPO:-}/.venv/bin/python3" \
|
|
132
|
+
"$(command -v python3 2>/dev/null || true)"
|
|
133
|
+
}
|
|
134
|
+
# 严格档:配置/发票生成硬要求 openpyxl + yaml(--publish-packs 走这一档)
|
|
135
|
+
pick_py() {
|
|
136
|
+
local c
|
|
137
|
+
for c in $(py_candidates); do
|
|
138
|
+
[ -n "$c" ] || continue
|
|
139
|
+
[ -x "$c" ] || continue
|
|
140
|
+
"$c" -c 'import openpyxl, yaml' 2>/dev/null || continue
|
|
141
|
+
"$c" -c 'import PIL, playwright' 2>/dev/null || \
|
|
142
|
+
echo "[$(date '+%F %T')] ⚠️ 解释器 $c 缺 pillow/playwright ⇒ 浏览器流程会失败(修:node install-argent.mjs --venv-only)" >&2
|
|
143
|
+
printf '%s' "$c"; return 0
|
|
144
|
+
done
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
# 轻档(2026-10-09):能力包档**只跑 stdlib**(`capability_pack.py --digest-tgz` / `--emit-server-manifest`)
|
|
148
|
+
# ⇒ 不该被 openpyxl 挡在门外(体检机器上没装办公依赖是常态)。
|
|
149
|
+
pick_py_light() {
|
|
150
|
+
local c
|
|
151
|
+
for c in $(py_candidates); do
|
|
152
|
+
[ -n "$c" ] || continue
|
|
153
|
+
[ -x "$c" ] || continue
|
|
154
|
+
"$c" -c 'import json,tarfile,hashlib' 2>/dev/null || continue
|
|
155
|
+
printf '%s' "$c"; return 0
|
|
156
|
+
done
|
|
157
|
+
return 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ── 能力包档(2026-10-09 新增)─────────────────────────────────────────────────
|
|
161
|
+
# 为什么要有它:`CORE_FILES` 只有三个安装器 ⇒ **能力包从来没人核**。而 hr 今天就发生过
|
|
162
|
+
# 「**同 URL 覆盖发布**」(版本没提、字节变了)—— 文件名与 HTTP 200 都没变,谁都没发现。
|
|
163
|
+
# 判据(任一不满足即判红,复用本脚本既有的「HTTP 取不到即红」风格):
|
|
164
|
+
# ① HTTP 取得到(状态码 200);② 取回来的 tgz **重算内容摘要 == 清单 sha256**。
|
|
165
|
+
# ⛔ 摘要**只用 CLI 算**(`capability_pack.py --digest-tgz`):importlib 加载那个模块时
|
|
166
|
+
# `scripts/` 不在 sys.path ⇒ `import flow_registry` 静默降级成未合并的 base 注册表 ⇒
|
|
167
|
+
# 带流程的能力(full/invoice/daily-report)摘要会**算错**(2026-10-08 实测)。
|
|
168
|
+
# 另外做一次**仓库 ↔ 线上清单**对账(同一份 CLI 的 `--emit-server-manifest` 输出):
|
|
169
|
+
# · 版本不同 ⇒ ⚠(发布在途/落后,**不判红**,否则开发中恒红);
|
|
170
|
+
# · **版本相同但摘要不同** ⇒ ✗ 判红 —— 那正是「同 URL 覆盖发布」的形态:客户拿到的字节
|
|
171
|
+
# 与仓库里那一版对不上,而版本号骗过了所有人。
|
|
172
|
+
# 用法:check_packs <light-py> ⇒ 设 PACK_DRIFT=0/1(返回值恒 0,判据走变量,免得被 set -e 咬)
|
|
173
|
+
check_packs() {
|
|
174
|
+
local py="$1"
|
|
175
|
+
local tmpd; tmpd="$(mktemp -d -t argent-packs-check)"
|
|
176
|
+
local rc=0
|
|
177
|
+
PACK_DRIFT=0
|
|
178
|
+
echo
|
|
179
|
+
echo "── 能力包档(服务端清单 → 逐个 HTTP 取回 + **重算内容摘要**)──"
|
|
180
|
+
if [[ -z "$py" ]]; then
|
|
181
|
+
echo " ✗ 找不到可用解释器(stdlib 即可)⇒ 无法重算包摘要(⛔ 不用 importlib 兜底:会静默算错)"
|
|
182
|
+
rm -rf "$tmpd"; PACK_DRIFT=1; return 0
|
|
183
|
+
fi
|
|
184
|
+
local remote_manifest
|
|
185
|
+
# 部署形态是**扁平**的(`/home/whyshu/server/config/…`,服务按 `services/../config` 解析);
|
|
186
|
+
# 仓库形态则是 `src/config/…` ⇒ 两个都试,取第一个存在的(别把"我猜错了目录"报成"清单没了")。
|
|
187
|
+
remote_manifest="$(ssh -i "$SSH_KEY" -o ConnectTimeout=10 "$SSH_HOST" \
|
|
188
|
+
"for p in ${SRV_DEPLOY}/config/capability-packs.json ${SRV_DEPLOY}/src/config/capability-packs.json; do \
|
|
189
|
+
if [ -f \"\$p\" ]; then cat \"\$p\"; break; fi; done" 2>/dev/null || echo '')"
|
|
190
|
+
if [[ -z "$remote_manifest" ]]; then
|
|
191
|
+
echo " ✗ 取不到服务器上部署的能力清单(试过 ${SRV_DEPLOY}/config/ 与 ${SRV_DEPLOY}/src/config/)"
|
|
192
|
+
rm -rf "$tmpd"; PACK_DRIFT=1; return 0
|
|
193
|
+
fi
|
|
194
|
+
printf '%s' "$remote_manifest" > "$tmpd/manifest.json"
|
|
195
|
+
# 发布侧真源(本机 whyshu-svr 仓库那一份):与**已部署**的那份不一致 = 改了没部署。
|
|
196
|
+
# ⚠️ 比对前两边都去掉**尾随换行**:`$( )` 会剥掉命令替换结果的所有尾换行,而文件末尾有一个
|
|
197
|
+
# ⇒ 直接 `diff <(printf '%s' "$remote") <file>` 会**恒报不等**(2026-10-09 实测的假告警)。
|
|
198
|
+
local local_manifest="${WHYSHU_SVR_DIR:-$HOME/Code/whyshu-svr}/src/config/capability-packs.json"
|
|
199
|
+
if [[ -f "$local_manifest" ]]; then
|
|
200
|
+
if [[ "$remote_manifest" == "$(cat "$local_manifest")" ]]; then
|
|
201
|
+
echo " ✓ 已部署清单 == 本机 whyshu-svr 工作树那一份(未部署改动 = 0)"
|
|
202
|
+
else
|
|
203
|
+
echo " ⚠ 已部署清单 ≠ 本机 whyshu-svr 工作树那一份 —— 有改动没部署(⚠ 不判红;本脚本只核 dl/)"
|
|
204
|
+
fi
|
|
205
|
+
else
|
|
206
|
+
echo " · 本机没有 ${local_manifest}(跳过「发布侧真源」对账)"
|
|
207
|
+
fi
|
|
208
|
+
local rows
|
|
209
|
+
rows="$("$py" -c 'import json,sys
|
|
210
|
+
d=json.load(open(sys.argv[1],encoding="utf-8"))
|
|
211
|
+
for cid,e in (d.get("capabilities") or {}).items():
|
|
212
|
+
print("\t".join([cid,str(e.get("version","")),str(e.get("sha256","")),str(e.get("url",""))]))' \
|
|
213
|
+
"$tmpd/manifest.json" 2>/dev/null || true)"
|
|
214
|
+
if [[ -z "$rows" ]]; then
|
|
215
|
+
echo " ✗ 服务端清单里**一个能力都没有**(清单空/结构非法)—— 客户机拿到的会是空清单"
|
|
216
|
+
rm -rf "$tmpd"; PACK_DRIFT=1; return 0
|
|
217
|
+
fi
|
|
218
|
+
local cid ver sha url code got f
|
|
219
|
+
while IFS=$'\t' read -r cid ver sha url; do
|
|
220
|
+
[[ -n "$cid" ]] || continue
|
|
221
|
+
if [[ -z "$url" ]]; then
|
|
222
|
+
echo " ✗ ${cid} v${ver}:清单没有 url(客户端无从取包)"; PACK_DRIFT=1; continue
|
|
223
|
+
fi
|
|
224
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 60 -H 'Cache-Control: no-cache' "$url" 2>/dev/null || echo 000)"
|
|
225
|
+
if [[ "$code" != "200" ]]; then
|
|
226
|
+
echo " ✗ ${cid} v${ver}:**HTTP ${code}**(${url})—— 包取不到;修:bash scripts/release-argent.sh --publish-packs"
|
|
227
|
+
PACK_DRIFT=1; continue
|
|
228
|
+
fi
|
|
229
|
+
f="$tmpd/${cid}.tgz"
|
|
230
|
+
if ! curl -fsSL --max-time 300 -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' "$url" -o "$f" 2>/dev/null; then
|
|
231
|
+
echo " ✗ ${cid} v${ver}:状态码 200 但**内容取不回来**(多半是权限 ⇒ nginx 403 / 传输中断)"
|
|
232
|
+
PACK_DRIFT=1; continue
|
|
233
|
+
fi
|
|
234
|
+
got="$("$py" scripts/capability_pack.py --digest-tgz "$f" 2>/dev/null | head -1 | tr -d '[:space:]' || true)"
|
|
235
|
+
if [[ -z "$got" ]]; then
|
|
236
|
+
echo " ✗ ${cid} v${ver}:摘要重算失败(--digest-tgz 非零退出)"; PACK_DRIFT=1; continue
|
|
237
|
+
fi
|
|
238
|
+
if [[ "$got" == "$sha" ]]; then
|
|
239
|
+
echo " ✓ ${cid} v${ver} 内容摘要一致(${got:0:8},$(wc -c < "$f" | tr -d ' ') 字节)"
|
|
240
|
+
else
|
|
241
|
+
echo " ✗ ${cid} v${ver} **内容摘要不符**:清单 ${sha:0:8} vs HTTP 取回重算 ${got:0:8}"
|
|
242
|
+
echo " ↳ 这正是「同 URL 覆盖发布」的形态(包换了、版本没提,HTTP 与文件名都还是好的)"
|
|
243
|
+
PACK_DRIFT=1
|
|
244
|
+
fi
|
|
245
|
+
done <<< "$rows"
|
|
246
|
+
|
|
247
|
+
# ── 仓库 ↔ 线上清单对账(同一份 CLI 派生,不手抄任何值)──
|
|
248
|
+
echo " ── 仓库 ↔ 线上清单对账(capability_pack.py --emit-server-manifest)──"
|
|
249
|
+
local derived; derived="$("$py" scripts/capability_pack.py --emit-server-manifest 2>/dev/null || true)"
|
|
250
|
+
if [[ -z "$derived" ]]; then
|
|
251
|
+
echo " ⚠ 派生不出仓库侧清单(注册表读不出来?)—— 跳过对账"
|
|
252
|
+
else
|
|
253
|
+
printf '%s' "$derived" > "$tmpd/derived.json"
|
|
254
|
+
local cmp_out; cmp_out="$("$py" -c 'import json,sys
|
|
255
|
+
der=(json.load(open(sys.argv[1],encoding="utf-8")).get("capabilities") or {})
|
|
256
|
+
live=(json.load(open(sys.argv[2],encoding="utf-8")).get("capabilities") or {})
|
|
257
|
+
warn=red=0
|
|
258
|
+
for cid in sorted(set(der)|set(live)):
|
|
259
|
+
d,l=der.get(cid),live.get(cid)
|
|
260
|
+
if d is None:
|
|
261
|
+
print(" ⚠ %s: 线上有、仓库注册表不认识(%s)—— 孤儿条目,无法重建/提版" % (cid,l.get("version"))); warn+=1; continue
|
|
262
|
+
if l is None:
|
|
263
|
+
print(" ⚠ %s: 仓库有、线上**没有**(%s)—— 还没发布?" % (cid,d.get("version"))); warn+=1; continue
|
|
264
|
+
if str(d.get("version"))!=str(l.get("version")):
|
|
265
|
+
print(" ⚠ %s: 版本不同 仓库 %s vs 线上 %s(发布在途/落后;**不判红**)" % (cid,d.get("version"),l.get("version"))); warn+=1; continue
|
|
266
|
+
if str(d.get("sha256"))!=str(l.get("sha256")):
|
|
267
|
+
print(" ✗ %s: **版本相同(%s)但内容摘要不同** —— 仓库 %s vs 线上 %s ⇒ 同版本覆盖发布的形态,客户拿到的字节与仓库这一版对不上" % (cid,d.get("version"),str(d.get("sha256"))[:8],str(l.get("sha256"))[:8])); red+=1
|
|
268
|
+
print(" ⇒ 对账:✗ %d | ⚠ %d%s" % (red,warn,"(✗ 即判红)" if red else "(⚠ 不判红)"))
|
|
269
|
+
print("__RED__" if red else "__OK__")' "$tmpd/derived.json" "$tmpd/manifest.json" 2>/dev/null || true)"
|
|
270
|
+
printf '%s\n' "$cmp_out" | grep -v '^__' || true
|
|
271
|
+
if printf '%s' "$cmp_out" | grep -q '__RED__'; then PACK_DRIFT=1; fi
|
|
272
|
+
fi
|
|
273
|
+
rm -rf "$tmpd"
|
|
274
|
+
if [[ "$PACK_DRIFT" -eq 0 ]]; then
|
|
275
|
+
echo " ✅ 能力包档:清单里每个包都 HTTP 可达且内容摘要与清单一致"
|
|
276
|
+
else
|
|
277
|
+
echo " ✗ 能力包档**不通过** —— 面向客户的下载面与清单对不上"
|
|
278
|
+
fi
|
|
279
|
+
return 0
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# ── 模式 P:发布能力包(--publish-packs)── 缺口③:把包托管到 dl/packs/ 并生成服务端清单 ──
|
|
283
|
+
# 为什么需要:客户端的 `--from-packs <url>` 早就支持按 url 装包,但包一直没地方放(清单 url 是占位)。
|
|
284
|
+
# 本模式:① 构建每个能力的**确定性 tgz** → ② rsync 到 `${DL_DIR}/packs/` → ③ 复核 HTTP 可达
|
|
285
|
+
# → ④ 生成 whyshu-svr 的清单(含整包内容摘要 + url):**默认 merge**(只刷新注册表认识的能力,
|
|
286
|
+
# 现网独有的条目原样保留;删条目须显式 --prune)⇒ 先 `--dry-run` 打印 diff,再落盘。
|
|
287
|
+
# ⛔ 不再"整表替换":2026-10-06 前的整表输出会抹掉现网独有条目(实测会把 daily-report-lite 抹掉)。
|
|
288
|
+
if [[ "$MODE" == "publish-packs" ]]; then
|
|
289
|
+
cd "$REPO_DIR"
|
|
290
|
+
# 解释器解析(2026-09-22 重启事故:默认值写死 ~/.dsh/argent/venv ⇒ /tmp 被清后静默空转)
|
|
291
|
+
pick_py() {
|
|
292
|
+
local c
|
|
293
|
+
# 候选(2026-09-22):ARGENT_PY → DSH_HOME/argent/venv → ~/.dsh/argent/venv → 仓库 .venv → PATH
|
|
294
|
+
for c in "${ARGENT_PY:-}" "${DSH_HOME:-$HOME/.dsh}/argent/venv/bin/python3" \
|
|
295
|
+
"$HOME/.dsh/argent/venv/bin/python3" "${REPO:-}/.venv/bin/python3" \
|
|
296
|
+
"$(command -v python3 2>/dev/null || true)"; do
|
|
297
|
+
[ -n "$c" ] || continue
|
|
298
|
+
[ -x "$c" ] || continue
|
|
299
|
+
"$c" -c 'import openpyxl, yaml' 2>/dev/null || continue # 硬要求:配置/发票生成必需
|
|
300
|
+
"$c" -c 'import PIL, playwright' 2>/dev/null || \
|
|
301
|
+
echo "[$(date '+%F %T')] ⚠️ 解释器 $c 缺 pillow/playwright ⇒ 浏览器流程会失败(修:node install-argent.mjs --venv-only)" >&2
|
|
302
|
+
printf '%s' "$c"; return 0
|
|
303
|
+
done
|
|
304
|
+
return 1
|
|
305
|
+
}
|
|
306
|
+
if ! PY="$(pick_py)"; then
|
|
307
|
+
echo "❌ 找不到可用解释器(需含 openpyxl + yaml):ARGENT_PY / ~/.dsh/argent/venv/bin/python3 / PATH 的 python3" >&2
|
|
308
|
+
echo " 修复: python3 -m venv ~/.dsh/argent/venv && ~/.dsh/argent/venv/bin/pip install openpyxl pyyaml" >&2
|
|
309
|
+
exit 2
|
|
310
|
+
fi
|
|
311
|
+
OUT="${PACKS_OUT:-$(mktemp -d -t argent-packs)}"
|
|
312
|
+
DL_BASE="${DL_BASE:-https://whyshu.com/dl}"
|
|
313
|
+
echo "── 构建能力包(确定性 tgz)→ ${OUT} ──"
|
|
314
|
+
CAPS="$("$PY" scripts/capability_pack.py --list 2>/dev/null | awk '/^ [a-z]/{print $1}')"
|
|
315
|
+
[[ -n "$CAPS" ]] || CAPS="invoice full daily-report hr"
|
|
316
|
+
for c in $CAPS; do
|
|
317
|
+
"$PY" scripts/capability_pack.py --build "$c" --out-dir "${OUT}/${c}" --tar --force >/dev/null 2>&1 \
|
|
318
|
+
&& echo " ✓ ${c}" || { echo " ✗ ${c} 构建失败"; exit 1; }
|
|
319
|
+
done
|
|
320
|
+
echo
|
|
321
|
+
echo "── 上传 → ${SSH_HOST}:${DL_DIR}/packs/ ──"
|
|
322
|
+
ssh -i "$SSH_KEY" -o ConnectTimeout=10 "$SSH_HOST" "mkdir -p ${DL_DIR}/packs"
|
|
323
|
+
# ⛔ 红线(2026-10-06):客户机/服务器投递禁用 `scp` ⇒ 改 `rsync`(同一份字节、同一目标目录,
|
|
324
|
+
# 行为等价;rsync 走 ssh 且支持增量,避免 scp 在新旧 OpenSSH 间的兼容差异与半截失败)。
|
|
325
|
+
rsync -az --no-owner --no-group -e "ssh -i $SSH_KEY -o ConnectTimeout=10" "${OUT}"/*.tgz "${SSH_HOST}:${DL_DIR}/packs/"
|
|
326
|
+
# 模式归一(600 ⇒ nginx 403;见 remote_chmod 注释):包是 HTTP 直接下发物,必须 world-readable。
|
|
327
|
+
remote_chmod 644 "${DL_DIR}/packs/*.tgz"
|
|
328
|
+
echo " ✓ 已上传 $(ls "${OUT}"/*.tgz | wc -l | tr -d ' ') 个包"
|
|
329
|
+
echo
|
|
330
|
+
echo "── 复核(HTTP 可达 + 与本地字节一致)──"
|
|
331
|
+
OK=1
|
|
332
|
+
for f in "${OUT}"/*.tgz; do
|
|
333
|
+
n="$(basename "$f")"; l="$(md5_of "$f")"
|
|
334
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' -I "${DL_BASE}/packs/${n}" || echo 000)"
|
|
335
|
+
h="$(curl -s "${DL_BASE}/packs/${n}" | md5 -q 2>/dev/null || curl -s "${DL_BASE}/packs/${n}" | md5sum | awk '{print $1}')"
|
|
336
|
+
if [[ "$code" == "200" && "$h" == "$l" ]]; then echo " ✓ ${n}(HTTP 200,md5 ${l:0:8})"; else
|
|
337
|
+
echo " ✗ ${n}(HTTP ${code},md5 本地 ${l:0:8} vs 远端 ${h:0:8})"; OK=0; fi
|
|
338
|
+
done
|
|
339
|
+
[[ "$OK" -eq 1 ]] || { echo "✗ 部分包复核不通过——先别改服务端清单"; exit 1; }
|
|
340
|
+
echo
|
|
341
|
+
echo "── 生成服务端能力清单(whyshu-svr;默认 merge,只刷新注册表认识的能力)──"
|
|
342
|
+
SRV="${WHYSHU_SVR_DIR:-$HOME/Code/whyshu-svr}/src/config/capability-packs.json"
|
|
343
|
+
# ④a 先 dry-run **看 diff**(不写盘):工具已保证不误删现网独有条目,这一步是发布前的第二双眼睛。
|
|
344
|
+
"$PY" scripts/capability_pack.py --emit-server-manifest --dl-base "$DL_BASE" --out "$SRV" --dry-run
|
|
345
|
+
# ④b 复核无误 ⇒ 落盘(仍是 merge 语义:不认识的条目原样保留、键序不动)
|
|
346
|
+
"$PY" scripts/capability_pack.py --emit-server-manifest --dl-base "$DL_BASE" --out "$SRV"
|
|
347
|
+
echo " ⇒ 下一步:把 ${SRV} 随 whyshu-svr 一起部署(rsync + pm2 startOrReload whyshu-svr)"
|
|
348
|
+
exit 0
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# ── 模式 A:只体检(--check-dl)/ 只同步(--publish-dl)——不打 tag、不 push ──
|
|
352
|
+
if [[ "$MODE" != "release" ]]; then
|
|
353
|
+
cd "$REPO_DIR"
|
|
354
|
+
echo "── dl/ 体检(仓库 ↔ ${SSH_HOST}:${DL_DIR})──"
|
|
355
|
+
DRIFT=0
|
|
356
|
+
for f in "${CORE_FILES[@]}"; do
|
|
357
|
+
[[ -f "$f" ]] || { echo " ⚠ 仓库无 ${f}(跳过)"; continue; }
|
|
358
|
+
l="$(md5_of "$f")"; r="$(remote_md5_of "$f")"; h="$(http_md5_of "$f")"
|
|
359
|
+
tag="✓"
|
|
360
|
+
if [[ -z "$r" ]]; then tag="✗"; DRIFT=1; echo " ✗ ${f}:服务器上没有这个文件"; continue; fi
|
|
361
|
+
[[ "$l" == "$r" ]] || { tag="✗"; DRIFT=1; }
|
|
362
|
+
note=""
|
|
363
|
+
[[ "$l" == "$r" ]] || note="(服务器 ${r:0:8} vs 仓库 ${l:0:8})"
|
|
364
|
+
if [[ -n "$h" && "$h" != "$l" ]]; then note="${note}(HTTP 下发 ${h:0:8})"; DRIFT=1; tag="✗"; fi
|
|
365
|
+
# 2026-10-09:HTTP 拉不到内容同样判红(实测事故:rsync -a 把开发树的 600 带进生产 ⇒ nginx 403
|
|
366
|
+
# ⇒ 官网那条 `curl .../dl/install-argent.mjs | node` 一行命令当场废掉,而旧逻辑把空串当"没有证据"放过)。
|
|
367
|
+
if [[ -z "$h" ]]; then tag="✗"; DRIFT=1; note="${note}(**HTTP 取不到下发内容**:多为文件权限 ⇒ nginx 403;修:chmod 644)"; fi
|
|
368
|
+
echo " ${tag} ${f} ${l:0:8}${note}"
|
|
369
|
+
done
|
|
370
|
+
# ── 能力包档(2026-10-09 新增):安装器之外,**能力包也要逐个核**。见 check_packs 注释 ──
|
|
371
|
+
# 只在"体检"语义下把结果并进 DRIFT;--publish-dl 不改包(要重发用 --publish-packs),
|
|
372
|
+
# 所以那一档只报告、不据此早退(否则会挡住它本来要干的"修安装器 403")。
|
|
373
|
+
check_packs "$(pick_py_light || true)"
|
|
374
|
+
if [[ "$MODE" == "check-dl" && "$PACK_DRIFT" -ne 0 ]]; then DRIFT=1; fi
|
|
375
|
+
if [[ "$DRIFT" -eq 0 ]]; then
|
|
376
|
+
echo "✅ dl/ 与仓库一致(含 HTTP 下发内容一致 + 能力包内容摘要一致)"
|
|
377
|
+
exit 0
|
|
378
|
+
fi
|
|
379
|
+
if [[ "$MODE" == "check-dl" ]]; then
|
|
380
|
+
echo "✗ dl/ 与仓库**不一致**——修法:bash scripts/release-argent.sh --publish-dl(应急同步安装器,不打 tag)"
|
|
381
|
+
echo " 能力包不符则用:bash scripts/release-argent.sh --publish-packs(重建+上传+刷新服务端清单)"
|
|
382
|
+
echo " 或走正式发布:bash scripts/release-argent.sh <版本>"
|
|
383
|
+
exit 1
|
|
384
|
+
fi
|
|
385
|
+
echo
|
|
386
|
+
echo "── 同步分发文件(--publish-dl:不打 tag、不 push)──"
|
|
387
|
+
for f in "${CORE_FILES[@]}"; do
|
|
388
|
+
[[ -f "$f" ]] || continue
|
|
389
|
+
if [[ "$(md5_of "$f")" == "$(remote_md5_of "$f")" ]]; then
|
|
390
|
+
echo " · ${f} 已一致,跳过"
|
|
391
|
+
else
|
|
392
|
+
# ⛔ 红线:投递禁用 `scp`(见脚本头"投递纪律")⇒ 一律 rsync。
|
|
393
|
+
rsync -az --no-owner --no-group -e "ssh -i $SSH_KEY -o ConnectTimeout=10" "$f" "${SSH_HOST}:${DL_DIR}/${f}"
|
|
394
|
+
echo " ✓ ${f} → ${DL_DIR}/${f}"
|
|
395
|
+
fi
|
|
396
|
+
# ⭐ 模式归一**无条件**执行(2026-10-09 实测):内容一致 ⇒ rsync 跳过 ⇒ **权限照旧**,
|
|
397
|
+
# 600 的文件会一直 403(本次反证就是"已一致,跳过"之后 HTTP 仍取不到 ⇒ 复核判红)。
|
|
398
|
+
# "内容对"与"用户拿得到"是两件事,投递必须两件都保证。
|
|
399
|
+
remote_chmod 644 "${DL_DIR}/${f}"
|
|
400
|
+
done
|
|
401
|
+
echo
|
|
402
|
+
echo "── 复核(服务器 md5 + HTTP 下发内容)──"
|
|
403
|
+
OK=1
|
|
404
|
+
for f in "${CORE_FILES[@]}"; do
|
|
405
|
+
[[ -f "$f" ]] || continue
|
|
406
|
+
l="$(md5_of "$f")"; r="$(remote_md5_of "$f")"; h="$(http_md5_of "$f")"
|
|
407
|
+
if [[ "$l" == "$r" && -n "$h" && "$h" == "$l" ]]; then
|
|
408
|
+
echo " ✓ ${f} 一致(${l:0:8};HTTP 下发同字节)"
|
|
409
|
+
else
|
|
410
|
+
echo " ✗ ${f} 仍不一致:仓库 ${l:0:8} / 服务器 ${r:0:8} / HTTP ${h:0:8}"
|
|
411
|
+
[[ -n "$h" ]] || echo " ↳ HTTP 取不到内容(403?)—— 面向用户的那条命令会直接失败:服务器上 chmod 644 ${DL_DIR}/${f}"
|
|
412
|
+
OK=0
|
|
413
|
+
fi
|
|
414
|
+
done
|
|
415
|
+
[[ "$OK" -eq 1 ]] || exit 1
|
|
416
|
+
if [[ "$PACK_DRIFT" -ne 0 ]]; then
|
|
417
|
+
echo "✅ 安装器三点已与仓库对齐;但**能力包档有 ✗**(本模式不改包)⇒ 跑:bash scripts/release-argent.sh --publish-packs"
|
|
418
|
+
exit 0
|
|
419
|
+
fi
|
|
420
|
+
echo "✅ dl/ 已与仓库对齐(下次正式发布仍会再同步一遍,不冲突)"
|
|
421
|
+
exit 0
|
|
422
|
+
fi
|
|
423
|
+
VERSION="${VERSION_ARG#v}" # 容错:0.7.1 或 v0.7.1 均可
|
|
424
|
+
TAG="v${VERSION}"
|
|
425
|
+
|
|
426
|
+
# ── 1. 校验 ──────────────────────────────────────────────────────────────────
|
|
427
|
+
cd "$REPO_DIR"
|
|
428
|
+
echo "── 发布校验(版本 ${VERSION})──"
|
|
429
|
+
FAIL=0
|
|
430
|
+
|
|
431
|
+
# 1a. VERSION 文件与参数一致
|
|
432
|
+
if [[ -f VERSION ]]; then
|
|
433
|
+
CUR="$(cat VERSION | tr -d '[:space:]')"
|
|
434
|
+
if [[ "$CUR" != "$VERSION" ]]; then
|
|
435
|
+
echo "✗ VERSION 文件内容(${CUR})与参数(${VERSION})不一致——请先更新 VERSION 并提交"
|
|
436
|
+
FAIL=1
|
|
437
|
+
else
|
|
438
|
+
echo "✓ VERSION = ${VERSION}(一致)"
|
|
439
|
+
fi
|
|
440
|
+
else
|
|
441
|
+
echo "✗ 仓库无 VERSION 文件"
|
|
442
|
+
FAIL=1
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
# 1b. git working tree clean
|
|
446
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
447
|
+
echo "✗ git working tree 有未提交改动:"
|
|
448
|
+
git status --porcelain | head -10
|
|
449
|
+
FAIL=1
|
|
450
|
+
else
|
|
451
|
+
echo "✓ git working tree clean"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# 1c. CHANGELOG.md 含对应版本条目(缺则警告,不阻断——发布候选可后补)
|
|
455
|
+
# 判据兼容两种抬头:`## v0.9.9 xxx`(旧写法)与 `## 0.9.9(日期)`(≥0.7.3 现行写法)。
|
|
456
|
+
# 原判据写死 `^## v${VERSION} `(v + 半角空格)⇒ 自 0.7.3 起恒不匹配 ⇒ 门槛 3 实际"永远只警告"。
|
|
457
|
+
V_RE="${VERSION//./\\.}"
|
|
458
|
+
if [[ -f CHANGELOG.md ]] && grep -qE "^## (v)?${V_RE}([ ((]|$)" CHANGELOG.md; then
|
|
459
|
+
echo "✓ CHANGELOG.md 含 ## ${VERSION} 条目"
|
|
460
|
+
else
|
|
461
|
+
echo "⚠ CHANGELOG.md 缺 ## ${VERSION} 条目——建议先补充(发布门槛,docs/release.md)"
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
# 1d. tag 未存在
|
|
465
|
+
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
|
466
|
+
echo "✗ tag ${TAG} 已存在($(git log -1 --format='%h %s' "${TAG}"))——如需重新发布请删除旧 tag"
|
|
467
|
+
FAIL=1
|
|
468
|
+
else
|
|
469
|
+
echo "✓ tag ${TAG} 可创建"
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
# 1e. push 目标 remote 存在
|
|
473
|
+
if [[ -z "$REMOTE" ]]; then
|
|
474
|
+
echo "✗ 仓库无 git remote(无法 push tag)——可用 ARGENT_RELEASE_REMOTE 指定"
|
|
475
|
+
FAIL=1
|
|
476
|
+
else
|
|
477
|
+
echo "✓ push 目标 remote = ${REMOTE}"
|
|
478
|
+
fi
|
|
479
|
+
|
|
480
|
+
if [[ $FAIL -eq 1 ]]; then
|
|
481
|
+
echo "✗ 校验未通过,发布中止(可先 --dry-run 排查)"
|
|
482
|
+
exit 1
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
# ── 2. 计划 / dry-run ────────────────────────────────────────────────────────
|
|
486
|
+
echo
|
|
487
|
+
echo "── 发布计划(版本 ${VERSION},tag ${TAG})──"
|
|
488
|
+
echo " 1. git tag ${TAG} + git push ${REMOTE} ${TAG}"
|
|
489
|
+
echo " 2. rsync → ${SSH_HOST}:${DL_DIR}/:${CORE_FILES[*]}"
|
|
490
|
+
if [[ $FULL -eq 1 ]]; then
|
|
491
|
+
echo " 3. rsync 目录 → ${SSH_HOST}:${DL_DIR}/:${DIR_FILES[*]}(--full,覆盖服务器同名内容)"
|
|
492
|
+
else
|
|
493
|
+
echo " 3. (未指定 --full:skills/ plugins/ 目录不同步——服务器有独立静态资源)"
|
|
494
|
+
fi
|
|
495
|
+
echo " 4. 验证:本地/服务器 md5 + curl -sI ${DL_URL}/install-argent.mjs"
|
|
496
|
+
|
|
497
|
+
if [[ $DRY_RUN -eq 1 ]]; then
|
|
498
|
+
echo
|
|
499
|
+
echo "✅ [dry-run] 校验通过,计划如上(未打 tag、未同步)。确认后执行:bash release-argent.sh ${VERSION}"
|
|
500
|
+
exit 0
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
# ── 3. 执行 ──────────────────────────────────────────────────────────────────
|
|
504
|
+
echo
|
|
505
|
+
echo "── 打 tag + push ──"
|
|
506
|
+
git tag "${TAG}"
|
|
507
|
+
git push "${REMOTE}" "${TAG}"
|
|
508
|
+
echo "✓ tag ${TAG} 已 push"
|
|
509
|
+
|
|
510
|
+
echo
|
|
511
|
+
echo "── 同步分发文件到服务器 ──"
|
|
512
|
+
SSH_ARGS=(-i "$SSH_KEY" -o ConnectTimeout=10 "$SSH_HOST")
|
|
513
|
+
# ⛔ 红线:投递禁用 `scp`(见脚本头"投递纪律")⇒ 一律 rsync;目录投递(--full)用的是同一形态。
|
|
514
|
+
RSYNC_SSH="ssh -i $SSH_KEY -o ConnectTimeout=10"
|
|
515
|
+
for f in "${CORE_FILES[@]}"; do
|
|
516
|
+
if [[ ! -f "$f" ]]; then
|
|
517
|
+
echo "⚠ 仓库无 ${f}(跳过)"
|
|
518
|
+
continue
|
|
519
|
+
fi
|
|
520
|
+
rsync -az --no-owner --no-group -e "$RSYNC_SSH" "$f" "${SSH_HOST}:${DL_DIR}/${f}"
|
|
521
|
+
remote_chmod 644 "${DL_DIR}/${f}" # 模式归一(600 ⇒ nginx 403)
|
|
522
|
+
echo " ✓ ${f} → ${DL_DIR}/${f}"
|
|
523
|
+
done
|
|
524
|
+
if [[ $FULL -eq 1 ]]; then
|
|
525
|
+
for d in "${DIR_FILES[@]}"; do
|
|
526
|
+
if [[ ! -d "$d" ]]; then
|
|
527
|
+
echo "⚠ 仓库无 ${d}/(跳过)"
|
|
528
|
+
continue
|
|
529
|
+
fi
|
|
530
|
+
rsync -az --no-owner --no-group -e "$RSYNC_SSH" "$d/" "${SSH_HOST}:${DL_DIR}/${d}/"
|
|
531
|
+
# 目录面:`a+rX` = 目录可进入/可列举、文件可读(可执行位只给本来就有的)——同样"开发树模式不进生产"
|
|
532
|
+
remote_chmod -R a+rX "${DL_DIR}/${d}"
|
|
533
|
+
echo " ✓ ${d}/ → ${DL_DIR}/${d}/(--full 覆盖)"
|
|
534
|
+
done
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
# ── 4. 验证 ──────────────────────────────────────────────────────────────────
|
|
538
|
+
echo
|
|
539
|
+
echo "── 验证 ──"
|
|
540
|
+
VERIFY_OK=1
|
|
541
|
+
for f in "${CORE_FILES[@]}"; do
|
|
542
|
+
[[ -f "$f" ]] || continue
|
|
543
|
+
local_md5="$(md5 -q "$f" 2>/dev/null || md5sum "$f" | awk '{print $1}')"
|
|
544
|
+
remote_md5="$(ssh "${SSH_ARGS[@]}" "md5sum ${DL_DIR}/${f} 2>/dev/null | awk '{print \$1}'" 2>/dev/null || echo '')"
|
|
545
|
+
if [[ "$local_md5" == "$remote_md5" && -n "$remote_md5" ]]; then
|
|
546
|
+
echo " ✓ ${f} md5 一致(${local_md5})"
|
|
547
|
+
else
|
|
548
|
+
echo " ✗ ${f} md5 不一致:本地 ${local_md5} vs 服务器 ${remote_md5}"
|
|
549
|
+
VERIFY_OK=0
|
|
550
|
+
fi
|
|
551
|
+
done
|
|
552
|
+
http_code="$(curl -sI -o /dev/null -w '%{http_code}' "${DL_URL}/install-argent.mjs")"
|
|
553
|
+
if [[ "$http_code" == "200" ]]; then
|
|
554
|
+
echo " ✓ ${DL_URL}/install-argent.mjs HTTP ${http_code}"
|
|
555
|
+
else
|
|
556
|
+
echo " ✗ ${DL_URL}/install-argent.mjs HTTP ${http_code}(期望 200)"
|
|
557
|
+
VERIFY_OK=0
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
# ── 5. 发布报告 ─────────────────────────────────────────────────────────────
|
|
561
|
+
echo
|
|
562
|
+
echo "════════ 发布报告 ════════"
|
|
563
|
+
echo "版本: ${VERSION}"
|
|
564
|
+
echo "tag: ${TAG}($(git log -1 --format='%h %s' "${TAG}"))"
|
|
565
|
+
echo "提交: $(git log -1 --format='%h %s' HEAD)"
|
|
566
|
+
if [[ $FULL -eq 1 ]]; then
|
|
567
|
+
echo "同步: ${CORE_FILES[*]} + 目录 ${DIR_FILES[*]}"
|
|
568
|
+
else
|
|
569
|
+
echo "同步: ${CORE_FILES[*]}(skills/ plugins/ 未同步,--full 可选)"
|
|
570
|
+
fi
|
|
571
|
+
echo "验证: $([[ $VERIFY_OK -eq 1 ]] && echo '全部通过' || echo '存在失败(见上)')"
|
|
572
|
+
echo "════════════════════════"
|
|
573
|
+
[[ $VERIFY_OK -eq 1 ]] || exit 1
|
|
574
|
+
echo "✅ 发布完成:${TAG} 已发布(dl/ 分发文件已同步并验证)"
|