weaver-work-cli 0.1.8 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +11 -11
  2. package/dist/cmd/action.js +76 -0
  3. package/dist/cmd/index.js +2 -0
  4. package/dist/cmd/refs/index.js +103 -0
  5. package/dist/cmd/register.js +31 -0
  6. package/dist/cmd/root.js +1 -0
  7. package/dist/cmd/setup/index.js +25 -5
  8. package/dist/cmd/skills/index.js +37 -5
  9. package/dist/core/connector.js +54 -0
  10. package/dist/core/context.js +2 -1
  11. package/dist/core/input.js +48 -0
  12. package/dist/core/output.js +298 -3
  13. package/dist/core/refs.js +432 -0
  14. package/dist/core/state.js +26 -0
  15. package/dist/internal/e10/auth/commands.js +2 -2
  16. package/dist/internal/e10/auth/session.js +14 -0
  17. package/dist/internal/e10/auth/xiaoe.js +116 -51
  18. package/dist/internal/e10/request-runtime.js +5 -0
  19. package/dist/internal/skills/install.js +148 -28
  20. package/dist/internal/skills/reader.js +12 -9
  21. package/dist/shortcuts/blog/continuation.js +74 -0
  22. package/dist/shortcuts/blog/errors.js +60 -0
  23. package/dist/shortcuts/blog/host.js +175 -0
  24. package/dist/shortcuts/blog/index.js +324 -0
  25. package/dist/shortcuts/blog/manifest.js +415 -0
  26. package/dist/shortcuts/blog/operations/comment.js +125 -0
  27. package/dist/shortcuts/blog/operations/comments.js +31 -0
  28. package/dist/shortcuts/blog/operations/edit.js +131 -0
  29. package/dist/shortcuts/blog/operations/export.js +46 -0
  30. package/dist/shortcuts/blog/operations/like.js +80 -0
  31. package/dist/shortcuts/blog/operations/publish.js +157 -0
  32. package/dist/shortcuts/blog/operations/query-user.js +58 -0
  33. package/dist/shortcuts/blog/operations/query.js +96 -0
  34. package/dist/shortcuts/blog/operations/registry.js +54 -0
  35. package/dist/shortcuts/blog/operations/remind.js +176 -0
  36. package/dist/shortcuts/blog/operations/shared.js +330 -0
  37. package/dist/shortcuts/blog/operations/types.js +16 -0
  38. package/dist/shortcuts/blog/operations.js +9 -0
  39. package/dist/shortcuts/caizhilian/continuation.js +70 -0
  40. package/dist/shortcuts/caizhilian/errors.js +53 -0
  41. package/dist/shortcuts/caizhilian/host.js +190 -0
  42. package/dist/shortcuts/caizhilian/index.js +186 -0
  43. package/dist/shortcuts/caizhilian/manifest.js +42 -0
  44. package/dist/shortcuts/caizhilian/operations/api.js +801 -0
  45. package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
  46. package/dist/shortcuts/caizhilian/operations/files.js +306 -0
  47. package/dist/shortcuts/caizhilian/operations/form.js +170 -0
  48. package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
  49. package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
  50. package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
  51. package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
  52. package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
  53. package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
  54. package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
  55. package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
  56. package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
  57. package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
  58. package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
  59. package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
  60. package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
  61. package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
  62. package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
  63. package/dist/shortcuts/caizhilian/operations/types.js +57 -0
  64. package/dist/shortcuts/caizhilian/operations.js +21 -0
  65. package/dist/shortcuts/contract/continuation.js +70 -0
  66. package/dist/shortcuts/contract/errors.js +62 -0
  67. package/dist/shortcuts/contract/host.js +205 -0
  68. package/dist/shortcuts/contract/index.js +318 -0
  69. package/dist/shortcuts/contract/manifest.js +596 -0
  70. package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
  71. package/dist/shortcuts/contract/operations/form-info.js +371 -0
  72. package/dist/shortcuts/contract/operations/read.js +118 -0
  73. package/dist/shortcuts/contract/operations/registry.js +43 -0
  74. package/dist/shortcuts/contract/operations/request.js +300 -0
  75. package/dist/shortcuts/contract/operations/shared.js +133 -0
  76. package/dist/shortcuts/contract/operations/subject.js +157 -0
  77. package/dist/shortcuts/contract/operations/template.js +81 -0
  78. package/dist/shortcuts/contract/operations/types.js +7 -0
  79. package/dist/shortcuts/contract/operations/workflow.js +52 -0
  80. package/dist/shortcuts/contract/operations/write.js +131 -0
  81. package/dist/shortcuts/contract/operations.js +8 -0
  82. package/dist/shortcuts/hrm/index.js +83 -2
  83. package/dist/shortcuts/hrm/operations/employee.js +160 -30
  84. package/dist/shortcuts/hrm/operations/org.js +17 -3
  85. package/dist/shortcuts/hrm/operations/position.js +18 -2
  86. package/dist/shortcuts/index.js +5 -1
  87. package/dist/shortcuts/invoice/index.js +74 -36
  88. package/dist/shortcuts/invoice/manifest.js +674 -4
  89. package/dist/shortcuts/invoice/markdown.js +172 -0
  90. package/dist/shortcuts/invoice/operations/add.js +3 -3
  91. package/dist/shortcuts/invoice/operations/delete.js +2 -2
  92. package/dist/shortcuts/invoice/operations/get.js +10 -2
  93. package/dist/shortcuts/invoice/operations/import.js +2 -2
  94. package/dist/shortcuts/invoice/operations/registry.js +8 -1
  95. package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
  96. package/dist/shortcuts/invoice/operations/reim.js +85 -2
  97. package/dist/shortcuts/invoice/operations/shared.js +75 -8
  98. package/dist/shortcuts/invoice/operations/update.js +2 -2
  99. package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
  100. package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
  101. package/dist/shortcuts/invoice/projection.js +328 -0
  102. package/dist/shortcuts/meeting/host.js +18 -1
  103. package/dist/shortcuts/meeting/manifest.js +30 -18
  104. package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
  105. package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
  106. package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
  107. package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
  108. package/dist/shortcuts/meeting/operations/env.js +8 -1
  109. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  110. package/dist/shortcuts/meeting/operations/signs.js +216 -2
  111. package/dist/shortcuts/workflow/endpoints.js +2 -1
  112. package/dist/shortcuts/workflow/errors.js +8 -2
  113. package/dist/shortcuts/workflow/host.js +6 -3
  114. package/dist/shortcuts/workflow/operations/form.js +931 -589
  115. package/dist/shortcuts/workflow/operations/read.js +128 -205
  116. package/dist/shortcuts/workflow/operations/shared.js +21 -0
  117. package/dist/shortcuts/workflow/operations/write.js +750 -52
  118. package/dist/shortcuts/workflow/operations.js +36 -10
  119. package/docs/_catalog.md +6 -0
  120. package/docs/agent-invoice.md +5 -1
  121. package/docs/agent-io-channel-design.md +468 -0
  122. package/docs/agent-skill-install.md +9 -4
  123. package/docs/blog.md +123 -0
  124. package/docs/caizhilian.md +304 -0
  125. package/docs/contract.md +91 -0
  126. package/docs/e10-auth.md +2 -2
  127. package/docs/hrm.md +19 -9
  128. package/docs/invoice.md +5 -1
  129. package/docs/meeting.md +1 -1
  130. package/docs/operation-manual.md +15 -3
  131. package/docs/skill-review-2026-09-19.md +2 -2
  132. package/package.json +2 -2
  133. package/scripts/package-skill.mjs +32 -20
  134. package/skill-template/business-info.json +113 -14
  135. package/skill-template/domains/blog.md +24 -0
  136. package/skill-template/domains/caizhilian.md +22 -0
  137. package/skill-template/domains/jinchengda.md +23 -0
  138. package/skill-template/domains/meeting.md +1 -1
  139. package/skill-template/domains/workflow.md +1 -1
  140. package/skill-template/skill-template.md +1 -1
  141. package/skills/weaver-e10-blog/SKILL.md +111 -0
  142. package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
  143. package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
  144. package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
  145. package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
  146. package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
  147. package/skills/weaver-e10-blog/references/blog-export.md +42 -0
  148. package/skills/weaver-e10-blog/references/blog-like.md +50 -0
  149. package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
  150. package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
  151. package/skills/weaver-e10-blog/references/blog-query.md +49 -0
  152. package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
  153. package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
  154. package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
  155. package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
  156. package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
  157. package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
  158. package/skills/weaver-e10-caizhilian/references/document.md +111 -0
  159. package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
  160. package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
  161. package/skills/weaver-e10-caizhilian/references/files.md +120 -0
  162. package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
  163. package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
  164. package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
  165. package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
  166. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
  167. package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
  168. package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
  169. package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
  170. package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
  171. package/skills/weaver-e10-calendar/SKILL.md +4 -3
  172. package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
  173. package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
  174. package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
  175. package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
  176. package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
  177. package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
  178. package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
  179. package/skills/weaver-e10-esb/SKILL.md +2 -2
  180. package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
  181. package/skills/weaver-e10-hrm/SKILL.md +54 -6
  182. package/skills/weaver-e10-hrm/references/employee.md +89 -15
  183. package/skills/weaver-e10-hrm/references/org.md +7 -4
  184. package/skills/weaver-e10-hrm/references/position.md +14 -7
  185. package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
  186. package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
  187. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
  188. package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
  189. package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
  190. package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
  191. package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
  192. package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
  193. package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
  194. package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
  195. package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
  196. package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
  197. package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
  198. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
  199. package/skills/weaver-e10-jucailin/SKILL.md +4 -3
  200. package/skills/weaver-e10-mail/SKILL.md +4 -11
  201. package/skills/weaver-e10-meeting/SKILL.md +66 -32
  202. package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
  203. package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
  204. package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
  205. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
  206. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
  207. package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
  208. package/skills/weaver-e10-okr/SKILL.md +6 -11
  209. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  210. package/skills/weaver-e10-plan/SKILL.md +3 -3
  211. package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
  212. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
  213. package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
  214. package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
  215. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
  216. package/skills/weaver-e10-shared/SKILL.md +36 -25
  217. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
  218. package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
  219. package/skills/weaver-e10-shijingran/SKILL.md +2 -1
  220. package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
  221. package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
  222. package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
  223. package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
  224. package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
  225. package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
  226. package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
  227. package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
  228. package/skills/weaver-e10-workflow/SKILL.md +32 -37
  229. package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
  230. package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
  231. package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
  232. package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
  233. package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
  234. package/skills/weaver-e10-workflow/references/workflow-query.md +38 -20
  235. package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
  236. package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
  237. package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
  238. package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
  239. package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
  240. package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
  241. package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
  242. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
  243. package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
  244. package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
  245. package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
  246. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
  247. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
  248. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
  249. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
  250. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
  251. package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
  252. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
  253. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
  254. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
  255. package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
  256. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
  257. package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
  258. package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
  259. package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
  260. package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
  261. package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
  262. package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
  263. package/scripts/sync-connector-skills.mjs +0 -172
