weaver-work-cli 0.1.8 → 0.1.21

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 (271) hide show
  1. package/README.md +25 -17
  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 +435 -0
  14. package/dist/core/state.js +26 -0
  15. package/dist/internal/e10/auth/commands.js +34 -19
  16. package/dist/internal/e10/auth/session.js +74 -2
  17. package/dist/internal/e10/auth/xiaoe.js +359 -94
  18. package/dist/internal/e10/context.js +18 -6
  19. package/dist/internal/e10/request-runtime.js +8 -1
  20. package/dist/internal/skills/install.js +148 -28
  21. package/dist/internal/skills/reader.js +12 -9
  22. package/dist/shortcuts/blog/continuation.js +74 -0
  23. package/dist/shortcuts/blog/errors.js +60 -0
  24. package/dist/shortcuts/blog/host.js +175 -0
  25. package/dist/shortcuts/blog/index.js +324 -0
  26. package/dist/shortcuts/blog/manifest.js +415 -0
  27. package/dist/shortcuts/blog/operations/comment.js +125 -0
  28. package/dist/shortcuts/blog/operations/comments.js +31 -0
  29. package/dist/shortcuts/blog/operations/edit.js +131 -0
  30. package/dist/shortcuts/blog/operations/export.js +46 -0
  31. package/dist/shortcuts/blog/operations/like.js +80 -0
  32. package/dist/shortcuts/blog/operations/publish.js +157 -0
  33. package/dist/shortcuts/blog/operations/query-user.js +58 -0
  34. package/dist/shortcuts/blog/operations/query.js +96 -0
  35. package/dist/shortcuts/blog/operations/registry.js +54 -0
  36. package/dist/shortcuts/blog/operations/remind.js +176 -0
  37. package/dist/shortcuts/blog/operations/shared.js +330 -0
  38. package/dist/shortcuts/blog/operations/types.js +16 -0
  39. package/dist/shortcuts/blog/operations.js +9 -0
  40. package/dist/shortcuts/caizhilian/continuation.js +70 -0
  41. package/dist/shortcuts/caizhilian/errors.js +53 -0
  42. package/dist/shortcuts/caizhilian/host.js +190 -0
  43. package/dist/shortcuts/caizhilian/index.js +186 -0
  44. package/dist/shortcuts/caizhilian/manifest.js +42 -0
  45. package/dist/shortcuts/caizhilian/operations/api.js +801 -0
  46. package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
  47. package/dist/shortcuts/caizhilian/operations/files.js +306 -0
  48. package/dist/shortcuts/caizhilian/operations/form.js +170 -0
  49. package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
  50. package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
  51. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +1227 -0
  52. package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
  53. package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
  54. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +1024 -0
  55. package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
  56. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
  57. package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
  58. package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
  59. package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
  60. package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
  61. package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
  62. package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
  63. package/dist/shortcuts/caizhilian/operations/specs.js +698 -0
  64. package/dist/shortcuts/caizhilian/operations/types.js +57 -0
  65. package/dist/shortcuts/caizhilian/operations.js +21 -0
  66. package/dist/shortcuts/contract/continuation.js +70 -0
  67. package/dist/shortcuts/contract/errors.js +62 -0
  68. package/dist/shortcuts/contract/host.js +205 -0
  69. package/dist/shortcuts/contract/index.js +318 -0
  70. package/dist/shortcuts/contract/manifest.js +596 -0
  71. package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
  72. package/dist/shortcuts/contract/operations/form-info.js +371 -0
  73. package/dist/shortcuts/contract/operations/read.js +118 -0
  74. package/dist/shortcuts/contract/operations/registry.js +43 -0
  75. package/dist/shortcuts/contract/operations/request.js +300 -0
  76. package/dist/shortcuts/contract/operations/shared.js +133 -0
  77. package/dist/shortcuts/contract/operations/subject.js +157 -0
  78. package/dist/shortcuts/contract/operations/template.js +81 -0
  79. package/dist/shortcuts/contract/operations/types.js +7 -0
  80. package/dist/shortcuts/contract/operations/workflow.js +52 -0
  81. package/dist/shortcuts/contract/operations/write.js +131 -0
  82. package/dist/shortcuts/contract/operations.js +8 -0
  83. package/dist/shortcuts/hrm/index.js +83 -2
  84. package/dist/shortcuts/hrm/manifest.js +16 -9
  85. package/dist/shortcuts/hrm/operations/employee.js +160 -30
  86. package/dist/shortcuts/hrm/operations/org.js +17 -3
  87. package/dist/shortcuts/hrm/operations/position.js +18 -2
  88. package/dist/shortcuts/hrm/operations/registry.js +2 -0
  89. package/dist/shortcuts/hrm/operations/resolve.js +449 -0
  90. package/dist/shortcuts/index.js +5 -1
  91. package/dist/shortcuts/invoice/index.js +74 -36
  92. package/dist/shortcuts/invoice/manifest.js +674 -4
  93. package/dist/shortcuts/invoice/markdown.js +172 -0
  94. package/dist/shortcuts/invoice/operations/add.js +3 -3
  95. package/dist/shortcuts/invoice/operations/delete.js +2 -2
  96. package/dist/shortcuts/invoice/operations/get.js +10 -2
  97. package/dist/shortcuts/invoice/operations/import.js +2 -2
  98. package/dist/shortcuts/invoice/operations/registry.js +8 -1
  99. package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
  100. package/dist/shortcuts/invoice/operations/reim.js +85 -2
  101. package/dist/shortcuts/invoice/operations/shared.js +75 -8
  102. package/dist/shortcuts/invoice/operations/update.js +2 -2
  103. package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
  104. package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
  105. package/dist/shortcuts/invoice/projection.js +328 -0
  106. package/dist/shortcuts/meeting/host.js +18 -1
  107. package/dist/shortcuts/meeting/manifest.js +30 -18
  108. package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
  109. package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
  110. package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
  111. package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
  112. package/dist/shortcuts/meeting/operations/env.js +8 -1
  113. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  114. package/dist/shortcuts/meeting/operations/signs.js +216 -2
  115. package/dist/shortcuts/workflow/endpoints.js +2 -1
  116. package/dist/shortcuts/workflow/errors.js +8 -2
  117. package/dist/shortcuts/workflow/host.js +11 -4
  118. package/dist/shortcuts/workflow/operations/form.js +1016 -590
  119. package/dist/shortcuts/workflow/operations/read.js +913 -231
  120. package/dist/shortcuts/workflow/operations/shared.js +21 -0
  121. package/dist/shortcuts/workflow/operations/write.js +750 -52
  122. package/dist/shortcuts/workflow/operations.js +36 -10
  123. package/docs/_catalog.md +6 -0
  124. package/docs/agent-invoice.md +5 -1
  125. package/docs/agent-io-channel-design.md +468 -0
  126. package/docs/agent-skill-install.md +9 -4
  127. package/docs/blog.md +123 -0
  128. package/docs/caizhilian.md +304 -0
  129. package/docs/contract.md +91 -0
  130. package/docs/e10-auth.md +4 -2
  131. package/docs/hrm.md +53 -11
  132. package/docs/invoice.md +5 -1
  133. package/docs/meeting.md +1 -1
  134. package/docs/operation-manual.md +15 -3
  135. package/docs/skill-review-2026-09-19.md +2 -2
  136. package/package.json +1 -1
  137. package/scripts/package-skill.mjs +32 -20
  138. package/scripts/xiaoe_env.py +118 -0
  139. package/skill-template/business-info.json +113 -14
  140. package/skill-template/domains/blog.md +24 -0
  141. package/skill-template/domains/caizhilian.md +22 -0
  142. package/skill-template/domains/hrm.md +2 -1
  143. package/skill-template/domains/jinchengda.md +23 -0
  144. package/skill-template/domains/meeting.md +1 -1
  145. package/skill-template/domains/workflow.md +1 -1
  146. package/skill-template/skill-template.md +1 -1
  147. package/skills/weaver-e10-blog/SKILL.md +111 -0
  148. package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
  149. package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
  150. package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
  151. package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
  152. package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
  153. package/skills/weaver-e10-blog/references/blog-export.md +42 -0
  154. package/skills/weaver-e10-blog/references/blog-like.md +50 -0
  155. package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
  156. package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
  157. package/skills/weaver-e10-blog/references/blog-query.md +49 -0
  158. package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
  159. package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
  160. package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
  161. package/skills/weaver-e10-caizhilian/SKILL.md +217 -0
  162. package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
  163. package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
  164. package/skills/weaver-e10-caizhilian/references/document.md +111 -0
  165. package/skills/weaver-e10-caizhilian/references/file-collection.md +173 -0
  166. package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
  167. package/skills/weaver-e10-caizhilian/references/files.md +120 -0
  168. package/skills/weaver-e10-caizhilian/references/folder.md +122 -0
  169. package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
  170. package/skills/weaver-e10-caizhilian/references/setting-attach.md +440 -0
  171. package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
  172. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
  173. package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
  174. package/skills/weaver-e10-caizhilian/references/source-manifest.json +5694 -0
  175. package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
  176. package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
  177. package/skills/weaver-e10-calendar/SKILL.md +4 -3
  178. package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
  179. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
  180. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
  181. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
  182. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
  183. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
  184. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
  185. package/skills/weaver-e10-esb/SKILL.md +2 -2
  186. package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
  187. package/skills/weaver-e10-hrm/SKILL.md +104 -9
  188. package/skills/weaver-e10-hrm/references/employee.md +89 -15
  189. package/skills/weaver-e10-hrm/references/org.md +114 -5
  190. package/skills/weaver-e10-hrm/references/position.md +14 -7
  191. package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
  192. package/skills/weaver-e10-jinchengda/SKILL.md +167 -0
  193. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
  194. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
  195. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
  196. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
  197. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
  198. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
  199. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
  200. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
  201. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
  202. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
  203. package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
  204. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
  205. package/skills/weaver-e10-jucailin/SKILL.md +4 -3
  206. package/skills/weaver-e10-mail/SKILL.md +4 -11
  207. package/skills/weaver-e10-meeting/SKILL.md +66 -32
  208. package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
  209. package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
  210. package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
  211. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
  212. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
  213. package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
  214. package/skills/weaver-e10-okr/SKILL.md +6 -11
  215. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  216. package/skills/weaver-e10-plan/SKILL.md +3 -3
  217. package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
  218. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
  219. package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
  220. package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
  221. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
  222. package/skills/weaver-e10-shared/SKILL.md +42 -34
  223. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +16 -43
  224. package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
  225. package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
  226. package/skills/weaver-e10-shijingran/SKILL.md +2 -1
  227. package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
  228. package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
  229. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
  230. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
  231. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
  232. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
  233. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  234. package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
  235. package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
  236. package/skills/weaver-e10-workflow/SKILL.md +41 -46
  237. package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
  238. package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
  239. package/skills/weaver-e10-workflow/references/workflow-create.md +83 -109
  240. package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
  241. package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
  242. package/skills/weaver-e10-workflow/references/workflow-query.md +40 -27
  243. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  244. package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
  245. package/skills/weaver-e10-yepiaotong/SKILL.md +74 -39
  246. package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
  247. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
  248. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  249. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  250. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  251. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  252. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  253. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  254. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
  255. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  256. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  257. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  258. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  259. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  260. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  261. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  262. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  263. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  264. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  265. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  266. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  267. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  268. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  269. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  270. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  271. package/scripts/sync-connector-skills.mjs +0 -172
