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
@@ -1,5 +1,16 @@
1
1
  {
2
2
  "skills": [
3
+ {"name":"vertu-aisales","description":"查询 VERTU 员工获授权的客户聊天记录、沟通摘要,分析客户跟进和成交障碍。通过 vps-work vertu-aisales 调用 AI Native Sales MCP;销售订单、业绩和回款数字使用销售业务技能。","files":["skills/vertu-aisales/SKILL.md"]},
4
+ {
5
+ "name": "vertu-erp",
6
+ "description": "通过 vps-work 调用当前账号获授权的 Vertu ERP MCP,查询业务表结构或执行只读 SQL。适用于明确需要 ERP 底层数据、已有业务命令不能覆盖的分析;常规业务报表优先使用对应业务技能。",
7
+ "files": ["skills/vertu-erp/SKILL.md"]
8
+ },
9
+ {
10
+ "name": "vps-dealer-ops",
11
+ "description": "经销商专用只读查询:本企业进销对比、五件套、业绩目标、产品排名与占比、提发货、自然进店,以及原账号授权的订单、库存、结算、合同付款和物流。不适用于其他企业、人事、考勤或业务写入。",
12
+ "files": ["skills/vps-dealer-ops/SKILL.md"]
13
+ },
3
14
  {
4
15
  "name": "vps-shared",
5
16
  "description": "vps-work 共用约定:登录、当前用户身份、活目录 catalog / exec、数据查询与反馈。业务域技能开始前应先读本文件。",
package/README.md CHANGED
@@ -214,6 +214,10 @@ vps-work skill +delete-personal --slug sales-follow-up
214
214
  | `vps-work auth use --tenant <uuid>` | 切换当前企业;Agent 不得自行执行 |
215
215
  | `vps-work catalog [--domain sales]` | 当前企业身份可见的活目录(服务端裁剪) |
216
216
  | `vps-work exec --skill <key> --script <key>` | 按 catalog 执行,不必为每个新脚本发 npm |
217
+ | `vps-work vertu-aisales tools` | 获取当前企业获授权的客户沟通工具及参数 schema |
218
+ | `vps-work vertu-aisales call --tool <name> --args @params.json` | 按可见员工范围查询聊天;多个实例用 `--service-id` 指定 |
219
+ | `vps-work vertu-erp tools` | 获取当前账号授权的 ERP MCP 工具及参数 schema |
220
+ | `vps-work vertu-erp call --tool <name> --args @params.json` | 调用 ERP 只读工具,参数按实时 schema 填写;支持 `@-` 标准输入 |
217
221
  | `vps-work install [--tag latest]` | 执行 `npm install -g vps-work@<tag>`(与 `npx vps-work@latest install` 等价) |
218
222
 
219
223
  CI / 自动化常用环境变量:`VERTU_BOT_INBOUND_KEY`(Bearer)、`VERTU_APP_ID` + `VERTU_APP_KEY`(App Key)。
@@ -244,6 +248,12 @@ CI / 自动化常用环境变量:`VERTU_BOT_INBOUND_KEY`(Bearer)、`VERTU_
244
248
 
245
249
  完整列表:`vps-work sales --help`(各域同理)。
246
250
 
251
+ ERP 统一经 `vps-work` 调用平台 `/hermes-console/api/platform-tools/vertu-erp`。服务端使用当前登录身份,逐次验证原企业、有效成员和 `vertu-erp:super-read` 显式授权。无需 ERP 插件或 Secure 执行池,不下发个人 MCP bearer,也没有指定账号例外;旧 MCP 地址返回 410。仅提供 `erp-query`、`erp-list-tables`、`erp-table-columns`,参数以 `vertu-erp tools` 为准。上游数据库凭证仅存服务端;只读保证须由数据库角色执行。
252
+
253
+ 配套技能为 `vertu-erp`。常规业务优先使用已有业务命令;确需 ERP 底层分析时才调用此入口。这里的广泛查询能力依赖已有服务端授权,没有客户端“全权限”开关,也不补充 Odoo 行级规则或授予数据库写权限。
254
+
255
+ 企业专用集成的命令与技能统一使用企业前缀:ERP 为 `vertu-erp`;AI Native Sales 为 `vertu-aisales`。企业前缀表示能力归属,不替代服务端租户和账号授权。
256
+
247
257
  常见查询示例:
248
258
 
249
259
  ```bash
@@ -0,0 +1,271 @@
1
+ import { Command } from 'commander';
2
+ import { buildPurchaseCommand } from '../commands/purchase/index.js';
3
+ import { formatErrorForCli, VertuError } from '../core/errors.js';
4
+ import { buildAuthCommand } from '../commands/auth/index.js';
5
+ import { buildAgentCommand } from '../commands/agent/index.js';
6
+ import { buildApprovalCommand } from '../commands/approval/index.js';
7
+ import { buildImCommand } from '../commands/im/index.js';
8
+ import { buildMailCommand } from '../commands/mail/index.js';
9
+ import { buildOkrCommand } from '../commands/okr/index.js';
10
+ import { buildReportCommand } from '../commands/report/index.js';
11
+ import { buildAttendanceCommand } from '../commands/attendance/index.js';
12
+ import { buildTaskCommand } from '../commands/task/index.js';
13
+ import { buildToolsCommand } from '../commands/tools/index.js';
14
+ // `dev` 命令已弃用并并入 `task`(task-center / task-assistant 直连库)。
15
+ import { buildHrCommand } from '../commands/hr/index.js';
16
+ import { buildProductCommand } from '../commands/product/index.js';
17
+ import { buildSalesCommand } from '../commands/sales/index.js';
18
+ import { buildStoreCommand } from '../commands/store/index.js';
19
+ import { buildVemoryCommand } from '../commands/vemory/index.js';
20
+ import { buildMeetingCommand } from '../commands/meeting/index.js';
21
+ import { buildCalendarCommand } from '../commands/calendar/index.js';
22
+ import { buildCreativeCommand } from '../commands/creative/index.js';
23
+ import { buildCorporateCommand } from '../commands/corporate/index.js';
24
+ import { buildDocumentsCommand } from '../commands/documents/index.js';
25
+ import { buildOfficeCommand } from '../commands/office/index.js';
26
+ import { buildKnowledgeCommand } from '../commands/knowledge/index.js';
27
+ import { buildDriveCommand } from '../commands/drive/index.js';
28
+ import { buildPopupCommand } from '../commands/popup/index.js';
29
+ import { buildSkillCommand } from '../commands/skill/index.js';
30
+ import { buildCatalogCommand } from '../commands/catalog.js';
31
+ import { buildExecCommand } from '../commands/exec.js';
32
+ import { buildVertuAiSalesCommand } from '../commands/vertu-aisales.js';
33
+ import { buildVertuErpCommand } from '../commands/vertu-erp.js';
34
+ import { buildCliInstallCommand } from '../commands/cli-install.js';
35
+ import { CLI_BIN, CLI_BIN_ALIASES } from '../core/cli-bin.js';
36
+ import { buildCliRootHelpAfter, buildCliRootHelpBefore } from '../core/cli-root-help.js';
37
+ import { normalizeSnakeFlags } from '../utils/normalize-argv.js';
38
+ const PKG_VERSION = typeof __VERTU_VERSION__ === 'string' && __VERTU_VERSION__ ? __VERTU_VERSION__ : '0.0.0-dev';
39
+ /**
40
+ * Compute a suggestion for an unknown long flag.
41
+ *
42
+ * commander 13's built-in `suggestSimilar` uses Damerau–Levenshtein with a
43
+ * hard-coded `maxDistance = 3`, which silently fails the most common AI
44
+ * mistake on this CLI: typing the schema-shortened form (`--model`) when the
45
+ * registered flag is the longer kebab-cased schema key (`--model-name`,
46
+ * `--knowledge-types`, `--start-date`). The edit distance there is ≥4, so
47
+ * commander emits no hint at all.
48
+ *
49
+ * We replace it with a two-stage matcher tuned for that pattern:
50
+ * 1. **Prefix-of-candidate** (`--model` → `--model-name`): treat candidates
51
+ * that start with `<flag>-` as the strongest hit. AI agents typing the
52
+ * shortened form are almost always after the prefix-extended flag.
53
+ * 2. **Levenshtein with cap=6**: catch typos and snake/kebab swaps the
54
+ * argv normalizer didn't already canonicalize. Min similarity 0.4 keeps
55
+ * the bar for spurious matches similar to commander's default.
56
+ *
57
+ * Returns the suggestion line (with leading `\n` to match commander's format)
58
+ * or an empty string when no candidate is close enough.
59
+ */
60
+ function smartFlagSuggest(flag, candidates) {
61
+ if (!flag.startsWith('--') || flag.length < 4)
62
+ return '';
63
+ const word = flag.slice(2);
64
+ const stripped = candidates
65
+ .filter((c) => c.startsWith('--') && c.length > 3)
66
+ .map((c) => c.slice(2));
67
+ const seen = new Set();
68
+ // Defensively drop exact matches: in real usage commander wouldn't reach the
69
+ // unknown-option path for a registered flag, but keeping the suggester pure
70
+ // makes it safe to compose elsewhere.
71
+ const cands = stripped.filter((c) => {
72
+ if (c === word || seen.has(c))
73
+ return false;
74
+ seen.add(c);
75
+ return true;
76
+ });
77
+ if (cands.length === 0)
78
+ return '';
79
+ const prefixHits = cands
80
+ .filter((c) => c.length > word.length && c.startsWith(`${word}-`))
81
+ .sort();
82
+ if (prefixHits.length) {
83
+ const formatted = prefixHits.map((c) => `--${c}`);
84
+ return prefixHits.length === 1
85
+ ? `\n(Did you mean ${formatted[0]}?)`
86
+ : `\n(Did you mean one of ${formatted.join(', ')}?)`;
87
+ }
88
+ const editDistance = (a, b) => {
89
+ if (Math.abs(a.length - b.length) > 8)
90
+ return Math.max(a.length, b.length);
91
+ // Flat 1-D matrix indexed as `(rows+1) cells per row`; avoids
92
+ // `noUncheckedIndexedAccess` complaints from nested array slots that
93
+ // TypeScript otherwise types as `number | undefined`.
94
+ const w = b.length + 1;
95
+ const d = new Array((a.length + 1) * w);
96
+ for (let i = 0; i <= a.length; i++)
97
+ d[i * w] = i;
98
+ for (let j = 0; j <= b.length; j++)
99
+ d[j] = j;
100
+ for (let i = 1; i <= a.length; i++) {
101
+ for (let j = 1; j <= b.length; j++) {
102
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
103
+ const del = d[(i - 1) * w + j] + 1;
104
+ const ins = d[i * w + (j - 1)] + 1;
105
+ const sub = d[(i - 1) * w + (j - 1)] + cost;
106
+ let v = Math.min(del, ins, sub);
107
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
108
+ v = Math.min(v, d[(i - 2) * w + (j - 2)] + 1);
109
+ }
110
+ d[i * w + j] = v;
111
+ }
112
+ }
113
+ return d[a.length * w + b.length];
114
+ };
115
+ const MAX_DIST = 6;
116
+ const MIN_SIM = 0.4;
117
+ let bestDist = MAX_DIST;
118
+ let best = [];
119
+ for (const c of cands) {
120
+ const dist = editDistance(word, c);
121
+ const len = Math.max(word.length, c.length);
122
+ if ((len - dist) / len <= MIN_SIM)
123
+ continue;
124
+ if (dist < bestDist) {
125
+ bestDist = dist;
126
+ best = [c];
127
+ }
128
+ else if (dist === bestDist) {
129
+ best.push(c);
130
+ }
131
+ }
132
+ if (!best.length)
133
+ return '';
134
+ best.sort();
135
+ const formatted = best.map((c) => `--${c}`);
136
+ return best.length === 1
137
+ ? `\n(Did you mean ${formatted[0]}?)`
138
+ : `\n(Did you mean one of ${formatted.join(', ')}?)`;
139
+ }
140
+ function asInternal(cmd) {
141
+ return cmd;
142
+ }
143
+ /** 沿命令树向上收集所有可用长选项(含 --help),供拼写建议使用。 */
144
+ function collectLongFlags(start) {
145
+ const candidateFlags = [];
146
+ for (let cur = start; cur; cur = cur.parent ? asInternal(cur.parent) : null) {
147
+ for (const opt of cur.options ?? []) {
148
+ if (opt.long)
149
+ candidateFlags.push(opt.long);
150
+ }
151
+ candidateFlags.push('--help');
152
+ }
153
+ return candidateFlags;
154
+ }
155
+ function patchSmartSuggester(cmd) {
156
+ const target = asInternal(cmd);
157
+ const original = target.unknownOption.bind(target);
158
+ target.unknownOption = function patched(flag) {
159
+ if (this._allowUnknownOption)
160
+ return;
161
+ if (!flag.startsWith('--'))
162
+ return original(flag);
163
+ const suggestion = smartFlagSuggest(flag, collectLongFlags(this));
164
+ if (!suggestion)
165
+ return original(flag);
166
+ this.error(`error: unknown option '${flag}'${suggestion}`, {
167
+ code: 'commander.unknownOption',
168
+ });
169
+ };
170
+ }
171
+ /**
172
+ * Apply commander's "did you mean" affordances to every command in the tree.
173
+ *
174
+ * commander 13 stores `_showSuggestionAfterError` and `_showHelpAfterError`
175
+ * per-Command and **does not** inherit them from the parent. So setting them
176
+ * only on the root program would leave nested subcommands
177
+ * dynamic leaves, …) printing the bare `unknown option '--model'` without any
178
+ * hint. We walk the registered tree once, after every subcommand has been
179
+ * attached (built-ins + dynamic), and turn both behaviours on. We also patch
180
+ * each command's `unknownOption` with the smarter suggester (see above).
181
+ *
182
+ * The tree walk is intentionally idempotent: calling `showSuggestionAfterError`
183
+ * twice is a no-op, so re-running this after late additions stays safe.
184
+ */
185
+ function applyErrorAffordances(root) {
186
+ const stack = [root];
187
+ while (stack.length) {
188
+ const node = stack.pop();
189
+ if (!node)
190
+ continue;
191
+ node.showSuggestionAfterError(true);
192
+ node.showHelpAfterError('(use -h or --help for usage)');
193
+ patchSmartSuggester(node);
194
+ stack.push(...node.commands);
195
+ }
196
+ }
197
+ // Exposed for unit tests; keeping the implementation co-located with the
198
+ // command-tree walker so the heuristic can evolve next to the integration.
199
+ export { smartFlagSuggest };
200
+ async function main() {
201
+ const program = new Command(CLI_BIN)
202
+ .version(PKG_VERSION, '-V, --version')
203
+ .description('VPS business CLI for enterprise tenants, employees and partners.')
204
+ .option('--base-url <url>', 'Override Vertu service URL for shortcuts and auth')
205
+ .addHelpText('beforeAll', buildCliRootHelpBefore())
206
+ .addHelpText('after', buildCliRootHelpAfter());
207
+ program.addCommand(buildCliInstallCommand());
208
+ program.addCommand(buildAuthCommand());
209
+ program.addCommand(buildAgentCommand());
210
+ program.addCommand(buildSalesCommand());
211
+ program.addCommand(buildStoreCommand());
212
+ program.addCommand(buildHrCommand());
213
+ program.addCommand(buildProductCommand());
214
+ program.addCommand(buildPurchaseCommand());
215
+ program.addCommand(buildApprovalCommand());
216
+ program.addCommand(buildOkrCommand());
217
+ program.addCommand(buildReportCommand());
218
+ program.addCommand(buildAttendanceCommand());
219
+ program.addCommand(buildTaskCommand());
220
+ program.addCommand(buildToolsCommand());
221
+ program.addCommand(buildImCommand());
222
+ program.addCommand(buildMailCommand());
223
+ program.addCommand(buildVemoryCommand());
224
+ program.addCommand(buildMeetingCommand());
225
+ program.addCommand(buildCalendarCommand());
226
+ program.addCommand(buildCreativeCommand());
227
+ program.addCommand(buildCorporateCommand());
228
+ program.addCommand(buildDocumentsCommand());
229
+ program.addCommand(buildOfficeCommand());
230
+ program.addCommand(buildKnowledgeCommand());
231
+ program.addCommand(buildDriveCommand());
232
+ program.addCommand(buildPopupCommand());
233
+ program.addCommand(buildCatalogCommand());
234
+ program.addCommand(buildExecCommand());
235
+ program.addCommand(buildVertuErpCommand());
236
+ program.addCommand(buildVertuAiSalesCommand());
237
+ program.addCommand(buildSkillCommand());
238
+ // Accept snake_case long flags (e.g. `--start_date`) as aliases of the
239
+ // canonical kebab-case form. server-side `parameters_schema` keys are
240
+ // snake_case, so users / AI agents reading the schema often type that form
241
+ // first. Normalizing once at the entry point keeps commander unaware of the
242
+ // alternate spelling and avoids touching every dynamic option registration.
243
+ const userArgv = normalizeSnakeFlags(process.argv.slice(2));
244
+ const fullArgv = [...process.argv.slice(0, 2), ...userArgv];
245
+ // Apply after built-in commands are registered, so the
246
+ // walker covers the whole tree in one pass.
247
+ applyErrorAffordances(program);
248
+ try {
249
+ await program.parseAsync(fullArgv);
250
+ }
251
+ catch (err) {
252
+ handleError(err);
253
+ }
254
+ }
255
+ function handleError(err) {
256
+ process.stderr.write(`${formatErrorForCli(err)}\n`);
257
+ if (err instanceof VertuError) {
258
+ process.exit(err.exitCode);
259
+ }
260
+ process.exit(1);
261
+ }
262
+ function isCliEntryPoint() {
263
+ const entry = process.argv[1] ?? '';
264
+ return (entry.includes('vps-work.ts') ||
265
+ entry.includes('vps-work.cjs') ||
266
+ entry.endsWith(`${CLI_BIN}`) ||
267
+ CLI_BIN_ALIASES.some((alias) => entry.endsWith(alias)));
268
+ }
269
+ if (isCliEntryPoint()) {
270
+ void main().catch(handleError);
271
+ }
@@ -0,0 +1,264 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { setTimeout as sleep } from 'node:timers/promises';
3
+ import { Command } from 'commander';
4
+ import { VertuError } from '../../core/errors.js';
5
+ import { DEFAULT_AGENT_APP_ID, formatAgentEnvExports, VERTU_APP_ID_ENV, VERTU_APP_KEY_ENV, } from '../../core/vertu-agent-env.js';
6
+ import { bindVpsServiceAgentCredential, loadStoredAgentKey, persistVpsServiceAgentCredential, } from '../../core/vps-service-auth.js';
7
+ import { readVpsServiceConfig } from '../../core/vps-service-config.js';
8
+ import { resolveVpsServiceUrl } from '../../core/sales-query-client.js';
9
+ import { promptText } from '../../utils/prompt.js';
10
+ import { expandAtFileToken } from '../../utils/at-file.js';
11
+ import { imRequest } from '../../core/im-client.js';
12
+ const HARNESS_COLLABORATION_KIND = 'vps-harness-agent-collaboration';
13
+ function isVpsSessionExpired(expiresAt) {
14
+ if (!expiresAt) {
15
+ return true;
16
+ }
17
+ const parsed = Date.parse(expiresAt);
18
+ return !Number.isFinite(parsed) || parsed <= Date.now() + 30_000;
19
+ }
20
+ function requiredUserId(raw) {
21
+ const value = Number(raw);
22
+ if (!Number.isInteger(value) || value <= 0) {
23
+ throw new VertuError('--user-id 必须是正整数。');
24
+ }
25
+ return value;
26
+ }
27
+ export function collaborationTurnFromHistory(out, requestId) {
28
+ if (!out || typeof out !== 'object' || !('messages' in out))
29
+ return null;
30
+ const messages = out.messages;
31
+ if (!Array.isArray(messages))
32
+ return null;
33
+ for (const value of [...messages].reverse()) {
34
+ if (!value || typeof value !== 'object')
35
+ continue;
36
+ const message = value;
37
+ const metadata = message.metadata;
38
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))
39
+ continue;
40
+ const envelope = metadata[HARNESS_COLLABORATION_KIND];
41
+ if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope))
42
+ continue;
43
+ const record = envelope;
44
+ const turn = Number(record.turn);
45
+ if (record.request_id !== requestId
46
+ || (record.type !== 'request' && record.type !== 'response')
47
+ || !Number.isInteger(turn)
48
+ || turn <= 0)
49
+ continue;
50
+ return {
51
+ body: typeof message.body === 'string' ? message.body : '',
52
+ messageId: typeof message.id === 'string' ? message.id : '',
53
+ turn,
54
+ type: record.type,
55
+ };
56
+ }
57
+ return null;
58
+ }
59
+ export function buildAgentCommand() {
60
+ const cmd = new Command('agent').description('Agent host credentials (app_id + app_key) after human sign-in.');
61
+ cmd
62
+ .command('bind')
63
+ .description('用当前 Vertu 会话为指定 app_id 签发/轮换 app_key')
64
+ .option('--base-url <url>', 'Vertu 服务地址')
65
+ .option('--app-id <id>', `可信应用 ID(默认 ${DEFAULT_AGENT_APP_ID},可用环境变量 ${VERTU_APP_ID_ENV})`)
66
+ .action(async (opts, command) => {
67
+ const merged = command.optsWithGlobals();
68
+ const cfg = await readVpsServiceConfig();
69
+ const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
70
+ const sessionToken = cfg.sessionToken?.trim();
71
+ if (!sessionToken || isVpsSessionExpired(cfg.expiresAt)) {
72
+ throw new VertuError('签发 app_key 需要人工登录会话;Agent 不得自行执行账号密码登录。');
73
+ }
74
+ const appId = opts.appId?.trim() ??
75
+ merged.appId?.trim() ??
76
+ process.env[VERTU_APP_ID_ENV]?.trim() ??
77
+ cfg.agentAppId ??
78
+ (await promptText('Agent app_id', DEFAULT_AGENT_APP_ID));
79
+ const bound = await bindVpsServiceAgentCredential(serviceUrl, sessionToken, appId);
80
+ await persistVpsServiceAgentCredential(serviceUrl, bound);
81
+ console.log(`已绑定 Agent:app_id=${bound.appId},login=${bound.login}`);
82
+ console.log(`过期:${bound.expiresAt ?? '长效(永不过期)'}`);
83
+ console.log('宿主环境:执行 `vps-work agent env` 查看 export 语句');
84
+ });
85
+ cmd
86
+ .command('env')
87
+ .description('输出 Agent 宿主应注入的环境变量(export / $env:)')
88
+ .option('--base-url <url>', 'Vertu 服务地址')
89
+ .option('--app-id <id>', '应用 ID(默认读配置或 cursor)')
90
+ .option('--shell <name>', 'bash | powershell', 'bash')
91
+ .action(async (opts, command) => {
92
+ const merged = command.optsWithGlobals();
93
+ const cfg = await readVpsServiceConfig();
94
+ const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
95
+ const appId = opts.appId?.trim() ??
96
+ merged.appId?.trim() ??
97
+ cfg.agentAppId ??
98
+ DEFAULT_AGENT_APP_ID;
99
+ const login = cfg.login?.trim();
100
+ if (!login) {
101
+ throw new VertuError('未找到宿主注入的 Agent 账号;Agent 不得自行执行账号密码登录。');
102
+ }
103
+ const appKey = process.env[VERTU_APP_KEY_ENV]?.trim() ??
104
+ (await loadStoredAgentKey(serviceUrl, appId));
105
+ if (!appKey) {
106
+ throw new VertuError('无 app_key。请由宿主或人工管理员完成绑定;Agent 不得自行执行账号密码登录。');
107
+ }
108
+ const shell = opts.shell ?? merged.shell ?? 'bash';
109
+ if (shell === 'powershell') {
110
+ const esc = (s) => s.replace(/'/g, "''");
111
+ console.log(`$env:VERTU_VPS_SERVICE_URL='${esc(serviceUrl)}'`);
112
+ console.log(`$env:VERTU_APP_ID='${esc(appId)}'`);
113
+ console.log(`$env:VERTU_APP_KEY='${esc(appKey)}'`);
114
+ console.log(`$env:VERTU_USER_LOGIN='${esc(login)}'`);
115
+ return;
116
+ }
117
+ process.stdout.write(formatAgentEnvExports(serviceUrl, appId, appKey, login));
118
+ });
119
+ cmd
120
+ .command('ask')
121
+ .description('向另一位员工的 VPS Harness Agent 发起请求并等待答复')
122
+ .requiredOption('--user-id <id>', '对方 user_id')
123
+ .requiredOption('--message <text|@file>', '请求内容;长内容可使用 @file / @-')
124
+ .option('--base-url <url>', 'Vertu 服务地址')
125
+ .option('--stop-at <datetime>', '循环协作截止时间(ISO 8601 或本地 YYYY-MM-DDTHH:mm)')
126
+ .option('--duration-minutes <n>', '从现在起循环协作多少分钟(与 --stop-at 二选一)')
127
+ .option('--timeout <seconds>', '等待对方首轮答复的秒数', '180')
128
+ .option('--no-wait', '只发送请求,不等待答复')
129
+ .action(async (opts, command) => {
130
+ const merged = command.optsWithGlobals();
131
+ const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
132
+ const userId = requiredUserId(opts.userId);
133
+ const message = (await expandAtFileToken(opts.message ?? '', '--message')).trim();
134
+ if (!message)
135
+ throw new VertuError('--message 不能为空。');
136
+ if (message.length > 20_000)
137
+ throw new VertuError('--message 不能超过 20000 个字符。');
138
+ if (opts.stopAt && opts.durationMinutes) {
139
+ throw new VertuError('--stop-at 与 --duration-minutes 只能使用一个。');
140
+ }
141
+ let stopAt;
142
+ if (opts.stopAt) {
143
+ const parsed = Date.parse(opts.stopAt);
144
+ if (!Number.isFinite(parsed) || parsed <= Date.now()) {
145
+ throw new VertuError('--stop-at 必须是未来的有效日期时间。');
146
+ }
147
+ stopAt = new Date(parsed).toISOString();
148
+ }
149
+ else if (opts.durationMinutes) {
150
+ const minutes = Number(opts.durationMinutes);
151
+ if (!Number.isFinite(minutes) || minutes <= 0 || minutes > 1440) {
152
+ throw new VertuError('--duration-minutes 必须大于 0 且不超过 1440 分钟。');
153
+ }
154
+ stopAt = new Date(Date.now() + minutes * 60_000).toISOString();
155
+ }
156
+ const timeoutSeconds = Number(opts.timeout);
157
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 1800) {
158
+ throw new VertuError('--timeout 必须在 1 到 1800 秒之间。');
159
+ }
160
+ const requestId = randomUUID();
161
+ const sent = await imRequest('POST', '/v1/im/user-robots/harness/send', {
162
+ baseUrl: serviceUrl,
163
+ body: {
164
+ body: message,
165
+ idempotency_key: `vps-harness-request-${requestId}`,
166
+ message_type: 'text',
167
+ target_user_id: userId,
168
+ metadata: {
169
+ [HARNESS_COLLABORATION_KIND]: {
170
+ objective: message,
171
+ protocol: 1,
172
+ request_id: requestId,
173
+ ...(stopAt ? { stop_at: stopAt } : {}),
174
+ turn: 0,
175
+ type: 'request',
176
+ },
177
+ },
178
+ },
179
+ });
180
+ const channelId = extractAgentAskChannelId(sent);
181
+ if (opts.wait === false) {
182
+ console.log(JSON.stringify({ channel_id: channelId, request_id: requestId, status: 'sent' }, null, 2));
183
+ return;
184
+ }
185
+ const deadline = Date.now() + timeoutSeconds * 1000;
186
+ while (Date.now() < deadline) {
187
+ await sleep(1_000);
188
+ const history = await imRequest('GET', `/v1/im/channels/${encodeURIComponent(channelId)}/messages`, { baseUrl: serviceUrl, query: { limit: 50 } });
189
+ const response = collaborationTurnFromHistory(history, requestId);
190
+ if (response) {
191
+ console.log(JSON.stringify({
192
+ answer: response.body,
193
+ channel_id: channelId,
194
+ message_id: response.messageId,
195
+ request_id: requestId,
196
+ status: response.type === 'request' ? 'continuing' : 'completed',
197
+ turn: response.turn,
198
+ }, null, 2));
199
+ return;
200
+ }
201
+ }
202
+ throw new VertuError(`等待员工 VPS Harness Agent 答复超时(${timeoutSeconds} 秒)。请确认对方在线且已开启 Agent 联系开关。`);
203
+ });
204
+ cmd
205
+ .command('message-user')
206
+ .description('以我的 VPS Harness Agent 身份给员工发起私聊')
207
+ .requiredOption('--user-id <id>', '对方 user_id')
208
+ .requiredOption('--message <text|@file>', '消息内容;长内容可使用 @file / @-')
209
+ .option('--base-url <url>', 'Vertu 服务地址')
210
+ .action(async (opts, command) => {
211
+ const merged = command.optsWithGlobals();
212
+ const serviceUrl = resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
213
+ const userId = requiredUserId(opts.userId);
214
+ const message = (await expandAtFileToken(opts.message ?? '', '--message')).trim();
215
+ if (!message)
216
+ throw new VertuError('--message 不能为空。');
217
+ if (message.length > 20_000)
218
+ throw new VertuError('--message 不能超过 20000 个字符。');
219
+ const requestId = randomUUID();
220
+ const sent = await imRequest('POST', '/v1/im/user-robots/harness/message-user', {
221
+ baseUrl: serviceUrl,
222
+ body: {
223
+ body: message,
224
+ idempotency_key: `vps-harness-human-${requestId}`,
225
+ message_type: 'text',
226
+ target_user_id: userId,
227
+ },
228
+ });
229
+ console.log(JSON.stringify({
230
+ channel_id: extractAgentAskChannelId(sent),
231
+ message_id: extractHarnessMessageId(sent),
232
+ request_id: requestId,
233
+ status: 'sent',
234
+ }, null, 2));
235
+ });
236
+ return cmd;
237
+ }
238
+ function extractHarnessMessageId(out) {
239
+ if (!out || typeof out !== 'object' || !('message' in out))
240
+ return null;
241
+ const message = out.message;
242
+ if (!message || typeof message !== 'object' || !('id' in message))
243
+ return null;
244
+ const id = message.id;
245
+ return typeof id === 'string' && id ? id : null;
246
+ }
247
+ function extractAgentAskChannelId(out) {
248
+ if (out && typeof out === 'object' && 'channel_id' in out) {
249
+ const id = out.channel_id;
250
+ if (typeof id === 'string' && id)
251
+ return id;
252
+ }
253
+ if (!out || typeof out !== 'object' || !('channel' in out)) {
254
+ throw new VertuError('Vertu IM 未返回 channel_id。');
255
+ }
256
+ const channel = out.channel;
257
+ if (!channel || typeof channel !== 'object' || !('id' in channel)) {
258
+ throw new VertuError('Vertu IM 未返回 channel.id。');
259
+ }
260
+ const id = channel.id;
261
+ if (typeof id !== 'string' || !id)
262
+ throw new VertuError('Vertu IM 返回了无效的 channel.id。');
263
+ return id;
264
+ }
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ import { attachApprovalShortcuts } from './shortcuts.js';
3
+ export function buildApprovalCommand() {
4
+ const approval = new Command('approval').description('Approval shortcuts: pending queue, approval history, pass/reject guidance.');
5
+ attachApprovalShortcuts(approval);
6
+ return approval;
7
+ }