@@ -0,0 +1,432 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { appendFileSync, chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { CliError, ExitCode } from './errors.js';
5
+ import { refsDir, refsIndexPath } from './state.js';
6
+ /**
7
+ * 结果引用(result reference):把超过 stdout 预算的完整结果落到 CLI state 目录,
8
+ * stdout 只回一个短句柄,下游用 `--input-ref <refId>` 取回。
9
+ *
10
+ * 全部使用同步 fs API:CLI 是一次一进程的模型,同步写盘既无竞态,也能保持
11
+ * `Output.write()` 的同步签名不变(41 处调用点无需改)。
12
+ *
13
+ * Windows 兼容要点:
14
+ * - refId 只含 `[a-z0-9-]`,绝不含 `:` `*` `?` `" `<` `>` `|`(时间戳用 YYYYMMDD-HHMMSS);
15
+ * - 不使用 `chmod`(Windows 无 POSIX 权限语义);
16
+ * - 删除失败(ENOENT / EPERM / EBUSY,文件被占用)一律忽略,留给下次 GC;
17
+ * - 原子写用「临时文件 + rename」,rename 失败时回退为直接写。
18
+ */
19
+ export const REF_SCHEMA_VERSION = 1;
20
+ export class RefError extends CliError {
21
+ type = 'policy';
22
+ subtype;
23
+ retryable = false;
24
+ constructor(subtype, message, exitCode = ExitCode.UsageError) {
25
+ super(message, exitCode);
26
+ this.name = 'RefError';
27
+ this.subtype = subtype;
28
+ }
29
+ }
30
+ function slug(value) {
31
+ return value
32
+ .toLowerCase()
33
+ .replace(/[^a-z0-9]+/g, '-')
34
+ .replace(/^-+|-+$/g, '')
35
+ .slice(0, 40);
36
+ }
37
+ function pad(value) {
38
+ return String(value).padStart(2, '0');
39
+ }
40
+ /** 生成 Windows 安全的文件名片段:`20260922-105401-invoice-list-a1b2c3`。 */
41
+ export function refIdFor(operation, now = new Date()) {
42
+ const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
43
+ + `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
44
+ const name = operation ? slug(operation) : 'result';
45
+ return `${stamp}-${name || 'result'}-${randomBytes(8).toString('hex')}`;
46
+ }
47
+ function dateDirOf(now) {
48
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
49
+ }
50
+ function sha256Of(value) {
51
+ return createHash('sha256').update(value, 'utf8').digest('hex');
52
+ }
53
+ function ensureDir(dir) {
54
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
55
+ // POSIX permissions are enforced here. On Windows, the directory inherits
56
+ // the current user's profile ACL; chmod has different semantics there.
57
+ if (process.platform !== 'win32') {
58
+ chmodSync(dir, 0o700);
59
+ }
60
+ }
61
+ function ensurePrivateFile(file) {
62
+ if (process.platform !== 'win32') {
63
+ chmodSync(file, 0o600);
64
+ }
65
+ }
66
+ function isInsideRefs(ctx, file) {
67
+ const root = resolve(refsDir(ctx));
68
+ const absolute = resolve(file);
69
+ const isChild = (parent, candidate) => {
70
+ const relativePath = relative(parent, candidate);
71
+ return relativePath !== ''
72
+ && relativePath !== '..'
73
+ && !relativePath.startsWith(`..${sep}`)
74
+ && !isAbsolute(relativePath);
75
+ };
76
+ if (!isChild(root, absolute))
77
+ return false;
78
+ try {
79
+ return isChild(realpathSync(root), realpathSync(absolute));
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /** 原子写:临时文件 + rename;Windows 上 rename 失败(占用/权限)回退直接写。 */
86
+ function atomicWriteSync(file, data) {
87
+ const tmp = `${file}.${randomBytes(8).toString('hex')}.tmp`;
88
+ writeFileSync(tmp, data, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
89
+ try {
90
+ renameSync(tmp, file);
91
+ ensurePrivateFile(file);
92
+ }
93
+ catch {
94
+ writeFileSync(file, data, { encoding: 'utf8', mode: 0o600 });
95
+ ensurePrivateFile(file);
96
+ try {
97
+ unlinkSync(tmp);
98
+ }
99
+ catch {
100
+ /* Windows 下 tmp 可能已被占用,忽略 */
101
+ }
102
+ }
103
+ }
104
+ /** 删除失败一律忽略:ENOENT(已删)/ EPERM / EBUSY(Windows 文件占用)都留给下次 GC。 */
105
+ function silentUnlink(file) {
106
+ try {
107
+ unlinkSync(file);
108
+ return true;
109
+ }
110
+ catch (error) {
111
+ return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT');
112
+ }
113
+ }
114
+ function readIndex(ctx) {
115
+ const path = refsIndexPath(ctx);
116
+ if (!existsSync(path))
117
+ return [];
118
+ const rows = [];
119
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
120
+ const trimmed = line.trim();
121
+ if (!trimmed)
122
+ continue;
123
+ try {
124
+ const parsed = JSON.parse(trimmed);
125
+ if (parsed
126
+ && typeof parsed.refId === 'string'
127
+ && typeof parsed.file === 'string'
128
+ && typeof parsed.bytes === 'number'
129
+ && Number.isFinite(parsed.bytes)
130
+ && parsed.bytes >= 0
131
+ && typeof parsed.sha256 === 'string'
132
+ && /^[a-f0-9]{64}$/u.test(parsed.sha256)
133
+ && isInsideRefs(ctx, parsed.file))
134
+ rows.push({ ...parsed, file: resolve(parsed.file) });
135
+ }
136
+ catch {
137
+ /* 索引行损坏则跳过,不因一行坏数据毁掉全部 */
138
+ }
139
+ }
140
+ return rows;
141
+ }
142
+ function writeIndex(ctx, rows) {
143
+ ensureDir(refsDir(ctx));
144
+ const data = rows.map((row) => JSON.stringify(row)).join('\n');
145
+ atomicWriteSync(refsIndexPath(ctx), rows.length ? `${data}\n` : '');
146
+ }
147
+ function appendIndex(ctx, meta) {
148
+ ensureDir(refsDir(ctx));
149
+ try {
150
+ appendFileSync(refsIndexPath(ctx), `${JSON.stringify(meta)}\n`, { encoding: 'utf8', mode: 0o600 });
151
+ ensurePrivateFile(refsIndexPath(ctx));
152
+ }
153
+ catch {
154
+ // 追加失败(索引被占用)不阻塞主流程,ref 文件本身已写好;索引缺失仍可通过目录扫描找回。
155
+ }
156
+ }
157
+ function scanFiles(ctx) {
158
+ const root = refsDir(ctx);
159
+ if (!existsSync(root))
160
+ return [];
161
+ const files = [];
162
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
163
+ if (!entry.isDirectory())
164
+ continue;
165
+ const dir = join(root, entry.name);
166
+ for (const inner of readdirSync(dir, { withFileTypes: true })) {
167
+ if (inner.isFile() && inner.name.endsWith('.json'))
168
+ files.push(join(dir, inner.name));
169
+ }
170
+ }
171
+ return files;
172
+ }
173
+ function metaFromFile(file) {
174
+ try {
175
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
176
+ if (!parsed || typeof parsed.refId !== 'string')
177
+ return null;
178
+ return {
179
+ refId: parsed.refId,
180
+ ...(parsed.operation ? { operation: parsed.operation } : {}),
181
+ file,
182
+ bytes: statSync(file).size,
183
+ sha256: sha256Of(JSON.stringify(parsed.envelope ?? null, null, 2)),
184
+ createdAt: parsed.createdAt,
185
+ expiresAt: parsed.expiresAt,
186
+ ttlMs: typeof parsed.ttlMs === 'number' ? parsed.ttlMs : 0,
187
+ };
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ }
193
+ function findMeta(ctx, refId) {
194
+ const fromIndex = readIndex(ctx).find((row) => row.refId === refId);
195
+ if (fromIndex)
196
+ return fromIndex;
197
+ // 索引丢失时回退到目录扫描,保证 ref 仍可用。
198
+ for (const file of scanFiles(ctx)) {
199
+ if (basename(file, '.json') === refId) {
200
+ const meta = metaFromFile(file);
201
+ if (meta)
202
+ return meta;
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ export function defaultTtlMs() {
208
+ return 30 * 60_000;
209
+ }
210
+ export function writeRef(ctx, options) {
211
+ const now = options.now ?? new Date();
212
+ const ttlMs = options.ttlMs ?? defaultTtlMs();
213
+ const refId = refIdFor(options.operation, now);
214
+ const dir = join(refsDir(ctx), dateDirOf(now));
215
+ ensureDir(dir);
216
+ const file = join(dir, `${refId}.json`);
217
+ const payload = {
218
+ schemaVersion: REF_SCHEMA_VERSION,
219
+ refId,
220
+ ...(options.operation ? { operation: options.operation } : {}),
221
+ createdAt: now.toISOString(),
222
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
223
+ ttlMs,
224
+ envelope: options.value ?? null,
225
+ };
226
+ const fileContents = JSON.stringify(payload, null, 2);
227
+ atomicWriteSync(file, fileContents);
228
+ const meta = {
229
+ refId,
230
+ ...(options.operation ? { operation: options.operation } : {}),
231
+ file,
232
+ bytes: Buffer.byteLength(fileContents, 'utf8'),
233
+ sha256: sha256Of(JSON.stringify(options.value ?? null, null, 2)),
234
+ createdAt: payload.createdAt,
235
+ expiresAt: payload.expiresAt,
236
+ ttlMs,
237
+ };
238
+ appendIndex(ctx, meta);
239
+ return meta;
240
+ }
241
+ export function readRef(ctx, refId) {
242
+ const meta = findMeta(ctx, refId);
243
+ if (!meta || !isInsideRefs(ctx, meta.file) || !existsSync(meta.file)) {
244
+ throw new RefError('ref_missing', `未找到结果引用:${refId}`);
245
+ }
246
+ try {
247
+ if (!lstatSync(meta.file).isFile()) {
248
+ throw new RefError('ref_corrupt', `结果引用不是普通文件:${refId}`, ExitCode.PolicyError);
249
+ }
250
+ }
251
+ catch (error) {
252
+ if (error instanceof RefError)
253
+ throw error;
254
+ throw new RefError('ref_missing', `未找到结果引用:${refId}`);
255
+ }
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(readFileSync(meta.file, 'utf8'));
259
+ }
260
+ catch {
261
+ throw new RefError('ref_corrupt', `结果引用文件损坏:${refId}`, ExitCode.PolicyError);
262
+ }
263
+ if (parsed.schemaVersion !== REF_SCHEMA_VERSION
264
+ || parsed.refId !== refId
265
+ || typeof parsed.createdAt !== 'string'
266
+ || typeof parsed.expiresAt !== 'string'
267
+ || !Number.isFinite(Date.parse(parsed.createdAt))
268
+ || !Number.isFinite(Date.parse(parsed.expiresAt))
269
+ || parsed.createdAt !== meta.createdAt
270
+ || parsed.expiresAt !== meta.expiresAt
271
+ || parsed.ttlMs !== meta.ttlMs) {
272
+ throw new RefError('ref_corrupt', `结果引用元数据不匹配:${refId}`, ExitCode.PolicyError);
273
+ }
274
+ const expiresAt = Date.parse(parsed.expiresAt);
275
+ if (expiresAt < Date.now()) {
276
+ silentUnlink(meta.file);
277
+ writeIndex(ctx, readIndex(ctx).filter((row) => row.refId !== refId));
278
+ throw new RefError('ref_expired', `结果引用已过期:${refId}`, ExitCode.PolicyError);
279
+ }
280
+ const serialized = JSON.stringify(parsed.envelope ?? null, null, 2);
281
+ if (sha256Of(serialized) !== meta.sha256) {
282
+ throw new RefError('ref_corrupt', `结果引用校验失败:${refId}`, ExitCode.PolicyError);
283
+ }
284
+ return { meta, envelope: parsed.envelope };
285
+ }
286
+ export function listRefs(ctx) {
287
+ const seen = new Set();
288
+ const rows = [];
289
+ for (const row of readIndex(ctx)) {
290
+ if (seen.has(row.refId) || !existsSync(row.file))
291
+ continue;
292
+ seen.add(row.refId);
293
+ rows.push(row);
294
+ }
295
+ for (const file of scanFiles(ctx)) {
296
+ const id = basename(file, '.json');
297
+ if (seen.has(id))
298
+ continue;
299
+ const meta = metaFromFile(file);
300
+ if (meta) {
301
+ seen.add(id);
302
+ rows.push(meta);
303
+ }
304
+ }
305
+ return rows.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
306
+ }
307
+ /** 惰性 GC:过期优先,其次按数量与总字节上限淘汰最旧的。 */
308
+ export function pruneRefs(ctx, options = {}) {
309
+ const maxCount = options.maxCount ?? 50;
310
+ const maxBytes = options.maxBytes ?? 200 * 1024 * 1024;
311
+ const preserved = new Set(options.preserveRefIds ?? []);
312
+ const rows = listRefs(ctx);
313
+ const now = Date.now();
314
+ const expired = [];
315
+ const alive = [];
316
+ const ageCutoff = options.olderThanMs === undefined ? undefined : now - options.olderThanMs;
317
+ for (const row of rows) {
318
+ const at = Date.parse(row.expiresAt);
319
+ const created = Date.parse(row.createdAt);
320
+ const tooOld = ageCutoff !== undefined && Number.isFinite(created) && created <= ageCutoff;
321
+ if (((Number.isFinite(at) && at < now) || tooOld) && !preserved.has(row.refId))
322
+ expired.push(row);
323
+ else
324
+ alive.push(row);
325
+ }
326
+ // Keep protected and newest refs first; remove old unprotected entries to
327
+ // satisfy count/byte limits without deleting the result just produced.
328
+ const byAge = [...alive].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
329
+ const victims = new Set(expired.map((row) => row.refId));
330
+ const protectedRows = byAge.filter((row) => preserved.has(row.refId));
331
+ const candidates = byAge.filter((row) => !preserved.has(row.refId));
332
+ const allowedCandidates = Math.max(0, Math.floor(maxCount) - protectedRows.length);
333
+ if (candidates.length > allowedCandidates) {
334
+ for (const row of candidates.slice(0, candidates.length - allowedCandidates))
335
+ victims.add(row.refId);
336
+ }
337
+ let total = byAge.filter((row) => !victims.has(row.refId)).reduce((sum, row) => sum + row.bytes, 0);
338
+ if (total > maxBytes) {
339
+ for (const row of candidates) {
340
+ if (total <= maxBytes)
341
+ break;
342
+ if (victims.has(row.refId))
343
+ continue;
344
+ victims.add(row.refId);
345
+ total -= row.bytes;
346
+ }
347
+ }
348
+ let cleanupIncomplete = false;
349
+ for (const row of rows) {
350
+ if (!victims.has(row.refId))
351
+ continue;
352
+ if (!silentUnlink(row.file))
353
+ cleanupIncomplete = true;
354
+ // 目录空了顺手清掉日期目录,Windows 上失败也无所谓。
355
+ try {
356
+ rmdirSync(dirname(row.file));
357
+ }
358
+ catch {
359
+ /* 目录非空或占用,忽略 */
360
+ }
361
+ }
362
+ const kept = rows.filter((row) => !victims.has(row.refId));
363
+ if (rows.length > 0 || existsSync(refsIndexPath(ctx)))
364
+ writeIndex(ctx, kept);
365
+ const keptBytes = kept.reduce((sum, row) => sum + row.bytes, 0);
366
+ return {
367
+ removed: victims.size,
368
+ kept: kept.length,
369
+ bytes: keptBytes,
370
+ overLimit: cleanupIncomplete || kept.length > maxCount || keptBytes > maxBytes,
371
+ cleanupIncomplete,
372
+ };
373
+ }
374
+ /** 轻量 JSON 路径取值,支持 `a.b[0].c`、`$.data.items[0].fid`,并允许省略 `data.` 前缀。 */
375
+ export function pickValue(value, path) {
376
+ let cursor = value;
377
+ for (const token of tokenizePath(path)) {
378
+ if (cursor === null || cursor === undefined) {
379
+ throw new RefError('ref_pick_failed', `--pick 路径 ${path} 在 ${token} 处无法继续`);
380
+ }
381
+ if (typeof token === 'number') {
382
+ if (!Array.isArray(cursor)) {
383
+ throw new RefError('ref_pick_failed', `--pick 路径 ${path} 的 ${token} 不是数组下标可访问的值`);
384
+ }
385
+ cursor = cursor[token];
386
+ continue;
387
+ }
388
+ if (typeof cursor !== 'object') {
389
+ throw new RefError('ref_pick_failed', `--pick 路径 ${path} 无法从非对象中取字段 ${token}`);
390
+ }
391
+ const holder = cursor;
392
+ if (!(token in holder)) {
393
+ throw new RefError('ref_pick_failed', `--pick 路径 ${path} 中不存在字段 ${token}`);
394
+ }
395
+ cursor = holder[token];
396
+ }
397
+ return cursor;
398
+ }
399
+ function tokenizePath(path) {
400
+ let cursor = path.trim().replace(/^\$/, '').replace(/^data\./, '');
401
+ const tokens = [];
402
+ const pattern = /([^.[\]]+)|\[(\d+)\]/g;
403
+ let match;
404
+ while ((match = pattern.exec(cursor)) !== null) {
405
+ if (match[1] !== undefined)
406
+ tokens.push(match[1]);
407
+ else
408
+ tokens.push(Number(match[2]));
409
+ }
410
+ if (!tokens.length) {
411
+ throw new RefError('ref_pick_failed', `无法解析 --pick 路径:${path}`);
412
+ }
413
+ return tokens;
414
+ }
415
+ /** 把 `--input-ref` 解析成可直接作为 operation 入参的对象。 */
416
+ export function resolveRefInput(ctx, options) {
417
+ const { envelope } = readRef(ctx, options.refId);
418
+ const data = envelope && typeof envelope === 'object' && !Array.isArray(envelope) && 'data' in envelope
419
+ ? envelope.data
420
+ : envelope;
421
+ const target = options.pick ? pickValue(data, options.pick) : data;
422
+ if (target && typeof target === 'object' && !Array.isArray(target)) {
423
+ if (options.pickAs) {
424
+ throw new RefError('ref_pick_as_object', '--pick-as 只用于把单个值映射到输入字段;当前选择结果已经是对象');
425
+ }
426
+ return target;
427
+ }
428
+ if (!options.pickAs || !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(options.pickAs)) {
429
+ throw new RefError('ref_pick_scalar_requires_key', '选择结果是单个值;请加 --pick-as <输入字段名>,例如 --pick items[0].fid --pick-as fid');
430
+ }
431
+ return { [options.pickAs]: target ?? null };
432
+ }
@@ -0,0 +1,26 @@
1
+ import { homedir } from 'node:os';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ export function stateRoot(ctx) {
4
+ const home = process.env.WEAVER_WORK_CLI_HOME
5
+ || process.env.WORK_CLI_HOME
6
+ || resolve(homedir(), '.weaver-work-cli');
7
+ // --config may point into a repository or shared directory. Keep transient
8
+ // business results in the CLI home instead of beside an arbitrary config.
9
+ if (ctx?.getGlobalOptions?.().config)
10
+ return resolve(home);
11
+ // The normal config path is rooted in the CLI home; retaining this fallback
12
+ // also supports isolated/test contexts that supply their own config store.
13
+ const configured = ctx?.config?.path?.();
14
+ if (configured)
15
+ return dirname(resolve(configured));
16
+ return resolve(home);
17
+ }
18
+ export function stateDir(ctx) {
19
+ return join(stateRoot(ctx), 'state');
20
+ }
21
+ export function refsDir(ctx) {
22
+ return join(stateDir(ctx), 'refs');
23
+ }
24
+ export function refsIndexPath(ctx) {
25
+ return join(refsDir(ctx), 'index.jsonl');
26
+ }
@@ -2,7 +2,7 @@ import { execFile } from 'node:child_process';
2
2
  import { createServer } from 'node:http';
3
3
  import { CliError, ExitCode } from '../../../core/errors.js';
4
4
  import { clearInvoiceTokenForSession } from '../../../shortcuts/invoice/host.js';
5
- import { buildE10CookieHeader, E10AuthSession, clearProfileAuthFile, extractRawSetCookie, findAuthFile, getActiveProfile, getProfileAuthPath, getProfileDir, listProfiles, profileNameFromUrl, readProfileConfig, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
5
+ import { buildE10CookieHeader, E10AuthSession, clearProfileAuthFile, extractRawSetCookie, findAuthFile, getActiveProfile, getProfileAuthPath, getProfileDir, listProfiles, profileNameFromUrl, readProfileConfig, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, switchActiveProfile, } from './session.js';
6
6
  import { getE10AuthRoot } from './paths.js';
7
7
  import { XIAOE_AGENT_TYPE, hasXiaoEEnv, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
8
8
  export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, } from './xiaoe.js';
@@ -718,7 +718,7 @@ export function registerE10AuthCommands(parent, action) {
718
718
  .action(action((ctx, name) => {
719
719
  if (typeof name !== 'string')
720
720
  throw new CliError('profile name must be a string', ExitCode.UsageError);
721
- setActiveProfile(name);
721
+ switchActiveProfile(name);
722
722
  const result = {
723
723
  active: name,
724
724
  dir: getProfileDir(name),
@@ -2,6 +2,7 @@ import { constants, createDecipheriv, createHash, publicEncrypt } from 'node:cry
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  import { CliError, ExitCode } from '../../../core/errors.js';
5
+ import { isConnectorMode } from '../../../core/connector.js';
5
6
  import { writeWithRetry } from '../../../core/write-with-retry.js';
6
7
  import { encryptAuthData, parseAuthFile } from './crypto.js';
7
8
  import { getE10ActiveProfilePath, getE10ProfilesRoot } from './paths.js';
@@ -58,7 +59,20 @@ export function getActiveProfile() {
58
59
  }
59
60
  return 'default';
60
61
  }
62
+ /**
63
+ * 写活跃 profile 指针(并确保该 profile 目录存在)。
64
+ *
65
+ * 连接器模式(全局参数 `--is-connector`)下**直接跳过**:连接器授权只新增自己的
66
+ * `profiles/<CONNECTOR_PROFILE>/` 凭据,绝不改动用户既有的活跃 profile 选择。
67
+ * 需要无条件切换(`auth profile use` 这类显式操作)请用 `switchActiveProfile`。
68
+ */
61
69
  export function setActiveProfile(name) {
70
+ if (isConnectorMode())
71
+ return;
72
+ switchActiveProfile(name);
73
+ }
74
+ /** 无条件写活跃 profile 指针,用于 `auth profile use` 等显式切换场景。 */
75
+ export function switchActiveProfile(name) {
62
76
  mkdirSync(getE10ProfilesRoot(), { recursive: true });
63
77
  mkdirSync(resolve(getE10ProfilesRoot(), name), { recursive: true });
64
78
  writeWithRetry(getE10ActiveProfilePath(), name, 'utf-8');