weaver-work-cli 0.1.5 → 0.1.7

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 (225) hide show
  1. package/README.md +14 -9
  2. package/dist/cmd/skills/index.js +60 -0
  3. package/dist/internal/e10/auth/commands.js +48 -32
  4. package/dist/internal/e10/auth/session.js +44 -3
  5. package/dist/internal/e10/auth/xiaoe.js +363 -11
  6. package/dist/internal/e10/context.js +4 -2
  7. package/dist/internal/skills/detector.js +42 -0
  8. package/dist/internal/skills/install.js +50 -3
  9. package/dist/shortcuts/archive/render/search-format.js +12 -3
  10. package/dist/shortcuts/ebuilder-form/continuation.js +70 -0
  11. package/dist/shortcuts/ebuilder-form/errors.js +54 -0
  12. package/dist/shortcuts/ebuilder-form/host.js +1238 -0
  13. package/dist/shortcuts/ebuilder-form/index.js +108 -0
  14. package/dist/shortcuts/ebuilder-form/manifest.js +347 -0
  15. package/dist/shortcuts/ebuilder-form/operations/read.js +157 -0
  16. package/dist/shortcuts/ebuilder-form/operations/registry.js +56 -0
  17. package/dist/shortcuts/ebuilder-form/operations/shared.js +132 -0
  18. package/dist/shortcuts/ebuilder-form/operations/types.js +19 -0
  19. package/dist/shortcuts/ebuilder-form/operations/write.js +277 -0
  20. package/dist/shortcuts/ebuilder-form/operations.js +8 -0
  21. package/dist/shortcuts/ehr/operations/salary.js +2 -140
  22. package/dist/shortcuts/im/continuation.js +70 -0
  23. package/dist/shortcuts/{yimiaoban → im}/errors.js +14 -14
  24. package/dist/shortcuts/{yimiaoban → im}/host.js +26 -26
  25. package/dist/shortcuts/{yimiaoban → im}/index.js +42 -42
  26. package/dist/shortcuts/{yimiaoban → im}/manifest.js +7 -6
  27. package/dist/shortcuts/{yimiaoban → im}/operations/file-user.js +54 -41
  28. package/dist/shortcuts/{yimiaoban → im}/operations/group-read.js +46 -44
  29. package/dist/shortcuts/{yimiaoban → im}/operations/group-write.js +113 -66
  30. package/dist/shortcuts/{yimiaoban → im}/operations/msg-sync.js +164 -141
  31. package/dist/shortcuts/{yimiaoban → im}/operations/msg-write.js +47 -47
  32. package/dist/shortcuts/{yimiaoban → im}/operations/person.js +10 -10
  33. package/dist/shortcuts/{yimiaoban → im}/operations/registry.js +10 -8
  34. package/dist/shortcuts/{yimiaoban → im}/operations/session.js +82 -83
  35. package/dist/shortcuts/im/operations/shared.js +442 -0
  36. package/dist/shortcuts/{yimiaoban → im}/operations/types.js +1 -1
  37. package/dist/shortcuts/im/operations/user-remind.js +201 -0
  38. package/dist/shortcuts/{yimiaoban → im}/operations/write-util.js +7 -7
  39. package/dist/shortcuts/{yimiaoban → im}/operations.js +3 -3
  40. package/dist/shortcuts/index.js +10 -2
  41. package/dist/shortcuts/invoice/manifest.js +113 -10
  42. package/dist/shortcuts/invoice/operations/import.js +39 -14
  43. package/dist/shortcuts/invoice/operations/reim.js +38 -18
  44. package/dist/shortcuts/invoice/operations/shared.js +35 -6
  45. package/dist/shortcuts/jiuchuanhui/manifest.js +3 -3
  46. package/dist/shortcuts/jiuchuanhui/operations/registry.js +2 -2
  47. package/dist/shortcuts/{yimiaoban → okr}/continuation.js +10 -10
  48. package/dist/shortcuts/okr/errors.js +70 -0
  49. package/dist/shortcuts/okr/host.js +169 -0
  50. package/dist/shortcuts/okr/index.js +155 -0
  51. package/dist/shortcuts/okr/manifest.js +70 -0
  52. package/dist/shortcuts/okr/operations/link.js +305 -0
  53. package/dist/shortcuts/okr/operations/read.js +481 -0
  54. package/dist/shortcuts/okr/operations/registry.js +31 -0
  55. package/dist/shortcuts/okr/operations/shared.js +381 -0
  56. package/dist/shortcuts/okr/operations/types.js +88 -0
  57. package/dist/shortcuts/okr/operations/write.js +838 -0
  58. package/dist/shortcuts/okr/operations.js +8 -0
  59. package/dist/shortcuts/project/continuation.js +70 -0
  60. package/dist/shortcuts/project/errors.js +53 -0
  61. package/dist/shortcuts/project/host.js +160 -0
  62. package/dist/shortcuts/project/index.js +106 -0
  63. package/dist/shortcuts/project/manifest.js +36 -0
  64. package/dist/shortcuts/project/operations/read.js +551 -0
  65. package/dist/shortcuts/project/operations/registry.js +31 -0
  66. package/dist/shortcuts/project/operations/shared.js +68 -0
  67. package/dist/shortcuts/project/operations/types.js +39 -0
  68. package/dist/shortcuts/project/operations/write.js +266 -0
  69. package/dist/shortcuts/project/operations.js +8 -0
  70. package/dist/shortcuts/workflow/continuation.js +508 -0
  71. package/dist/shortcuts/workflow/endpoints.js +149 -0
  72. package/dist/shortcuts/workflow/errors.js +112 -0
  73. package/dist/shortcuts/workflow/host.js +224 -0
  74. package/dist/shortcuts/workflow/index.js +116 -0
  75. package/dist/shortcuts/workflow/manifest.js +30 -0
  76. package/dist/shortcuts/workflow/operations/form.js +4159 -0
  77. package/dist/shortcuts/workflow/operations/read.js +3778 -0
  78. package/dist/shortcuts/workflow/operations/registry.js +33 -0
  79. package/dist/shortcuts/workflow/operations/shared.js +167 -0
  80. package/dist/shortcuts/workflow/operations/types.js +44 -0
  81. package/dist/shortcuts/workflow/operations/write.js +1991 -0
  82. package/dist/shortcuts/workflow/operations.js +119 -0
  83. package/dist/shortcuts/workflow/state.js +182 -0
  84. package/docs/SKILL.md +5 -3
  85. package/docs/_catalog.md +10 -1
  86. package/docs/agent-invoice.md +2 -2
  87. package/docs/agent-skill-install.md +2 -2
  88. package/docs/e10-auth.md +3 -1
  89. package/docs/ebuilder-form.md +38 -0
  90. package/docs/im.md +104 -0
  91. package/docs/invoice.md +5 -5
  92. package/docs/okr.md +122 -0
  93. package/docs/operation-manual.md +10 -7
  94. package/docs/project.md +69 -0
  95. package/docs/skill-review-2026-09-19.md +68 -0
  96. package/package.json +3 -2
  97. package/scripts/package-skill.mjs +38 -11
  98. package/scripts/sync-connector-skills.mjs +172 -0
  99. package/skill-template/business-info.json +357 -16
  100. package/skill-template/domains/ebuilder-form.md +5 -0
  101. package/skill-template/domains/okr.md +18 -0
  102. package/skill-template/domains/shijingran.md +3 -0
  103. package/skill-template/domains/skill-maker.md +4 -4
  104. package/skill-template/domains/workflow.md +22 -0
  105. package/skill-template/domains/yepiaotong.md +4 -2
  106. package/skill-template/domains/yimiaoban.md +13 -10
  107. package/skill-template/product.json +5 -0
  108. package/skill-template/skill-template.md +1 -0
  109. package/skills/weaver-e10-calendar/SKILL.md +5 -1
  110. package/skills/weaver-e10-calendar/references/calendar-query.md +7 -0
  111. package/skills/weaver-e10-calendar/references/source-manifest.json +314 -32
  112. package/skills/weaver-e10-ebuilder-form/SKILL.md +123 -0
  113. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +90 -0
  114. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +86 -0
  115. package/skills/weaver-e10-ebuilder-form/references/disabled-capabilities.md +50 -0
  116. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +100 -0
  117. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +91 -0
  118. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +74 -0
  119. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +86 -0
  120. package/skills/weaver-e10-ebuilder-form/references/source-manifest.json +107 -0
  121. package/skills/weaver-e10-esb/SKILL.md +1 -1
  122. package/skills/weaver-e10-esb/references/source-manifest.json +15 -14
  123. package/skills/weaver-e10-hrm/SKILL.md +1 -1
  124. package/skills/weaver-e10-hrm/references/source-manifest.json +357 -38
  125. package/skills/weaver-e10-jiuchuanhui/SKILL.md +22 -4
  126. package/skills/weaver-e10-jiuchuanhui/references/clue.md +4 -2
  127. package/skills/weaver-e10-jiuchuanhui/references/contact.md +1 -1
  128. package/skills/weaver-e10-jiuchuanhui/references/customer.md +3 -3
  129. package/skills/weaver-e10-jiuchuanhui/references/disabled-capabilities.md +1 -1
  130. package/skills/weaver-e10-jiuchuanhui/references/discovery-config.md +10 -10
  131. package/skills/weaver-e10-jiuchuanhui/references/general-helpers.md +3 -3
  132. package/skills/weaver-e10-jiuchuanhui/references/sale.md +7 -7
  133. package/skills/weaver-e10-jiuchuanhui/references/source-manifest.json +3 -3
  134. package/skills/weaver-e10-jucailin/SKILL.md +2 -2
  135. package/skills/weaver-e10-mail/SKILL.md +22 -6
  136. package/skills/weaver-e10-meeting/SKILL.md +22 -1
  137. package/skills/weaver-e10-okr/SKILL.md +139 -0
  138. package/skills/weaver-e10-okr/references/okr-align.md +59 -0
  139. package/skills/weaver-e10-okr/references/okr-keyresult.md +68 -0
  140. package/skills/weaver-e10-okr/references/okr-link-routing.md +67 -0
  141. package/skills/weaver-e10-okr/references/okr-query.md +105 -0
  142. package/skills/weaver-e10-okr/references/okr-write.md +94 -0
  143. package/skills/weaver-e10-okr/references/source-manifest.json +672 -0
  144. package/skills/weaver-e10-plan/SKILL.md +3 -3
  145. package/skills/weaver-e10-plan/references/plan-report-write.md +1 -1
  146. package/skills/weaver-e10-plan/references/source-manifest.json +20 -20
  147. package/skills/weaver-e10-qiyecheng/SKILL.md +21 -1
  148. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +17 -17
  149. package/skills/weaver-e10-shared/SKILL.md +79 -11
  150. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +41 -8
  151. package/skills/weaver-e10-shared/references/non-interactive-environment.md +78 -0
  152. package/skills/weaver-e10-shared/references/weaver-e10-installation.md +39 -0
  153. package/skills/weaver-e10-shijingran/SKILL.md +83 -0
  154. package/skills/weaver-e10-shijingran/references/field-resolution.md +105 -0
  155. package/skills/weaver-e10-shijingran/references/operations.md +154 -0
  156. package/skills/weaver-e10-shijingran/references/source-manifest.json +1380 -0
  157. package/skills/weaver-e10-shijingran/references/write-operations.md +90 -0
  158. package/skills/weaver-e10-skill-maker/SKILL.md +26 -9
  159. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +39 -8
  160. package/skills/weaver-e10-skill-maker/references/detector-validation.md +25 -8
  161. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +66 -5
  162. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +86 -0
  163. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +59 -5
  164. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +16 -8
  165. package/skills/weaver-e10-wenshuding/SKILL.md +28 -4
  166. package/skills/weaver-e10-wenshuding/references/archive-search.md +14 -0
  167. package/skills/weaver-e10-wenshuding/references/source-manifest.json +27 -26
  168. package/skills/weaver-e10-workflow/SKILL.md +250 -0
  169. package/skills/weaver-e10-workflow/references/source-manifest.json +1062 -0
  170. package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +96 -0
  171. package/skills/weaver-e10-workflow/references/workflow-batch.md +275 -0
  172. package/skills/weaver-e10-workflow/references/workflow-create.md +182 -0
  173. package/skills/weaver-e10-workflow/references/workflow-edit.md +106 -0
  174. package/skills/weaver-e10-workflow/references/workflow-memory.md +165 -0
  175. package/skills/weaver-e10-workflow/references/workflow-operate.md +153 -0
  176. package/skills/weaver-e10-workflow/references/workflow-query.md +821 -0
  177. package/skills/weaver-e10-workflow/references/workflow-share.md +117 -0
  178. package/skills/weaver-e10-workflow/references/workflow-view.md +189 -0
  179. package/skills/weaver-e10-yepiaotong/SKILL.md +28 -28
  180. package/skills/weaver-e10-yepiaotong/references/invoice-disabled-capabilities.md +11 -5
  181. package/skills/weaver-e10-yepiaotong/references/invoice-import.md +14 -8
  182. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +9 -43
  183. package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +8 -0
  184. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +13 -17
  185. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +6 -7
  186. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +2 -2
  187. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +1155 -553
  188. package/skills/weaver-e10-yimiaoban/SKILL.md +56 -38
  189. package/skills/weaver-e10-yimiaoban/references/ding-write.md +8 -8
  190. package/skills/weaver-e10-yimiaoban/references/error-codes.md +16 -0
  191. package/skills/weaver-e10-yimiaoban/references/field-resolution.md +10 -10
  192. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +12 -12
  193. package/skills/weaver-e10-yimiaoban/references/group-read.md +16 -16
  194. package/skills/weaver-e10-yimiaoban/references/group-write.md +17 -15
  195. package/skills/weaver-e10-yimiaoban/references/msg-read.md +21 -17
  196. package/skills/weaver-e10-yimiaoban/references/msg-write.md +10 -10
  197. package/skills/weaver-e10-yimiaoban/references/person.md +6 -6
  198. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +15 -11
  199. package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +11 -10
  200. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +2164 -154
  201. package/skills/weaver-e10-yimiaoban/references/user-remind.md +75 -0
  202. package/skills/weaver-e10-ziguanjia/SKILL.md +4 -2
  203. package/skills/weaver-e10-ziguanjia/references/source-manifest.json +99 -29
  204. package/tools/weaver-skill-detector-1.0.13/SKILL.md +253 -0
  205. package/tools/weaver-skill-detector-1.0.13/_meta.json +6 -0
  206. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +298 -0
  207. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +4190 -0
  208. package/dist/shortcuts/yimiaoban/operations/shared.js +0 -236
  209. package/docs/yimiaoban.md +0 -86
  210. package/skills/weaver-e10-calendar/product.json +0 -8
  211. package/skills/weaver-e10-esb/product.json +0 -8
  212. package/skills/weaver-e10-hrm/product.json +0 -8
  213. package/skills/weaver-e10-jiuchuanhui/product.json +0 -8
  214. package/skills/weaver-e10-jucailin/product.json +0 -8
  215. package/skills/weaver-e10-mail/product.json +0 -8
  216. package/skills/weaver-e10-meeting/product.json +0 -8
  217. package/skills/weaver-e10-plan/product.json +0 -8
  218. package/skills/weaver-e10-qiyecheng/product.json +0 -8
  219. package/skills/weaver-e10-skill-maker/product.json +0 -8
  220. package/skills/weaver-e10-wenshuding/product.json +0 -8
  221. package/skills/weaver-e10-yepiaotong/product.json +0 -8
  222. package/skills/weaver-e10-yepiaotong/references/invoice-red.md +0 -261
  223. package/skills/weaver-e10-yimiaoban/product.json +0 -8
  224. package/skills/weaver-e10-ziguanjia/product.json +0 -8
  225. /package/dist/shortcuts/{yimiaoban → im}/operations/msg-parse.js +0 -0
