vps-work 2.4.1 → 2.5.1

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 (125) hide show
  1. package/.well-known/skills/index.json +11 -0
  2. package/README.md +10 -0
  3. package/dist/packages/vps-work/src/bin/vps-work.js +271 -0
  4. package/dist/packages/vps-work/src/commands/agent/index.js +264 -0
  5. package/dist/packages/vps-work/src/commands/approval/index.js +7 -0
  6. package/dist/packages/vps-work/src/commands/approval/shortcuts.js +259 -0
  7. package/dist/packages/vps-work/src/commands/attendance/applications.js +21 -0
  8. package/dist/packages/vps-work/src/commands/attendance/index.js +9 -0
  9. package/dist/packages/vps-work/src/commands/attendance/scheduling.js +183 -0
  10. package/dist/packages/vps-work/src/commands/attendance/shortcuts.js +131 -0
  11. package/dist/packages/vps-work/src/commands/auth/check.js +34 -0
  12. package/dist/packages/vps-work/src/commands/auth/index.js +21 -0
  13. package/dist/packages/vps-work/src/commands/auth/list.js +50 -0
  14. package/dist/packages/vps-work/src/commands/auth/scopes.js +17 -0
  15. package/dist/packages/vps-work/src/commands/auth/status.js +127 -0
  16. package/dist/packages/vps-work/src/commands/auth/tenants.js +35 -0
  17. package/dist/packages/vps-work/src/commands/auth/use.js +40 -0
  18. package/dist/packages/vps-work/src/commands/calendar/index.js +7 -0
  19. package/dist/packages/vps-work/src/commands/calendar/shortcuts.js +598 -0
  20. package/dist/packages/vps-work/src/commands/catalog.js +29 -0
  21. package/dist/packages/vps-work/src/commands/cli-install.js +70 -0
  22. package/dist/packages/vps-work/src/commands/collab-invoke.js +19 -0
  23. package/dist/packages/vps-work/src/commands/corporate/index.js +204 -0
  24. package/dist/packages/vps-work/src/commands/creative/index.js +369 -0
  25. package/dist/packages/vps-work/src/commands/documents/index.js +991 -0
  26. package/dist/packages/vps-work/src/commands/drive/index.js +435 -0
  27. package/dist/packages/vps-work/src/commands/exec.js +46 -0
  28. package/dist/packages/vps-work/src/commands/hr/business.js +22 -0
  29. package/dist/packages/vps-work/src/commands/hr/data-view-shortcuts.js +519 -0
  30. package/dist/packages/vps-work/src/commands/hr/index.js +15 -0
  31. package/dist/packages/vps-work/src/commands/hr/job-responsibilities.js +266 -0
  32. package/dist/packages/vps-work/src/commands/hr/payslip.js +45 -0
  33. package/dist/packages/vps-work/src/commands/hr/shortcuts.js +146 -0
  34. package/dist/packages/vps-work/src/commands/im/im-message-payload.js +150 -0
  35. package/dist/packages/vps-work/src/commands/im/index.js +7 -0
  36. package/dist/packages/vps-work/src/commands/im/shortcuts.js +856 -0
  37. package/dist/packages/vps-work/src/commands/knowledge/index.js +36 -0
  38. package/dist/packages/vps-work/src/commands/login.js +52 -0
  39. package/dist/packages/vps-work/src/commands/logout.js +12 -0
  40. package/dist/packages/vps-work/src/commands/mail/email.js +132 -0
  41. package/dist/packages/vps-work/src/commands/mail/index.js +9 -0
  42. package/dist/packages/vps-work/src/commands/mail/shortcuts.js +43 -0
  43. package/dist/packages/vps-work/src/commands/meeting/index.js +7 -0
  44. package/dist/packages/vps-work/src/commands/meeting/shortcuts.js +120 -0
  45. package/dist/packages/vps-work/src/commands/odoo-invoke.js +21 -0
  46. package/dist/packages/vps-work/src/commands/odoo-leaf-registry.js +13 -0
  47. package/dist/packages/vps-work/src/commands/office/index.js +59 -0
  48. package/dist/packages/vps-work/src/commands/okr/index.js +7 -0
  49. package/dist/packages/vps-work/src/commands/okr/shortcuts.js +75 -0
  50. package/dist/packages/vps-work/src/commands/org-structure.js +347 -0
  51. package/dist/packages/vps-work/src/commands/popup/index.js +147 -0
  52. package/dist/packages/vps-work/src/commands/product/abnormal-statistics.js +250 -0
  53. package/dist/packages/vps-work/src/commands/product/data-view-shortcuts.js +253 -0
  54. package/dist/packages/vps-work/src/commands/product/index.js +11 -0
  55. package/dist/packages/vps-work/src/commands/purchase/index.js +45 -0
  56. package/dist/packages/vps-work/src/commands/reauth.js +127 -0
  57. package/dist/packages/vps-work/src/commands/report/index.js +7 -0
  58. package/dist/packages/vps-work/src/commands/report/missing-daily-reports.js +9 -0
  59. package/dist/packages/vps-work/src/commands/report/shortcuts.js +251 -0
  60. package/dist/packages/vps-work/src/commands/sales/index.js +11 -0
  61. package/dist/packages/vps-work/src/commands/sales/period-params.js +75 -0
  62. package/dist/packages/vps-work/src/commands/sales/shortcuts-personnel.js +124 -0
  63. package/dist/packages/vps-work/src/commands/sales/shortcuts.js +800 -0
  64. package/dist/packages/vps-work/src/commands/shortcut-common.js +88 -0
  65. package/dist/packages/vps-work/src/commands/shortcut-params.js +22 -0
  66. package/dist/packages/vps-work/src/commands/skill/index.js +136 -0
  67. package/dist/packages/vps-work/src/commands/skill/personal.js +167 -0
  68. package/dist/packages/vps-work/src/commands/store/index.js +82 -0
  69. package/dist/packages/vps-work/src/commands/task/index.js +7 -0
  70. package/dist/packages/vps-work/src/commands/task/task-center.js +595 -0
  71. package/dist/packages/vps-work/src/commands/tools/index.js +127 -0
  72. package/dist/packages/vps-work/src/commands/vemory/index.js +7 -0
  73. package/dist/packages/vps-work/src/commands/vemory/shortcuts.js +53 -0
  74. package/dist/packages/vps-work/src/commands/vertu-aisales.js +30 -0
  75. package/dist/packages/vps-work/src/commands/vertu-erp.js +30 -0
  76. package/dist/packages/vps-work/src/core/aisales-mcp-client.js +35 -0
  77. package/dist/packages/vps-work/src/core/auth.js +240 -0
  78. package/dist/packages/vps-work/src/core/cli-bin.js +11 -0
  79. package/dist/packages/vps-work/src/core/cli-root-help.js +190 -0
  80. package/dist/packages/vps-work/src/core/client.js +158 -0
  81. package/dist/packages/vps-work/src/core/config.js +183 -0
  82. package/dist/packages/vps-work/src/core/corporate-client.js +52 -0
  83. package/dist/packages/vps-work/src/core/creative-attachment-download.js +48 -0
  84. package/dist/packages/vps-work/src/core/documents-client.js +57 -0
  85. package/dist/packages/vps-work/src/core/endpoints.js +46 -0
  86. package/dist/packages/vps-work/src/core/erp-mcp-client.js +11 -0
  87. package/dist/packages/vps-work/src/core/errors.js +107 -0
  88. package/dist/packages/vps-work/src/core/execute-catalog-client.js +42 -0
  89. package/dist/packages/vps-work/src/core/hermes-client.js +70 -0
  90. package/dist/packages/vps-work/src/core/im-attachment-download.js +109 -0
  91. package/dist/packages/vps-work/src/core/im-client.js +64 -0
  92. package/dist/packages/vps-work/src/core/keychain.js +58 -0
  93. package/dist/packages/vps-work/src/core/mail-client.js +82 -0
  94. package/dist/packages/vps-work/src/core/mcp-client.js +129 -0
  95. package/dist/packages/vps-work/src/core/office-client.js +25 -0
  96. package/dist/packages/vps-work/src/core/office-files.js +82 -0
  97. package/dist/packages/vps-work/src/core/paths.js +70 -0
  98. package/dist/packages/vps-work/src/core/public-result.js +43 -0
  99. package/dist/packages/vps-work/src/core/sales-invoice-attachments-client.js +75 -0
  100. package/dist/packages/vps-work/src/core/sales-query-client.js +452 -0
  101. package/dist/packages/vps-work/src/core/skill-catalog.js +158 -0
  102. package/dist/packages/vps-work/src/core/skill-key.js +19 -0
  103. package/dist/packages/vps-work/src/core/v-drive-store.js +392 -0
  104. package/dist/packages/vps-work/src/core/vertu-agent-env.js +32 -0
  105. package/dist/packages/vps-work/src/core/vps-auth-scopes.js +73 -0
  106. package/dist/packages/vps-work/src/core/vps-service-auth.js +382 -0
  107. package/dist/packages/vps-work/src/core/vps-service-config.js +83 -0
  108. package/dist/packages/vps-work/src/core/vps-service-http.js +187 -0
  109. package/dist/packages/vps-work/src/utils/at-file.js +34 -0
  110. package/dist/packages/vps-work/src/utils/cli-option-helpers.js +52 -0
  111. package/dist/packages/vps-work/src/utils/frontmatter.js +26 -0
  112. package/dist/packages/vps-work/src/utils/normalize-argv.js +51 -0
  113. package/dist/packages/vps-work/src/utils/parse-json-arg.js +85 -0
  114. package/dist/packages/vps-work/src/utils/prompt.js +46 -0
  115. package/dist/packages/vps-work/src/utils/read-text.js +44 -0
  116. package/dist/packages/vps-work/src/utils/schema-to-options.js +307 -0
  117. package/dist/packages/vps-work/src/utils/unwrap-envelope.js +31 -0
  118. package/dist/shared/im-org-tree/index.js +200 -0
  119. package/dist/vps-work.cjs +246 -44
  120. package/package.json +1 -1
  121. package/scripts/package-desktop-runtime.mjs +43 -0
  122. package/skills/README.md +1 -0
  123. package/skills/vertu-aisales/SKILL.md +18 -0
  124. package/skills/vertu-erp/SKILL.md +17 -0
  125. package/skills/vps-dealer-ops/SKILL.md +92 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * IM 通讯录组织架构:部门列表口径与树构建(vps-service / im-desktop 共用)。
