@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.
Files changed (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * daily-report-reconcile.mjs —— 「为什么和平台概览差几个数」的**只读**对账工具(2026-10-12)。
4
+ *
5
+ * ## 它回答什么
6
+ * 客户在平台概览看到「销售量 602」,卡片上写「订单数 595」⇒ 问"为什么有出入"。本工具用
7
+ * **与日报同一条**取数/聚合实现(`lib/daily-report-core.mjs`,⛔ 不复制第二份口径)把同一窗口
8
+ * 逐候选重算一遍,并排给出:
9
+ * ① 我方口径(订单/件数/销售额/取消/客单价);② 候选口径对照表(仅付款已批准 / 含发货信息 /
10
+ * 含取消(全部创建)/ 未付款数 / 唯一买家数 / 件数 …);③ 窗口平移敏感度(±1h/±2h/±3h);
11
+ * ④ 与快照的漂移(【报告时 vs 现在】—— 「平台会事后改历史订单状态」的直接证据);
12
+ * ⑤ 给了 `--platform-kpi` ⇒ 命中哪个候选 / 「无候选命中 ⇒ 差异不是口径而是时点或平台未知口径」。
13
+ *
14
+ * ## 用法
15
+ * node scripts/daily-report-reconcile.mjs --store <名或 uid> --date YYYY-MM-DD \
16
+ * [--platform-kpi "订单数量=602,已售件数=640"] [--json] \
17
+ * [--api-base <url>] [--env <path>] [--allow-token-refresh]
18
+ * · `--date` **支持任意历史日**(窗口 = 该日在站点本地时区的自然日,与当天日报给客户的窗口一致);
19
+ * · `--platform-kpi` 也接受 JSON:`--platform-kpi '{"订单数量":602}'`;
20
+ * · `--json` 只把结构化结果打到 stdout(日志一律 stderr);`--help` 纯读即退。
21
+ *
22
+ * ## 退出码
23
+ * 0 = 有候选命中,或(没给 `--platform-kpi` 时)漂移对照已给出 ⇒ 差异有解释;
24
+ * 2 = **无候选命中**(给了 KPI 却谁都不等于它 ⇒ 差异是时点/平台未知口径,可判红,别硬掰口径);
25
+ * 1 = 用法错/店铺匹配不到/取数失败;3 = 凭据不可用(未开启刷新)。
26
+ *
27
+ * ## 只读纪律(本文件的红线)
28
+ * · ⛔ 不写任何客户表、⛔ 不写快照(读快照只为对照"报告时");
29
+ * · token:优先用凭据文件里**仍有效**的 `access_token`(剩余 > 15min);
30
+ * 确实需要 refresh 时**必须**把轮换后的 `refresh_token` **原子写回**(tmp + rename,
31
+ * 保持原文件权限)—— 否则下次日报会因旧 token 撞 429(2026-09-04/05 断供的根因);
32
+ * 该写回**必须显式开开关**:`--allow-token-refresh`;默认只读失败退出并给下一步提示;
33
+ * · 唯一会写盘的路径 = `persistStoreAtomic()`,且只在 `--allow-token-refresh` 分支被调用。
34
+ *
35
+ * ## API 基址从哪来(引擎侧不硬编码平台域名)
36
+ * `--api-base` → `ML_API_BASE` → **快照里记录的 `apiBase`**(抓取时刻的事实)→ 同店任一快照的
37
+ * `apiBase` → 报错。`vertical-lint` 的 engine-words 档只拦新增业务词且 ⛔ 不抬基线,所以
38
+ * 引擎面新文件里不能出现平台域名;快照带 `apiBase` 正好也是"数据来源可追溯"该有的样子。
39
+ */
40
+ import { readFileSync, writeFileSync, renameSync, existsSync, statSync, readdirSync } from 'node:fs'
41
+ import { join } from 'node:path'
42
+ import { resolveDataDir } from './lib/argent-datadir.mjs'
43
+ import { isDirectRun } from './lib/is-direct-run.mjs'
44
+ import {
45
+ SITE_TZ, SITE_CURRENCY, fallbackSite,
46
+ reportWindows, aggregateOrders, candidateMetrics, windowShiftSensitivity,
47
+ parsePlatformKpi, matchCandidates, localStamp, isoWithOffset,
48
+ loadEnvFile, matchStores, storeKey, tokenLeftMs, refreshStoreToken, fetchOrders,
49
+ } from './lib/daily-report-core.mjs'
50
+
51
+ const argv = process.argv.slice(2)
52
+ const has = (f) => argv.includes(f)
53
+ const valOf = (f) => {
54
+ const i = argv.indexOf(f)
55
+ return i >= 0 && argv[i + 1] !== undefined ? argv[i + 1] : null
56
+ }
57
+
58
+ const USAGE = `日报口径只读对账(解释"为什么和平台差几个数")
59
+
60
+ 用法:
61
+ node scripts/daily-report-reconcile.mjs --store <名或 uid> --date YYYY-MM-DD [选项]
62
+
63
+ 选项:
64
+ --store <名或 uid> 必填;名称子串或 uid 全等(与日报 --store 同一份匹配判据)
65
+ --date YYYY-MM-DD 必填;支持任意历史日(窗口 = 该日站点本地自然日)
66
+ --platform-kpi <spec> "订单数量=602,已售件数=640" 或 JSON {"订单数量":602}
67
+ --json 只输出结构化 JSON(stdout),日志走 stderr
68
+ --api-base <url> API 基址(缺省取 ML_API_BASE,再取快照记录的 apiBase)
69
+ --env <path> 凭据 env 文件(缺省 <数据目录>/daily-report.env)
70
+ --allow-token-refresh 允许刷新 access_token,并**原子写回**轮换后的 refresh_token
71
+ --help 打印本用法即退(纯读,不读凭据、不联网)
72
+
73
+ 退出码: 0 有命中/漂移已解释 | 2 无候选命中 | 1 用法或取数失败 | 3 凭据不可用(未开启刷新)
74
+ `
75
+
76
+ if (has('-h') || has('--help')) {
77
+ console.log(USAGE.trimEnd())
78
+ process.exit(0)
79
+ }
80
+
81
+ function die(msg, code = 1) {
82
+ console.error(`✗ ${msg}`)
83
+ process.exit(code)
84
+ }
85
+
86
+ const STORE_ARG = valOf('--store')
87
+ const DATE_ARG = valOf('--date')
88
+ if (!STORE_ARG || !DATE_ARG) {
89
+ console.error(USAGE.trimEnd())
90
+ die('缺少 --store 或 --date(用法见上)', 1)
91
+ }
92
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(DATE_ARG)) die(`--date 需要 YYYY-MM-DD(实得 ${DATE_ARG})`, 1)
93
+
94
+ const FLAG_JSON = has('--json')
95
+ const FLAG_ALLOW_REFRESH = has('--allow-token-refresh')
96
+ const ARG_API_BASE = valOf('--api-base')
97
+ const DATA_DIR = resolveDataDir({ env: process.env }).dir
98
+ const ENV_PATH = valOf('--env') || join(DATA_DIR, 'daily-report.env')
99
+ const STORES_PATH = process.env.ML_API_STORES_JSON || join(DATA_DIR, 'ml-api-stores.json')
100
+ const CACHE_DIR = join(DATA_DIR, 'daily-report-cache')
101
+ const snapshotPath = (date, store) => join(CACHE_DIR, `${date}-${storeKey(store)}.json`)
102
+
103
+ function readJson(path, label) {
104
+ try { return JSON.parse(readFileSync(path, 'utf8')) } catch (e) {
105
+ die(`读不到${label} ${path}:${e.message}`, 1)
106
+ }
107
+ }
108
+
109
+ // ── 只读:先定位店铺与快照 ────────────────────────────────
110
+ if (!existsSync(STORES_PATH)) die(`未找到店铺授权文件 ${STORES_PATH}(先在该店跑一次初始化/授权)`, 1)
111
+ const allStores = (readJson(STORES_PATH, '店铺授权文件').stores) || []
112
+ const matched = matchStores(allStores, [STORE_ARG])
113
+ if (matched.length !== 1) {
114
+ die(matched.length
115
+ ? `--store ${STORE_ARG} 匹配到 ${matched.length} 家(${matched.map((x) => x.name).join('、')})⇒ 请再具体一点`
116
+ : `--store ${STORE_ARG} 没匹配到店铺|可用:${allStores.map((x) => x.name).join(' / ') || '(无)'}`, 1)
117
+ }
118
+ const store = matched[0]
119
+ const site = fallbackSite(store.site)
120
+ const tz = SITE_TZ[site]
121
+
122
+ const now = new Date()
123
+ const reportDate = new Date(`${DATE_ARG}T12:00:00Z`)
124
+ // 对账窗口 = 该日**站点本地自然日** [D 00:00, D+1 00:00) —— 与当天日报给客户的窗口逐字同一个
125
+ // (日报的窗口是 [报告日 00:00, 今天 00:00),而报告日 = 昨天 ⇒ 两者等价)。跨任意历史日都对。
126
+ const W = reportWindows(store.site, reportDate, new Date(reportDate.getTime() + 86400000),
127
+ { salesWindowDays: (loadEnvFile(ENV_PATH).SALES_WINDOW_DAYS ?? process.env.SALES_WINDOW_DAYS) })
128
+ const windows = { weekStartMs: W.weekStart, reportStartMs: W.reportStart, todayStartMs: W.todayStart }
129
+ const env = { ...loadEnvFile(ENV_PATH), ...process.env }
130
+
131
+ const snapshotFile = snapshotPath(DATE_ARG, store)
132
+ const snapshot = existsSync(snapshotFile) ? readJson(snapshotFile, '日报快照') : null
133
+
134
+ // ── API 基址:显式 → env → 快照记录 → 同店任一快照 → 报错 ──────────
135
+ function apiBaseFromCache() {
136
+ try {
137
+ for (const f of readdirSync(CACHE_DIR)) {
138
+ if (!f.endsWith(`-${storeKey(store)}.json`)) continue
139
+ try {
140
+ const d = JSON.parse(readFileSync(join(CACHE_DIR, f), 'utf8'))
141
+ if (d && d.apiBase) return { base: d.apiBase, from: `同店快照 ${join(CACHE_DIR, f)}` }
142
+ } catch { /* 坏快照跳过 */ }
143
+ }
144
+ } catch { /* 缓存目录不存在 */ }
145
+ return null
146
+ }
147
+ function resolveBase() {
148
+ if (ARG_API_BASE) return { base: ARG_API_BASE, from: '--api-base' }
149
+ if (process.env.ML_API_BASE) return { base: process.env.ML_API_BASE, from: 'ML_API_BASE' }
150
+ if (snapshot && snapshot.apiBase) return { base: snapshot.apiBase, from: `快照 ${snapshotFile}` }
151
+ const c = apiBaseFromCache()
152
+ if (c) return c
153
+ return die('拿不到 API 基址:请给 --api-base 或设 ML_API_BASE,或让该店至少有一份带 apiBase 的日报快照', 1)
154
+ }
155
+
156
+ /**
157
+ * 唯一会写盘的路径:把轮换后的 token **原子写回**凭据文件(tmp + rename,保持原权限)。
158
+ * 只在 `--allow-token-refresh` 分支被调用(见下方 `collect()`)。
159
+ */
160
+ function persistStoreAtomic(rotated) {
161
+ const doc = JSON.parse(readFileSync(STORES_PATH, 'utf8'))
162
+ const list = doc.stores || []
163
+ const i = list.findIndex((x) => String(x.user_id) === String(rotated.user_id))
164
+ if (i < 0) { console.error(`⚠️ 写回:授权文件里找不到 uid=${rotated.user_id},跳过(未写盘)`); return null }
165
+ list[i] = {
166
+ ...list[i],
167
+ access_token: rotated.access_token,
168
+ access_expires_at: rotated.access_expires_at,
169
+ refresh_token: rotated.refresh_token,
170
+ }
171
+ const tmp = `${STORES_PATH}.tmp`
172
+ let mode = 0o600
173
+ try { mode = statSync(STORES_PATH).mode & 0o777 } catch { /* 兜底 600 */ }
174
+ writeFileSync(tmp, JSON.stringify({ stores: list }, null, 2) + '\n', { mode })
175
+ renameSync(tmp, STORES_PATH)
176
+ return tmp
177
+ }
178
+
179
+ // ── 取数(共用实现)────────────────────────────────────────
180
+ async function collect() {
181
+ const { base, from } = resolveBase()
182
+ const left = tokenLeftMs(store)
183
+ const fresh = left != null && left > 15 * 60 * 1000
184
+ if (!fresh && !FLAG_ALLOW_REFRESH) {
185
+ die(`凭据不可用(access_token 剩余 ${left == null ? '未知' : Math.round(left / 1000) + 's'},需 > 900s)`
186
+ + '\n ⇒ 本工具**默认只读**:不刷新、不写凭据文件。'
187
+ + '\n ⇒ 要么先跑一次日报(它会刷新并原子写回),要么显式 --allow-token-refresh(会原子写回轮换后的 refresh_token)。', 3)
188
+ }
189
+ const token = await refreshStoreToken(env, store, {
190
+ base, allowRefresh: FLAG_ALLOW_REFRESH, onRotate: persistStoreAtomic,
191
+ })
192
+ const orders = await fetchOrders(token, store.user_id,
193
+ new Date(windows.reportStartMs).toISOString(), new Date(windows.todayStartMs).toISOString(), { base })
194
+ return { orders, base, from, refreshed: !fresh }
195
+ }
196
+
197
+ // ── 输出 ───────────────────────────────────────────────────
198
+ const num = (n) => (n == null ? '—' : Number(n).toLocaleString('en-US'))
199
+ const money = (code, n) => (n == null ? '—' : `${code} ${Number(n).toLocaleString('en-US', { maximumFractionDigits: 2 })}`)
200
+ /** 中文/ASCII 混排的定宽:中文按 2 列计(表格对齐用;对齐只为好读,不参与判据)。 */
201
+ const widthOf = (s) => [...String(s)].reduce((a, c) => a + (/[\u4e00-\u9fff\uff00-\uffef]/.test(c) ? 2 : 1), 0)
202
+ const padTo = (s, w) => s + ' '.repeat(Math.max(0, w - widthOf(s)))
203
+
204
+ async function main() {
205
+ const { orders, base, from, refreshed } = await collect()
206
+ const m = candidateMetrics(orders, windows, { tz })
207
+ const shift = windowShiftSensitivity(orders, windows)
208
+ const kpis = parsePlatformKpi(valOf('--platform-kpi'))
209
+ const verdict = matchCandidates(m, kpis)
210
+ const code = kpis.length && verdict.misses.length ? 2 : 0
211
+
212
+ // ── 漂移:【报告时(快照) vs 现在】 ──────────────────────
213
+ const snap = snapshot && snapshot.summary
214
+ ? { orders: snapshot.summary.orders, units: snapshot.summary.units, cancelled: snapshot.summary.cancelled, gmv: snapshot.summary.gmv }
215
+ : null
216
+ const nowM = { orders: m.summary.orders, units: m.summary.units, cancelled: m.summary.cancelled, gmv: m.summary.gmv }
217
+ const drift = snap ? {
218
+ orders: { was: snap.orders, now: nowM.orders, delta: nowM.orders - snap.orders },
219
+ units: { was: snap.units, now: nowM.units, delta: nowM.units - snap.units },
220
+ cancelled: { was: snap.cancelled, now: nowM.cancelled, delta: nowM.cancelled - snap.cancelled },
221
+ } : null
222
+ const capturedLocal = (snapshot && (snapshot.capturedAtLocal
223
+ || (snapshot.capturedAt ? localStamp(tz, new Date(snapshot.capturedAt)) : null))) || null
224
+ const win = {
225
+ tz,
226
+ fromLocal: `${DATE_ARG} 00:00`,
227
+ toLocal: `${m.window.toLocal} 00:00`,
228
+ from: new Date(windows.reportStartMs).toISOString(),
229
+ to: new Date(windows.todayStartMs).toISOString(),
230
+ }
231
+
232
+ if (FLAG_JSON) {
233
+ console.log(JSON.stringify({
234
+ store: { name: store.name, site, user_id: String(store.user_id) },
235
+ date: DATE_ARG,
236
+ window: win,
237
+ capturedAt: snapshot ? (snapshot.capturedAt || null) : null,
238
+ capturedAtLocal: capturedLocal,
239
+ snapshotFile: snapshot ? snapshotFile : null,
240
+ apiBase: base,
241
+ apiBaseFrom: from,
242
+ tokenRefreshed: refreshed,
243
+ fetchedOrders: orders.length,
244
+ reportAt: snap,
245
+ now: nowM,
246
+ drift,
247
+ candidates: m.candidates,
248
+ values: m.values,
249
+ counts: m.counts,
250
+ shift,
251
+ platformKpi: kpis,
252
+ verdict,
253
+ exitCode: code,
254
+ }, null, 2))
255
+ process.exit(code)
256
+ }
257
+
258
+ const L = []
259
+ L.push(`📊 **日报口径对账** · ${store.name}(${site})· ${DATE_ARG}`)
260
+ L.push(`抓取时刻(快照记录):${capturedLocal ? `${capturedLocal}(${tz})` : '未记录(老快照 ⇒ 回答不了"当时那一刻",只能给"现在")'}`)
261
+ L.push(`窗口:[${win.fromLocal} → ${win.toLocal}](${tz})|UTC ${win.from} → ${win.to}`)
262
+ L.push(`本次取数:${isoWithOffset(tz, now)}(重拉 ${orders.length} 笔原始订单;API 基址来源:${from}${refreshed ? ';已刷新并原子写回凭据' : ''})`)
263
+ L.push('')
264
+ L.push('【1】我方口径(同一窗口、站点本地自然日)')
265
+ L.push(`订单数 ${num(m.summary.orders)} | 已售件数 ${num(m.summary.units)} | 销售额 ${money(SITE_CURRENCY[site] || 'USD', m.summary.gmv)}`
266
+ + ` | 取消 ${num(m.summary.cancelled)} | 客单价 ${num(m.summary.avg != null ? Math.round(m.summary.avg) : null)}`)
267
+ L.push(`(窗口内原始订单 ${num(m.counts.window)} 笔 = 有效 ${num(m.counts.valid)} + 取消 ${num(m.counts.cancelled)} + 未付款 ${num(m.counts.paymentRequired)})`)
268
+ L.push('')
269
+ L.push('【2】候选口径对照表(同一窗口,逐候选重算)')
270
+ const w0 = Math.max(...m.candidates.map((c) => widthOf(c.key))) + 2
271
+ for (const c of m.candidates) L.push(` ${padTo(c.key, w0)}${num(c.value)} ${c.desc}`)
272
+ L.push('')
273
+ L.push('【3】窗口平移敏感度(我方口径的订单数;看"边界时刻"能差多少)')
274
+ const shifts = [...shift.shifts.filter((s) => s.hours < 0), { hours: 0, orders: shift.base },
275
+ ...shift.shifts.filter((s) => s.hours > 0)]
276
+ L.push(' ' + shifts.map((s) => `${s.hours === 0 ? '基准' : (s.hours > 0 ? '+' : '') + s.hours + 'h'} ${num(s.orders)}`).join(' | '))
277
+ L.push('')
278
+ L.push('【4】与快照的漂移(【报告时 vs 现在】—— 平台会**事后变更**历史订单状态的直接证据)')
279
+ if (!drift) {
280
+ L.push(` 无快照(${snapshotFile} 不存在)⇒ 只能给"现在",无从对照"报告时"`)
281
+ } else {
282
+ for (const [k, label] of [['orders', '订单数'], ['units', '已售件数'], ['cancelled', '取消']]) {
283
+ const d = drift[k]
284
+ L.push(` ${padTo(label, 10)}${num(d.was)} → ${num(d.now)}(${d.delta > 0 ? '+' : ''}${num(d.delta)})`)
285
+ }
286
+ L.push(' ⚠️ 平台侧同一指标也在滚动更新 ⇒ **跨时点比较必然可能差个位数**(不是口径错,是快照时点不同)')
287
+ }
288
+ L.push('')
289
+ L.push('【5】平台 KPI 判定')
290
+ if (!kpis.length) {
291
+ L.push(' (未给 --platform-kpi ⇒ 只给上面的事实,不下判定)')
292
+ } else {
293
+ for (const h of verdict.hits) L.push(` ${h.label} = ${num(h.value)} ⇒ ✅ 命中候选:${h.matched.join('、')}`)
294
+ for (const mi of verdict.misses) L.push(` ${mi.label} = ${num(mi.value)} ⇒ ❌ 无候选命中(一个候选都不等于它)`)
295
+ L.push(verdict.misses.length
296
+ ? ' ⇒ **差异不是口径,而是时点(快照 vs 现在)或平台侧另有口径(平台概览标签未知)**'
297
+ : ' ⇒ 差异可由候选口径解释(见上命中项)')
298
+ }
299
+ L.push('')
300
+ L.push(`结论:${verdict.misses.length
301
+ ? '对不上不是"算错",是**时点 / 平台口径**差异 —— 请与客户核对"同一时刻 + 同一标签"再比。'
302
+ : (drift ? '差异已解释(见【4】漂移与【5】命中项)。' : '已给出同一窗口的逐候选口径;未给平台 KPI ⇒ 不下判定。')}`)
303
+ console.log(L.join('\n'))
304
+ return code
305
+ }
306
+
307
+ if (isDirectRun(import.meta.url)) {
308
+ main().then((c) => process.exit(c)).catch((err) => {
309
+ // 采集失败(限流/网络/JSON):只给错误摘要,⛔ 绝不 fallback 到推送,⛔ 不写任何文件
310
+ die(`取数失败:${err.message}`, 1)
311
+ })
312
+ }