@@ -1,7 +1,11 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
1
5
  import { CliError, ExitCode } from '../../../core/errors.js';
2
6
  import { buildE10CookieHeader, E10AuthSession, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
3
7
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
4
- export const XIAOE_AGENT_TYPE = 'Xiao.E';
8
+ export const XIAOE_AGENT_TYPE = 'XiaoE';
5
9
  const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
6
10
  'XIAOE_E10_AUTH_MAP',
7
11
  'XIAOE_E10_DOMAIN_ETEAMSID_MAP',
@@ -27,6 +31,13 @@ const XIAOE_SINGLE_ETEAMSID_ENV_KEYS = [
27
31
  'XIAOE_E10_ETEAMSID',
28
32
  'XIAOE_E10_ETEAM_ID',
29
33
  ];
34
+ const XIAOE_TOKEN_SCRIPT_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PATH';
35
+ const XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY = 'XIAOE_TOKEN_SCRIPT_PYTHON';
36
+ const XIAOE_TOKEN_SCRIPT_TIMEOUT_MS = 15_000;
37
+ const XIAOE_CLIENT_TOKEN_TIMEOUT_MS = 15_000;
38
+ export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
39
+ export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
40
+ export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
30
41
  function envString(env, ...keys) {
31
42
  for (const key of keys) {
32
43
  const value = env[key];
@@ -41,6 +52,29 @@ function hasEnvPrefix(env, prefix) {
41
52
  export function hasXiaoEEnv(env = process.env) {
42
53
  return hasEnvPrefix(env, 'XIAOE');
43
54
  }
55
+ export function describeXiaoECredentialSource(source) {
56
+ if (source === 'xiaoeUserData/tokens.py') {
57
+ return {
58
+ credentialSourceStep: 1,
59
+ credentialSourceType: 'token_script',
60
+ credentialSourceLabel: 'xiaoeUserData/tokens.py',
61
+ };
62
+ }
63
+ if (source === XIAOE_CLIENT_SETTINGS_SOURCE || source === XIAOE_USER_TOKEN_SOURCE) {
64
+ return {
65
+ credentialSourceStep: 2,
66
+ credentialSourceType: 'xiaoe_client',
67
+ credentialSourceLabel: source === XIAOE_USER_TOKEN_SOURCE
68
+ ? 'XiaoE client user token'
69
+ : 'XiaoE client settings',
70
+ };
71
+ }
72
+ return {
73
+ credentialSourceStep: 3,
74
+ credentialSourceType: 'xiaoe_env',
75
+ credentialSourceLabel: 'XIAOE_* environment variables',
76
+ };
77
+ }
44
78
  function normalizeUrl(value) {
45
79
  return value.replace(/\/+$/, '');
46
80
  }
@@ -54,6 +88,36 @@ function readString(data, ...keys) {
54
88
  }
55
89
  return '';
56
90
  }
91
+ function cookieString(value) {
92
+ if (!value)
93
+ return '';
94
+ if (typeof value === 'string')
95
+ return value;
96
+ if (typeof value === 'object' && !Array.isArray(value)) {
97
+ return Object.entries(value)
98
+ .filter(([, cookieValue]) => cookieValue !== undefined && cookieValue !== null && cookieValue !== '')
99
+ .map(([name, cookieValue]) => `${name}=${String(cookieValue)}`)
100
+ .join('; ');
101
+ }
102
+ return '';
103
+ }
104
+ function cookieValue(rawCookie, name) {
105
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
106
+ const match = rawCookie.match(new RegExp(`(?:^|;\\s*)${escaped}=([^;]+)`));
107
+ return match?.[1] || '';
108
+ }
109
+ function recordCandidates(value) {
110
+ return [
111
+ value,
112
+ value.data,
113
+ value.result,
114
+ value.auth,
115
+ value.session,
116
+ value.e10,
117
+ value.e10Auth,
118
+ value.e10_auth,
119
+ ].filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry));
120
+ }
57
121
  function normalizeDomainKey(value) {
58
122
  const raw = normalizeUrl(value.trim());
59
123
  if (!raw)
@@ -84,11 +148,22 @@ function domainMatches(baseUrl, candidate) {
84
148
  || Boolean(base.host && target.host && base.host === target.host);
85
149
  }
86
150
  function readXiaoECredentialRecord(value, source, fallbackBaseUrl = '') {
87
- const baseUrl = readString(value, 'domain', 'baseUrl', 'base_url', 'url', 'host') || fallbackBaseUrl;
88
- const eteamsId = readString(value, 'eteamsId', 'eteamsid', 'ETEAMSID', 'eteamId', 'eteam_id', 'eTeamId');
151
+ const candidates = recordCandidates(value);
152
+ const baseUrl = candidates
153
+ .map((entry) => readString(entry, 'domain', 'baseUrl', 'base_url', 'url', 'host', 'e10BaseUrl', 'e10_base_url'))
154
+ .find(Boolean)
155
+ || fallbackBaseUrl;
156
+ const rawCookie = candidates
157
+ .map((entry) => cookieString(entry.cookies || entry.cookie || entry.rawCookie || entry.raw_cookie))
158
+ .find(Boolean)
159
+ || '';
160
+ const eteamsId = candidates
161
+ .map((entry) => readString(entry, 'eteamsId', 'eteamsid', 'ETEAMSID', 'eteamId', 'eteam_id', 'eTeamId', 'eteamID', 'eteam_id'))
162
+ .find(Boolean)
163
+ || cookieValue(rawCookie, 'ETEAMSID');
89
164
  if (!baseUrl || !eteamsId)
90
165
  return null;
91
- return { baseUrl: normalizeUrl(baseUrl), eteamsId, source };
166
+ return { baseUrl: normalizeUrl(baseUrl), eteamsId, source, rawCookie };
92
167
  }
93
168
  function parseXiaoEUnifiedLoginMap(raw, source) {
94
169
  const value = raw.trim();
@@ -172,6 +247,235 @@ export function resolveXiaoEDefaultBaseUrl(env = process.env) {
172
247
  return normalizeUrl(firstCredential.baseUrl);
173
248
  return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
174
249
  }
250
+ function uniqueStrings(values) {
251
+ const seen = new Set();
252
+ const result = [];
253
+ for (const value of values) {
254
+ if (!value || seen.has(value))
255
+ continue;
256
+ seen.add(value);
257
+ result.push(value);
258
+ }
259
+ return result;
260
+ }
261
+ function xiaoETokenScriptCandidatePaths(env = process.env) {
262
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
263
+ if (explicit)
264
+ return [explicit];
265
+ if (process.platform === 'win32') {
266
+ const localAppData = envString(env, 'LOCALAPPDATA');
267
+ const userProfile = envString(env, 'USERPROFILE');
268
+ return uniqueStrings([
269
+ localAppData ? join(localAppData, 'xiaoeUserData', 'tokens.py') : '',
270
+ userProfile ? join(userProfile, 'AppData', 'Local', 'xiaoeUserData', 'tokens.py') : '',
271
+ ]);
272
+ }
273
+ const envHome = envString(env, 'HOME');
274
+ const nodeHome = homedir();
275
+ return uniqueStrings([
276
+ envHome ? join(envHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
277
+ nodeHome ? join(nodeHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
278
+ ]);
279
+ }
280
+ function xiaoETokenScriptPath(env = process.env) {
281
+ return xiaoETokenScriptCandidatePaths(env).find((path) => existsSync(path)) || '';
282
+ }
283
+ export function hasXiaoETokenScript(env = process.env) {
284
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY);
285
+ if (explicit)
286
+ return existsSync(explicit);
287
+ const scriptPath = xiaoETokenScriptPath(env);
288
+ return Boolean(scriptPath && existsSync(scriptPath));
289
+ }
290
+ function xiaoETokenScriptPythonCandidates(env = process.env) {
291
+ const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY);
292
+ if (explicit)
293
+ return [explicit];
294
+ return uniqueStrings(process.platform === 'win32'
295
+ ? ['python', 'python3', 'py']
296
+ : ['python3', '/usr/bin/python3', 'python']);
297
+ }
298
+ function isXiaoETokenScriptEnvFailure(message) {
299
+ return /No module named|ModuleNotFoundError|ENOENT|command not found|not recognized as an internal|cannot find the file/i
300
+ .test(message);
301
+ }
302
+ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
303
+ const wrapper = [
304
+ 'import ast,contextlib,io,json,runpy,sys',
305
+ `path = ${JSON.stringify(scriptPath)}`,
306
+ 'buf = io.StringIO()',
307
+ 'data = None',
308
+ 'with contextlib.redirect_stdout(buf):',
309
+ ' runpy.run_path(path, run_name="__main__")',
310
+ 'text = buf.getvalue().strip()',
311
+ 'if text:',
312
+ ' lines = [line.strip() for line in text.splitlines() if line.strip()]',
313
+ ' candidates = [text] + (lines[-1:] if lines else [])',
314
+ ' last_error = None',
315
+ ' for candidate in candidates:',
316
+ ' try:',
317
+ ' data = json.loads(candidate)',
318
+ ' break',
319
+ ' except Exception as exc:',
320
+ ' last_error = exc',
321
+ ' try:',
322
+ ' data = ast.literal_eval(candidate)',
323
+ ' break',
324
+ ' except Exception as exc:',
325
+ ' last_error = exc',
326
+ ' if data is None:',
327
+ ' raise SystemExit("tokens.py stdout is not valid JSON object")',
328
+ 'else:',
329
+ ' raise SystemExit("tokens.py produced empty stdout")',
330
+ 'print(json.dumps(data, ensure_ascii=False))',
331
+ ].join('\n');
332
+ return await new Promise((resolve, reject) => {
333
+ execFile(pythonCommand, ['-c', wrapper], {
334
+ env,
335
+ timeout: XIAOE_TOKEN_SCRIPT_TIMEOUT_MS,
336
+ maxBuffer: 1024 * 1024,
337
+ }, (error, stdout, stderr) => {
338
+ if (error) {
339
+ const detail = (stderr || error.message || 'unknown error').trim().split('\n').slice(-1)[0];
340
+ reject(new CliError('XiaoE token script failed: ' + detail, ExitCode.AuthError));
341
+ return;
342
+ }
343
+ const text = stdout.trim();
344
+ if (!text) {
345
+ reject(new CliError('XiaoE token script returned empty stdout.', ExitCode.AuthError));
346
+ return;
347
+ }
348
+ try {
349
+ const parsed = JSON.parse(text);
350
+ resolve(parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null);
351
+ }
352
+ catch {
353
+ reject(new CliError('XiaoE token script stdout is not valid JSON.', ExitCode.AuthError));
354
+ }
355
+ });
356
+ });
357
+ }
358
+ async function runXiaoETokenScript(env = process.env) {
359
+ const scriptPath = xiaoETokenScriptPath(env);
360
+ if (!scriptPath || !existsSync(scriptPath))
361
+ return null;
362
+ let lastError = null;
363
+ for (const pythonCommand of xiaoETokenScriptPythonCandidates(env)) {
364
+ try {
365
+ return await runXiaoETokenScriptOnce(pythonCommand, scriptPath, env);
366
+ }
367
+ catch (error) {
368
+ lastError = error;
369
+ const message = error instanceof Error ? error.message : String(error);
370
+ // Only keep probing other interpreters when this one could not even load
371
+ // the script's dependencies; a real script error would repeat everywhere.
372
+ if (!isXiaoETokenScriptEnvFailure(message))
373
+ break;
374
+ }
375
+ }
376
+ throw lastError instanceof Error ? lastError : new CliError('XiaoE token script failed.', ExitCode.AuthError);
377
+ }
378
+ async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
379
+ const tokenData = await runXiaoETokenScript(env);
380
+ if (!tokenData)
381
+ return null;
382
+ return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
383
+ }
384
+ export function xiaoEClientRobotsRoot(env = process.env) {
385
+ if (process.platform === 'win32') {
386
+ const localAppData = envString(env, 'LOCALAPPDATA');
387
+ return localAppData ? join(localAppData, 'XiaoE', 'Robots') : '';
388
+ }
389
+ const home = envString(env, 'HOME') || homedir();
390
+ return home ? join(home, 'Library', 'Application Support', 'XiaoE', 'Robots') : '';
391
+ }
392
+ export function findXiaoEClientSettingsFile(env = process.env) {
393
+ const root = xiaoEClientRobotsRoot(env);
394
+ if (!root || !existsSync(root))
395
+ return '';
396
+ let entries = [];
397
+ try {
398
+ entries = readdirSync(root);
399
+ }
400
+ catch {
401
+ return '';
402
+ }
403
+ const files = entries
404
+ .map((name) => join(root, name, 'user_settings.json'))
405
+ .filter((path) => {
406
+ try {
407
+ return statSync(path).isFile();
408
+ }
409
+ catch {
410
+ return false;
411
+ }
412
+ });
413
+ if (files.length === 0)
414
+ return '';
415
+ return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs)[0];
416
+ }
417
+ /**
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`.
420
+ */
421
+ export function readXiaoEClientCredential(env = process.env) {
422
+ 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
+ }
429
+ const settingsFile = findXiaoEClientSettingsFile(env);
430
+ if (!settingsFile)
431
+ return null;
432
+ try {
433
+ const parsed = JSON.parse(readFileSync(settingsFile, 'utf8'));
434
+ const authToken = readString(parsed, 'session_id', 'sessionId');
435
+ const erpaUrl = normalizeUrl(readString(parsed, 'erpa', 'erpa_url', 'erpaUrl'));
436
+ if (!authToken || !erpaUrl)
437
+ return null;
438
+ return { authToken, erpaUrl, source: XIAOE_CLIENT_SETTINGS_SOURCE };
439
+ }
440
+ catch {
441
+ return null;
442
+ }
443
+ }
444
+ export function hasXiaoEClientCredential(env = process.env) {
445
+ return Boolean(envString(env, 'XIAOE_USER_TOKEN')) || Boolean(findXiaoEClientSettingsFile(env));
446
+ }
447
+ async function fetchXiaoEClientEteamId(credential, domain) {
448
+ const controller = new AbortController();
449
+ const timer = setTimeout(() => controller.abort(), XIAOE_CLIENT_TOKEN_TIMEOUT_MS);
450
+ 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);
463
+ }
464
+ const data = await resp.json().catch(() => null);
465
+ return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
466
+ }
467
+ finally {
468
+ clearTimeout(timer);
469
+ }
470
+ }
471
+ async function resolveXiaoEClientCredential(baseUrl, env = process.env) {
472
+ const clientCredential = readXiaoEClientCredential(env);
473
+ if (!clientCredential)
474
+ return null;
475
+ const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
476
+ const payload = await fetchXiaoEClientEteamId(clientCredential, targetBaseUrl);
477
+ return readXiaoECredentialRecord(payload, clientCredential.source, targetBaseUrl);
478
+ }
175
479
  export function resolveXiaoEPreferredBaseUrl(options) {
176
480
  const env = options.env || process.env;
177
481
  const explicit = normalizeUrl(options.explicitBaseUrl || '');
@@ -200,15 +504,18 @@ async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = ''
200
504
  }