3
+ * 数据源以 `/v1/im/org/departments` 返回的 VPS 原生企业组织为准。
4
+ */
5
+ /** 身份同步占位部门在通讯录中的展示名,与 vps_org_units.unit_key=unassigned 对应。 */
6
+ export const UNASSIGNED_ORG_DEPARTMENT_NAME = '未分配部门';
7
+ /** 通讯录不展示身份同步占位部门。 */
8
+ export function isUnassignedDirectoryDepartment(department) {
9
+ return department.name.trim() === UNASSIGNED_ORG_DEPARTMENT_NAME
10
+ || department.complete_name?.trim() === UNASSIGNED_ORG_DEPARTMENT_NAME;
11
+ }
12
+ export function excludeUnassignedDirectoryDepartments(departments) {
13
+ return departments.filter((department) => !isUnassignedDirectoryDepartment(department));
14
+ }
15
+ export function imOrgDepartmentDisplayName(department) {
16
+ const name = department.name.trim();
17
+ if (name) {
18
+ return name;
19
+ }
20
+ const complete = department.complete_name?.trim();
21
+ if (complete) {
22
+ return complete;
23
+ }
24
+ return `部门 ${department.department_id}`;
25
+ }
26
+ export function sortImOrgDepartmentsByName(a, b) {
27
+ return imOrgDepartmentDisplayName(a).localeCompare(imOrgDepartmentDisplayName(b), 'zh-CN');
28
+ }
29
+ /** 通讯录口径:去掉本部门及全部子部门都没有在职人员的节点。 */
30
+ export function filterImOrgDepartmentsWithSubtreeStaff(departments) {
31
+ const byId = new Map();
32
+ const childrenByParent = new Map();
33
+ for (const dept of departments) {
34
+ byId.set(dept.department_id, dept);
35
+ const parentId = dept.parent_id;
36
+ if (parentId !== null) {
37
+ const siblings = childrenByParent.get(parentId) ?? [];
38
+ siblings.push(dept.department_id);
39
+ childrenByParent.set(parentId, siblings);
40
+ }
41
+ }
42
+ const subtreeTotals = new Map();
43
+ const computeSubtreeTotal = (departmentId) => {
44
+ const cached = subtreeTotals.get(departmentId);
45
+ if (cached !== undefined) {
46
+ return cached;
47
+ }
48
+ const dept = byId.get(departmentId);
49
+ if (!dept) {
50
+ return 0;
51
+ }
52
+ let total = dept.employee_count;
53
+ for (const childId of childrenByParent.get(departmentId) ?? []) {
54
+ total += computeSubtreeTotal(childId);
55
+ }
56
+ subtreeTotals.set(departmentId, total);
57
+ return total;
58
+ };
59
+ for (const dept of departments) {
60
+ computeSubtreeTotal(dept.department_id);
61
+ }
62
+ return departments.filter((dept) => (subtreeTotals.get(dept.department_id) ?? 0) > 0);
63
+ }
64
+ /**
65
+ * 构建森林:父节点不在当前列表中的部门提升为根(与 IM 通讯录一致)。
66
+ */
67
+ export function buildImOrgDepartmentForest(departments) {
68
+ const nodes = new Map();
69
+ for (const department of departments) {
70
+ nodes.set(department.department_id, {
71
+ department,
72
+ children: [],
73
+ subtreeEmployeeCount: 0,
74
+ });
75
+ }
76
+ const roots = [];
77
+ for (const node of nodes.values()) {
78
+ const parentId = node.department.parent_id;
79
+ const parent = parentId !== null ? nodes.get(parentId) : undefined;
80
+ if (parent) {
81
+ parent.children.push(node);
82
+ }
83
+ else {
84
+ roots.push(node);
85
+ }
86
+ }
87
+ const fillSubtreeCount = (node) => {
88
+ node.subtreeEmployeeCount =
89
+ node.department.employee_count +
90
+ node.children.reduce((sum, child) => sum + fillSubtreeCount(child), 0);
91
+ return node.subtreeEmployeeCount;
92
+ };
93
+ roots.sort((a, b) => sortImOrgDepartmentsByName(a.department, b.department));
94
+ const sortRecursive = (items) => {
95
+ items.sort((a, b) => sortImOrgDepartmentsByName(a.department, b.department));
96
+ for (const item of items) {
97
+ sortRecursive(item.children);
98
+ }
99
+ };
100
+ sortRecursive(roots);
101
+ roots.forEach(fillSubtreeCount);
102
+ return roots;
103
+ }
104
+ /** 企业根名称与公司名重复时,这一级只是容器:有下级则提升子部门,没有则不再单独占一层。 */
105
+ export function flattenDuplicateCompanyRoot(forest, companyName) {
106
+ const company = companyName.trim();
107
+ if (!company)
108
+ return { forest, collapsedRoots: [] };
109
+ const collapsedRoots = [];
110
+ const next = [];
111
+ for (const node of forest) {
112
+ const name = node.department.name.trim();
113
+ const path = node.department.complete_name?.trim() || name;
114
+ const sameCompany = name === company && (path === company || path === name);
115
+ if (!sameCompany) {
116
+ next.push(node);
117
+ continue;
118
+ }
119
+ collapsedRoots.push(node);
120
+ next.push(...node.children);
121
+ }
122
+ return { forest: next, collapsedRoots };
123
+ }
124
+ function matchesImOrgDepartmentKeyword(department, keyword) {
125
+ const q = keyword.trim().toLowerCase();
126
+ if (!q) {
127
+ return true;
128
+ }
129
+ const haystack = [
130
+ department.department_id,
131
+ department.parent_id,
132
+ department.name,
133
+ department.complete_name,
134
+ department.employee_count,
135
+ ]
136
+ .join(' ')
137
+ .toLowerCase();
138
+ return haystack.includes(q);
139
+ }
140
+ export function filterImOrgDepartmentForestByKeyword(nodes, keyword) {
141
+ if (!keyword.trim()) {
142
+ return nodes;
143
+ }
144
+ const filtered = [];
145
+ for (const node of nodes) {
146
+ const childMatches = filterImOrgDepartmentForestByKeyword(node.children, keyword);
147
+ if (matchesImOrgDepartmentKeyword(node.department, keyword) || childMatches.length > 0) {
148
+ filtered.push({
149
+ department: node.department,
150
+ subtreeEmployeeCount: node.subtreeEmployeeCount,
151
+ children: childMatches.length > 0 ? childMatches : node.children,
152
+ });
153
+ }
154
+ }
155
+ return filtered;
156
+ }
157
+ export function collectImOrgDepartmentForestIds(nodes) {
158
+ const ids = [];
159
+ const walk = (items) => {
160
+ for (const node of items) {
161
+ ids.push(node.department.department_id);
162
+ walk(node.children);
163
+ }
164
+ };
165
+ walk(nodes);
166
+ return ids;
167
+ }
168
+ export function buildImOrgDepartmentChildrenIndex(departments) {
169
+ const childrenByParent = new Map();
170
+ const knownIds = new Set(departments.map((dept) => dept.department_id));
171
+ for (const department of departments) {
172
+ const parentId = department.parent_id;
173
+ if (parentId === null || !knownIds.has(parentId)) {
174
+ continue;
175
+ }
176
+ const siblings = childrenByParent.get(parentId) ?? [];
177
+ siblings.push(department.department_id);
178
+ childrenByParent.set(parentId, siblings);
179
+ }
180
+ return childrenByParent;
181
+ }
182
+ export function resolveImOrgDepartmentSubtreeScope(childrenByParent, selectedDepartmentIds) {
183
+ if (!selectedDepartmentIds.length) {
184
+ return new Set();
185
+ }
186
+ const scope = new Set();
187
+ const walk = (departmentId) => {
188
+ if (scope.has(departmentId)) {
189
+ return;
190
+ }
191
+ scope.add(departmentId);
192
+ for (const childId of childrenByParent.get(departmentId) ?? []) {
193
+ walk(childId);
194
+ }
195
+ };
196
+ for (const departmentId of selectedDepartmentIds) {
197
+ walk(departmentId);
198
+ }
199
+ return scope;
200
+ }
package/dist/vps-work.cjs CHANGED
@@ -18246,17 +18246,17 @@ var require_dns = __commonJS({
18246
18246
  const timestamp = Date.now();
18247
18247
  const records2 = { records: { 4: null, 6: null } };
18248
18248
  let minTTL = this.#maxTTL;
18249
- for (const record3 of addresses2) {
18250
- record3.timestamp = timestamp;
18251
- if (typeof record3.ttl === "number") {
18252
- record3.ttl = Math.min(record3.ttl, this.#maxTTL);
18253
- minTTL = Math.min(minTTL, record3.ttl);
18249
+ for (const record4 of addresses2) {
18250
+ record4.timestamp = timestamp;
18251
+ if (typeof record4.ttl === "number") {
18252
+ record4.ttl = Math.min(record4.ttl, this.#maxTTL);
18253
+ minTTL = Math.min(minTTL, record4.ttl);
18254
18254
  } else {
18255
- record3.ttl = this.#maxTTL;
18255
+ record4.ttl = this.#maxTTL;
18256
18256
  }
18257
- const familyRecords = records2.records[record3.family] ?? { ips: [] };
18258
- familyRecords.ips.push(record3);
18259
- records2.records[record3.family] = familyRecords;
18257
+ const familyRecords = records2.records[record4.family] ?? { ips: [] };
18258
+ familyRecords.ips.push(record4);
18259
+ records2.records[record4.family] = familyRecords;
18260
18260
  }
18261
18261
  this.storage.set(origin.hostname, records2, { ttl: minTTL });
18262
18262
  }
@@ -54875,14 +54875,14 @@ function collaborationTurnFromHistory(out, requestId) {
54875
54875
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) continue;
54876
54876
  const envelope = metadata[HARNESS_COLLABORATION_KIND];
54877
54877
  if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) continue;
54878
- const record3 = envelope;
54879
- const turn = Number(record3.turn);
54880
- if (record3.request_id !== requestId || record3.type !== "request" && record3.type !== "response" || !Number.isInteger(turn) || turn <= 0) continue;
54878
+ const record4 = envelope;
54879
+ const turn = Number(record4.turn);
54880
+ if (record4.request_id !== requestId || record4.type !== "request" && record4.type !== "response" || !Number.isInteger(turn) || turn <= 0) continue;
54881
54881
  return {
54882
54882
  body: typeof message.body === "string" ? message.body : "",
54883
54883
  messageId: typeof message.id === "string" ? message.id : "",
54884
54884
  turn,
54885
- type: record3.type
54885
+ type: record4.type
54886
54886
  };
54887
54887
  }
54888
54888
  return null;
@@ -55373,6 +55373,11 @@ async function parseJson2(res) {
55373
55373
  }
55374
55374
  async function hermesRequest(method, path3, opts = {}) {
55375
55375
  const base = resolveHermesAgentAdminUrl(opts.baseUrl);
55376
+ const target = new URL(base);
55377
+ const trusted = new URL(resolveHermesAgentAdminUrl());
55378
+ if (target.origin !== trusted.origin || target.username || target.password || target.search || target.hash || target.pathname !== "/") {
55379
+ throw new VertuError("Hermes \u8BF7\u6C42\u76EE\u6807\u4E0D\u53D7\u4FE1\u4EFB\uFF1B\u8BF7\u7531\u5BBF\u4E3B\u914D\u7F6E\u670D\u52A1\u5730\u5740\uFF0C\u4E0D\u80FD\u901A\u8FC7\u4E34\u65F6\u53C2\u6570\u8F6C\u53D1\u51ED\u636E\u3002");
55380
+ }
55376
55381
  const res = await (0, import_undici5.request)(`${base}${path3}`, {
55377
55382
  method,
55378
55383
  headers: headers2(),
@@ -58375,8 +58380,8 @@ function printResult(opts, result) {
58375
58380
  console.log(typeof result === "string" ? result : JSON.stringify(result));
58376
58381
  }
58377
58382
  function outputRows(value) {
58378
- const record3 = asRecord2(value);
58379
- const rows2 = record3 ? record3.rows : void 0;
58383
+ const record4 = asRecord2(value);
58384
+ const rows2 = record4 ? record4.rows : void 0;
58380
58385
  return Array.isArray(rows2) ? rows2.filter((row) => asRecord2(row) !== null) : [];
58381
58386
  }
58382
58387
  function isValidateOnly2(opts) {
@@ -58407,31 +58412,31 @@ function findIdentityRecord(payload) {
58407
58412
  continue;
58408
58413
  }
58409
58414
  seen.add(current);
58410
- const record3 = asRecord2(current);
58411
- if (!record3) {
58415
+ const record4 = asRecord2(current);
58416
+ if (!record4) {
58412
58417
  continue;
58413
58418
  }
58414
- if ("employee_id" in record3 || "employeeId" in record3 || "manager_id" in record3 || "managerEmployeeId" in record3 || "user_id" in record3 || "employee" in record3 || "manager" in record3) {
58415
- return record3;
58419
+ if ("employee_id" in record4 || "employeeId" in record4 || "manager_id" in record4 || "managerEmployeeId" in record4 || "user_id" in record4 || "employee" in record4 || "manager" in record4) {
58420
+ return record4;
58416
58421
  }
58417
58422
  for (const key of ["result", "data", "payload", "context", "user", "userinfo"]) {
58418
- if (key in record3) {
58419
- queue.push(record3[key]);
58423
+ if (key in record4) {
58424
+ queue.push(record4[key]);
58420
58425
  }
58421
58426
  }
58422
58427
  }
58423
58428
  return {};
58424
58429
  }
58425
58430
  function extractHrIdentity(payload) {
58426
- const record3 = findIdentityRecord(payload);
58427
- const employee = asRecord2(record3.employee);
58428
- const manager = asRecord2(record3.manager);
58431
+ const record4 = findIdentityRecord(payload);
58432
+ const employee = asRecord2(record4.employee);
58433
+ const manager = asRecord2(record4.manager);
58429
58434
  return {
58430
- employeeId: numberValue(record3.employee_id) ?? numberValue(record3.employeeId) ?? numberValue(employee?.id),
58431
- employeeName: stringValue(record3.employee_name) ?? stringValue(record3.employeeName) ?? stringValue(employee?.name) ?? stringValue(record3.name),
58432
- managerEmployeeId: numberValue(record3.manager_id) ?? numberValue(record3.manager_employee_id) ?? numberValue(record3.managerEmployeeId) ?? numberValue(manager?.id),
58433
- managerName: stringValue(record3.manager_name) ?? stringValue(record3.managerName) ?? stringValue(manager?.name),
58434
- managerUserId: numberValue(record3.manager_user_id) ?? numberValue(record3.managerUserId) ?? numberValue(manager?.user_id) ?? numberValue(manager?.userId)
58435
+ employeeId: numberValue(record4.employee_id) ?? numberValue(record4.employeeId) ?? numberValue(employee?.id),
58436
+ employeeName: stringValue(record4.employee_name) ?? stringValue(record4.employeeName) ?? stringValue(employee?.name) ?? stringValue(record4.name),
58437
+ managerEmployeeId: numberValue(record4.manager_id) ?? numberValue(record4.manager_employee_id) ?? numberValue(record4.managerEmployeeId) ?? numberValue(manager?.id),
58438
+ managerName: stringValue(record4.manager_name) ?? stringValue(record4.managerName) ?? stringValue(manager?.name),
58439
+ managerUserId: numberValue(record4.manager_user_id) ?? numberValue(record4.managerUserId) ?? numberValue(manager?.user_id) ?? numberValue(manager?.userId)
58435
58440
  };
58436
58441
  }
58437
58442
  async function fetchCurrentHrIdentity(opts, cmd) {
@@ -58531,8 +58536,8 @@ function dataViewRows(value) {
58531
58536
  return outputRows(value);
58532
58537
  }
58533
58538
  function dataViewTotalRows(value) {
58534
- const record3 = asRecord2(value);
58535
- const pagination = asRecord2(record3?.pagination);
58539
+ const record4 = asRecord2(value);
58540
+ const pagination = asRecord2(record4?.pagination);
58536
58541
  return numberValue(pagination?.total_rows) ?? 0;
58537
58542
  }
58538
58543
  function addHeadcountOptions(cmd) {
@@ -58899,8 +58904,8 @@ function asRecord3(value) {
58899
58904
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
58900
58905
  }
58901
58906
  function rows(value) {
58902
- const record3 = asRecord3(value);
58903
- const raw = record3 ? record3.rows : void 0;
58907
+ const record4 = asRecord3(value);
58908
+ const raw = record4 ? record4.rows : void 0;
58904
58909
  return Array.isArray(raw) ? raw.filter((row) => asRecord3(row) !== null) : [];
58905
58910
  }
58906
58911
  function num(value) {
@@ -59310,9 +59315,9 @@ function asRecord4(value) {
59310
59315
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
59311
59316
  }
59312
59317
  function remoteMessage(value, fallback) {
59313
- const record3 = asRecord4(value);
59318
+ const record4 = asRecord4(value);
59314
59319
  for (const key of ["message", "error", "statusText"]) {
59315
- const candidate = record3?.[key];
59320
+ const candidate = record4?.[key];
59316
59321
  if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
59317
59322
  }
59318
59323
  return fallback;
@@ -60968,8 +60973,8 @@ function printMutationPreview(opts, method, endpoint, body) {
60968
60973
  });
60969
60974
  }
60970
60975
  function decorateEvents(payload, scope, range, calendarId) {
60971
- const record3 = asRecord5(payload) ?? {};
60972
- const calendars = recordArray(record3.calendars);
60976
+ const record4 = asRecord5(payload) ?? {};
60977
+ const calendars = recordArray(record4.calendars);
60973
60978
  const calendarNames = new Map(
60974
60979
  calendars.flatMap((calendar) => {
60975
60980
  const id = typeof calendar.id === "string" ? calendar.id : "";
@@ -60979,7 +60984,7 @@ function decorateEvents(payload, scope, range, calendarId) {
60979
60984
  if (calendarId && !calendarNames.has(calendarId)) {
60980
60985
  throw new VertuError(`\u65E5\u5386\u4E0D\u5B58\u5728\u6216\u65E0\u6743\u67E5\u770B\uFF1A${calendarId}`);
60981
60986
  }
60982
- const rows2 = recordArray(record3.events).filter((event) => !calendarId || event.calendar_id === calendarId).map((event) => ({
60987
+ const rows2 = recordArray(record4.events).filter((event) => !calendarId || event.calendar_id === calendarId).map((event) => ({
60983
60988
  ...event,
60984
60989
  calendar_name: typeof event.calendar_id === "string" ? calendarNames.get(event.calendar_id) ?? null : null
60985
60990
  }));
@@ -62058,12 +62063,12 @@ function buildPath2(path3, query2) {
62058
62063
  }
62059
62064
  function errorMessage3(payload, statusCode) {
62060
62065
  if (typeof payload === "object" && payload !== null) {
62061
- const record3 = payload;
62062
- if (typeof record3["error"] === "string") {
62063
- return record3["error"];
62066
+ const record4 = payload;
62067
+ if (typeof record4["error"] === "string") {
62068
+ return record4["error"];
62064
62069
  }
62065
- if (typeof record3["message"] === "string") {
62066
- return record3["message"];
62070
+ if (typeof record4["message"] === "string") {
62071
+ return record4["message"];
62067
62072
  }
62068
62073
  }
62069
62074
  return `HTTP ${statusCode}`;
@@ -64840,6 +64845,199 @@ function buildExecCommand() {
64840
64845
  });
64841
64846
  }
64842
64847
 
64848
+ // src/core/mcp-client.ts
64849
+ var PROTOCOL = "2025-03-26";
64850
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
64851
+ var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
64852
+ function rpcResult(value, id) {
64853
+ const rpc = record3(value);
64854
+ if (rpc.id !== id) return void 0;
64855
+ if (rpc.error) throw new VertuError("MCP \u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u53C2\u6570\u548C\u670D\u52A1\u7AEF\u6388\u6743\u3002");
64856
+ if (!("result" in rpc)) throw new VertuError("MCP \u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
64857
+ return record3(rpc.result);
64858
+ }
64859
+ async function readResult(response, id) {
64860
+ const reader = response.body?.getReader();
64861
+ if (!reader) throw new VertuError("MCP \u8FD4\u56DE\u4E86\u7A7A\u54CD\u5E94\u3002");
64862
+ const sse = response.headers.get("content-type")?.includes("text/event-stream");
64863
+ const decoder = new TextDecoder();
64864
+ let buffer = "";
64865
+ let size = 0;
64866
+ try {
64867
+ while (true) {
64868
+ const chunk = await reader.read();
64869
+ if (chunk.done) {
64870
+ buffer += decoder.decode();
64871
+ break;
64872
+ }
64873
+ size += chunk.value.byteLength;
64874
+ if (size > MAX_RESPONSE_BYTES) throw new VertuError("MCP \u7ED3\u679C\u8FC7\u5927\uFF0C\u8BF7\u7F29\u5C0F\u67E5\u8BE2\u8303\u56F4\u3002");
64875
+ buffer += decoder.decode(chunk.value, { stream: true });
64876
+ if (sse) {
64877
+ let boundary;
64878
+ while (boundary = /\r?\n\r?\n/.exec(buffer)) {
64879
+ const frame = buffer.slice(0, boundary.index);
64880
+ buffer = buffer.slice(boundary.index + boundary[0].length);
64881
+ const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
64882
+ if (!data) continue;
64883
+ const result = rpcResult(JSON.parse(data), id);
64884
+ if (result) return result;
64885
+ }
64886
+ }
64887
+ }
64888
+ if (!sse) {
64889
+ const result = rpcResult(JSON.parse(buffer), id);
64890
+ if (result) return result;
64891
+ }
64892
+ throw new VertuError("MCP \u672A\u8FD4\u56DE\u5339\u914D\u7684\u8BF7\u6C42\u7ED3\u679C\u3002");
64893
+ } finally {
64894
+ await reader.cancel().catch(() => {
64895
+ });
64896
+ }
64897
+ }
64898
+ async function runMcp(endpoint, authorizationHeaders, allowedTools, operation) {
64899
+ let sessionId;
64900
+ let protocol = PROTOCOL;
64901
+ let nextId = 0;
64902
+ const headers3 = () => ({
64903
+ ...authorizationHeaders,
64904
+ "content-type": "application/json",
64905
+ accept: "application/json, text/event-stream",
64906
+ "mcp-protocol-version": protocol,
64907
+ ...sessionId ? { "mcp-session-id": sessionId } : {}
64908
+ });
64909
+ const send = async (method, params, notification = false) => {
64910
+ const id = ++nextId;
64911
+ const response = await fetch(endpoint, {
64912
+ method: "POST",
64913
+ headers: headers3(),
64914
+ redirect: "error",
64915
+ signal: AbortSignal.timeout(6e4),
64916
+ body: JSON.stringify({ jsonrpc: "2.0", ...notification ? {} : { id }, method, params })
64917
+ });
64918
+ if (!response.ok) {
64919
+ await response.body?.cancel();
64920
+ throw new VertuError(`MCP \u8BF7\u6C42\u88AB\u62D2\u7EDD\u6216\u670D\u52A1\u4E0D\u53EF\u7528\uFF08HTTP ${response.status}\uFF09\u3002`, { statusCode: response.status });
64921
+ }
64922
+ if (method === "initialize") sessionId = response.headers.get("mcp-session-id") || void 0;
64923
+ if (notification) {
64924
+ await response.body?.cancel();
64925
+ return {};
64926
+ }
64927
+ return readResult(response, id);
64928
+ };
64929
+ try {
64930
+ const initialized = await send("initialize", {
64931
+ protocolVersion: PROTOCOL,
64932
+ capabilities: {},
64933
+ clientInfo: { name: "vps-work", version: "1" }
64934
+ });
64935
+ if (initialized.protocolVersion !== PROTOCOL) throw new VertuError("MCP \u534F\u8BAE\u7248\u672C\u4E0D\u517C\u5BB9\u3002");
64936
+ protocol = initialized.protocolVersion;
64937
+ await send("notifications/initialized", void 0, true);
64938
+ const tools2 = [];
64939
+ const cursors = /* @__PURE__ */ new Set();
64940
+ let cursor;
64941
+ do {
64942
+ const page = await send("tools/list", cursor ? { cursor } : {});
64943
+ if (!Array.isArray(page.tools)) throw new VertuError("MCP \u5DE5\u5177\u76EE\u5F55\u65E0\u6548\u3002");
64944
+ tools2.push(...page.tools.map(record3).filter((tool) => allowedTools.has(String(tool.name))));
64945
+ cursor = typeof page.nextCursor === "string" && page.nextCursor ? page.nextCursor : void 0;
64946
+ if (cursor && (cursors.has(cursor) || cursors.size >= 100)) throw new VertuError("MCP \u5DE5\u5177\u76EE\u5F55\u5206\u9875\u5F02\u5E38\u3002");
64947
+ if (cursor) cursors.add(cursor);
64948
+ } while (cursor);
64949
+ if (!operation) return { tools: tools2 };
64950
+ if (!tools2.some((tool) => tool.name === operation.tool)) throw new VertuError("\u5F53\u524D MCP \u672A\u5F00\u653E\u8BE5\u5DE5\u5177\u3002");
64951
+ const result = await send("tools/call", { name: operation.tool, arguments: operation.arguments });
64952
+ if (result.isError === true) throw new VertuError("MCP \u5DE5\u5177\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u53C2\u6570\u3001SQL \u548C\u670D\u52A1\u7AEF\u6388\u6743\u3002");
64953
+ return result;
64954
+ } catch (error) {
64955
+ if (error instanceof VertuError) throw error;
64956
+ throw new VertuError("MCP \u901A\u4FE1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u548C\u670D\u52A1\u72B6\u6001\u3002");
64957
+ } finally {
64958
+ if (sessionId) {
64959
+ await fetch(endpoint, { method: "DELETE", headers: headers3(), redirect: "error", signal: AbortSignal.timeout(5e3) }).then((response) => response.body?.cancel()).catch(() => {
64960
+ });
64961
+ }
64962
+ }
64963
+ }
64964
+
64965
+ // src/core/aisales-mcp-client.ts
64966
+ var TENANT = "00000000-0000-4000-8000-000000000001";
64967
+ var TOOL_NAMES = /* @__PURE__ */ new Set(["business.resolve_subject", "business.catalog", "business.query", "business.compare", "business.get_evidence", "business.explain_metric", "sales.operation_summary", "sales.customer_cohort", "sales.customer_journey"]);
64968
+ async function listAiSalesServices() {
64969
+ const base = resolveVpsServiceUrl().replace(/\/+$/, "");
64970
+ const headers3 = vpsServiceAuthHeaders(base);
64971
+ if (!headers3.authorization) throw new VertuError("\u9700\u8981\u5F53\u524D\u8D26\u53F7\u7684 VPS \u767B\u5F55\u51ED\u636E\u3002");
64972
+ const response = await fetch(`${base}/v1/tenant/mcp/services`, {
64973
+ headers: headers3,
64974
+ redirect: "error",
64975
+ signal: AbortSignal.timeout(15e3)
64976
+ });
64977
+ if (!response.ok) {
64978
+ await response.body?.cancel();
64979
+ throw new VertuError(`\u65E0\u6CD5\u8BFB\u53D6\u4F01\u4E1A MCP \u6388\u6743\uFF08HTTP ${response.status}\uFF09\u3002`);
64980
+ }
64981
+ const catalog = await response.json();
64982
+ if (catalog.tenant_id !== TENANT) throw new VertuError("vertu-aisales \u4EC5\u5BF9\u83B7\u6388\u6743\u7684 VERTU \u4F01\u4E1A\u6210\u5458\u5F00\u653E\u3002");
64983
+ return (Array.isArray(catalog.items) ? catalog.items : []).filter((item) => item.capability === "vertu-aisales" && /^[0-9a-f-]{36}$/i.test(item.id)).map((item) => ({ id: item.id, name: item.name || "VERTU AI Sales" }));
64984
+ }
64985
+ async function runAiSalesMcp(operation, serviceId) {
64986
+ if (operation && !TOOL_NAMES.has(operation.tool)) throw new VertuError("\u672A\u77E5\u7684 VERTU AI Sales \u5DE5\u5177\uFF0C\u8BF7\u5148\u8FD0\u884C vps-work vertu-aisales tools\u3002");
64987
+ const services = (await listAiSalesServices()).filter((item) => !serviceId || item.id === serviceId);
64988
+ if (services.length !== 1) throw new VertuError(services.length ? "\u5B58\u5728\u591A\u4E2A AI Sales \u670D\u52A1\uFF0C\u8BF7\u8FD0\u884C vps-work vertu-aisales services\uFF0C\u518D\u7528 --service-id \u6307\u5B9A\u5B9E\u4F8B\u3002" : "\u5F53\u524D\u8D26\u53F7\u6CA1\u6709\u53EF\u7528\u7684 vertu-aisales \u670D\u52A1\uFF0C\u8BF7\u7531\u7BA1\u7406\u5458\u5728\u4F01\u4E1A MCP \u540E\u53F0\u5F00\u901A\u3002");
64989
+ const base = resolveVpsServiceUrl().replace(/\/+$/, "");
64990
+ return runMcp(`${base}/v1/tenant/mcp/services/${services[0].id}`, vpsServiceAuthHeaders(base), TOOL_NAMES, operation);
64991
+ }
64992
+
64993
+ // src/commands/vertu-aisales.ts
64994
+ function buildVertuAiSalesCommand() {
64995
+ const command = new Command("vertu-aisales").description("VERTU \u5BA2\u6237\u6C9F\u901A\u67E5\u8BE2\uFF08\u5F53\u524D\u4F01\u4E1A\u548C\u5458\u5DE5\u6570\u636E\u6388\u6743\uFF09");
64996
+ command.command("services").description("\u5217\u51FA\u5F53\u524D\u8D26\u53F7\u53EF\u7528\u7684 AI Sales \u670D\u52A1\u5B9E\u4F8B").action(async () => console.log(JSON.stringify({ items: await listAiSalesServices() }, null, 2)));
64997
+ command.command("tools").description("\u53D1\u73B0\u53EF\u8C03\u7528\u5DE5\u5177\u548C\u53C2\u6570 schema").option("--service-id <id>", "\u4F01\u4E1A MCP \u670D\u52A1\u5B9E\u4F8B ID").action(async (opts) => console.log(JSON.stringify(await runAiSalesMcp(void 0, opts.serviceId), null, 2)));
64998
+ command.command("call").description("\u8C03\u7528 AINativeSales \u9500\u552E\u53EA\u8BFB\u5DE5\u5177\uFF1B\u5148\u53D1\u73B0\u4E1A\u52A1\u5BF9\u8C61\u4E0E\u6307\u6807").requiredOption("--tool <name>", "tools \u8FD4\u56DE\u7684\u5DE5\u5177\u540D").option("--args <json>", "JSON \u5BF9\u8C61\uFF0C\u652F\u6301 @file / @-", "{}").option("--service-id <id>", "\u4F01\u4E1A MCP \u670D\u52A1\u5B9E\u4F8B ID").action(async (opts) => {
64999
+ const raw = await expandAtFileToken(opts.args, "--args");
65000
+ let args;
65001
+ try {
65002
+ args = JSON.parse(raw);
65003
+ } catch {
65004
+ throw new VertuError("--args \u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
65005
+ }
65006
+ if (!args || typeof args !== "object" || Array.isArray(args)) throw new VertuError("--args \u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
65007
+ console.log(JSON.stringify(await runAiSalesMcp({ tool: opts.tool, arguments: args }, opts.serviceId), null, 2));
65008
+ });
65009
+ return command;
65010
+ }
65011
+
65012
+ // src/core/erp-mcp-client.ts
65013
+ var tools = /* @__PURE__ */ new Set(["erp-query", "erp-list-tables", "erp-table-columns"]);
65014
+ async function runErpMcp(operation, baseUrl4) {
65015
+ if (operation && !tools.has(operation.tool)) throw new VertuError("\u4EC5\u652F\u6301 ERP \u53EA\u8BFB\u5DE5\u5177\uFF0C\u8BF7\u5148\u8FD0\u884C vps-work vertu-erp tools\u3002");
65016
+ const reply = await hermesRequest("POST", "/hermes-console/api/platform-tools/vertu-erp", { baseUrl: baseUrl4, body: operation ? { action: "call", tool: operation.tool, arguments: operation.arguments } : { action: "tools" } });
65017
+ if (!reply.result || typeof reply.result !== "object") throw new VertuError("ERP \u670D\u52A1\u54CD\u5E94\u65E0\u6548\u3002");
65018
+ return reply.result;
65019
+ }
65020
+
65021
+ // src/commands/vertu-erp.ts
65022
+ function buildVertuErpCommand() {
65023
+ const command = new Command("vertu-erp").description("\u4F7F\u7528\u5F53\u524D\u8D26\u53F7\u83B7\u6388\u6743\u7684 ERP MCP\uFF08\u53EA\u8BFB\uFF0C\u4E0D\u81EA\u52A8\u63D0\u6743\uFF09");
65024
+ command.command("tools").description("\u5217\u51FA ERP \u5DE5\u5177\u53CA\u5B9E\u65F6\u53C2\u6570 schema").option("--hermes-base-url <url>", "Hermes \u670D\u52A1\u6839\u5730\u5740").action(async (opts) => {
65025
+ console.log(JSON.stringify(await runErpMcp(void 0, opts.hermesBaseUrl), null, 2));
65026
+ });
65027
+ command.command("call").description("\u8C03\u7528 ERP \u53EA\u8BFB\u5DE5\u5177\uFF1B\u53C2\u6570\u4EE5 tools \u8FD4\u56DE\u7684 schema \u4E3A\u51C6").requiredOption("--tool <name>", "erp-query / erp-list-tables / erp-table-columns").option("--args <json>", "JSON \u53C2\u6570\u5BF9\u8C61\uFF0C\u652F\u6301 @file \u6216 @-\uFF08\u6807\u51C6\u8F93\u5165\uFF09", "{}").option("--hermes-base-url <url>", "Hermes \u670D\u52A1\u6839\u5730\u5740").action(async (opts) => {
65028
+ const raw = await expandAtFileToken(opts.args, "--args");
65029
+ let args;
65030
+ try {
65031
+ args = JSON.parse(raw);
65032
+ } catch {
65033
+ throw new VertuError("--args \u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
65034
+ }
65035
+ if (!args || typeof args !== "object" || Array.isArray(args)) throw new VertuError("--args \u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
65036
+ console.log(JSON.stringify(await runErpMcp({ tool: opts.tool, arguments: args }, opts.hermesBaseUrl), null, 2));
65037
+ });
65038
+ return command;
65039
+ }
65040
+
64843
65041
  // src/commands/cli-install.ts
64844
65042
  var import_node_child_process2 = require("child_process");
64845
65043
  function assertNodeEngine() {
@@ -64923,6 +65121,8 @@ function buildCliRootHelpAfter() {
64923
65121
  "USAGE:",
64924
65122
  ` ${b} catalog [--domain sales]`,
64925
65123
  ` ${b} exec --skill <key> --script <key> [--param k=v]`,
65124
+ ` ${b} vertu-aisales tools | vertu-aisales call --tool <name> --args @params.json`,
65125
+ ` ${b} vertu-erp tools | vertu-erp call --tool <name> --args @params.json`,
64926
65126
  ` ${b} <domain> +<shortcut> [options]`,
64927
65127
  ` ${b} auth <subcommand> [options]`,
64928
65128
  "",
@@ -65132,7 +65332,7 @@ function rewriteLongFlag(tok) {
65132
65332
  }
65133
65333
 
65134
65334
  // src/bin/vps-work.ts
65135
- var PKG_VERSION = "2.4.1" ? "2.4.1" : "0.0.0-dev";
65335
+ var PKG_VERSION = "2.5.1" ? "2.5.1" : "0.0.0-dev";
65136
65336
  function smartFlagSuggest(flag, candidates) {
65137
65337
  if (!flag.startsWith("--") || flag.length < 4) return "";
65138
65338
  const word = flag.slice(2);
@@ -65261,6 +65461,8 @@ async function main() {
65261
65461
  program2.addCommand(buildPopupCommand());
65262
65462
  program2.addCommand(buildCatalogCommand());
65263
65463
  program2.addCommand(buildExecCommand());
65464
+ program2.addCommand(buildVertuErpCommand());
65465
+ program2.addCommand(buildVertuAiSalesCommand());
65264
65466
  program2.addCommand(buildSkillCommand());
65265
65467
  const userArgv = normalizeSnakeFlags(process.argv.slice(2));
65266
65468
  const fullArgv = [...process.argv.slice(0, 2), ...userArgv];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vps-work",
3
- "version": "2.4.1",
3
+ "version": "2.5.1",
4
4
  "description": "VPS Work CLI - tenant-aware domain shortcuts and bundled agent skills for Cursor / Claude Code / Trae.",
5
5
  "license": "MIT",
6
6
  "author": "he-linyan",
@@ -0,0 +1,43 @@
1
+ // Build first: npm run build. Signing key is a file outside the repository.
2
+ // VPS_WORK_RELEASE_KEY_FILE, VPS_WORK_RELEASE_KEY_ID, VPS_WORK_MIN_DESKTOP
3
+ // and VPS_WORK_RUNTIME_OUTPUT are required. This script never publishes.
4
+ import { createHash, createPrivateKey, sign } from 'node:crypto';
5
+ import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
6
+ import { dirname, join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9
+ const required = name => { const value = process.env[name]; if (!value) throw Error(`Missing ${name}`); return value; };
10
+ const keyFile = required('VPS_WORK_RELEASE_KEY_FILE'), keyId = required('VPS_WORK_RELEASE_KEY_ID');
11
+ const minDesktop = required('VPS_WORK_MIN_DESKTOP'), output = resolve(required('VPS_WORK_RUNTIME_OUTPUT'));
12
+ const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
13
+ if (![pkg.version, minDesktop].every(value => /^\d+\.\d+\.\d+$/.test(value))) throw Error('Stable versions required');
14
+ const files = {};
15
+ const add = async path => {
16
+ const bytes = await readFile(join(root, path)), text = bytes.toString('utf8');
17
+ if (!Buffer.from(text).equals(bytes)) throw Error(`Runtime bundle only supports UTF-8 files: ${path}`);
18
+ files[path] = text;
19
+ };
20
+ const walk = async path => {
21
+ for (const entry of (await readdir(join(root, path), { withFileTypes: true })).sort((a,b) => a.name.localeCompare(b.name))) {
22
+ if (entry.isSymbolicLink()) throw Error('Runtime symlinks are not allowed');
23
+ if (entry.isDirectory()) await walk(`${path}/${entry.name}`);
24
+ else await add(`${path}/${entry.name}`);
25
+ }
26
+ };
27
+ for (const path of ['dist/vps-work.cjs', 'dist/office-runtime.cjs', 'dist/OFFICE-LICENSE', 'dist/OFFICE-NOTICE', 'package.json', '.well-known/skills/index.json']) await add(path);
28
+ await walk('skills');
29
+ const index = JSON.parse(files['.well-known/skills/index.json']);
30
+ for (const skill of index.skills) for (const path of skill.files) if (!files[path]) throw Error(`Missing indexed skill file: ${path}`);
31
+ const bundle = Buffer.from(JSON.stringify({ files }));
32
+ if (bundle.length > 24 * 1024 * 1024) throw Error('Runtime exceeds desktop limit');
33
+ const sha256 = createHash('sha256').update(bundle).digest('hex');
34
+ const name = `vps-work-${pkg.version}-${sha256}.json`;
35
+ const payload = Buffer.from(JSON.stringify({ product: 'vps-work', version: pkg.version, minDesktop, minNode: '20.0.0',
36
+ url: `https://vps-download.vertu.cn/app-releases/tools/vps-work/${name}`, sha256, bytes: bundle.length }));
37
+ const key = createPrivateKey(await readFile(keyFile));
38
+ if (key.asymmetricKeyType !== 'ed25519') throw Error('Ed25519 signing key required');
39
+ const envelope = { payload: payload.toString('base64'), signature: sign(null, payload, key).toString('base64'), keyId };
40
+ await mkdir(output, { recursive: true });
41
+ await writeFile(join(output, name), bundle);
42
+ await writeFile(join(output, 'stable.json'), JSON.stringify(envelope, null, 2));
43
+ console.log(`Prepared ${name} and stable.json. Upload the immutable bundle first, then stable.json. Nothing was published.`);