vps-work 2.5.0 → 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/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 +3 -3
- package/package.json +1 -1
- package/skills/vertu-aisales/SKILL.md +4 -2
|
@@ -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
|
+
}
|