201
505
  async function saveXiaoEUnifiedLogin(input) {
202
506
  const baseUrl = normalizeUrl(input.baseUrl);
203
- const userInfo = await resolveUserInfo(baseUrl, input.eteamsId, '', XIAOE_AGENT_TYPE);
507
+ const userInfo = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
204
508
  if (!userInfo.userId) {
205
509
  throw new CliError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', ExitCode.AuthError);
206
510
  }
207
511
  const tenantKey = userInfo.tenantKey || input.tenantKeyFallback || '';
208
512
  const profile = input.profileHint || profileNameFromUrl(baseUrl);
209
513
  setActiveProfile(profile);
514
+ const cookies = { ETEAMSID: input.eteamsId };
515
+ if (input.rawCookie)
516
+ cookies.raw = input.rawCookie;
210
517
  const authData = {
211
- cookies: { ETEAMSID: input.eteamsId },
518
+ cookies,
212
519
  userId: userInfo.userId,
213
520
  tenantKey,
214
521
  tenantName: userInfo.tenantName,
@@ -221,6 +528,9 @@ async function saveXiaoEUnifiedLogin(input) {
221
528
  passportUrl: baseUrl,
222
529
  cookieSavedAt: Date.now(),
223
530
  loginSource: 'xiaoe-unified',
531
+ credentialSourceStep: input.credentialSource.credentialSourceStep,
532
+ credentialSourceType: input.credentialSource.credentialSourceType,
533
+ credentialSourceLabel: input.credentialSource.credentialSourceLabel,
224
534
  };
225
535
  const authFile = saveAuthFile(authData, profile);
226
536
  saveProfileConfig(baseUrl, baseUrl, profile, tenantKey);
@@ -237,32 +547,74 @@ async function saveXiaoEUnifiedLogin(input) {
237
547
  authFile,
238
548
  agentType: XIAOE_AGENT_TYPE,
239
549
  version: '',
550
+ loginSource: 'xiaoe-unified',
551
+ ...input.credentialSource,
240
552
  },
241
553
  };
242
554
  }
