weaver-work-cli 0.1.7 → 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 (264) 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 -533
  115. package/dist/shortcuts/workflow/operations/read.js +462 -81
  116. package/dist/shortcuts/workflow/operations/shared.js +44 -0
  117. package/dist/shortcuts/workflow/operations/write.js +760 -56
  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 +33 -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 +40 -77
  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 +37 -20
  235. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  236. package/skills/weaver-e10-workflow/references/workflow-view.md +4 -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-issuing.md +5 -5
  241. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  242. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  243. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  244. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  245. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  246. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  247. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +90 -10
  248. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  249. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  250. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  251. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  252. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  253. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  254. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  255. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  256. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  257. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  258. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  259. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  260. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  261. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  262. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  263. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  264. package/scripts/sync-connector-skills.mjs +0 -172
@@ -6,7 +6,14 @@ import { CliError, ExitCode } from '../../../core/errors.js';
6
6
  import { buildE10CookieHeader, E10AuthSession, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
7
7
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
8
8
  export const XIAOE_AGENT_TYPE = 'XiaoE';
9
+ // `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
10
+ // 本 CLI 自定义的宿主集成变量一律使用自有前缀 `WEAVER_XIAOE_*`;历史 `XIAOE_*`
11
+ // 名称仅作兼容别名排在后面,读取优先级低于新名称。
9
12
  const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
13
+ 'WEAVER_XIAOE_AUTH_MAP',
14
+ 'WEAVER_XIAOE_E10_AUTH_MAP',
15
+ 'WEAVER_XIAOE_DOMAIN_ETEAMSID_MAP',
16
+ 'WEAVER_XIAOE_UNIFIED_LOGIN_MAP',
10
17
  'XIAOE_E10_AUTH_MAP',
11
18
  'XIAOE_E10_DOMAIN_ETEAMSID_MAP',
12
19
  'XIAOE_DOMAIN_ETEAMSID_MAP',
@@ -14,25 +21,30 @@ const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
14
21
  ];
15
22
  const XIAOE_SINGLE_DOMAIN_ENV_KEYS = [
16
23
  'XIAOE_DOMAIN',
24
+ 'WEAVER_XIAOE_E10_DOMAIN',
25
+ 'WEAVER_XIAOE_BASE_URL',
26
+ 'WEAVER_XIAOE_E10_BASE_URL',
17
27
  'XIAOE_E10_DOMAIN',
18
28
  'XIAOE_BASE_URL',
19
29
  'XIAOE_E10_BASE_URL',
20
30
  ];
21
31
  const XIAOE_DEFAULT_DOMAIN_ENV_KEYS = [
32
+ 'WEAVER_XIAOE_DEFAULT_DOMAIN',
33
+ 'WEAVER_XIAOE_DEFAULT_BASE_URL',
34
+ 'WEAVER_XIAOE_E10_DEFAULT_DOMAIN',
35
+ 'WEAVER_XIAOE_E10_DEFAULT_BASE_URL',
22
36
  'XIAOE_DEFAULT_DOMAIN',
23
37
  'XIAOE_DEFAULT_BASE_URL',
24
38
  'XIAOE_E10_DEFAULT_DOMAIN',
25
39
  'XIAOE_E10_DEFAULT_BASE_URL',
26
40
  ];
27
- const XIAOE_SINGLE_ETEAMSID_ENV_KEYS = [
28
- 'XIAOE_ETEAMSID',
29
- 'XIAOE_ETEAM_ID',
30
- 'XIAOE_ETEAMID',
31
- 'XIAOE_E10_ETEAMSID',
32
- 'XIAOE_E10_ETEAM_ID',
33
- ];
34
- const XIAOE_TOKEN_SCRIPT_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PATH';
35
- const XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PYTHON';
41
+ // The XiaoE host only injects domain variables (XIAOE_DOMAIN etc.) together
42
+ // with XIAOE_USER_TOKEN; it never injects an ETEAMSID environment variable, so
43
+ // no `XIAOE_*ETEAMSID*` credential channel exists here on purpose.
44
+ const XIAOE_TOKEN_SCRIPT_ENV_KEY = 'WEAVER_XIAOE_TOKEN_SCRIPT_PATH';
45
+ const XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY = 'WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON';
46
+ const XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PATH';
47
+ const XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PYTHON';
36
48
  const XIAOE_TOKEN_SCRIPT_TIMEOUT_MS = 15_000;
