weaver-work-cli 0.1.8 → 0.1.20

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 (263) hide show
  1. package/README.md +11 -11
  2. package/dist/cmd/action.js +76 -0
  3. package/dist/cmd/index.js +2 -0
  4. package/dist/cmd/refs/index.js +103 -0
  5. package/dist/cmd/register.js +31 -0
  6. package/dist/cmd/root.js +1 -0
  7. package/dist/cmd/setup/index.js +25 -5
  8. package/dist/cmd/skills/index.js +37 -5
  9. package/dist/core/connector.js +54 -0
  10. package/dist/core/context.js +2 -1
  11. package/dist/core/input.js +48 -0
  12. package/dist/core/output.js +298 -3
  13. package/dist/core/refs.js +432 -0
  14. package/dist/core/state.js +26 -0
  15. package/dist/internal/e10/auth/commands.js +2 -2
  16. package/dist/internal/e10/auth/session.js +14 -0
  17. package/dist/internal/e10/auth/xiaoe.js +116 -51
  18. package/dist/internal/e10/request-runtime.js +5 -0
  19. package/dist/internal/skills/install.js +148 -28
  20. package/dist/internal/skills/reader.js +12 -9
  21. package/dist/shortcuts/blog/continuation.js +74 -0
  22. package/dist/shortcuts/blog/errors.js +60 -0
  23. package/dist/shortcuts/blog/host.js +175 -0
  24. package/dist/shortcuts/blog/index.js +324 -0
  25. package/dist/shortcuts/blog/manifest.js +415 -0
  26. package/dist/shortcuts/blog/operations/comment.js +125 -0
  27. package/dist/shortcuts/blog/operations/comments.js +31 -0
  28. package/dist/shortcuts/blog/operations/edit.js +131 -0
  29. package/dist/shortcuts/blog/operations/export.js +46 -0
  30. package/dist/shortcuts/blog/operations/like.js +80 -0
  31. package/dist/shortcuts/blog/operations/publish.js +157 -0
  32. package/dist/shortcuts/blog/operations/query-user.js +58 -0
  33. package/dist/shortcuts/blog/operations/query.js +96 -0
  34. package/dist/shortcuts/blog/operations/registry.js +54 -0
  35. package/dist/shortcuts/blog/operations/remind.js +176 -0
  36. package/dist/shortcuts/blog/operations/shared.js +330 -0
  37. package/dist/shortcuts/blog/operations/types.js +16 -0
  38. package/dist/shortcuts/blog/operations.js +9 -0
  39. package/dist/shortcuts/caizhilian/continuation.js +70 -0
  40. package/dist/shortcuts/caizhilian/errors.js +53 -0
  41. package/dist/shortcuts/caizhilian/host.js +190 -0
  42. package/dist/shortcuts/caizhilian/index.js +186 -0
  43. package/dist/shortcuts/caizhilian/manifest.js +42 -0
  44. package/dist/shortcuts/caizhilian/operations/api.js +801 -0
  45. package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
  46. package/dist/shortcuts/caizhilian/operations/files.js +306 -0
  47. package/dist/shortcuts/caizhilian/operations/form.js +170 -0
  48. package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
  49. package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
  50. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
  51. package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
  52. package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
  53. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
  54. package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
  55. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
  56. package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
  57. package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
  58. package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
  59. package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
  60. package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
  61. package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
  62. package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
  63. package/dist/shortcuts/caizhilian/operations/types.js +57 -0
  64. package/dist/shortcuts/caizhilian/operations.js +21 -0
  65. package/dist/shortcuts/contract/continuation.js +70 -0
  66. package/dist/shortcuts/contract/errors.js +62 -0
  67. package/dist/shortcuts/contract/host.js +205 -0
  68. package/dist/shortcuts/contract/index.js +318 -0
  69. package/dist/shortcuts/contract/manifest.js +596 -0
  70. package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
  71. package/dist/shortcuts/contract/operations/form-info.js +371 -0
  72. package/dist/shortcuts/contract/operations/read.js +118 -0
  73. package/dist/shortcuts/contract/operations/registry.js +43 -0
  74. package/dist/shortcuts/contract/operations/request.js +300 -0
  75. package/dist/shortcuts/contract/operations/shared.js +133 -0
  76. package/dist/shortcuts/contract/operations/subject.js +157 -0
  77. package/dist/shortcuts/contract/operations/template.js +81 -0
  78. package/dist/shortcuts/contract/operations/types.js +7 -0
  79. package/dist/shortcuts/contract/operations/workflow.js +52 -0
  80. package/dist/shortcuts/contract/operations/write.js +131 -0
  81. package/dist/shortcuts/contract/operations.js +8 -0
  82. package/dist/shortcuts/hrm/index.js +83 -2
  83. package/dist/shortcuts/hrm/operations/employee.js +160 -30
  84. package/dist/shortcuts/hrm/operations/org.js +17 -3
  85. package/dist/shortcuts/hrm/operations/position.js +18 -2
  86. package/dist/shortcuts/index.js +5 -1
  87. package/dist/shortcuts/invoice/index.js +74 -36
  88. package/dist/shortcuts/invoice/manifest.js +674 -4
  89. package/dist/shortcuts/invoice/markdown.js +172 -0
  90. package/dist/shortcuts/invoice/operations/add.js +3 -3
  91. package/dist/shortcuts/invoice/operations/delete.js +2 -2
  92. package/dist/shortcuts/invoice/operations/get.js +10 -2
  93. package/dist/shortcuts/invoice/operations/import.js +2 -2
  94. package/dist/shortcuts/invoice/operations/registry.js +8 -1
  95. package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
  96. package/dist/shortcuts/invoice/operations/reim.js +85 -2
  97. package/dist/shortcuts/invoice/operations/shared.js +75 -8
  98. package/dist/shortcuts/invoice/operations/update.js +2 -2
  99. package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
  100. package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
  101. package/dist/shortcuts/invoice/projection.js +328 -0
  102. package/dist/shortcuts/meeting/host.js +18 -1
  103. package/dist/shortcuts/meeting/manifest.js +30 -18
  104. package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
  105. package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
  106. package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
  107. package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
  108. package/dist/shortcuts/meeting/operations/env.js +8 -1
  109. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  110. package/dist/shortcuts/meeting/operations/signs.js +216 -2
  111. package/dist/shortcuts/workflow/endpoints.js +2 -1
  112. package/dist/shortcuts/workflow/errors.js +8 -2
  113. package/dist/shortcuts/workflow/host.js +6 -3
  114. package/dist/shortcuts/workflow/operations/form.js +931 -589
  115. package/dist/shortcuts/workflow/operations/read.js +128 -205
  116. package/dist/shortcuts/workflow/operations/shared.js +21 -0
  117. package/dist/shortcuts/workflow/operations/write.js +750 -52
  118. package/dist/shortcuts/workflow/operations.js +36 -10
  119. package/docs/_catalog.md +6 -0
  120. package/docs/agent-invoice.md +5 -1
  121. package/docs/agent-io-channel-design.md +468 -0
  122. package/docs/agent-skill-install.md +9 -4
  123. package/docs/blog.md +123 -0
  124. package/docs/caizhilian.md +304 -0
  125. package/docs/contract.md +91 -0
  126. package/docs/e10-auth.md +2 -2
  127. package/docs/hrm.md +19 -9
  128. package/docs/invoice.md +5 -1
  129. package/docs/meeting.md +1 -1
  130. package/docs/operation-manual.md +15 -3
  131. package/docs/skill-review-2026-09-19.md +2 -2
  132. package/package.json +2 -2
  133. package/scripts/package-skill.mjs +32 -20
  134. package/skill-template/business-info.json +113 -14
  135. package/skill-template/domains/blog.md +24 -0
  136. package/skill-template/domains/caizhilian.md +22 -0
  137. package/skill-template/domains/jinchengda.md +23 -0
  138. package/skill-template/domains/meeting.md +1 -1
  139. package/skill-template/domains/workflow.md +1 -1
  140. package/skill-template/skill-template.md +1 -1
  141. package/skills/weaver-e10-blog/SKILL.md +111 -0
  142. package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
  143. package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
  144. package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
  145. package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
  146. package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
  147. package/skills/weaver-e10-blog/references/blog-export.md +42 -0
  148. package/skills/weaver-e10-blog/references/blog-like.md +50 -0
  149. package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
  150. package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
  151. package/skills/weaver-e10-blog/references/blog-query.md +49 -0
  152. package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
  153. package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
  154. package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
  155. package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
  156. package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
  157. package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
  158. package/skills/weaver-e10-caizhilian/references/document.md +111 -0
  159. package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
  160. package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
  161. package/skills/weaver-e10-caizhilian/references/files.md +120 -0
  162. package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
  163. package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
  164. package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
  165. package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
  166. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
  167. package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
  168. package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
  169. package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
  170. package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
  171. package/skills/weaver-e10-calendar/SKILL.md +4 -3
  172. package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
  173. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
  174. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
  175. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
  176. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
  177. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
  178. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
  179. package/skills/weaver-e10-esb/SKILL.md +2 -2
  180. package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
  181. package/skills/weaver-e10-hrm/SKILL.md +54 -6
  182. package/skills/weaver-e10-hrm/references/employee.md +89 -15
  183. package/skills/weaver-e10-hrm/references/org.md +7 -4
  184. package/skills/weaver-e10-hrm/references/position.md +14 -7
  185. package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
  186. package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
  187. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
  188. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
  189. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
  190. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
  191. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
  192. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
  193. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
  194. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
  195. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
  196. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
  197. package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
  198. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
  199. package/skills/weaver-e10-jucailin/SKILL.md +4 -3
  200. package/skills/weaver-e10-mail/SKILL.md +4 -11
  201. package/skills/weaver-e10-meeting/SKILL.md +66 -32
  202. package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
  203. package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
  204. package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
  205. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
  206. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
  207. package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
  208. package/skills/weaver-e10-okr/SKILL.md +6 -11
  209. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  210. package/skills/weaver-e10-plan/SKILL.md +3 -3
  211. package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
  212. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
  213. package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
  214. package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
  215. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
  216. package/skills/weaver-e10-shared/SKILL.md +36 -25
  217. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
  218. package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
  219. package/skills/weaver-e10-shijingran/SKILL.md +2 -1
  220. package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
  221. package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
  222. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
  223. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
  224. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
  225. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
  226. package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
  227. package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
  228. package/skills/weaver-e10-workflow/SKILL.md +32 -37
  229. package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
  230. package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
  231. package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
  232. package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
  233. package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
  234. package/skills/weaver-e10-workflow/references/workflow-query.md +38 -20
  235. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  236. package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
  237. package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
  238. package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
  239. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
  240. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  241. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  242. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  243. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  244. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  245. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  246. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
  247. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  248. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  249. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  250. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  251. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  252. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  253. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  254. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  255. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  256. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  257. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  258. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  259. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  260. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  261. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  262. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  263. package/scripts/sync-connector-skills.mjs +0 -172