243
555
  export async function tryXiaoEUnifiedLogin(options = {}) {
244
556
  const env = options.env || process.env;
557
+ const explicitBaseUrl = normalizeUrl(options.baseUrl || '');
245
558
  const baseUrl = resolveXiaoEPreferredBaseUrl({
246
- explicitBaseUrl: options.baseUrl,
559
+ explicitBaseUrl,
247
560
  env,
248
561
  });
249
- if (!baseUrl)
562
+ const mayUseTokenScript = options.allowTokenScript || hasXiaoEEnv(env);
563
+ if (!baseUrl && !mayUseTokenScript)
250
564
  return null;
251
- const credential = resolveXiaoEUnifiedLoginCredential(baseUrl, env);
565
+ let credential = null;
566
+ if (mayUseTokenScript) {
567
+ const tokenScriptPath = xiaoETokenScriptPath(env);
568
+ if (!tokenScriptPath) {
569
+ options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the built-in XiaoE client credential next.');
570
+ }
571
+ else {
572
+ options.logger?.info('Trying XiaoE token script credential...');
573
+ try {
574
+ credential = await resolveXiaoETokenScriptCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
575
+ }
576
+ catch (error) {
577
+ const message = error instanceof Error ? error.message : String(error);
578
+ options.logger?.warn('XiaoE token script failed. ' + message);
579
+ }
580
+ if (!credential) {
581
+ options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the built-in XiaoE client credential next.');
582
+ }
583
+ }
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.
587
+ if (!credential && hasXiaoEClientCredential(env)) {
588
+ try {
589
+ credential = await resolveXiaoEClientCredential(explicitBaseUrl || DEFAULT_E10_BASE_URL, env);
590
+ }
591
+ catch (error) {
592
+ const message = error instanceof Error ? error.message : String(error);
593
+ options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
594
+ }
595
+ if (!credential) {
596
+ options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try XIAOE_* credentials next.');
597
+ }
598
+ }
599
+ }
600
+ if (!credential && baseUrl)
601
+ credential = resolveXiaoEUnifiedLoginCredential(baseUrl, env);
252
602
  if (!credential)
253
603
  return null;
254
604
  options.logger?.info('Detected XiaoE unified login for this E10 domain. Trying XiaoE auth...');
255
605
  try {
256
606
  return await saveXiaoEUnifiedLogin({
257
- baseUrl,
607
+ baseUrl: credential.baseUrl || baseUrl || DEFAULT_E10_BASE_URL,
258
608
  eteamsId: credential.eteamsId,
609
+ rawCookie: credential.rawCookie,
259
610
  tenantKeyFallback: options.tenantKeyFallback,
260
611
  profileHint: options.profileHint,
612
+ credentialSource: describeXiaoECredentialSource(credential.source),
261
613
  });
262
614
  }
263
615
  catch (error) {
264
616
  const message = error instanceof Error ? error.message : String(error);
265
- options.logger?.warn('XiaoE unified login failed; falling back to standard E10 auth. ' + message);
617
+ options.logger?.warn('XiaoE unified login failed. ' + message);
266
618
  return null;
267
619
  }
268
620
  }