37
49
  const XIAOE_CLIENT_TOKEN_TIMEOUT_MS = 15_000;
38
50
  export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
@@ -50,29 +62,38 @@ function hasEnvPrefix(env, prefix) {
50
62
  return Object.keys(env).some((key) => key === prefix || key.startsWith(`${prefix}_`));
51
63
  }
52
64
  export function hasXiaoEEnv(env = process.env) {
53
- return hasEnvPrefix(env, 'XIAOE');
65
+ // `XIAOE_*` are the platform-injected variables; `WEAVER_XIAOE_*` are this
66
+ // CLI's own host-integration variables. Either one marks a XiaoE host.
67
+ return hasEnvPrefix(env, 'XIAOE') || hasEnvPrefix(env, 'WEAVER_XIAOE');
54
68
  }
55
69
  export function describeXiaoECredentialSource(source) {
56
- if (source === 'xiaoeUserData/tokens.py') {
70
+ // Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
71
+ // it is available in every sandbox run and needs no local desktop client.
72
+ if (source === XIAOE_USER_TOKEN_SOURCE) {
57
73
  return {
58
74
  credentialSourceStep: 1,
75
+ credentialSourceType: 'xiaoe_platform',
76
+ credentialSourceLabel: 'XiaoE platform user token',
77
+ };
78
+ }
79
+ if (source === 'xiaoeUserData/tokens.py') {
80
+ return {
81
+ credentialSourceStep: 2,
59
82
  credentialSourceType: 'token_script',
60
83
  credentialSourceLabel: 'xiaoeUserData/tokens.py',
61
84
  };
62
85
  }
63
- if (source === XIAOE_CLIENT_SETTINGS_SOURCE || source === XIAOE_USER_TOKEN_SOURCE) {
86
+ if (source === XIAOE_CLIENT_SETTINGS_SOURCE) {
64
87
  return {
65
- credentialSourceStep: 2,
88
+ credentialSourceStep: 3,
66
89
  credentialSourceType: 'xiaoe_client',
67
- credentialSourceLabel: source === XIAOE_USER_TOKEN_SOURCE
68
- ? 'XiaoE client user token'
69
- : 'XiaoE client settings',
90
+ credentialSourceLabel: 'XiaoE client settings',
70
91
  };
71
92
  }
72
93
  return {
73
- credentialSourceStep: 3,
94
+ credentialSourceStep: 4,
74
95
  credentialSourceType: 'xiaoe_env',
75
- credentialSourceLabel: 'XIAOE_* environment variables',
96
+ credentialSourceLabel: 'WEAVER_XIAOE_* environment variables',
76
97
  };
77
98
  }
78
99
  function normalizeUrl(value) {
@@ -223,25 +244,27 @@ function listXiaoEUnifiedLoginCredentials(env = process.env) {
223
244
  if (raw)
224
245
  candidates.push(...parseXiaoEUnifiedLoginMap(raw, key));
225
246
  }
226
- const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
227
- const singleEteamsId = envString(env, ...XIAOE_SINGLE_ETEAMSID_ENV_KEYS);
228
- if (singleDomain && singleEteamsId) {
229
- candidates.push({
230
- baseUrl: normalizeUrl(singleDomain),
231
- eteamsId: singleEteamsId,
232
- source: `${XIAOE_SINGLE_DOMAIN_ENV_KEYS[0]}+${XIAOE_SINGLE_ETEAMSID_ENV_KEYS[0]}`,
233
- });
234
- }
235
247
  return candidates;
236
248
  }
237
249
  export function resolveXiaoEUnifiedLoginCredential(baseUrl, env = process.env) {
238
250
  const candidates = listXiaoEUnifiedLoginCredentials(env);
239
251
  return candidates.find((candidate) => domainMatches(baseUrl, candidate.baseUrl)) || null;
240
252
  }
253
+ function normalizeBaseUrlValue(value) {
254
+ const raw = normalizeUrl(value.trim());
255
+ if (!raw)
256
+ return '';
257
+ return /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
258
+ }
241
259
  export function resolveXiaoEDefaultBaseUrl(env = process.env) {
242
260
  const explicitDefault = envString(env, ...XIAOE_DEFAULT_DOMAIN_ENV_KEYS);
243
261
  if (explicitDefault)
244
- return normalizeUrl(explicitDefault);
262
+ return normalizeBaseUrlValue(explicitDefault);
263
+ // The injected XIAOE_DOMAIN (XIAOE_USER_TOKEN's target domain) is a fact
264
+ // about the current host and must win over the CLI's built-in fallback.
265
+ const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
266
+ if (singleDomain)
267
+ return normalizeBaseUrlValue(singleDomain);
245
268
  const firstCredential = listXiaoEUnifiedLoginCredentials(env)[0];
246
269
  if (firstCredential?.baseUrl)
247
270
  return normalizeUrl(firstCredential.baseUrl);
@@ -259,7 +282,7 @@ function uniqueStrings(values) {
259
282
  return result;
260
283
  }
261
284
  function xiaoETokenScriptCandidatePaths(env = process.env) {
262
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
285
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
263
286
  if (explicit)
264
287
  return [explicit];
265
288
  if (process.platform === 'win32') {
@@ -281,14 +304,14 @@ function xiaoETokenScriptPath(env = process.env) {
281
304
  return xiaoETokenScriptCandidatePaths(env).find((path) => existsSync(path)) || '';
282
305
  }
283
306
  export function hasXiaoETokenScript(env = process.env) {
284
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
307
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
285
308
  if (explicit)
286
309
  return existsSync(explicit);
287
310
  const scriptPath = xiaoETokenScriptPath(env);
288
311
  return Boolean(scriptPath && existsSync(scriptPath));
289
312
  }
290
313
  function xiaoETokenScriptPythonCandidates(env = process.env) {
291
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY);
314
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY, XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY);
292
315
  if (explicit)
293
316
  return [explicit];
294
317
  return uniqueStrings(process.platform === 'win32'
@@ -418,14 +441,31 @@ export function findXiaoEClientSettingsFile(env = process.env) {
418
441
  * Mirror of the host `xiaoeUserData/tokens.py` credential chain, implemented in
419
442
  * Node so the CLI does not depend on a Python runtime that has `requests`.
420
443
  */
421
- export function readXiaoEClientCredential(env = process.env) {
444
+ /**
445
+ * Platform-injected credential: XIAOE_USER_TOKEN (+ the XiaoE service address).
446
+ * This is the only source that works in a Linux sandbox with no desktop client
447
+ * and no Python runtime, so it is tried first.
448
+ */
449
+ export function readXiaoEPlatformCredential(env = process.env) {
422
450
  const envToken = envString(env, 'XIAOE_USER_TOKEN');
423
- if (envToken) {
424
- const erpaUrl = normalizeUrl(envString(env, 'XIAOE_ERP', 'XIAOE_ERPA', 'XIAOE_ERP_URL', 'XIAOE_DOMAIN'));
425
- if (/^https?:\/\//i.test(erpaUrl)) {
426
- return { authToken: envToken, erpaUrl, source: XIAOE_USER_TOKEN_SOURCE };
427
- }
428
- }
451
+ if (!envToken)
452
+ return null;
453
+ const rawErpa = envString(env, 'WEAVER_XIAOE_ERP', 'WEAVER_XIAOE_ERPA', 'WEAVER_XIAOE_ERP_URL', 'XIAOE_ERP', 'XIAOE_ERPA', 'XIAOE_ERP_URL', 'XIAOE_DOMAIN');
454
+ // Hosts may inject the domain without a scheme (e.g. "weapp.example.cn");
455
+ // default to https instead of rejecting the credential outright.
456
+ const erpaUrl = normalizeBaseUrlValue(rawErpa);
457
+ if (!erpaUrl)
458
+ return null;
459
+ return { authToken: envToken, erpaUrl, source: XIAOE_USER_TOKEN_SOURCE };
460
+ }
461
+ export function hasXiaoEPlatformCredential(env = process.env) {
462
+ return Boolean(readXiaoEPlatformCredential(env));
463
+ }
464
+ /**
465
+ * Desktop XiaoE client session: the newest XiaoE/Robots/<id>/user_settings.json.
466
+ * Kept for local desktop hosts only; sandboxes must rely on the platform credential.
467
+ */
468
+ export function readXiaoEClientCredential(env = process.env) {
429
469
  const settingsFile = findXiaoEClientSettingsFile(env);
430
470
  if (!settingsFile)
431
471
  return null;
@@ -442,7 +482,7 @@ export function readXiaoEClientCredential(env = process.env) {
442
482
  }
443
483
  }
444
484
  export function hasXiaoEClientCredential(env = process.env) {
445
- return Boolean(envString(env, 'XIAOE_USER_TOKEN')) || Boolean(findXiaoEClientSettingsFile(env));
485
+ return Boolean(findXiaoEClientSettingsFile(env));
446
486
  }
447
487
  async function fetchXiaoEClientEteamId(credential, domain) {
448
488
  const controller = new AbortController();
@@ -468,13 +508,22 @@ async function fetchXiaoEClientEteamId(credential, domain) {
468
508
  clearTimeout(timer);
469
509
  }
470
510
  }
511
+ async function resolveXiaoETokenCredential(credential, baseUrl) {
512
+ const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
513
+ const payload = await fetchXiaoEClientEteamId(credential, targetBaseUrl);
514
+ return readXiaoECredentialRecord(payload, credential.source, targetBaseUrl);
515
+ }
516
+ async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
517
+ const credential = readXiaoEPlatformCredential(env);
518
+ if (!credential)
519
+ return null;
520
+ return resolveXiaoETokenCredential(credential, baseUrl);
521
+ }
471
522
  async function resolveXiaoEClientCredential(baseUrl, env = process.env) {
472
523
  const clientCredential = readXiaoEClientCredential(env);
473
524
  if (!clientCredential)
474
525
  return null;
475
- const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
476
- const payload = await fetchXiaoEClientEteamId(clientCredential, targetBaseUrl);
477
- return readXiaoECredentialRecord(payload, clientCredential.source, targetBaseUrl);
526
+ return resolveXiaoETokenCredential(clientCredential, baseUrl);
478
527
  }
479
528
  export function resolveXiaoEPreferredBaseUrl(options) {
480
529
  const env = options.env || process.env;
@@ -563,37 +612,53 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
563
612
  if (!baseUrl && !mayUseTokenScript)
564
613
  return null;
565
614
  let credential = null;
566
- if (mayUseTokenScript) {
615
+ // Step 1 — platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN).
616
+ // Works in sandboxes: no desktop client, no Python, no developer-machine paths.
617
+ if (hasXiaoEPlatformCredential(env)) {
618
+ options.logger?.info('Trying XiaoE platform credential from XIAOE_USER_TOKEN...');
619
+ try {
620
+ credential = await resolveXiaoEPlatformCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
621
+ }
622
+ catch (error) {
623
+ const message = error instanceof Error ? error.message : String(error);
624
+ options.logger?.warn('XiaoE platform credential failed. ' + message);
625
+ }
626
+ if (!credential) {
627
+ options.logger?.warn('XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
628
+ }
629
+ }
630
+ // Step 2 — host tokens.py (desktop host only; Linux sandboxes have no such path).
631
+ if (!credential && mayUseTokenScript) {
567
632
  const tokenScriptPath = xiaoETokenScriptPath(env);
568
633
  if (!tokenScriptPath) {
569
- options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the built-in XiaoE client credential next.');
634
+ options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the local XiaoE client session next.');
570
635
  }
571
636
  else {
572
637
  options.logger?.info('Trying XiaoE token script credential...');
573
638
  try {
574
- credential = await resolveXiaoETokenScriptCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
639
+ credential = await resolveXiaoETokenScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
575
640
  }
576
641
  catch (error) {
577
642
  const message = error instanceof Error ? error.message : String(error);
578
643
  options.logger?.warn('XiaoE token script failed. ' + message);
579
644
  }
580
645
  if (!credential) {
581
- options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the built-in XiaoE client credential next.');
646
+ options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the local XiaoE client session next.');
582
647
  }
583
648
  }
584
- // Built-in Node equivalent of tokens.py: reads the local XiaoE client
585
- // session and exchanges it for an eteamId, so a Python runtime without
586
- // `requests` no longer blocks unified login.
649
+ // Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
650
+ // client session and exchanges it for an eteamId, so a Python runtime
651
+ // without `requests` no longer blocks unified login.
587
652
  if (!credential && hasXiaoEClientCredential(env)) {
588
653
  try {
589
- credential = await resolveXiaoEClientCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
654
+ credential = await resolveXiaoEClientCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
590
655
  }
591
656
  catch (error) {
592
657
  const message = error instanceof Error ? error.message : String(error);
593
658
  options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
594
659
  }
595
660
  if (!credential) {
596
- options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try XIAOE_* credentials next.');
661
+ options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try WEAVER_XIAOE_* credentials next.');
597
662
  }
598
663
  }
599
664
  }
@@ -131,6 +131,11 @@ export class E10RequestRuntime {
131
131
  composeHeaders(request, trustedHeaders = {}) {
132
132
  return {
133
133
  Accept: 'application/json, text/plain, */*',
134
+ // E10 各端(含 eBuilder 表单运行态)依赖这两个头;缺失时 EB formdata 接口会直接返回
135
+ // 业务失败(CLI 侧表现为 business_error「泛微接口返回业务失败」,且响应无可读 message,
136
+ // 极易被误判为「EB 未配置/环境不对」)。standalone skill 的脚本一直带这两个头。
137
+ langType: 'zh_CN',
138
+ timeZoneOffset: String(new Date().getTimezoneOffset()),
134
139
  ...request.headers,
135
140
  Cookie: this.session.cookieHeader(),
136
141
  eteamsid: this.session.eteamsId,
@@ -1,13 +1,153 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { basename, relative, resolve, sep } from 'node:path';
3
+ import { basename, posix, relative, resolve, sep, win32 } from 'node:path';
4
4
  const SKILL_PREFIX = 'weaver-e10-';
5
5
  const LEGACY_SKILL_PREFIX = 'weaver-work-cli-';
6
6
  const SHARED_SKILL_NAME = 'weaver-e10-shared';
7
- const LEGACY_SHARED_SKILL_NAME = 'weaver-work-cli-shared';
8
- export function defaultCodexSkillsRoot(env = process.env) {
9
- const codexHome = env.CODEX_HOME || resolve(homedir(), '.codex');
10
- return resolve(codexHome, 'skills');
7
+ function envValue(env, name) {
8
+ const direct = env[name];
9
+ if (typeof direct === 'string' && direct.trim())
10
+ return direct.trim();
11
+ const expected = name.toUpperCase();
12
+ for (const [key, value] of Object.entries(env)) {
13
+ if (key.toUpperCase() === expected && typeof value === 'string' && value.trim())
14
+ return value.trim();
15
+ }
16
+ return '';
17
+ }
18
+ function hasEnvFamily(env, prefix, ignoredNames = []) {
19
+ const expected = `${prefix.toUpperCase()}_`;
20
+ const ignored = new Set(ignoredNames.map((name) => name.toUpperCase()));
21
+ return Object.entries(env).some(([key, value]) => (key.toUpperCase().startsWith(expected)
22
+ && !ignored.has(key.toUpperCase())
23
+ && typeof value === 'string'
24
+ && Boolean(value.trim())));
25
+ }
26
+ function normalizedAgentType(value) {
27
+ const normalized = value.trim().toLowerCase();
28
+ if (normalized === 'codex-cli')
29
+ return 'codex';
30
+ if (normalized === 'work-buddy')
31
+ return 'workbuddy';
32
+ return normalized || 'unknown';
33
+ }
34
+ function isKnownSkillsHost(host) {
35
+ return host === 'codex' || host === 'workbuddy';
36
+ }
37
+ export function resolveSkillsTargetPath(value, options = {}) {
38
+ const trimmed = value.trim();
39
+ if (!trimmed)
40
+ throw new Error('skills target path must not be empty');
41
+ const platform = options.platform ?? process.platform;
42
+ const pathApi = platform === 'win32' ? win32 : posix;
43
+ const userHome = options.homeDir ?? homedir();
44
+ if (trimmed === '~')
45
+ return pathApi.resolve(userHome);
46
+ if (/^~[\\/]/u.test(trimmed)) {
47
+ const segments = trimmed.slice(2).split(/[\\/]+/u).filter(Boolean);
48
+ return pathApi.resolve(userHome, ...segments);
49
+ }
50
+ return pathApi.resolve(trimmed);
51
+ }
52
+ function hostSkillsRoot(host, env, options) {
53
+ const platform = options.platform ?? process.platform;
54
+ const pathApi = platform === 'win32' ? win32 : posix;
55
+ const userHome = options.homeDir ?? homedir();
56
+ const envName = host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME';
57
+ const configuredHome = envValue(env, envName);
58
+ const hostHome = configuredHome
59
+ ? resolveSkillsTargetPath(configuredHome, options)
60
+ : pathApi.resolve(userHome, host === 'codex' ? '.codex' : '.workbuddy');
61
+ return pathApi.resolve(hostHome, 'skills');
62
+ }
63
+ /**
64
+ * 根据当前进程的环境信号探测宿主。目录存在只代表曾安装过某个宿主,
65
+ * 不能证明当前命令由该宿主调用,因此不在这里参与判定。
66
+ *
67
+ * WEAVER_AGENT_TYPE 是跨宿主的显式信号;没有它时,当前进程的运行时变量优先于
68
+ * 可能被用户长期配置的 *_HOME。两个宿主仍然同时有信号时保持 unknown。
69
+ */
70
+ export function detectSkillsHost(env = process.env) {
71
+ const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
72
+ if (explicitAgent)
73
+ return normalizedAgentType(explicitAgent);
74
+ const hasCodexRuntime = hasEnvFamily(env, 'CODEX', ['CODEX_HOME']);
75
+ const hasWorkBuddyRuntime = hasEnvFamily(env, 'WORKBUDDY', ['WORKBUDDY_HOME']);
76
+ if (hasCodexRuntime !== hasWorkBuddyRuntime)
77
+ return hasCodexRuntime ? 'codex' : 'workbuddy';
78
+ const hasCodexHome = Boolean(envValue(env, 'CODEX_HOME'));
79
+ const hasWorkBuddyHome = Boolean(envValue(env, 'WORKBUDDY_HOME'));
80
+ if (hasCodexHome !== hasWorkBuddyHome)
81
+ return hasCodexHome ? 'codex' : 'workbuddy';
82
+ return 'unknown';
83
+ }
84
+ export function resolveSkillsRoot(env = process.env, options = {}) {
85
+ const configuredRoot = envValue(env, 'WEAVER_SKILLS_ROOT');
86
+ if (configuredRoot) {
87
+ const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
88
+ const detectedHost = explicitAgent ? normalizedAgentType(explicitAgent) : detectSkillsHost(env);
89
+ return {
90
+ root: resolveSkillsTargetPath(configuredRoot, options),
91
+ host: detectedHost === 'unknown' ? 'explicit' : detectedHost,
92
+ source: 'explicit-env',
93
+ environmentVariable: 'WEAVER_SKILLS_ROOT',
94
+ };
95
+ }
96
+ const host = detectSkillsHost(env);
97
+ if (isKnownSkillsHost(host)) {
98
+ const environmentVariable = envValue(env, host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
99
+ ? (host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
100
+ : undefined;
101
+ return {
102
+ root: hostSkillsRoot(host, env, options),
103
+ host,
104
+ source: environmentVariable ? 'explicit-env' : 'host-detected',
105
+ ...(environmentVariable ? { environmentVariable } : {}),
106
+ };
107
+ }
108
+ const codexRoot = hostSkillsRoot('codex', env, options);
109
+ const workBuddyRoot = hostSkillsRoot('workbuddy', env, options);
110
+ const pathExists = options.pathExists ?? existsSync;
111
+ if (host === 'unknown') {
112
+ const existing = [
113
+ { root: codexRoot, host: 'codex' },
114
+ { root: workBuddyRoot, host: 'workbuddy' },
115
+ ].filter((candidate) => pathExists(candidate.root));
116
+ if (existing.length === 1) {
117
+ return { ...existing[0], source: 'filesystem-detected' };
118
+ }
119
+ return {
120
+ root: codexRoot,
121
+ host,
122
+ source: 'fallback',
123
+ requiresExplicitTarget: true,
124
+ reason: existing.length > 1 ? 'ambiguous-host' : 'unknown-host',
125
+ candidates: [codexRoot, workBuddyRoot],
126
+ };
127
+ }
128
+ return {
129
+ root: codexRoot,
130
+ host,
131
+ source: 'fallback',
132
+ requiresExplicitTarget: true,
133
+ reason: 'unsupported-host',
134
+ candidates: [codexRoot, workBuddyRoot],
135
+ };
136
+ }
137
+ export function defaultCodexSkillsRoot(env = process.env, options = {}) {
138
+ return hostSkillsRoot('codex', env, options);
139
+ }
140
+ export function unresolvedSkillsRootMessage(resolution, targetOption = '--target-dir') {
141
+ const reason = resolution.reason === 'ambiguous-host'
142
+ ? '检测到多个可能的宿主目录'
143
+ : resolution.reason === 'unsupported-host'
144
+ ? `检测到当前智能体 ${resolution.host},但不知道它的 skills 目录`
145
+ : '未检测到当前智能体及其 skills 目录';
146
+ return [
147
+ `${reason},为避免装错位置,本次未执行安装。`,
148
+ `请使用 ${targetOption} <当前智能体的 skills 根目录>,或设置 WEAVER_SKILLS_ROOT 后重试。`,
149
+ ...(resolution.candidates?.length ? [`已知候选目录:${resolution.candidates.join(',')}`] : []),
150
+ ].join('\n');
11
151
  }
12
152
  export function normalizePackagedSkillName(value) {
13
153
  const trimmed = value.trim();
@@ -65,7 +205,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
65
205
  const skillNames = resolvePackagedSkillNames(resolvedSourceRoot, requested);
66
206
  const productContent = productMetadataContent(resolvedSourceRoot);
67
207
  mkdirSync(resolvedTargetRoot, { recursive: true, mode: 0o700 });
68
- const installed = skillNames.flatMap((name) => {
208
+ const installed = skillNames.map((name) => {
69
209
  const source = resolveInside(resolvedSourceRoot, name);
70
210
  const target = resolve(resolvedTargetRoot, basename(name));
71
211
  statSync(resolve(source, 'SKILL.md'));
@@ -81,23 +221,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
81
221
  target,
82
222
  files: countFiles(target),
83
223
  };
84
- if (name !== SHARED_SKILL_NAME)
85
- return [current];
86
- const legacyTarget = resolve(resolvedTargetRoot, LEGACY_SHARED_SKILL_NAME);
87
- rmSync(legacyTarget, { recursive: true, force: true });
88
- cpSync(source, legacyTarget, {
89
- recursive: true,
90
- force: true,
91
- filter: (entry) => shouldCopy(entry, source, Boolean(productContent)),
92
- });
93
- rewriteSkillName(resolve(legacyTarget, 'SKILL.md'), LEGACY_SHARED_SKILL_NAME);
94
- writeProductMetadata(legacyTarget, productContent);
95
- return [current, {
96
- name: LEGACY_SHARED_SKILL_NAME,
97
- source,
98
- target: legacyTarget,
99
- files: countFiles(legacyTarget),
100
- }];
224
+ return current;
101
225
  });
102
226
  return {
103
227
  sourceRoot: resolvedSourceRoot,
@@ -106,10 +230,6 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
106
230
  installed,
107
231
  };
108
232
  }
109
- function rewriteSkillName(skillPath, name) {
110
- const content = readFileSync(skillPath, 'utf8');
111
- writeFileSync(skillPath, content.replace(/^name:\s*weaver-e10-shared\s*$/mu, `name: ${name}`), 'utf8');
112
- }
113
233
  function productMetadataContent(sourceRoot) {
114
234
  const productPath = productMetadataPath(sourceRoot);
115
235
  if (!existsSync(productPath))
@@ -16,13 +16,14 @@ export class SkillReader {
16
16
  }
17
17
  listPath(target) {
18
18
  const { name, subpath } = splitTarget(target);
19
- const skillRoot = this.ensureSkill(name);
19
+ const skill = this.resolveSkill(name);
20
+ const skillRoot = skill.root;
20
21
  const dir = this.resolveInside(skillRoot, subpath);
21
22
  const info = statSync(dir);
22
23
  if (!info.isDirectory()) {
23
24
  throw new Error(`path is a file, not a directory: ${target}`);
24
25
  }
25
- const listed = cleanOutputPath(name, subpath);
26
+ const listed = cleanOutputPath(skill.name, subpath);
26
27
  const entries = readdirSync(dir, { withFileTypes: true })
27
28
  .map((entry) => ({
28
29
  path: cleanOutputPath(listed, entry.name),
@@ -33,16 +34,15 @@ export class SkillReader {
33
34
  }
34
35
  read(target, pathArg = '') {
35
36
  const split = splitTarget(target);
36
- const name = split.name;
37
+ const skill = this.resolveSkill(split.name);
37
38
  const subpath = pathArg || split.subpath || 'SKILL.md';
38
- const skillRoot = this.ensureSkill(name);
39
- const file = this.resolveInside(skillRoot, subpath);
39
+ const file = this.resolveInside(skill.root, subpath);
40
40
  const info = statSync(file);
41
41
  if (info.isDirectory()) {
42
- throw new Error(`path is a directory, not a file: ${cleanOutputPath(name, subpath)}`);
42
+ throw new Error(`path is a directory, not a file: ${cleanOutputPath(skill.name, subpath)}`);
43
43
  }
44
44
  return {
45
- skill: name,
45
+ skill: skill.name,
46
46
  path: cleanOutputPath(subpath),
47
47
  content: readFileSync(file, 'utf-8'),
48
48
  };
@@ -64,6 +64,9 @@ export class SkillReader {
64
64
  }
65
65
  }
66
66
  ensureSkill(name) {
67
+ return this.resolveSkill(name).root;
68
+ }
69
+ resolveSkill(name) {
67
70
  if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
68
71
  throw new Error(`unknown skill: ${name}`);
69
72
  }
@@ -73,14 +76,14 @@ export class SkillReader {
73
76
  if (normalized !== name) {
74
77
  const normalizedRoot = this.resolveInside(this.root, normalized);
75
78
  if (existsDirectoryWithSkill(normalizedRoot))
76
- return normalizedRoot;
79
+ return { name: normalized, root: normalizedRoot };
77
80
  }
78
81
  }
79
82
  const info = statSync(skillRoot);
80
83
  if (!info.isDirectory())
81
84
  throw new Error(`unknown skill: ${name}`);
82
85
  statSync(resolve(skillRoot, 'SKILL.md'));
83
- return skillRoot;
86
+ return { name, root: skillRoot };
84
87
  }
85
88
  resolveInside(base, subpath = '') {
86
89
  const cleaned = subpath.replace(/\\/g, '/');
@@ -0,0 +1,74 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { blogValidationError } from './errors.js';
7
+ function stateRoot(ctx) {
8
+ const configured = ctx?.config?.path();
9
+ if (configured)
10
+ return resolve(configured, '..');
11
+ return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
12
+ }
13
+ async function loadKey(ctx) {
14
+ const keysDir = resolve(stateRoot(ctx), 'keys');
15
+ const filename = resolve(keysDir, 'blog-continuation.key');
16
+ await mkdir(keysDir, { recursive: true, mode: 0o700 });
17
+ if (existsSync(filename))
18
+ return readFile(filename);
19
+ const key = randomBytes(32);
20
+ try {
21
+ await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
22
+ if (process.platform !== 'win32')
23
+ await chmod(filename, 0o600).catch(() => undefined);
24
+ return key;
25
+ }
26
+ catch {
27
+ return readFile(filename);
28
+ }
29
+ }
30
+ function encode(value) {
31
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
32
+ }
33
+ function signature(payload, key) {
34
+ return createHmac('sha256', key).update(payload).digest('base64url');
35
+ }
36
+ /**
37
+ * prepare -> apply 确认链的 signed continuation(HMAC-SHA256,默认 10 分钟有效)。
38
+ * 密钥持久化在 `<stateRoot>/keys/blog-continuation.key`,保证多次 CLI 进程调用可校验。
39
+ */
40
+ export function createBlogContinuationSigner(ctx) {
41
+ return {
42
+ async issue(payload, ttlMs = 10 * 60_000) {
43
+ const issuedAt = Date.now();
44
+ const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
45
+ const sig = signature(body, await loadKey(ctx));
46
+ return `${body}.${sig}`;
47
+ },
48
+ async verify(token, expectedOperation) {
49
+ if (typeof token !== 'string' || !token.includes('.')) {
50
+ throw blogValidationError('continuation_invalid', 'continuation 无效');
51
+ }
52
+ const [body, provided] = token.split('.');
53
+ const expected = signature(body, await loadKey(ctx));
54
+ const valid = provided.length === expected.length
55
+ && timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
56
+ if (!valid)
57
+ throw blogValidationError('continuation_invalid', 'continuation 签名校验失败');
58
+ let value;
59
+ try {
60
+ value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
61
+ }
62
+ catch {
63
+ throw blogValidationError('continuation_invalid', 'continuation 内容无法解析');
64
+ }
65
+ if (value.operation !== expectedOperation) {
66
+ throw blogValidationError('continuation_mismatch', 'continuation 不属于当前操作');
67
+ }
68
+ if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
69
+ throw blogValidationError('continuation_expired', 'continuation 已过期');
70
+ }
71
+ return value;
72
+ },
73
+ };
74
+ }