@@ -0,0 +1,408 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { caizhilianValidationError } from '../errors.js';
3
+ /** 原型污染防护:禁止出现在输入对象中的键。 */
4
+ const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
5
+ export function asObject(value) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
7
+ throw caizhilianValidationError('input_not_object', '输入必须是 JSON 对象');
8
+ }
9
+ return value;
10
+ }
11
+ export function assertNoForbiddenKeys(input) {
12
+ for (const key of Object.keys(input)) {
13
+ if (FORBIDDEN_KEYS.has(key)) {
14
+ throw caizhilianValidationError('forbidden_key', `输入包含非法字段:${key}`);
15
+ }
16
+ }
17
+ }
18
+ /** 移除值为 undefined 的键,避免把空值写进请求体。 */
19
+ export function compact(body) {
20
+ for (const key of Object.keys(body)) {
21
+ if (body[key] === undefined)
22
+ delete body[key];
23
+ }
24
+ return body;
25
+ }
26
+ export function pickDefined(value, keys) {
27
+ const result = {};
28
+ for (const key of keys) {
29
+ if (value[key] !== undefined)
30
+ result[key] = value[key];
31
+ }
32
+ return result;
33
+ }
34
+ /** 取业务响应中的 data 段(源 response_convention.data_path = data)。 */
35
+ export function responseData(response) {
36
+ const data = response?.data;
37
+ if (data === null || data === undefined)
38
+ return {};
39
+ if (typeof data !== 'object')
40
+ return { value: data };
41
+ return data;
42
+ }
43
+ export function fingerprint(value) {
44
+ return createHash('sha256').update(JSON.stringify(value ?? null)).digest('hex').slice(0, 32);
45
+ }
46
+ // ── 标量校验 ──────────────────────────────────────────────
47
+ export function requireText(value, field) {
48
+ if (value === undefined || value === null) {
49
+ throw caizhilianValidationError('field_required', `缺少必填参数: ${field}`);
50
+ }
51
+ const text = typeof value === 'string' ? value.trim() : String(value).trim();
52
+ if (!text)
53
+ throw caizhilianValidationError('field_required', `缺少必填参数: ${field}`);
54
+ return text;
55
+ }
56
+ export function optionalText(value, field) {
57
+ if (value === undefined || value === null)
58
+ return undefined;
59
+ const text = typeof value === 'string' ? value.trim() : String(value).trim();
60
+ return text ? text : undefined;
61
+ }
62
+ /** 数字 ID(源统一按字符串提交,这里同时接受数字与数字字符串)。 */
63
+ export function requireId(value, field) {
64
+ const text = requireText(value, field);
65
+ if (!/^\d+$/.test(text))
66
+ throw caizhilianValidationError('id_invalid', `${field} 必须是数字 ID`);
67
+ return text;
68
+ }
69
+ export function optionalId(value, field) {
70
+ const text = optionalText(value, field);
71
+ if (text === undefined)
72
+ return undefined;
73
+ if (!/^\d+$/.test(text))
74
+ throw caizhilianValidationError('id_invalid', `${field} 必须是数字 ID`);
75
+ return text;
76
+ }
77
+ export function requireIdList(value, field) {
78
+ const list = requireList(value, field);
79
+ return list.map((item, index) => requireId(item, `${field}[${index}]`));
80
+ }
81
+ export function optionalIdList(value, field) {
82
+ if (value === undefined || value === null || value === '')
83
+ return undefined;
84
+ return requireIdList(value, field);
85
+ }
86
+ /**
87
+ * 拒绝超出 JS 安全整数范围(2^53)的整数入参。
88
+ *
89
+ * E10 的 ID 是 19 位雪花号,超过安全整数范围;用 JSON 数字传入时在 `JSON.parse` 阶段
90
+ * 就已经丢精度(如 1261580569640443937 → 1261580569640444000),拿这种 ID 去查询
91
+ * 服务端只会静默返回空结果,调用方却以为是「接口不支持」。所以在入口处显式报错,
92
+ * 要求把大整数(ID)改用字符串传入。
93
+ */
94
+ export function assertNoLossyIntegers(value, path = 'input') {
95
+ if (typeof value === 'number') {
96
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
97
+ throw caizhilianValidationError('number_out_of_range', `${path} 的整数超出安全整数范围(${value}),大整数(如 ID)请改用字符串传入`);
98
+ }
99
+ return;
100
+ }
101
+ if (Array.isArray(value)) {
102
+ value.forEach((item, index) => assertNoLossyIntegers(item, `${path}[${index}]`));
103
+ return;
104
+ }
105
+ if (value !== null && typeof value === 'object') {
106
+ for (const [key, item] of Object.entries(value)) {
107
+ assertNoLossyIntegers(item, `${path}.${key}`);
108
+ }
109
+ }
110
+ }
111
+ export function requireList(value, field) {
112
+ if (!Array.isArray(value) || value.length === 0) {
113
+ throw caizhilianValidationError('list_required', `${field} 必填且至少一项`);
114
+ }
115
+ return value;
116
+ }
117
+ export function optionalList(value, field) {
118
+ if (value === undefined || value === null)
119
+ return undefined;
120
+ if (!Array.isArray(value))
121
+ throw caizhilianValidationError('list_invalid', `${field} 必须是数组`);
122
+ return value.length ? value : undefined;
123
+ }
124
+ /** 字符串数组:源把单值也接受为 [值]。 */
125
+ export function normalizeStringList(value, field) {
126
+ if (value === undefined || value === null || value === '')
127
+ return undefined;
128
+ const list = Array.isArray(value) ? value : [value];
129
+ const result = list
130
+ .map((item) => (typeof item === 'string' ? item.trim() : item === undefined || item === null ? '' : String(item)))
131
+ .filter((item) => item !== '');
132
+ if (!result.length)
133
+ return undefined;
134
+ void field;
135
+ return result;
136
+ }
137
+ export function assertEnum(value, allowed, field) {
138
+ const text = requireText(value, field);
139
+ if (!allowed.includes(text)) {
140
+ throw caizhilianValidationError('enum_invalid', `${field} 必须是 ${allowed.join(' / ')} 之一,收到: ${JSON.stringify(text)}`);
141
+ }
142
+ return text;
143
+ }
144
+ export function optionalEnum(value, allowed, field) {
145
+ if (value === undefined || value === null || value === '')
146
+ return undefined;
147
+ return assertEnum(value, allowed, field);
148
+ }
149
+ export function toBoolean(value, fallback = false) {
150
+ if (value === undefined || value === null || value === '')
151
+ return fallback;
152
+ if (typeof value === 'boolean')
153
+ return value;
154
+ const text = String(value).trim().toLowerCase();
155
+ if (text === 'true' || text === '1')
156
+ return true;
157
+ if (text === 'false' || text === '0')
158
+ return false;
159
+ return fallback;
160
+ }
161
+ /** 分页参数(源 parse_page_args:page_no>=1、page_size>=1)。 */
162
+ export function parsePage(args, defaultPageSize, defaultPageNo = 1) {
163
+ const pageNo = toPositiveInt(args.page_no, 'page_no', defaultPageNo);
164
+ const pageSize = toPositiveInt(args.page_size, 'page_size', defaultPageSize);
165
+ return { pageNo, pageSize };
166
+ }
167
+ export function toPositiveInt(value, field, fallback) {
168
+ if (value === undefined || value === null || value === '')
169
+ return fallback;
170
+ const parsed = Number(value);
171
+ if (!Number.isInteger(parsed) || parsed <= 0) {
172
+ throw caizhilianValidationError('number_invalid', `${field} 必须是正整数`);
173
+ }
174
+ return parsed;
175
+ }
176
+ /** 源统一用 has_next = len(list) >= page_size 判断翻页(total 服务端可能为 0,不可信)。 */
177
+ export function hasNextPage(list, pageSize) {
178
+ return Array.isArray(list) && list.length >= pageSize;
179
+ }
180
+ // ── 时间 ──────────────────────────────────────────────────
181
+ const CN_TZ_OFFSET_MS = 8 * 60 * 60 * 1000;
182
+ function pad(value) {
183
+ return value < 10 ? `0${value}` : String(value);
184
+ }
185
+ /** 毫秒时间戳 → 东八区 'YYYY-MM-DD HH:MM:SS'(源 _utils.timestamp_to_date_str)。 */
186
+ export function timestampToDateStr(tsMs, fallback = '') {
187
+ if (tsMs === undefined || tsMs === null || tsMs === '')
188
+ return fallback;
189
+ const parsed = Number(tsMs);
190
+ if (!Number.isFinite(parsed) || parsed < 0)
191
+ return fallback;
192
+ return formatDateTimeMs(parsed + CN_TZ_OFFSET_MS, true);
193
+ }
194
+ function formatDateTimeMs(shiftedMs, withSeconds) {
195
+ const t = new Date(shiftedMs);
196
+ const date = `${t.getUTCFullYear()}-${pad(t.getUTCMonth() + 1)}-${pad(t.getUTCDate())}`;
197
+ const time = `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}${withSeconds ? `:${pad(t.getUTCSeconds())}` : ''}`;
198
+ return `${date} ${time}`;
199
+ }
200
+ export const TIME_RANGE_VALUES = [
201
+ 'today',
202
+ 'yesterday',
203
+ 'this_week',
204
+ 'last_week',
205
+ 'this_month',
206
+ 'last_month',
207
+ 'this_year',
208
+ 'last_year',
209
+ 'last_7_days',
210
+ 'last_30_days',
211
+ 'last_90_days',
212
+ ];
213
+ const DAY_MS = 24 * 60 * 60 * 1000;
214
+ /**
215
+ * 解析 create_time 之类的时间范围入参(源 _utils.TimeRange.resolve)。
216
+ * 支持枚举关键字与 last_<N>_days;也接受调用方直接给出 'YYYY-MM-DD HH:MM~YYYY-MM-DD HH:MM'。
217
+ * 输出统一为东八区本地时间区间字符串,用 '~' 连接。
218
+ */
219
+ export function resolveTimeRange(value, field = 'create_time', now = new Date()) {
220
+ const text = requireText(value, field);
221
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}~\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(text))
222
+ return text;
223
+ const cn = now.getTime() + CN_TZ_OFFSET_MS;
224
+ const shifted = new Date(cn);
225
+ const todayMs = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate());
226
+ const nowStr = formatDateTimeMs(cn, false);
227
+ const at = (ms, hour, minute) => formatDateTimeMs(ms + hour * 3600_000 + minute * 60_000, false);
228
+ const lastNDays = /^last_(\d+)_days$/.exec(text);
229
+ switch (text) {
230
+ case 'today':
231
+ return `${at(todayMs, 0, 0)}~${nowStr}`;
232
+ case 'yesterday':
233
+ return `${at(todayMs - DAY_MS, 0, 0)}~${at(todayMs - DAY_MS, 23, 59)}`;
234
+ case 'this_week':
235
+ return `${at(todayMs - weekdayMondayOffset(shifted) * DAY_MS, 0, 0)}~${nowStr}`;
236
+ case 'last_week': {
237
+ const thisMonday = todayMs - weekdayMondayOffset(shifted) * DAY_MS;
238
+ return `${at(thisMonday - 7 * DAY_MS, 0, 0)}~${at(thisMonday - DAY_MS, 23, 59)}`;
239
+ }
240
+ case 'this_month':
241
+ return `${at(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), 1), 0, 0)}~${nowStr}`;
242
+ case 'last_month': {
243
+ const firstOfThis = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), 1);
244
+ const lastOfPrevious = firstOfThis - DAY_MS;
245
+ const previous = new Date(lastOfPrevious);
246
+ return `${at(Date.UTC(previous.getUTCFullYear(), previous.getUTCMonth(), 1), 0, 0)}~${at(lastOfPrevious, 23, 59)}`;
247
+ }
248
+ case 'this_year':
249
+ return `${at(Date.UTC(shifted.getUTCFullYear(), 0, 1), 0, 0)}~${nowStr}`;
250
+ case 'last_year': {
251
+ const year = shifted.getUTCFullYear() - 1;
252
+ return `${at(Date.UTC(year, 0, 1), 0, 0)}~${at(Date.UTC(year, 11, 31), 23, 59)}`;
253
+ }
254
+ default:
255
+ break;
256
+ }
257
+ if (lastNDays) {
258
+ const days = Number(lastNDays[1]);
259
+ if (days > 0)
260
+ return `${at(todayMs - (days - 1) * DAY_MS, 0, 0)}~${nowStr}`;
261
+ }
262
+ throw caizhilianValidationError('time_range_invalid', `${field} 不支持: ${JSON.stringify(text)},可选值: ${[...TIME_RANGE_VALUES, 'last_<N>_days'].join(' / ')}`);
263
+ }
264
+ function weekdayMondayOffset(date) {
265
+ return (date.getUTCDay() + 6) % 7;
266
+ }
267
+ // ── 输入白名单 ────────────────────────────────────────────
268
+ /**
269
+ * 旧字段别名归一化(遵循模块 CLI 生成规范):
270
+ * - 只把 `deprecatedInputAliases` 登记的旧字段映射到 canonical 字段;
271
+ * - canonical 与旧字段同时出现且取值不一致 → 结构化校验错误(禁止任一方静默覆盖);
272
+ * - 归一化后删除旧字段,保证后续纯函数校验与 handler 只看到 canonical 字段。
273
+ */
274
+ export function normalizeAliases(input, aliases) {
275
+ const out = { ...input };
276
+ for (const [canonical, oldKeys] of Object.entries(aliases)) {
277
+ for (const oldKey of oldKeys) {
278
+ if (!(oldKey in out))
279
+ continue;
280
+ const oldValue = out[oldKey];
281
+ if (oldValue === undefined || oldValue === null || oldValue === '') {
282
+ delete out[oldKey];
283
+ continue;
284
+ }
285
+ const canonicalValue = out[canonical];
286
+ const hasCanonical = canonicalValue !== undefined && canonicalValue !== null && canonicalValue !== '';
287
+ if (hasCanonical && JSON.stringify(canonicalValue) !== JSON.stringify(oldValue)) {
288
+ throw caizhilianValidationError('alias_conflict', `字段 ${canonical} 与旧字段 ${oldKey} 同时出现且取值不一致,请只保留一个`);
289
+ }
290
+ if (!hasCanonical)
291
+ out[canonical] = oldValue;
292
+ delete out[oldKey];
293
+ }
294
+ }
295
+ return out;
296
+ }
297
+ /** 顶层输入严格白名单:schema 已声明 additionalProperties: false,运行时同样拒绝未知字段。 */
298
+ export function assertKnownKeys(input, allowed, operation) {
299
+ const unknown = Object.keys(input).filter((key) => !allowed.includes(key));
300
+ if (unknown.length > 0) {
301
+ throw caizhilianValidationError('unknown_field', `${operation} 不支持字段:${unknown.join('、')}`, { unknown });
302
+ }
303
+ }
304
+ /**
305
+ * 与源 `if args.get(key)` 真值语义一致的可选字符串:
306
+ * 仅过滤 undefined / null / 空串,保留原始空白(正文、HTML 不做 trim)。
307
+ */
308
+ export function optionalRawText(value) {
309
+ if (value === undefined || value === null)
310
+ return undefined;
311
+ const text = typeof value === 'string' ? value : String(value);
312
+ return text === '' ? undefined : text;
313
+ }
314
+ // ── Python 语义小工具(源脚本直译)────────────────────────
315
+ /** 源 `isinstance(x, dict)`:排除数组与 null 的普通对象判断。 */
316
+ export function isPlainObject(value) {
317
+ return !!value && typeof value === 'object' && !Array.isArray(value);
318
+ }
319
+ /** 源 Python repr 的字符串形态(报错文案里用 !r 输出)。 */
320
+ export function pyRepr(value) {
321
+ return typeof value === 'string' ? `'${value}'` : JSON.stringify(value) ?? String(value);
322
+ }
323
+ /**
324
+ * 源 int(x) 语义:数字截断取整,字符串必须整体是整数,None/'' 用默认值,
325
+ * 其余抛错(对应 Python 的 TypeError / ValueError)。
326
+ */
327
+ export function pyInt(value, fallback) {
328
+ if (value === undefined)
329
+ return fallback;
330
+ if (typeof value === 'number') {
331
+ if (!Number.isFinite(value))
332
+ throw new TypeError('int() argument out of range');
333
+ return Math.trunc(value);
334
+ }
335
+ const text = value === null ? '' : String(value).trim();
336
+ if (!/^[+-]?\d+$/.test(text))
337
+ throw new TypeError(`int() invalid literal: ${pyRepr(value)}`);
338
+ return Number(text);
339
+ }
340
+ // ── 源文案对齐的小工具 ────────────────────────────────────
341
+ /** 源 `str(args.get(k) or "").strip()` + 各自的空值文案。 */
342
+ export function requireNonEmpty(value, message, subtype = 'field_required') {
343
+ const text = value === undefined || value === null ? '' : String(value).trim();
344
+ if (!text)
345
+ throw caizhilianValidationError(subtype, message);
346
+ return text;
347
+ }
348
+ /** 源 `",".join(str(i) for i in ids)`(非列表时直接 str(ids))。 */
349
+ export function joinIds(value) {
350
+ return Array.isArray(value) ? value.map((item) => String(item)).join(',') : String(value);
351
+ }
352
+ /** 源 `if not ids` 的真值判断(None / 空串 / 空列表 / 0 均视为空)。 */
353
+ export function isFalsyIds(value) {
354
+ if (value === undefined || value === null)
355
+ return true;
356
+ if (Array.isArray(value))
357
+ return value.length === 0;
358
+ if (typeof value === 'string')
359
+ return value.trim() === '';
360
+ if (typeof value === 'number')
361
+ return value === 0;
362
+ return false;
363
+ }
364
+ /** 源 sorted(x) + ', '.join(...) 的报错文案片段。 */
365
+ export function sortedJoin(values) {
366
+ return [...values].sort().join(', ');
367
+ }
368
+ /** 源 f-string 里 `{args.get(k)}` 的 str() 形态(None → 'None')。 */
369
+ export function pyStr(value) {
370
+ if (value === undefined || value === null)
371
+ return 'None';
372
+ if (typeof value === 'string')
373
+ return value;
374
+ return JSON.stringify(value) ?? String(value);
375
+ }
376
+ /** 源 `str(record.get(key, ""))`:键缺失取空串,键存在但为 None 取 'None'。 */
377
+ export function pyGetStr(record, key) {
378
+ return key in record ? pyStr(record[key]) : '';
379
+ }
380
+ /** 源 Python bool() 语义(注意 'false' 这类非空字符串为真、NaN 为真)。 */
381
+ export function pyBool(value) {
382
+ if (value === undefined || value === null)
383
+ return false;
384
+ if (typeof value === 'boolean')
385
+ return value;
386
+ if (typeof value === 'number')
387
+ return value !== 0;
388
+ if (typeof value === 'string')
389
+ return value.length > 0;
390
+ if (Array.isArray(value))
391
+ return value.length > 0;
392
+ if (isPlainObject(value))
393
+ return Object.keys(value).length > 0;
394
+ return true;
395
+ }
396
+ /**
397
+ * 源 _plugin_helpers.validate_required_params:
398
+ * 键缺失(None)或纯空白字符串视为缺失;数字 0 / False 等非字符串假值不算缺失。
399
+ * 报错文案与源一致:`缺少必填参数: <key>`,按 required 顺序逐个校验。
400
+ */
401
+ export function validateRequiredParams(args, required) {
402
+ for (const key of required) {
403
+ const value = args[key];
404
+ if (value === undefined || value === null || (typeof value === 'string' && !value.trim())) {
405
+ throw caizhilianValidationError('field_required', `缺少必填参数: ${key}`);
406
+ }
407
+ }
408
+ }