@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,220 @@
1
+ #!/usr/bin/env python3
2
+ """货代集成契约(迭代 2 第一步,2026-09-20)。
3
+
4
+ ## 问题
5
+ "发票"是**能力**(闸门/守卫/状态机/缺口规则,与货代无关),而"某货代怎么开票"是**集成**。
6
+ 此前两者揉在一起:货代细节散落在 4 处 —— 注册表 1 条、角色 preset、提示词、
7
+ **每个货代一份 runner 脚本**(401 行 vs 1046 行,互不共享)。⇒ 接第 3 个货代要改 9 处。
8
+
9
+ ## 本模块定义什么
10
+ `scripts/integrations/<name>/adapter.py` 必须提供:
11
+
12
+ **元数据(声明式,供注册表/提示词/工作台读取)**
13
+ NAME 集成名(注册表 `integration:` 引用它)
14
+ CARRIER 货代在业务表里的**精确写法**(= `routing.match` 的值)
15
+ CAPABILITIES {'invoice': bool, 'labels': bool, 'portal_confirm': bool}
16
+ MANUAL_GATES [{'what','who','why'}] 必须人工的点(登录滑块/门户确认…)
17
+ ERROR_CLASSES [(正则, 失败类)] 门户错误文案 → retryable/needs_human/needs_agent
18
+ RUNNERS {'主入口': '脚本名'} 声明的脚本必须真实存在
19
+ WRITEBACK_FLOW 回写用的流程 id(复用同一份守卫)
20
+ TEMPLATE 模板/映射文件(可空)
21
+
22
+ **可调用(行为面,必须是纯函数、离线可测)**
23
+ match(row) -> bool 行是否属于本货代
24
+ preflight(row, cfg) -> {'ours': [], 'ops': []} 缺口分型(我方 / 运营)
25
+ plan(row, cfg) -> [{'phase','script','argv','writes'}] 可执行计划(引擎/角色据此派发)
26
+ classify_error(text) -> kind 门户文案 → 失败类(与 flow_failure 同一套 kind)
27
+
28
+ ## 边界(刻意)
29
+ 适配器**不重写既有 runner**:它只回答"这一票该怎么跑"(计划 + 分型 + 错误词表),
30
+ 真正执行仍是既有脚本 ⇒ 行为不变、可逐票对照。
31
+
32
+ 用法:
33
+ python3 scripts/integration_contract.py --list
34
+ python3 scripts/integration_contract.py --describe wenyao
35
+ python3 scripts/integration_contract.py --validate [--json]
36
+ python3 scripts/integration_contract.py --plan wenyao --record <record_id> [--carrier 上海文鳐]
37
+ 退出码:0 通过;1 有问题;2 参数/加载错误。
38
+ """
39
+ from __future__ import annotations
40
+
41
+ import argparse
42
+ import importlib.util
43
+ import json
44
+ import pathlib
45
+ import re
46
+ import sys
47
+
48
+ HERE = pathlib.Path(__file__).resolve().parent
49
+ INTEGRATIONS_DIR = HERE / "integrations"
50
+
51
+ META_STR = ("NAME", "CARRIER", "WRITEBACK_FLOW")
52
+ STATUS_DOC = "ready | scaffold(脚手架骨架,门禁会拦)"
53
+ META_OBJ = ("CAPABILITIES", "MANUAL_GATES", "ERROR_CLASSES", "RUNNERS")
54
+ CALLABLES = ("match", "preflight", "plan", "classify_error")
55
+ KINDS = ("retryable", "needs_human", "needs_agent")
56
+ CAP_KEYS = ("invoice", "labels", "portal_confirm")
57
+
58
+
59
+ def discover() -> list:
60
+ """列出 `scripts/integrations/*/adapter.py`。"""
61
+ if not INTEGRATIONS_DIR.exists():
62
+ return []
63
+ return sorted(p.parent.name for p in INTEGRATIONS_DIR.glob("*/adapter.py"))
64
+
65
+
66
+ def load(name: str):
67
+ """按目录名加载适配器模块(不走包导入,避免 __init__ 依赖)。"""
68
+ path = INTEGRATIONS_DIR / name / "adapter.py"
69
+ if not path.exists():
70
+ raise FileNotFoundError(f"集成的适配器不存在:{path}")
71
+ spec = importlib.util.spec_from_file_location(f"integration_{name}", path)
72
+ mod = importlib.util.module_from_spec(spec)
73
+ sys.modules[spec.name] = mod
74
+ spec.loader.exec_module(mod)
75
+ return mod
76
+
77
+
78
+ def validate(name: str) -> list:
79
+ """契约校验 → 问题清单(空 = 通过)。纯静态检查 + 脚本存在性,不联网、不写表。"""
80
+ problems: list = []
81
+ try:
82
+ m = load(name)
83
+ except Exception as exc: # noqa: BLE001
84
+ return [f"加载失败:{exc}"]
85
+
86
+ # 完成度(2026-09-20):脚手架生成的骨架必须显式标 `STATUS = "scaffold"`,
87
+ # 门禁据此拦住"看起来装好了、其实没实现"的集成(比让它静默通过安全得多)。
88
+ status = str(getattr(m, "STATUS", "ready") or "ready")
89
+ if status != "ready":
90
+ problems.append(f"STATUS={status}(尚未实现:补门户细节/runner 后改为 ready)")
91
+
92
+ for k in META_STR:
93
+ if not str(getattr(m, k, "") or "").strip():
94
+ problems.append(f"缺元数据 {k}")
95
+ for k in META_OBJ:
96
+ if not hasattr(m, k):
97
+ problems.append(f"缺元数据 {k}")
98
+
99
+ caps = getattr(m, "CAPABILITIES", {}) or {}
100
+ if not isinstance(caps, dict) or not caps:
101
+ problems.append("CAPABILITIES 必须是非空 dict")
102
+ else:
103
+ unknown = [k for k in caps if k not in CAP_KEYS]
104
+ if unknown:
105
+ problems.append(f"CAPABILITIES 含未知键:{unknown}(可用 {list(CAP_KEYS)})")
106
+ if not caps.get("invoice"):
107
+ problems.append("未声明 invoice 能力(发票集成的必备项)")
108
+
109
+ gates = getattr(m, "MANUAL_GATES", None)
110
+ if not isinstance(gates, list):
111
+ problems.append("MANUAL_GATES 必须是列表(可为空,但必须显式声明)")
112
+ else:
113
+ for g in gates:
114
+ if not isinstance(g, dict) or not all(g.get(k) for k in ("what", "who")):
115
+ problems.append(f"MANUAL_GATES 条目缺 what/who:{g!r}")
116
+
117
+ classes = getattr(m, "ERROR_CLASSES", None)
118
+ if not isinstance(classes, list) or not classes:
119
+ problems.append("ERROR_CLASSES 不能为空(门户错误文案必须能被分类)")
120
+ else:
121
+ for c in classes:
122
+ if not (isinstance(c, (list, tuple)) and len(c) == 2 and c[1] in KINDS):
123
+ problems.append(f"ERROR_CLASSES 条目须为 (正则, {list(KINDS)} 之一):{c!r}")
124
+ continue
125
+ try:
126
+ re.compile(str(c[0]))
127
+ except re.error as exc:
128
+ problems.append(f"ERROR_CLASSES 正则不合法 {c[0]!r}:{exc}")
129
+
130
+ runners = getattr(m, "RUNNERS", {}) or {}
131
+ if not isinstance(runners, dict) or not runners:
132
+ problems.append("RUNNERS 必须是非空 dict({'主入口': '脚本名'})")
133
+ else:
134
+ for label, script in runners.items():
135
+ if not (HERE / str(script)).exists():
136
+ problems.append(f"RUNNERS['{label}'] 指向的脚本不存在:{script}")
137
+
138
+ tmpl = getattr(m, "TEMPLATE", "")
139
+ if tmpl and not (HERE / str(tmpl)).exists():
140
+ problems.append(f"TEMPLATE 指向的文件不存在:{tmpl}")
141
+
142
+ for fn in CALLABLES:
143
+ f = getattr(m, fn, None)
144
+ if not callable(f):
145
+ problems.append(f"缺可调用 {fn}()")
146
+ return problems
147
+
148
+
149
+ def describe(name: str) -> dict:
150
+ """给人/角色看的集成说明(工作台、提示词、SOP 都读它,避免各写一份)。"""
151
+ m = load(name)
152
+ return {
153
+ "name": getattr(m, "NAME", name),
154
+ "carrier": getattr(m, "CARRIER", ""),
155
+ "capabilities": getattr(m, "CAPABILITIES", {}),
156
+ "manual_gates": getattr(m, "MANUAL_GATES", []),
157
+ "error_classes": [[str(p), k] for p, k in (getattr(m, "ERROR_CLASSES", []) or [])],
158
+ "runners": getattr(m, "RUNNERS", {}),
159
+ "template": getattr(m, "TEMPLATE", ""),
160
+ "writeback_flow": getattr(m, "WRITEBACK_FLOW", ""),
161
+ "problems": validate(name),
162
+ }
163
+
164
+
165
+ def plan(name: str, row: dict) -> list:
166
+ m = load(name)
167
+ return m.plan(row, {})
168
+
169
+
170
+ def main(argv=None) -> int:
171
+ ap = argparse.ArgumentParser(description="货代集成契约(校验/描述/出计划)")
172
+ ap.add_argument("--list", action="store_true")
173
+ ap.add_argument("--describe")
174
+ ap.add_argument("--validate", action="store_true")
175
+ ap.add_argument("--plan")
176
+ ap.add_argument("--record", help="--plan 用:record_id")
177
+ ap.add_argument("--carrier", help="--plan 用:货代公司(缺省用适配器声明的)")
178
+ ap.add_argument("--shipment", help="--plan 用:货件号(可选)")
179
+ ap.add_argument("--json", action="store_true")
180
+ a = ap.parse_args(argv)
181
+
182
+ if a.list:
183
+ names = discover()
184
+ print(json.dumps(names, ensure_ascii=False) if a.json else " " + "、".join(names))
185
+ return 0
186
+ if a.describe:
187
+ try:
188
+ d = describe(a.describe)
189
+ except Exception as exc: # noqa: BLE001
190
+ print(f"❌ {exc}", file=sys.stderr)
191
+ return 2
192
+ print(json.dumps(d, ensure_ascii=False, indent=2))
193
+ return 1 if d["problems"] else 0
194
+ if a.validate:
195
+ out, bad = {}, 0
196
+ for n in discover():
197
+ p = validate(n)
198
+ out[n] = p
199
+ bad += 1 if p else 0
200
+ if a.json:
201
+ print(json.dumps(out, ensure_ascii=False, indent=2))
202
+ else:
203
+ for n, p in out.items():
204
+ print(f" {'✅' if not p else '❌'} {n}" + ("" if not p else ":" + ";".join(p)))
205
+ return 1 if bad else 0
206
+ if a.plan:
207
+ row = {"_id": a.record or "", "货件号": a.shipment or "", "货代公司": a.carrier or ""}
208
+ try:
209
+ steps = plan(a.plan, row)
210
+ except Exception as exc: # noqa: BLE001
211
+ print(f"❌ {exc}", file=sys.stderr)
212
+ return 2
213
+ print(json.dumps({"integration": a.plan, "steps": steps}, ensure_ascii=False, indent=2))
214
+ return 0
215
+ ap.print_help()
216
+ return 0
217
+
218
+
219
+ if __name__ == "__main__":
220
+ raise SystemExit(main())
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """上海文鳐 发票集成适配器(迭代 2)。
3
+
4
+ 货代专属事实的**唯一出处**:此前散在注册表 routing、业务线提示词、执行脚本、模板映射真源
5
+ 与技能目录五处;**脚本清单不在本注释里复述** —— 契约按下方 `RUNNERS` / `TEMPLATE` 收集
6
+ (包内容集也由它派生;注释里再抄一份就是第二份清单,只会漂移)。
7
+ 本文件只回答"这一票该怎么跑 + 怎么分型 + 错误怎么归类",**不重写 runner**(执行仍是既有脚本)。
8
+
9
+ 门户:`my.ffl-tech.com`(**对方生产系统**,每次访问先经用户确认;登录需人工过一次滑块验证)。
10
+ 契约见 `scripts/integration_contract.py`。
11
+ """
12
+ from __future__ import annotations
13
+
14
+ NAME = "wenyao"
15
+ STATUS = "ready" # ready=可用;scaffold=脚手架生成的骨架(门禁会拦)
16
+ CARRIER = "上海文鳐" # = 注册表 routing.match 的精确写法
17
+ CAPABILITIES = {"invoice": True, "labels": True, "portal_confirm": True}
18
+
19
+ MANUAL_GATES = [
20
+ {"what": "文鳐门户首次/失效登录需人工过一次滑块验证", "who": "用户(终端)",
21
+ "why": "门户是对方生产系统,自动化过验证有风险;过一次后复用持久 profile",
22
+ "how": "python3 scripts/wenyao-login-human.py(人工完成滑块后回车继续)"},
23
+ {"what": "每次门户访问前经用户确认", "who": "用户",
24
+ "why": "登录/导入/导出都在对方生产系统留真实痕迹(skills/ml-invoice 记的红线)",
25
+ "how": "会话里问一句;获批后本票按 plan() 执行"},
26
+ {"what": "HS 编码(海关编码)由运营人工维护;**材质英文不在此列**", "who": "运营",
27
+ "why": "红线**只管 HS**:系统不预填/不写候选/不自动确认。材质英文走三级解析"
28
+ "(① per-SKU 字典 → ② 通用小词典 → ③ --material-en,政策A 允许 LLM 译);"
29
+ "三级都没命中时**填报阶段自己会阻断并指路**,不要当敏感字段停下",
30
+ "how": "HS 由运营在 lip 表填好后重勾「发票就绪」;材质英文缺 ⇒ 译成英文后加 --material-en 重跑"},
31
+ ]
32
+
33
+ # 门户错误文案 → 失败类(与 flow_failure 同一套 kind;顺序即优先级)
34
+ ERROR_CLASSES = [
35
+ (r"滑块|验证码|captcha|验证未通过", "needs_agent"),
36
+ (r"登录失效|未登录|needLogin|登录墙", "needs_human"),
37
+ (r"重复|已存在|参考单号", "needs_agent"),
38
+ (r"等待选择器超时|选择器超时|locator.*(timeout|超时)", "needs_agent"),
39
+ (r"net::ERR|ECONNRESET|ETIMEDOUT|503|502|429", "retryable"),
40
+ ]
41
+
42
+ RUNNERS = {
43
+ "全链路(P1–P14 分阶段)": "wenyao_run.py",
44
+ # ⚠️ 下面两个此前**没声明** ⇒ 不进能力包(`scripts/` 全量同步的机器上不显形,
45
+ # 纯包装机则 P3/放行闸门直接缺件):声明后包内容集随之带上它们。
46
+ "模板填报器(S 列材质英文三级解析的唯一实现)": "wenyao_fill_template.py",
47
+ "影子放行闸门(与黄金样本逐格比对)": "wenyao_shadow_diff.py",
48
+ "回写器(共用守卫)": "wenyao-invoice-writeback.mjs",
49
+ "字段契约生成器": "wenyao_skill_contract.py",
50
+ }
51
+ TEMPLATE = "wenyao-template-map.json"
52
+ WRITEBACK_FLOW = "wenyao-invoice"
53
+
54
+
55
+ def match(row: dict) -> bool:
56
+ return str((row or {}).get("货代公司") or "").strip() == CARRIER
57
+
58
+
59
+ def preflight(row: dict, cfg: dict | None = None) -> dict:
60
+ """缺口分型(运营必须填 / 我方要补)。判据来自注册表 required 的 role=input/state。"""
61
+ row = row or {}
62
+ ops, ours = [], []
63
+ for name in ("产品图", "HS编码", "是否带电", "是否带磁", "产品名称", "SKU", "数量", "箱数",
64
+ "重量", "长", "宽", "高"):
65
+ if not str(row.get(name) or "").strip():
66
+ ops.append(name)
67
+ if not str(row.get("货件号") or "").strip():
68
+ ours.append("货件号(应由 FULL 流程回填)")
69
+ return {"ours": ours, "ops": ops}
70
+
71
+
72
+ def plan(row: dict, cfg: dict | None = None) -> list:
73
+ """可执行计划(引擎/角色据此派发;**不改写既有 runner**)。
74
+
75
+ 文鳐是全链路脚本:一次调用按 P1–P14 顺序跑完,`--from` 可断点续跑。
76
+ """
77
+ row = row or {}
78
+ rec = str(row.get("_id") or row.get("record_id") or "")
79
+ ship = str(row.get("货件号") or "")
80
+ if not (rec or ship):
81
+ raise ValueError("plan() 需要 record_id 或货件号(缺一不可)")
82
+ ident = ["--record-id", rec] if rec else ["--shipment", ship]
83
+ return [
84
+ {"phase": "P1-路由", "script": "wenyao_run.py", "argv": [*ident, "--shadow"],
85
+ "writes": False, "why": "只读取证:核对货代/状态/字段齐备;材质英文三级解析"
86
+ "(SKU 字典→小词典→--material-en)未命中时本步即阻断并指路"},
87
+ {"phase": "P2-P14-全链路", "script": "wenyao_run.py",
88
+ "argv": [*ident, "--yes"], "writes": True,
89
+ "why": "模板填报 → 回写「发票文件」+置填写中 → 门户导入/勾选/导出 → 回写「大货标签」"},
90
+ ]
91
+
92
+
93
+ def classify_error(text: str) -> str:
94
+ import re
95
+ for pat, kind in ERROR_CLASSES:
96
+ if re.search(pat, str(text or ""), re.I):
97
+ return kind
98
+ return "needs_agent"
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """鑫中航-美墨 发票集成适配器(迭代 2)。
3
+
4
+ 门户:`http://106.13.216.234:10001`(老 IP 型系统,对方生产系统)。
5
+ 执行入口:`invoice_lip_runner.py`(两段式:生成 → 门户上传/下载标签),
6
+ 门户段实现在 `invoice_stageb.py`;模板与 defaults 来自 `invoice-mapping.json`。
7
+ 契约见 `scripts/integration_contract.py`。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ NAME = "xzh"
12
+ STATUS = "ready" # ready=可用;scaffold=脚手架生成的骨架(门禁会拦)
13
+ CARRIER = "鑫中航-美墨" # = 注册表 routing.match 的精确写法
14
+ CAPABILITIES = {"invoice": True, "labels": True, "portal_confirm": True}
15
+
16
+ MANUAL_GATES = [
17
+ {"what": "门户账号(invoice-mapping.json 的 xinzhonghang.user)由运营维护", "who": "运营",
18
+ "why": "对方生产系统账号,凭据不进仓库/不进对话", "how": "在 ~/.dsh/argent/invoice-mapping.json 维护"},
19
+ {"what": "HS 编码/材质由运营人工维护", "who": "运营",
20
+ "why": "报关敏感字段,系统不预填", "how": "运营在 lip 表填好后重勾「发票就绪」"},
21
+ ]
22
+
23
+ ERROR_CLASSES = [
24
+ (r"参考单号.*重复|已存在该单", "needs_agent"), # 幂等命中:视为已存在,继续下载标签
25
+ (r"登录|Login|账号|密码", "needs_human"),
26
+ (r"标签未就绪|标签.*生成中", "retryable"),
27
+ (r"等待选择器超时|选择器超时|locator.*(timeout|超时)", "needs_agent"),
28
+ (r"net::ERR|ECONNRESET|ETIMEDOUT|503|502|429", "retryable"),
29
+ ]
30
+
31
+ RUNNERS = {
32
+ "全链路(生成→上传→标签→回写)": "invoice_lip_runner.py",
33
+ "门户段(登录/上传/下载标签)": "invoice_stageb.py",
34
+ "回写器(共用守卫)": "wenyao-invoice-writeback.mjs",
35
+ }
36
+ TEMPLATE = "" # 模板路径在部署配置 invoice-mapping.json 里(每客户一份)
37
+ WRITEBACK_FLOW = "xzh-invoice"
38
+
39
+
40
+ def match(row: dict) -> bool:
41
+ return str((row or {}).get("货代公司") or "").strip() == CARRIER
42
+
43
+
44
+ def preflight(row: dict, cfg: dict | None = None) -> dict:
45
+ row = row or {}
46
+ ops, ours = [], []
47
+ for name in ("HS编码", "材质", "产品名称", "SKU", "数量", "箱数"):
48
+ if not str(row.get(name) or "").strip():
49
+ ops.append(name)
50
+ if not str(row.get("货件号") or "").strip():
51
+ ours.append("货件号(应由 FULL 流程回填)")
52
+ return {"ours": ours, "ops": ops}
53
+
54
+
55
+ def plan(row: dict, cfg: dict | None = None) -> list:
56
+ row = row or {}
57
+ rec = str(row.get("_id") or row.get("record_id") or "")
58
+ if not rec:
59
+ raise ValueError("plan() 需要 record_id(鑫中航入口按记录 id)")
60
+ return [
61
+ {"phase": "生成发票", "script": "invoice_lip_runner.py",
62
+ "argv": ["--record-id", rec, "--generate-only"], "writes": False,
63
+ "why": "先只生成 xlsx 并核对(不写表、不上传门户)"},
64
+ {"phase": "上传+标签+回写", "script": "invoice_lip_runner.py",
65
+ "argv": ["--record-id", rec], "writes": True,
66
+ "why": "门户上传(幂等:已有则视为已存在)→ 下载大货标签 → 回写「发票文件/大货标签」+置已完成"},
67
+ ]
68
+
69
+
70
+ def classify_error(text: str) -> str:
71
+ import re
72
+ for pat, kind in ERROR_CLASSES:
73
+ if re.search(pat, str(text or ""), re.I):
74
+ return kind
75
+ return "needs_agent"
@@ -0,0 +1,40 @@
1
+ {
2
+ "templates": {
3
+ "鑫中航": "/Users/chester/Documents/鑫中航发票模板.xlsx",
4
+ "上海文鳐": "",
5
+ "商达": "",
6
+ "允墨": ""
7
+ },
8
+ "defaults": {
9
+ "服务类型": "美转墨S1",
10
+ "报关方式": "买单报关",
11
+ "带电": "否",
12
+ "带磁": "否",
13
+ "申报币种": "USD",
14
+ "国内收货仓库": "深圳仓",
15
+ "收件人地址库": "MXRC03",
16
+ "用途": "家用",
17
+ "申报价值USD": "10",
18
+ "brand": "无",
19
+ "model": "无"
20
+ },
21
+ "hsCache": "/Users/chester/.dsh/argent/hs-cache.json",
22
+ "output": {
23
+ "invoice": "/Users/chester/.dsh/argent/invoice-output",
24
+ "labels": "/Users/chester/.dsh/argent/invoice-labels",
25
+ "logs": "/Users/chester/.dsh/argent/invoice-logs"
26
+ },
27
+ "xinzhonghang": {
28
+ "url": "http://106.13.216.234:10001/Login/VueLoginView",
29
+ "user": "100610"
30
+ },
31
+ "fullSource": {
32
+ "base": "TJrmbxuIyauNqrsJL9FclLfbnhc",
33
+ "table": "tbl4vDxpCa71YLpe"
34
+ },
35
+ "skuSource": {
36
+ "base": "TJrmbxuIyauNqrsJL9FclLfbnhc",
37
+ "table": "tblM4SMAB028VCbb"
38
+ },
39
+ "_storeIdNote": "店铺→卖家ID 不在此文件:唯一真源 = scripts/ml-store-ids.json(由 scripts/ml_store_ids.py 解析)。2026-09-19 前的副本键是 '1店-子账号' 这种短名,lip 的完整店名匹配不上 ⇒ 曾导致发票 PO 静默退化成紫鸟 store_id。"
40
+ }
@@ -0,0 +1,194 @@
1
+ #!/bin/bash
2
+ # ============================================================
3
+ # invoice-table.sh — 发票流程建表脚本(幂等,单表方案 v2,2026-08-20)
4
+ # 在正式 Base(FULL 货件管理)中创建:
5
+ # 「FULL任务」— 运营唯一填写表
6
+ # SKU=双向关联选择器(选项来自 SKU 主数据字典)
7
+ # 品名/品名英文/海关编码/材质/重量/尺寸/产品图 = lookup 自动带出
8
+ # 数量/箱数/店铺名称/货代/国内收货仓库/就绪 = 运营填写
9
+ # 「SKU 主数据」— 商品字典(人工维护)
10
+ # 用法: invoice-table.sh [--base-token <token>]
11
+ # 默认 Base: ~/.dsh/argent/fulfillment.env 的 FEISHU_BASE_TOKEN
12
+ # 说明: v3 API 不支持 link/auto_number 建表,建 FULL任务 走 v1 API 裸调。
13
+ # ============================================================
14
+ set -euo pipefail
15
+
16
+ BASE_TOKEN="${BASE_TOKEN:-}"
17
+ while [[ $# -gt 0 ]]; do
18
+ case "$1" in
19
+ --base-token) BASE_TOKEN="${2:-}"; shift 2 ;;
20
+ *) echo "未知参数: $1"; exit 1 ;;
21
+ esac
22
+ done
23
+
24
+ if [ -z "$BASE_TOKEN" ]; then
25
+ ENV_FILE="$HOME/.dsh/argent/fulfillment.env"
26
+ [ -f "$ENV_FILE" ] && set -a && source "$ENV_FILE" && set +a
27
+ BASE_TOKEN="${FEISHU_BASE_TOKEN:-}"
28
+ fi
29
+ [ -z "$BASE_TOKEN" ] && { echo "未提供 Base Token(--base-token 或 fulfillment.env)"; exit 1; }
30
+
31
+ python3 - "$BASE_TOKEN" << 'PYEOF'
32
+ import json, subprocess, sys
33
+
34
+ BASE = sys.argv[1]
35
+
36
+ def cli(*args, timeout=60):
37
+ p = subprocess.run(["lark-cli", *args, "--as", "bot", "--format", "json"],
38
+ capture_output=True, text=True, timeout=timeout)
39
+ try:
40
+ return json.loads(p.stdout)
41
+ except Exception:
42
+ return {"ok": False, "error": {"message": p.stderr[:200]}}
43
+
44
+ def table_id(name):
45
+ d = cli("base", "+table-list", "--base-token", BASE)
46
+ for t in d.get("data", {}).get("tables", []):
47
+ if t.get("name") == name:
48
+ return t.get("id")
49
+ return None
50
+
51
+ def field_names(tid):
52
+ d = cli("base", "+field-list", "--base-token", BASE, "--table-id", tid)
53
+ return [f.get("name") for f in d.get("data", {}).get("fields", [])]
54
+
55
+ def field_ids(tid):
56
+ d = cli("base", "+field-list", "--base-token", BASE, "--table-id", tid)
57
+ return {f.get("name"): f.get("id") for f in d.get("data", {}).get("fields", [])}
58
+
59
+ def ensure_view(tid, vname):
60
+ d = cli("base", "+view-list", "--base-token", BASE, "--table-id", tid)
61
+ for v in d.get("data", {}).get("views", []):
62
+ if v.get("name") == vname:
63
+ return v.get("id")
64
+ cli("base", "+view-create", "--base-token", BASE, "--table-id", tid,
65
+ "--json", json.dumps({"name": vname, "type": "grid"}))
66
+ d = cli("base", "+view-list", "--base-token", BASE, "--table-id", tid)
67
+ for v in d.get("data", {}).get("views", []):
68
+ if v.get("name") == vname:
69
+ return v.get("id")
70
+ return None
71
+
72
+ def hide_fields(tid, vid, hidden):
73
+ """v1 视图接口:隐藏字段(字段编组 API 不存在,用视图隐藏实现运营/系统分视图)。"""
74
+ if not vid:
75
+ return
76
+ cli("api", "PATCH",
77
+ f"/open-apis/bitable/v1/apps/{BASE}/tables/{tid}/views/{vid}",
78
+ "--data", json.dumps({"property": {"hidden_fields": hidden}}))
79
+
80
+ SKU_MASTER = "SKU 主数据"
81
+ FULL = "FULL任务"
82
+
83
+ # ── 1. SKU 主数据 ──
84
+ print("── 检查/创建「SKU 主数据」表 ──")
85
+ sku_tid = table_id(SKU_MASTER)
86
+ if sku_tid:
87
+ print(f"✓ SKU 主数据 已存在: {sku_tid}")
88
+ else:
89
+ d = cli("base", "+table-create", "--base-token", BASE, "--name", SKU_MASTER,
90
+ "--fields", json.dumps([
91
+ {"type": "text", "name": "SKU"},
92
+ {"type": "text", "name": "品名"},
93
+ {"type": "text", "name": "品名英文"},
94
+ {"type": "number", "name": "重量(kg/箱)"},
95
+ {"type": "number", "name": "长(cm)"},
96
+ {"type": "number", "name": "宽(cm)"},
97
+ {"type": "number", "name": "高(cm)"},
98
+ {"type": "text", "name": "材质中文"},
99
+ {"type": "text", "name": "材质英文"},
100
+ {"type": "text", "name": "海关编码"},
101
+ {"type": "attachment", "name": "产品图"},
102
+ {"type": "text", "name": "备注"},
103
+ ], ensure_ascii=False))
104
+ print("→ SKU 主数据 创建:", "ok" if d.get("ok") else d.get("error", {}).get("message", "?"))
105
+ sku_tid = table_id(SKU_MASTER)
106
+ if not sku_tid:
107
+ sys.exit(1)
108
+
109
+ # ── 2. FULL任务(v2:link SKU + lookup 自动带出)──
110
+ print("── 检查/创建「FULL任务」表 ──")
111
+ full_tid = table_id(FULL)
112
+ if full_tid:
113
+ flds = field_names(full_tid)
114
+ if "SKU名称" in flds:
115
+ print(f"✓ FULL任务 已存在且为 v2 结构: {full_tid}")
116
+ else:
117
+ print(f"⚠ FULL任务 存在但非 v2 结构(字段: {flds[:5]}...),请手工确认后删除重建")
118
+ sys.exit(1)
119
+ else:
120
+ base_fields = [
121
+ {"field_name": "序号", "type": 1005, "property": {"auto_serial": {"type": "auto_increment_number"}}},
122
+ {"field_name": "SKU", "type": 21, "property": {"table_id": sku_tid, "multiple": False, "back_field_name": "FULL任务关联"}},
123
+ {"field_name": "数量", "type": 2},
124
+ {"field_name": "箱数", "type": 2},
125
+ {"field_name": "店铺名称", "type": 3, "property": {"options": [{"name": "3店-主账号"}, {"name": "2店-子账号"}, {"name": "1店-子账号"}]}},
126
+ {"field_name": "货代", "type": 3, "property": {"options": [{"name": "鑫中航"}, {"name": "上海文鳐"}, {"name": "商达"}, {"name": "允墨"}]}},
127
+ {"field_name": "国内收货仓库", "type": 3, "property": {"options": [{"name": "深圳仓"}, {"name": "义乌仓"}, {"name": "广州仓"}]}},
128
+ {"field_name": "就绪", "type": 7},
129
+ {"field_name": "状态", "type": 3, "property": {"options": [{"name": "Pending"}, {"name": "运行中"}, {"name": "已完成"}, {"name": "失败"}]}},
130
+ {"field_name": "发票状态", "type": 3, "property": {"options": [{"name": "待生成"}, {"name": "填写中"}, {"name": "已上传"}, {"name": "标签已下载"}, {"name": "已完成"}, {"name": "失败"}, {"name": "不开发票"}]}},
131
+ {"field_name": "当前步骤", "type": 1},
132
+ {"field_name": "货件号", "type": 1},
133
+ {"field_name": "发票错误信息", "type": 1},
134
+ {"field_name": "发票文件", "type": 17},
135
+ {"field_name": "大货标签", "type": 17},
136
+ {"field_name": "产品标签", "type": 17},
137
+ {"field_name": "箱唛", "type": 17},
138
+ {"field_name": "创建时间", "type": 1001},
139
+ ]
140
+ d = cli("api", "POST", f"/open-apis/bitable/v1/apps/{BASE}/tables",
141
+ "--data", json.dumps({"table": {"name": FULL, "fields": base_fields}}, ensure_ascii=False))
142
+ if not d.get("ok"):
143
+ print("✗ FULL任务 建表失败:", d.get("error", {}).get("message", "?"))
144
+ sys.exit(1)
145
+ full_tid = d["data"]["table_id"]
146
+ print(f"→ FULL任务 已创建: {full_tid}(基础字段 {len(base_fields)} 个)")
147
+
148
+ # lookup 字段(11 个,自动带出字典数据)
149
+ lookups = ["SKU名称", "品名", "品名英文", "海关编码", "材质中文", "材质英文",
150
+ "重量(kg/箱)", "长(cm)", "宽(cm)", "高(cm)", "产品图"]
151
+ src_field = { # 源表(SKU 主数据)字段名
152
+ "SKU名称": "SKU", "品名": "品名", "品名英文": "品名英文", "海关编码": "海关编码",
153
+ "材质中文": "材质中文", "材质英文": "材质英文", "重量(kg/箱)": "重量(kg/箱)",
154
+ "长(cm)": "长(cm)", "宽(cm)": "宽(cm)", "高(cm)": "高(cm)", "产品图": "产品图",
155
+ }
156
+ where = {"logic": "and", "conditions": [["SKU", "==", {"type": "field_ref", "field": "SKU"}]]}
157
+ json_fields = [
158
+ {"name": name, "type": "lookup", "from": sku_tid, "select": src_field[name], "where": where}
159
+ for name in lookups
160
+ ]
161
+ d = cli("base", "+field-create", "--i-have-read-guide", "--base-token", BASE,
162
+ "--table-id", full_tid, "--json", json.dumps(json_fields, ensure_ascii=False))
163
+ print("→ lookup 字段:", "ok" if d.get("ok") else d.get("error", {}).get("message", "?"))
164
+
165
+ # ── 3. 视图配置(字段编组 API 不存在,用视图隐藏实现运营/系统分视图)──
166
+ print("── 配置视图(运营填写 / AI自动)──")
167
+ fids = field_ids(full_tid)
168
+ OP_FIELDS = ["SKU", "数量", "箱数", "店铺名称", "货代", "国内收货仓库", "就绪", "序号"]
169
+ ALL_NAMES = set(field_names(full_tid))
170
+ op_view = ensure_view(full_tid, "运营填写")
171
+ if op_view:
172
+ hide_fields(full_tid, op_view, [fids[n] for n in ALL_NAMES - set(OP_FIELDS)])
173
+ print("✓ 运营填写 视图(仅运营字段)")
174
+ ai_view = ensure_view(full_tid, "AI自动")
175
+ if ai_view:
176
+ hide_fields(full_tid, ai_view, [fids[n] for n in OP_FIELDS if n in fids])
177
+ print("✓ AI自动 视图(仅系统字段)")
178
+ sku_fids = field_ids(sku_tid)
179
+ op_view = ensure_view(sku_tid, "运营填写")
180
+ if op_view:
181
+ # 运营只维护中文/物理属性;品名英文/材质英文由 AI 翻译,FULL任务关联自动
182
+ hide_fields(sku_tid, op_view, [sku_fids[n] for n in ("FULL任务关联", "品名英文", "材质英文") if n in sku_fids])
183
+ print("✓ SKU主数据 运营填写 视图(维护字段,英文翻译字段已隐藏)")
184
+ ai_view = ensure_view(sku_tid, "AI自动")
185
+ if ai_view:
186
+ # 主字段(SKU)无法隐藏;AI自动 视图 = SKU + 英文翻译字段 + 自动反向关联
187
+ ai_show = {"SKU", "品名英文", "材质英文", "FULL任务关联"}
188
+ hide_fields(sku_tid, ai_view, [sku_fids[n] for n in set(sku_fids) - ai_show])
189
+ print("✓ SKU主数据 AI自动 视图(英文翻译 + 自动字段)")
190
+ PYEOF
191
+
192
+ echo ""
193
+ echo "完成。表清单:"
194
+ lark-cli base +table-list --base-token "$BASE_TOKEN" --as bot --format json 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); [print(' -', t['name'], t['id']) for t in d.get('data',{}).get('tables',[])]"