@@ -1,43 +1,56 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { accessSync, constants as fsConstants, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
- import { join } from 'node:path';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { CliError, ExitCode } from '../../../core/errors.js';
6
- import { buildE10CookieHeader, E10AuthSession, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
7
+ import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
7
8
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
8
9
  export const XIAOE_AGENT_TYPE = 'XiaoE';
10
+ // `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
11
+ // 本 CLI 自定义的宿主集成变量一律使用自有前缀 `WEAVER_XIAOE_*`;历史 `XIAOE_*`
12
+ // 名称仅作兼容别名排在后面,读取优先级低于新名称。
9
13
  const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
14
+ 'WEAVER_XIAOE_AUTH_MAP',
15
+ 'WEAVER_XIAOE_E10_AUTH_MAP',
16
+ 'WEAVER_XIAOE_DOMAIN_ETEAMSID_MAP',
17
+ 'WEAVER_XIAOE_UNIFIED_LOGIN_MAP',
10
18
  'XIAOE_E10_AUTH_MAP',
11
19
  'XIAOE_E10_DOMAIN_ETEAMSID_MAP',
12
20
  'XIAOE_DOMAIN_ETEAMSID_MAP',
13
21
  'XIAOE_UNIFIED_LOGIN_MAP',
14
22
  ];
15
23
  const XIAOE_SINGLE_DOMAIN_ENV_KEYS = [
16
- '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';
39
51
  export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
40
52
  export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
53
+ export const XIAOE_BUNDLED_ENV_SCRIPT_SOURCE = 'bundled scripts/xiaoe_env.py';
41
54
  function envString(env, ...keys) {
42
55
  for (const key of keys) {
43
56
  const value = env[key];
@@ -50,29 +63,40 @@ function hasEnvPrefix(env, prefix) {
50
63
  return Object.keys(env).some((key) => key === prefix || key.startsWith(`${prefix}_`));
51
64
  }
52
65
  export function hasXiaoEEnv(env = process.env) {
53
- return hasEnvPrefix(env, 'XIAOE');
66
+ // `XIAOE_*` are the platform-injected variables; `WEAVER_XIAOE_*` are this
67
+ // CLI's own host-integration variables. Either one marks a XiaoE host.
68
+ return hasEnvPrefix(env, 'XIAOE') || hasEnvPrefix(env, 'WEAVER_XIAOE');
54
69
  }
55
70
  export function describeXiaoECredentialSource(source) {
56
- if (source === 'xiaoeUserData/tokens.py') {
71
+ // Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
72
+ // it is available in every sandbox run and needs no local desktop client.
73
+ if (source === XIAOE_USER_TOKEN_SOURCE || source === XIAOE_BUNDLED_ENV_SCRIPT_SOURCE) {
57
74
  return {
58
75
  credentialSourceStep: 1,
76
+ credentialSourceType: 'xiaoe_platform',
77
+ credentialSourceLabel: source === XIAOE_BUNDLED_ENV_SCRIPT_SOURCE
78
+ ? 'XiaoE platform user token (bundled Python fallback)'
79
+ : 'XiaoE platform user token',
80
+ };
81
+ }
82
+ if (source === 'xiaoeUserData/tokens.py') {
83
+ return {
84
+ credentialSourceStep: 2,
59
85
  credentialSourceType: 'token_script',
60
86
  credentialSourceLabel: 'xiaoeUserData/tokens.py',
61
87
  };
62
88
  }
63
- if (source === XIAOE_CLIENT_SETTINGS_SOURCE || source === XIAOE_USER_TOKEN_SOURCE) {
89
+ if (source === XIAOE_CLIENT_SETTINGS_SOURCE) {
64
90
  return {
65
- credentialSourceStep: 2,
91
+ credentialSourceStep: 3,
66
92
  credentialSourceType: 'xiaoe_client',
67
- credentialSourceLabel: source === XIAOE_USER_TOKEN_SOURCE
68
- ? 'XiaoE client user token'
69
- : 'XiaoE client settings',
93
+ credentialSourceLabel: 'XiaoE client settings',
70
94
  };
71
95
  }
72
96
  return {
73
- credentialSourceStep: 3,
97
+ credentialSourceStep: 4,
74
98
  credentialSourceType: 'xiaoe_env',
75
- credentialSourceLabel: 'XIAOE_* environment variables',
99
+ credentialSourceLabel: 'WEAVER_XIAOE_* environment variables',
76
100
  };
77
101
  }
78
102
  function normalizeUrl(value) {
@@ -223,28 +247,36 @@ function listXiaoEUnifiedLoginCredentials(env = process.env) {
223
247
  if (raw)
224
248
  candidates.push(...parseXiaoEUnifiedLoginMap(raw, key));
225
249
  }
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
250
  return candidates;
236
251
  }
237
252
  export function resolveXiaoEUnifiedLoginCredential(baseUrl, env = process.env) {
238
253
  const candidates = listXiaoEUnifiedLoginCredentials(env);
239
254
  return candidates.find((candidate) => domainMatches(baseUrl, candidate.baseUrl)) || null;
240
255
  }
241
- export function resolveXiaoEDefaultBaseUrl(env = process.env) {
256
+ function normalizeBaseUrlValue(value) {
257
+ const raw = normalizeUrl(value.trim());
258
+ if (!raw)
259
+ return '';
260
+ return /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
261
+ }
262
+ function resolveXiaoEConfiguredBaseUrl(env) {
242
263
  const explicitDefault = envString(env, ...XIAOE_DEFAULT_DOMAIN_ENV_KEYS);
243
264
  if (explicitDefault)
244
- return normalizeUrl(explicitDefault);
265
+ return normalizeBaseUrlValue(explicitDefault);
266
+ // XIAOE_DOMAIN is the XiaoE/erpa service address used to exchange
267
+ // XIAOE_USER_TOKEN. Only dedicated E10 variables may select the target E10.
268
+ const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
269
+ if (singleDomain)
270
+ return normalizeBaseUrlValue(singleDomain);
245
271
  const firstCredential = listXiaoEUnifiedLoginCredentials(env)[0];
246
272
  if (firstCredential?.baseUrl)
247
273
  return normalizeUrl(firstCredential.baseUrl);
274
+ return '';
275
+ }
276
+ export function resolveXiaoEDefaultBaseUrl(env = process.env) {
277
+ const configured = resolveXiaoEConfiguredBaseUrl(env);
278
+ if (configured)
279
+ return configured;
248
280
  return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
249
281
  }
250
282
  function uniqueStrings(values) {
@@ -259,7 +291,7 @@ function uniqueStrings(values) {
259
291
  return result;
260
292
  }
261
293
  function xiaoETokenScriptCandidatePaths(env = process.env) {
262
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
294
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
263
295
  if (explicit)
264
296
  return [explicit];
265
297
  if (process.platform === 'win32') {
@@ -271,7 +303,7 @@ function xiaoETokenScriptCandidatePaths(env = process.env) {
271
303
  ]);
272
304
  }
273
305
  const envHome = envString(env, 'HOME');
274
- const nodeHome = homedir();
306
+ const nodeHome = envHome ? '' : homedir();
275
307
  return uniqueStrings([
276
308
  envHome ? join(envHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
277
309
  nodeHome ? join(nodeHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
@@ -280,29 +312,125 @@ function xiaoETokenScriptCandidatePaths(env = process.env) {
280
312
  function xiaoETokenScriptPath(env = process.env) {
281
313
  return xiaoETokenScriptCandidatePaths(env).find((path) => existsSync(path)) || '';
282
314
  }
315
+ export function xiaoEBundledEnvScriptPath() {
316
+ const scriptPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../scripts/xiaoe_env.py');
317
+ return existsSync(scriptPath) ? scriptPath : '';
318
+ }
283
319
  export function hasXiaoETokenScript(env = process.env) {
284
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
320
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
285
321
  if (explicit)
286
322
  return existsSync(explicit);
287
323
  const scriptPath = xiaoETokenScriptPath(env);
288
324
  return Boolean(scriptPath && existsSync(scriptPath));
289
325
  }
290
- function xiaoETokenScriptPythonCandidates(env = process.env) {
291
- const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY);
326
+ function isExecutableFile(path) {
327
+ try {
328
+ if (!statSync(path).isFile())
329
+ return false;
330
+ accessSync(path, fsConstants.X_OK);
331
+ return true;
332
+ }
333
+ catch {
334
+ return false;
335
+ }
336
+ }
337
+ function listXiaoEPythonRuntimeDirectories(resourcesRoot) {
338
+ if (!resourcesRoot || !existsSync(resourcesRoot))
339
+ return [];
340
+ try {
341
+ return readdirSync(resourcesRoot, { withFileTypes: true })
342
+ .filter((entry) => entry.isDirectory() && /^Python3(?:[-_.]\d[\d.]*)?$/iu.test(entry.name))
343
+ .map((entry) => join(resourcesRoot, entry.name))
344
+ .sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
345
+ }
346
+ catch {
347
+ return [];
348
+ }
349
+ }
350
+ function listExecutablePythonFiles(directory, platform) {
351
+ if (!directory || !existsSync(directory))
352
+ return [];
353
+ try {
354
+ const pattern = platform === 'win32' ? /^python(?:3(?:\.\d+)*)?\.exe$/iu : /^python3(?:\.\d+)*$/u;
355
+ return readdirSync(directory, { withFileTypes: true })
356
+ .filter((entry) => entry.isFile() && pattern.test(entry.name))
357
+ .map((entry) => join(directory, entry.name))
358
+ .filter(isExecutableFile)
359
+ .sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
360
+ }
361
+ catch {
362
+ return [];
363
+ }
364
+ }
365
+ /**
366
+ * Locate the Python runtime bundled with the desktop XiaoE client. The client
367
+ * ships the runtime expected by xiaoeUserData/tokens.py; the system Python may
368
+ * still be unavailable or incompatible with a host-provided script.
369
+ */
370
+ export function xiaoEClientPythonCandidates(env = process.env, platform = process.platform) {
371
+ const appRoots = [];
372
+ if (platform === 'darwin') {
373
+ const envHome = envString(env, 'HOME');
374
+ const nodeHome = homedir();
375
+ for (const home of uniqueStrings([envHome, nodeHome])) {
376
+ appRoots.push(join(home, 'Applications', '智能小e.app'));
377
+ }
378
+ appRoots.push('/Applications/智能小e.app');
379
+ }
380
+ else if (platform === 'win32') {
381
+ const localAppData = envString(env, 'LOCALAPPDATA');
382
+ const programFiles = uniqueStrings([
383
+ envString(env, 'ProgramW6432'),
384
+ envString(env, 'ProgramFiles'),
385
+ envString(env, 'ProgramFiles(x86)'),
386
+ ]);
387
+ const appNames = ['智能小e', 'XiaoE', 'XiaoEClient', 'XiaoEAI'];
388
+ if (localAppData) {
389
+ for (const appName of appNames) {
390
+ appRoots.push(join(localAppData, 'Programs', appName), join(localAppData, appName));
391
+ }
392
+ }
393
+ for (const programFilesRoot of programFiles) {
394
+ for (const appName of appNames)
395
+ appRoots.push(join(programFilesRoot, appName));
396
+ }
397
+ }
398
+ const candidates = [];
399
+ for (const appRoot of uniqueStrings(appRoots)) {
400
+ const resourcesRoots = platform === 'darwin'
401
+ ? [join(appRoot, 'Contents', 'Resources')]
402
+ : [join(appRoot, 'Resources'), appRoot];
403
+ for (const resourcesRoot of resourcesRoots) {
404
+ for (const runtimeRoot of listXiaoEPythonRuntimeDirectories(resourcesRoot)) {
405
+ const executableDirectories = platform === 'win32'
406
+ ? [runtimeRoot, join(runtimeRoot, 'bin')]
407
+ : [join(runtimeRoot, 'bin')];
408
+ for (const executableDirectory of executableDirectories) {
409
+ candidates.push(...listExecutablePythonFiles(executableDirectory, platform));
410
+ }
411
+ }
412
+ }
413
+ }
414
+ return uniqueStrings(candidates);
415
+ }
416
+ export function xiaoETokenScriptPythonCandidates(env = process.env, platform = process.platform) {
417
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY, XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY);
292
418
  if (explicit)
293
419
  return [explicit];
294
- return uniqueStrings(process.platform === 'win32'
295
- ? ['python', 'python3', 'py']
296
- : ['python3', '/usr/bin/python3', 'python']);
420
+ return uniqueStrings(platform === 'win32'
421
+ ? [...xiaoEClientPythonCandidates(env, platform), 'python', 'python3', 'py']
422
+ : [...xiaoEClientPythonCandidates(env, platform), 'python3', '/usr/bin/python3', 'python']);
297
423
  }
298
424
  function isXiaoETokenScriptEnvFailure(message) {
299
- return /No module named|ModuleNotFoundError|ENOENT|command not found|not recognized as an internal|cannot find the file/i
425
+ return /No module named|ModuleNotFoundError|ENOENT|EACCES|permission denied|command not found|not recognized as an internal|cannot find the file/i
300
426
  .test(message);
301
427
  }
302
- async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
428
+ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, baseUrl, env) {
303
429
  const wrapper = [
304
430
  'import ast,contextlib,io,json,runpy,sys',
305
431
  `path = ${JSON.stringify(scriptPath)}`,
432
+ `base_url = ${JSON.stringify(baseUrl)}`,
433
+ 'sys.argv = [path, base_url]',
306
434
  'buf = io.StringIO()',
307
435
  'data = None',
308
436
  'with contextlib.redirect_stdout(buf):',
@@ -324,9 +452,9 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
324
452
  ' except Exception as exc:',
325
453
  ' last_error = exc',
326
454
  ' if data is None:',
327
- ' raise SystemExit("tokens.py stdout is not valid JSON object")',
455
+ ' raise SystemExit("XiaoE credential script stdout is not valid JSON object")',
328
456
  'else:',
329
- ' raise SystemExit("tokens.py produced empty stdout")',
457
+ ' raise SystemExit("XiaoE credential script produced empty stdout")',
330
458
  'print(json.dumps(data, ensure_ascii=False))',
331
459
  ].join('\n');
332
460
  return await new Promise((resolve, reject) => {
@@ -337,12 +465,12 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
337
465
  }, (error, stdout, stderr) => {
338
466
  if (error) {
339
467
  const detail = (stderr || error.message || 'unknown error').trim().split('\n').slice(-1)[0];
340
- reject(new CliError('XiaoE token script failed: ' + detail, ExitCode.AuthError));
468
+ reject(new CliError('XiaoE credential script failed: ' + detail, ExitCode.AuthError));
341
469
  return;
342
470
  }
343
471
  const text = stdout.trim();
344
472
  if (!text) {
345
- reject(new CliError('XiaoE token script returned empty stdout.', ExitCode.AuthError));
473
+ reject(new CliError('XiaoE credential script returned empty stdout.', ExitCode.AuthError));
346
474
  return;
347
475
  }
348
476
  try {
@@ -350,19 +478,18 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
350
478
  resolve(parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null);
351
479
  }
352
480
  catch {
353
- reject(new CliError('XiaoE token script stdout is not valid JSON.', ExitCode.AuthError));
481
+ reject(new CliError('XiaoE credential script stdout is not valid JSON.', ExitCode.AuthError));
354
482
  }
355
483
  });
356
484
  });
357
485
  }
358
- async function runXiaoETokenScript(env = process.env) {
359
- const scriptPath = xiaoETokenScriptPath(env);
486
+ async function runXiaoEPythonCredentialScript(scriptPath, baseUrl, env) {
360
487
  if (!scriptPath || !existsSync(scriptPath))
361
488
  return null;
362
489
  let lastError = null;
363
490
  for (const pythonCommand of xiaoETokenScriptPythonCandidates(env)) {
364
491
  try {
365
- return await runXiaoETokenScriptOnce(pythonCommand, scriptPath, env);
492
+ return await runXiaoETokenScriptOnce(pythonCommand, scriptPath, baseUrl, env);
366
493
  }
367
494
  catch (error) {
368
495
  lastError = error;
@@ -373,14 +500,26 @@ async function runXiaoETokenScript(env = process.env) {
373
500
  break;
374
501
  }
375
502
  }
376
- throw lastError instanceof Error ? lastError : new CliError('XiaoE token script failed.', ExitCode.AuthError);
503
+ throw lastError instanceof Error ? lastError : new CliError('XiaoE credential script failed.', ExitCode.AuthError);
504
+ }
505
+ async function runXiaoETokenScript(baseUrl, env = process.env) {
506
+ return runXiaoEPythonCredentialScript(xiaoETokenScriptPath(env), baseUrl, env);
377
507
  }
378
508
  async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
379
- const tokenData = await runXiaoETokenScript(env);
509
+ const tokenData = await runXiaoETokenScript(baseUrl, env);
380
510
  if (!tokenData)
381
511
  return null;
382
512
  return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
383
513
  }
514
+ async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env) {
515
+ const scriptPath = xiaoEBundledEnvScriptPath();
516
+ if (!scriptPath)
517
+ return null;
518
+ const tokenData = await runXiaoEPythonCredentialScript(scriptPath, baseUrl, env);
519
+ if (!tokenData)
520
+ return null;
521
+ return readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl);
522
+ }
384
523
  export function xiaoEClientRobotsRoot(env = process.env) {
385
524
  if (process.platform === 'win32') {
386
525
  const localAppData = envString(env, 'LOCALAPPDATA');
@@ -415,17 +554,30 @@ export function findXiaoEClientSettingsFile(env = process.env) {
415
554
  return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs)[0];
416
555
  }
417
556
  /**
418
- * Mirror of the host `xiaoeUserData/tokens.py` credential chain, implemented in
419
- * Node so the CLI does not depend on a Python runtime that has `requests`.
557
+ * Platform-injected credential: XIAOE_USER_TOKEN (+ the XiaoE service address).
558
+ * This is the only source that works in a Linux sandbox with no desktop client
559
+ * and no Python runtime, so it is tried first.
420
560
  */
421
- export function readXiaoEClientCredential(env = process.env) {
561
+ export function readXiaoEPlatformCredential(env = process.env) {
422
562
  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
- }
563
+ if (!envToken)
564
+ return null;
565
+ const rawErpa = envString(env, 'WEAVER_XIAOE_ERP', 'WEAVER_XIAOE_ERPA', 'WEAVER_XIAOE_ERP_URL', 'XIAOE_ERP', 'XIAOE_ERPA', 'XIAOE_ERP_URL', 'XIAOE_DOMAIN');
566
+ // Hosts may inject the domain without a scheme (e.g. "weapp.example.cn");
567
+ // default to https instead of rejecting the credential outright.
568
+ const erpaUrl = normalizeBaseUrlValue(rawErpa);
569
+ if (!erpaUrl)
570
+ return null;
571
+ return { authToken: envToken, erpaUrl, source: XIAOE_USER_TOKEN_SOURCE };
572
+ }
573
+ export function hasXiaoEPlatformCredential(env = process.env) {
574
+ return Boolean(readXiaoEPlatformCredential(env));
575
+ }
576
+ /**
577
+ * Desktop XiaoE client session: the newest XiaoE/Robots/<id>/user_settings.json.
578
+ * Kept for local desktop hosts only; sandboxes must rely on the platform credential.
579
+ */
580
+ export function readXiaoEClientCredential(env = process.env) {
429
581
  const settingsFile = findXiaoEClientSettingsFile(env);
430
582
  if (!settingsFile)
431
583
  return null;
@@ -442,49 +594,119 @@ export function readXiaoEClientCredential(env = process.env) {
442
594
  }
443
595
  }
444
596
  export function hasXiaoEClientCredential(env = process.env) {
445
- return Boolean(envString(env, 'XIAOE_USER_TOKEN')) || Boolean(findXiaoEClientSettingsFile(env));
597
+ return Boolean(findXiaoEClientSettingsFile(env));
446
598
  }
447
599
  async function fetchXiaoEClientEteamId(credential, domain) {
448
600
  const controller = new AbortController();
449
601
  const timer = setTimeout(() => controller.abort(), XIAOE_CLIENT_TOKEN_TIMEOUT_MS);
450
602
  try {
451
- const resp = await fetch(`${credential.erpaUrl}${XIAOE_SKILL_ETEAMID_API_PATH}`, {
452
- method: 'POST',
453
- headers: {
454
- 'content-type': 'application/json',
455
- authorization: credential.authToken,
456
- },
457
- body: JSON.stringify({ domain: domain || DEFAULT_E10_BASE_URL }),
458
- redirect: 'manual',
459
- signal: controller.signal,
460
- });
461
- if (!resp.ok) {
462
- throw new CliError(`XiaoE client token endpoint returned HTTP ${resp.status}.`, ExitCode.AuthError);
603
+ let endpoint = new URL(`${credential.erpaUrl}${XIAOE_SKILL_ETEAMID_API_PATH}`);
604
+ let resp = null;
605
+ for (let redirects = 0; redirects <= 5; redirects += 1) {
606
+ resp = await fetch(endpoint, {
607
+ method: 'POST',
608
+ headers: {
609
+ 'content-type': 'application/json',
610
+ authorization: credential.authToken,
611
+ },
612
+ body: JSON.stringify({ domain: domain || DEFAULT_E10_BASE_URL }),
613
+ redirect: 'manual',
614
+ signal: controller.signal,
615
+ });
616
+ if (resp.status < 300 || resp.status >= 400)
617
+ break;
618
+ if (redirects === 5) {
619
+ throw new CliError('XiaoE client token endpoint redirected too many times.', ExitCode.AuthError);
620
+ }
621
+ const location = resp.headers.get('location');
622
+ if (!location) {
623
+ throw new CliError('XiaoE client token endpoint returned a redirect without Location.', ExitCode.AuthError);
624
+ }
625
+ const next = new URL(location, endpoint);
626
+ const sameOrigin = next.origin === endpoint.origin;
627
+ const sameHostHttpsUpgrade = endpoint.protocol === 'http:'
628
+ && next.protocol === 'https:'
629
+ && next.hostname === endpoint.hostname;
630
+ const safeProtocol = next.protocol === 'http:' || next.protocol === 'https:';
631
+ if (!safeProtocol || next.username || next.password || (!sameOrigin && !sameHostHttpsUpgrade)) {
632
+ throw new CliError('XiaoE client token endpoint attempted an unsafe cross-origin redirect.', ExitCode.AuthError);
633
+ }
634
+ await resp.body?.cancel().catch(() => undefined);
635
+ endpoint = next;
636
+ }
637
+ if (!resp?.ok) {
638
+ throw new CliError(`XiaoE client token endpoint returned HTTP ${resp?.status || 0}.`, ExitCode.AuthError);
463
639
  }
464
640
  const data = await resp.json().catch(() => null);
465
- return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
641
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
642
+ throw new CliError('XiaoE client token endpoint returned invalid JSON.', ExitCode.AuthError);
643
+ }
644
+ const payload = data;
645
+ const businessCode = payload.code;
646
+ const businessData = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
647
+ ? payload.data
648
+ : {};
649
+ if ((businessCode !== '0000' && businessCode !== 0 && businessCode !== 200) || !readString(businessData, 'eteamId')) {
650
+ const codeText = businessCode === undefined ? 'missing' : String(businessCode);
651
+ throw new CliError(`XiaoE client token exchange failed with business code ${codeText}.`, ExitCode.AuthError);
652
+ }
653
+ return payload;
466
654
  }
467
655
  finally {
468
656
  clearTimeout(timer);
469
657
  }
470
658
  }
659
+ async function resolveXiaoETokenCredential(credential, baseUrl) {
660
+ const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
661
+ const payload = await fetchXiaoEClientEteamId(credential, targetBaseUrl);
662
+ // The legacy gateway contract uses numeric code 0 and serves the E10 APIs
663
+ // from the same origin as the token exchange. Newer 0000/200 responses keep
664
+ // the XiaoE service origin separate and use the requested E10 target.
665
+ const resolvedBaseUrl = payload.code === 0 ? credential.erpaUrl : targetBaseUrl;
666
+ return readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl);
667
+ }
668
+ async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
669
+ const credential = readXiaoEPlatformCredential(env);
670
+ if (!credential)
671
+ return null;
672
+ return resolveXiaoETokenCredential(credential, baseUrl);
673
+ }
471
674
  async function resolveXiaoEClientCredential(baseUrl, env = process.env) {
472
675
  const clientCredential = readXiaoEClientCredential(env);
473
676
  if (!clientCredential)
474
677
  return null;
475
- const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
476
- const payload = await fetchXiaoEClientEteamId(clientCredential, targetBaseUrl);
477
- return readXiaoECredentialRecord(payload, clientCredential.source, targetBaseUrl);
678
+ return resolveXiaoETokenCredential(clientCredential, baseUrl);
478
679
  }
479
680
  export function resolveXiaoEPreferredBaseUrl(options) {
480
681
  const env = options.env || process.env;
481
682
  const explicit = normalizeUrl(options.explicitBaseUrl || '');
482
683
  if (explicit)
483
684
  return explicit;
484
- const xiaoEDefault = resolveXiaoEDefaultBaseUrl(env);
485
- if (hasXiaoEEnv(env))
486
- return xiaoEDefault || normalizeUrl(options.profileBaseUrl || '');
487
- return normalizeUrl(options.profileBaseUrl || '') || xiaoEDefault;
685
+ const configured = resolveXiaoEConfiguredBaseUrl(env);
686
+ if (configured)
687
+ return configured;
688
+ const profileBaseUrl = normalizeUrl(options.profileBaseUrl || '');
689
+ if (profileBaseUrl)
690
+ return profileBaseUrl;
691
+ return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
692
+ }
693
+ export function shouldUseXiaoEBrowserFallback(platform = process.platform) {
694
+ return platform === 'darwin' || platform === 'win32';
695
+ }
696
+ export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
697
+ const target = normalizeUrl(baseUrl);
698
+ const message = [
699
+ 'XiaoE unified login did not return a usable E10 session.',
700
+ target ? `Resolved E10 baseUrl: ${target}.` : 'No E10 baseUrl could be resolved.',
701
+ 'This web/Linux host cannot complete a localhost browser OAuth callback.',
702
+ 'Refresh the XiaoE web session or restore host credential injection before retrying.',
703
+ ].join(' ');
704
+ return Object.assign(new CliError(message, ExitCode.AuthError), {
705
+ type: 'authentication',
706
+ subtype: 'xiaoe_web_credential_unavailable',
707
+ retryable: false,
708
+ stage: 'xiaoe_unified_login',
709
+ });
488
710
  }
489
711
  async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = '') {
490
712
  const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
@@ -500,11 +722,11 @@ async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = ''
500
722
  redirect: 'manual',
501
723
  });
502
724
  const data = await resp.json().catch(() => ({}));
503
- return readTeamsCheckUserInfo(resp, data);
725
+ return { userInfo: readTeamsCheckUserInfo(resp, data), setCookie: extractRawSetCookie(resp) };
504
726
  }
505
727
  async function saveXiaoEUnifiedLogin(input) {
506
728
  const baseUrl = normalizeUrl(input.baseUrl);
507
- const userInfo = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
729
+ const { userInfo, setCookie } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
508
730
  if (!userInfo.userId) {
509
731
  throw new CliError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', ExitCode.AuthError);
510
732
  }
@@ -514,6 +736,7 @@ async function saveXiaoEUnifiedLogin(input) {
514
736
  const cookies = { ETEAMSID: input.eteamsId };
515
737
  if (input.rawCookie)
516
738
  cookies.raw = input.rawCookie;
739
+ const ztSession = extractSessionTicket(setCookie, input.rawCookie || '');
517
740
  const authData = {
518
741
  cookies,
519
742
  userId: userInfo.userId,
@@ -531,6 +754,7 @@ async function saveXiaoEUnifiedLogin(input) {
531
754
  credentialSourceStep: input.credentialSource.credentialSourceStep,
532
755
  credentialSourceType: input.credentialSource.credentialSourceType,
533
756
  credentialSourceLabel: input.credentialSource.credentialSourceLabel,
757
+ ...(ztSession ? { ztSession } : {}),
534
758
  };
535
759
  const authFile = saveAuthFile(authData, profile);
536
760
  saveProfileConfig(baseUrl, baseUrl, profile, tenantKey);
@@ -557,43 +781,84 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
557
781
  const explicitBaseUrl = normalizeUrl(options.baseUrl || '');
558
782
  const baseUrl = resolveXiaoEPreferredBaseUrl({
559
783
  explicitBaseUrl,
784
+ profileBaseUrl: options.profileBaseUrl,
560
785
  env,
561
786
  });
562
787
  const mayUseTokenScript = options.allowTokenScript || hasXiaoEEnv(env);
563
788
  if (!baseUrl && !mayUseTokenScript)
564
789
  return null;
565
790
  let credential = null;
566
- if (mayUseTokenScript) {
791
+ // Step 1 — platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN).
792
+ // Works in sandboxes: no desktop client, no Python, no developer-machine paths.
793
+ if (hasXiaoEPlatformCredential(env)) {
794
+ options.logger?.info('Trying XiaoE platform credential from XIAOE_USER_TOKEN...');
795
+ try {
796
+ credential = await resolveXiaoEPlatformCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
797
+ }
798
+ catch (error) {
799
+ const message = error instanceof Error ? error.message : String(error);
800
+ options.logger?.warn('XiaoE platform credential failed. ' + message);
801
+ }
802
+ if (!credential) {
803
+ options.logger?.warn(options.allowBundledEnvScript
804
+ ? 'XiaoE platform credential did not return usable ETEAMSID; will try the CLI-bundled xiaoe_env.py compatibility path next.'
805
+ : 'XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
806
+ }
807
+ }
808
+ // Step 1b — compatibility path used by the reference login package. The
809
+ // script is shipped with the CLI npm package (never with a Skill) and its
810
+ // stdout stays inside this process so credentials are not exposed to agents.
811
+ if (!credential && options.allowBundledEnvScript && hasXiaoEPlatformCredential(env)) {
812
+ const scriptPath = xiaoEBundledEnvScriptPath();
813
+ if (!scriptPath) {
814
+ options.logger?.warn('CLI-bundled xiaoe_env.py was not found; will try desktop XiaoE credential sources next.');
815
+ }
816
+ else {
817
+ options.logger?.info('Trying CLI-bundled xiaoe_env.py compatibility credential...');
818
+ try {
819
+ credential = await resolveXiaoEBundledEnvScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
820
+ }
821
+ catch (error) {
822
+ const message = error instanceof Error ? error.message : String(error);
823
+ options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
824
+ }
825
+ if (!credential) {
826
+ options.logger?.warn('CLI-bundled xiaoe_env.py did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
827
+ }
828
+ }
829
+ }
830
+ // Step 2 — host tokens.py (desktop host only; Linux sandboxes have no such path).
831
+ if (!credential && mayUseTokenScript) {
567
832
  const tokenScriptPath = xiaoETokenScriptPath(env);
568
833
  if (!tokenScriptPath) {
569
- options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the built-in XiaoE client credential next.');
834
+ options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the local XiaoE client session next.');
570
835
  }
571
836
  else {
572
837
  options.logger?.info('Trying XiaoE token script credential...');
573
838
  try {
574
- credential = await resolveXiaoETokenScriptCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
839
+ credential = await resolveXiaoETokenScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
575
840
  }
576
841
  catch (error) {
577
842
  const message = error instanceof Error ? error.message : String(error);
578
843
  options.logger?.warn('XiaoE token script failed. ' + message);
579
844
  }
580
845
  if (!credential) {
581
- options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the built-in XiaoE client credential next.');
846
+ options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the local XiaoE client session next.');
582
847
  }
583
848
  }
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.
849
+ // Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
850
+ // client session and exchanges it for an eteamId, so Python availability
851
+ // no longer blocks unified login.
587
852
  if (!credential && hasXiaoEClientCredential(env)) {
588
853
  try {
589
- credential = await resolveXiaoEClientCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
854
+ credential = await resolveXiaoEClientCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
590
855
  }
591
856
  catch (error) {
592
857
  const message = error instanceof Error ? error.message : String(error);
593
858
  options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
594
859
  }
595
860
  if (!credential) {
596
- options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try XIAOE_* credentials next.');
861
+ options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try WEAVER_XIAOE_* credentials next.');
597
862
  }
598
863
  }
599
864
  }