@@ -1,9 +1,9 @@
1
1
  import { CliError, ExitCode } from '../../core/errors.js';
2
2
  import { E10AuthSession } from './auth/session.js';
3
- import { hasXiaoEEnv, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
3
+ import { hasXiaoEEnv, hasXiaoETokenScript, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
4
4
  import { E10ApiClient } from './client.js';
5
5
  function shouldPreferXiaoEUnifiedLogin(options, env) {
6
- return hasXiaoEEnv(env) && !options.auth && !options.profile;
6
+ return !options.auth && !options.profile && (hasXiaoEEnv(env) || hasXiaoETokenScript(env));
7
7
  }
8
8
  export async function getE10Session(ctx) {
9
9
  const options = ctx.getGlobalOptions();
@@ -11,10 +11,12 @@ export async function getE10Session(ctx) {
11
11
  const xiaoELogin = await tryXiaoEUnifiedLogin({
12
12
  env: ctx.env,
13
13
  baseUrl: options.baseUrl,
14
+ allowTokenScript: true,
14
15
  logger: ctx.logger,
15
16
  });
16
17
  if (xiaoELogin)
17
18
  return xiaoELogin.session;
19
+ return null;
18
20
  }
19
21
  return E10AuthSession.fromOptions(options, ctx.cwd);
20
22
  }
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { CliError, ExitCode } from '../../core/errors.js';
5
+ export const PACKAGED_DETECTOR_VERSION = '1.0.13';
6
+ export const PACKAGED_DETECTOR_NAME = `weaver-skill-detector-${PACKAGED_DETECTOR_VERSION}`;
7
+ export function packagedDetectorDir() {
8
+ return fileURLToPath(new URL(`../../../tools/${PACKAGED_DETECTOR_NAME}/`, import.meta.url));
9
+ }
10
+ function detectorScriptPath(detectorDir) {
11
+ return join(detectorDir, 'scripts', 'validate_skill.py');
12
+ }
13
+ function legacySiblingDetectorDir() {
14
+ return resolve(packagedDetectorDir(), '..', '..', '..', PACKAGED_DETECTOR_NAME);
15
+ }
16
+ function detectorVersion(detectorDir) {
17
+ let version = PACKAGED_DETECTOR_VERSION;
18
+ const metaPath = join(detectorDir, '_meta.json');
19
+ if (existsSync(metaPath)) {
20
+ try {
21
+ const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
22
+ if (typeof meta.version === 'string' && meta.version)
23
+ version = meta.version;
24
+ }
25
+ catch {
26
+ version = PACKAGED_DETECTOR_VERSION;
27
+ }
28
+ }
29
+ return version;
30
+ }
31
+ export function resolveDetectorInfo(detectorDir) {
32
+ const candidates = detectorDir
33
+ ? [resolve(detectorDir)]
34
+ : [packagedDetectorDir(), legacySiblingDetectorDir()].map((candidate) => resolve(candidate));
35
+ for (const candidate of candidates) {
36
+ const scriptPath = detectorScriptPath(candidate);
37
+ if (existsSync(scriptPath)) {
38
+ return { detectorDir: candidate, scriptPath, version: detectorVersion(candidate) };
39
+ }
40
+ }
41
+ throw new CliError(`weaver skill detector is not available; checked: ${candidates.map(detectorScriptPath).join(', ')}`, ExitCode.UsageError);
42
+ }
@@ -63,6 +63,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
63
63
  const resolvedSourceRoot = resolve(sourceRoot);
64
64
  const resolvedTargetRoot = resolve(targetRoot);
65
65
  const skillNames = resolvePackagedSkillNames(resolvedSourceRoot, requested);
66
+ const productContent = productMetadataContent(resolvedSourceRoot);
66
67
  mkdirSync(resolvedTargetRoot, { recursive: true, mode: 0o700 });
67
68
  const installed = skillNames.flatMap((name) => {
68
69
  const source = resolveInside(resolvedSourceRoot, name);
@@ -71,8 +72,9 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
71
72
  cpSync(source, target, {
72
73
  recursive: true,
73
74
  force: true,
74
- filter: (entry) => shouldCopy(entry, source),
75
+ filter: (entry) => shouldCopy(entry, source, Boolean(productContent)),
75
76
  });
77
+ writeProductMetadata(target, productContent);
76
78
  const current = {
77
79
  name,
78
80
  source,
@@ -86,9 +88,10 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
86
88
  cpSync(source, legacyTarget, {
87
89
  recursive: true,
88
90
  force: true,
89
- filter: (entry) => shouldCopy(entry, source),
91
+ filter: (entry) => shouldCopy(entry, source, Boolean(productContent)),
90
92
  });
91
93
  rewriteSkillName(resolve(legacyTarget, 'SKILL.md'), LEGACY_SHARED_SKILL_NAME);
94
+ writeProductMetadata(legacyTarget, productContent);
92
95
  return [current, {
93
96
  name: LEGACY_SHARED_SKILL_NAME,
94
97
  source,
@@ -107,11 +110,55 @@ function rewriteSkillName(skillPath, name) {
107
110
  const content = readFileSync(skillPath, 'utf8');
108
111
  writeFileSync(skillPath, content.replace(/^name:\s*weaver-e10-shared\s*$/mu, `name: ${name}`), 'utf8');
109
112
  }
110
- function shouldCopy(entry, sourceRoot) {
113
+ function productMetadataContent(sourceRoot) {
114
+ const productPath = productMetadataPath(sourceRoot);
115
+ if (!existsSync(productPath))
116
+ return null;
117
+ const product = JSON.parse(readFileSync(productPath, 'utf8'));
118
+ const packageInfo = readPackageInfo(resolve(sourceRoot, '..', 'package.json'));
119
+ const metadata = {
120
+ schemaVersion: typeof product.schemaVersion === 'number' ? product.schemaVersion : 1,
121
+ product: stringOrEmpty(product.product),
122
+ productVersion: stringOrEmpty(packageInfo?.version) || stringOrEmpty(product.productVersion),
123
+ packageName: stringOrEmpty(packageInfo?.name) || stringOrEmpty(product.packageName),
124
+ command: stringOrEmpty(product.command),
125
+ nodeEngine: stringOrEmpty(packageInfo?.engines?.node) || stringOrEmpty(product.nodeEngine),
126
+ };
127
+ if (metadata.product !== 'weaver-work-cli' ||
128
+ metadata.packageName !== 'weaver-work-cli' ||
129
+ metadata.command !== 'weaver-work-cli' ||
130
+ !metadata.productVersion ||
131
+ !metadata.nodeEngine) {
132
+ throw new Error('skill-template/product.json or package.json is invalid for weaver-work-cli skills');
133
+ }
134
+ return `${JSON.stringify(metadata, null, 2)}\n`;
135
+ }
136
+ function productMetadataPath(sourceRoot) {
137
+ const templatePath = resolve(sourceRoot, '..', 'skill-template', 'product.json');
138
+ if (existsSync(templatePath))
139
+ return templatePath;
140
+ return resolve(sourceRoot, 'product.json');
141
+ }
142
+ function readPackageInfo(packagePath) {
143
+ if (!existsSync(packagePath))
144
+ return null;
145
+ return JSON.parse(readFileSync(packagePath, 'utf8'));
146
+ }
147
+ function stringOrEmpty(value) {
148
+ return typeof value === 'string' ? value : '';
149
+ }
150
+ function writeProductMetadata(target, productContent) {
151
+ if (!productContent)
152
+ return;
153
+ writeFileSync(resolve(target, 'product.json'), productContent, 'utf8');
154
+ }
155
+ function shouldCopy(entry, sourceRoot, skipSkillProduct) {
111
156
  const relativePath = relative(sourceRoot, entry);
112
157
  const name = basename(entry);
113
158
  if (!relativePath)
114
159
  return true;
160
+ if (skipSkillProduct && relativePath === 'product.json')
161
+ return false;
115
162
  if (name === '.DS_Store' || name.startsWith('.'))
116
163
  return false;
117
164
  const parts = relativePath.split(sep);
@@ -195,7 +195,16 @@ export function formatSearchText(rows, meta = undefined) {
195
195
  *
196
196
  * 全宗:xxx 分类:xxx 文件形成时间:2026-01-23 ← 第三段
197
197
  * 注意:三段间必须空行,单换行在 Markdown 是软换行会被折叠成空格。
198
+ * 第二、三段必须缩进 3 空格(对齐 `1. ` 后的内容列):不缩进会被判定为列表外段落,
199
+ * 渲染器会把下一条当作新列表起点,导致所有条目的序号都显示为 1。
198
200
  * ------------------------------------------------------------------ */
201
+ /** 卡片第二、三段缩进 3 空格,保持属于同一条有序列表项;不缩进会被渲染器重排成新列表(序号全变 1)。 */
202
+ function indentContinuation(text) {
203
+ return text
204
+ .split('\n')
205
+ .map((line) => (line.trim() ? ` ${line}` : ''))
206
+ .join('\n');
207
+ }
199
208
  export function formatSearchCard(rows, options = {}) {
200
209
  const baseUrl = String(options.baseUrl ?? '').trim().replace(/\/+$/, '');
201
210
  const items = rows.map((row) => {
@@ -214,10 +223,10 @@ export function formatSearchCard(rows, options = {}) {
214
223
  const highlight = markdownHighlight(highlightSourceOf(raw, view));
215
224
  if (highlight) {
216
225
  parts.push('');
217
- parts.push(highlight);
226
+ parts.push(indentContinuation(highlight));
218
227
  }
219
228
  parts.push('');
220
- parts.push(textMetaItems(view).join(' '));
229
+ parts.push(indentContinuation(textMetaItems(view).join(' ')));
221
230
  blocks.push(parts.join('\n'));
222
231
  });
223
232
  return blocks.join('\n\n');
@@ -261,7 +270,7 @@ h1{font-size:20px;margin-bottom:4px}
261
270
  .trow{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
262
271
  .idx{color:#94a3b8;font-weight:600}
263
272
  .lvl{flex:none;background:#eef4ff;color:#2563eb;border:1px solid #bfdbfe;border-radius:4px;padding:0 8px;font-size:12px}
264
- .tlink{font-size:15px;font-weight:600;color:#0f2c5c;text-decoration:none;word-break:break-all}
273
+ .tlink{font-size:15px;font-weight:600;color:#0f2c5c;text-decoration:none;word-wrap:break-word;overflow-wrap:break-word}
265
274
  .tlink:hover{color:#1d6ae5;text-decoration:underline}
266
275
  .tlink.plain{cursor:default}
267
276
  .hl{margin:8px 0 6px;color:#475569;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;font-size:13px}