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.
- package/.well-known/skills/index.json +11 -0
- package/README.md +10 -0
- package/dist/packages/vps-work/src/bin/vps-work.js +271 -0
- package/dist/packages/vps-work/src/commands/agent/index.js +264 -0
- package/dist/packages/vps-work/src/commands/approval/index.js +7 -0
- package/dist/packages/vps-work/src/commands/approval/shortcuts.js +259 -0
- package/dist/packages/vps-work/src/commands/attendance/applications.js +21 -0
- package/dist/packages/vps-work/src/commands/attendance/index.js +9 -0
- package/dist/packages/vps-work/src/commands/attendance/scheduling.js +183 -0
- package/dist/packages/vps-work/src/commands/attendance/shortcuts.js +131 -0
- package/dist/packages/vps-work/src/commands/auth/check.js +34 -0
- package/dist/packages/vps-work/src/commands/auth/index.js +21 -0
- package/dist/packages/vps-work/src/commands/auth/list.js +50 -0
- package/dist/packages/vps-work/src/commands/auth/scopes.js +17 -0
- package/dist/packages/vps-work/src/commands/auth/status.js +127 -0
- package/dist/packages/vps-work/src/commands/auth/tenants.js +35 -0
- package/dist/packages/vps-work/src/commands/auth/use.js +40 -0
- package/dist/packages/vps-work/src/commands/calendar/index.js +7 -0
- package/dist/packages/vps-work/src/commands/calendar/shortcuts.js +598 -0
- package/dist/packages/vps-work/src/commands/catalog.js +29 -0
- package/dist/packages/vps-work/src/commands/cli-install.js +70 -0
- package/dist/packages/vps-work/src/commands/collab-invoke.js +19 -0
- package/dist/packages/vps-work/src/commands/corporate/index.js +204 -0
- package/dist/packages/vps-work/src/commands/creative/index.js +369 -0
- package/dist/packages/vps-work/src/commands/documents/index.js +991 -0
- package/dist/packages/vps-work/src/commands/drive/index.js +435 -0
- package/dist/packages/vps-work/src/commands/exec.js +46 -0
- package/dist/packages/vps-work/src/commands/hr/business.js +22 -0
- package/dist/packages/vps-work/src/commands/hr/data-view-shortcuts.js +519 -0
- package/dist/packages/vps-work/src/commands/hr/index.js +15 -0
- package/dist/packages/vps-work/src/commands/hr/job-responsibilities.js +266 -0
- package/dist/packages/vps-work/src/commands/hr/payslip.js +45 -0
- package/dist/packages/vps-work/src/commands/hr/shortcuts.js +146 -0
- package/dist/packages/vps-work/src/commands/im/im-message-payload.js +150 -0
- package/dist/packages/vps-work/src/commands/im/index.js +7 -0
- package/dist/packages/vps-work/src/commands/im/shortcuts.js +856 -0
- package/dist/packages/vps-work/src/commands/knowledge/index.js +36 -0
- package/dist/packages/vps-work/src/commands/login.js +52 -0
- package/dist/packages/vps-work/src/commands/logout.js +12 -0
- package/dist/packages/vps-work/src/commands/mail/email.js +132 -0
- package/dist/packages/vps-work/src/commands/mail/index.js +9 -0
- package/dist/packages/vps-work/src/commands/mail/shortcuts.js +43 -0
- package/dist/packages/vps-work/src/commands/meeting/index.js +7 -0
- package/dist/packages/vps-work/src/commands/meeting/shortcuts.js +120 -0
- package/dist/packages/vps-work/src/commands/odoo-invoke.js +21 -0
- package/dist/packages/vps-work/src/commands/odoo-leaf-registry.js +13 -0
- package/dist/packages/vps-work/src/commands/office/index.js +59 -0
- package/dist/packages/vps-work/src/commands/okr/index.js +7 -0
- package/dist/packages/vps-work/src/commands/okr/shortcuts.js +75 -0
- package/dist/packages/vps-work/src/commands/org-structure.js +347 -0
- package/dist/packages/vps-work/src/commands/popup/index.js +147 -0
- package/dist/packages/vps-work/src/commands/product/abnormal-statistics.js +250 -0
- package/dist/packages/vps-work/src/commands/product/data-view-shortcuts.js +253 -0
- package/dist/packages/vps-work/src/commands/product/index.js +11 -0
- package/dist/packages/vps-work/src/commands/purchase/index.js +45 -0
- package/dist/packages/vps-work/src/commands/reauth.js +127 -0
- package/dist/packages/vps-work/src/commands/report/index.js +7 -0
- package/dist/packages/vps-work/src/commands/report/missing-daily-reports.js +9 -0
- package/dist/packages/vps-work/src/commands/report/shortcuts.js +251 -0
- package/dist/packages/vps-work/src/commands/sales/index.js +11 -0
- package/dist/packages/vps-work/src/commands/sales/period-params.js +75 -0
- package/dist/packages/vps-work/src/commands/sales/shortcuts-personnel.js +124 -0
- package/dist/packages/vps-work/src/commands/sales/shortcuts.js +800 -0
- package/dist/packages/vps-work/src/commands/shortcut-common.js +88 -0
- package/dist/packages/vps-work/src/commands/shortcut-params.js +22 -0
- package/dist/packages/vps-work/src/commands/skill/index.js +136 -0
- package/dist/packages/vps-work/src/commands/skill/personal.js +167 -0
- package/dist/packages/vps-work/src/commands/store/index.js +82 -0
- package/dist/packages/vps-work/src/commands/task/index.js +7 -0
- package/dist/packages/vps-work/src/commands/task/task-center.js +595 -0
- package/dist/packages/vps-work/src/commands/tools/index.js +127 -0
- package/dist/packages/vps-work/src/commands/vemory/index.js +7 -0
- package/dist/packages/vps-work/src/commands/vemory/shortcuts.js +53 -0
- package/dist/packages/vps-work/src/commands/vertu-aisales.js +30 -0
- package/dist/packages/vps-work/src/commands/vertu-erp.js +30 -0
- package/dist/packages/vps-work/src/core/aisales-mcp-client.js +35 -0
- package/dist/packages/vps-work/src/core/auth.js +240 -0
- package/dist/packages/vps-work/src/core/cli-bin.js +11 -0
- package/dist/packages/vps-work/src/core/cli-root-help.js +190 -0
- package/dist/packages/vps-work/src/core/client.js +158 -0
- package/dist/packages/vps-work/src/core/config.js +183 -0
- package/dist/packages/vps-work/src/core/corporate-client.js +52 -0
- package/dist/packages/vps-work/src/core/creative-attachment-download.js +48 -0
- package/dist/packages/vps-work/src/core/documents-client.js +57 -0
- package/dist/packages/vps-work/src/core/endpoints.js +46 -0
- package/dist/packages/vps-work/src/core/erp-mcp-client.js +11 -0
- package/dist/packages/vps-work/src/core/errors.js +107 -0
- package/dist/packages/vps-work/src/core/execute-catalog-client.js +42 -0
- package/dist/packages/vps-work/src/core/hermes-client.js +70 -0
- package/dist/packages/vps-work/src/core/im-attachment-download.js +109 -0
- package/dist/packages/vps-work/src/core/im-client.js +64 -0
- package/dist/packages/vps-work/src/core/keychain.js +58 -0
- package/dist/packages/vps-work/src/core/mail-client.js +82 -0
- package/dist/packages/vps-work/src/core/mcp-client.js +129 -0
- package/dist/packages/vps-work/src/core/office-client.js +25 -0
- package/dist/packages/vps-work/src/core/office-files.js +82 -0
- package/dist/packages/vps-work/src/core/paths.js +70 -0
- package/dist/packages/vps-work/src/core/public-result.js +43 -0
- package/dist/packages/vps-work/src/core/sales-invoice-attachments-client.js +75 -0
- package/dist/packages/vps-work/src/core/sales-query-client.js +452 -0
- package/dist/packages/vps-work/src/core/skill-catalog.js +158 -0
- package/dist/packages/vps-work/src/core/skill-key.js +19 -0
- package/dist/packages/vps-work/src/core/v-drive-store.js +392 -0
- package/dist/packages/vps-work/src/core/vertu-agent-env.js +32 -0
- package/dist/packages/vps-work/src/core/vps-auth-scopes.js +73 -0
- package/dist/packages/vps-work/src/core/vps-service-auth.js +382 -0
- package/dist/packages/vps-work/src/core/vps-service-config.js +83 -0
- package/dist/packages/vps-work/src/core/vps-service-http.js +187 -0
- package/dist/packages/vps-work/src/utils/at-file.js +34 -0
- package/dist/packages/vps-work/src/utils/cli-option-helpers.js +52 -0
- package/dist/packages/vps-work/src/utils/frontmatter.js +26 -0
- package/dist/packages/vps-work/src/utils/normalize-argv.js +51 -0
- package/dist/packages/vps-work/src/utils/parse-json-arg.js +85 -0
- package/dist/packages/vps-work/src/utils/prompt.js +46 -0
- package/dist/packages/vps-work/src/utils/read-text.js +44 -0
- package/dist/packages/vps-work/src/utils/schema-to-options.js +307 -0
- package/dist/packages/vps-work/src/utils/unwrap-envelope.js +31 -0
- package/dist/shared/im-org-tree/index.js +200 -0
- package/dist/vps-work.cjs +246 -44
- package/package.json +1 -1
- package/scripts/package-desktop-runtime.mjs +43 -0
- package/skills/README.md +1 -0
- package/skills/vertu-aisales/SKILL.md +18 -0
- package/skills/vertu-erp/SKILL.md +17 -0
- 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
|
+
}
|