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
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { VertuError } from '../../core/errors.js';
|
|
2
|
+
import { imRequest, printImResult } from '../../core/im-client.js';
|
|
3
|
+
import { attachSharedShortcutOptions, mergeShortcutParams, resolveShortcutVpsServiceUrl, } from '../shortcut-common.js';
|
|
4
|
+
import { parseUserIdList } from '../im/im-message-payload.js';
|
|
5
|
+
const DAY_MS = 86_400_000;
|
|
6
|
+
const MAX_RANGE_DAYS = 370;
|
|
7
|
+
const AVAILABILITY_RANGE_DAYS = 8;
|
|
8
|
+
function asRecord(value) {
|
|
9
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
10
|
+
? value
|
|
11
|
+
: null;
|
|
12
|
+
}
|
|
13
|
+
function recordArray(value) {
|
|
14
|
+
return Array.isArray(value)
|
|
15
|
+
? value.filter((item) => asRecord(item) !== null)
|
|
16
|
+
: [];
|
|
17
|
+
}
|
|
18
|
+
function shanghaiToday() {
|
|
19
|
+
return new Intl.DateTimeFormat('en-CA', {
|
|
20
|
+
timeZone: 'Asia/Shanghai',
|
|
21
|
+
year: 'numeric',
|
|
22
|
+
month: '2-digit',
|
|
23
|
+
day: '2-digit',
|
|
24
|
+
}).format(new Date());
|
|
25
|
+
}
|
|
26
|
+
function parseYmd(raw, flag) {
|
|
27
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
28
|
+
if (!match) {
|
|
29
|
+
throw new VertuError(`${flag} 需为 YYYY-MM-DD。`);
|
|
30
|
+
}
|
|
31
|
+
const year = Number(match[1]);
|
|
32
|
+
const month = Number(match[2]);
|
|
33
|
+
const day = Number(match[3]);
|
|
34
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
35
|
+
if (date.getUTCFullYear() !== year
|
|
36
|
+
|| date.getUTCMonth() !== month - 1
|
|
37
|
+
|| date.getUTCDate() !== day) {
|
|
38
|
+
throw new VertuError(`${flag} 不是有效日期。`);
|
|
39
|
+
}
|
|
40
|
+
return { year, month, day };
|
|
41
|
+
}
|
|
42
|
+
function ymdAtUtc(raw, flag) {
|
|
43
|
+
const { year, month, day } = parseYmd(raw, flag);
|
|
44
|
+
return Date.UTC(year, month - 1, day);
|
|
45
|
+
}
|
|
46
|
+
function addDays(raw, days) {
|
|
47
|
+
const timestamp = ymdAtUtc(raw, '日期') + days * DAY_MS;
|
|
48
|
+
return new Date(timestamp).toISOString().slice(0, 10);
|
|
49
|
+
}
|
|
50
|
+
function resolveDateRange(opts, maxDays = MAX_RANGE_DAYS) {
|
|
51
|
+
const today = shanghaiToday();
|
|
52
|
+
const startDate = opts.startDate?.trim() || opts.endDate?.trim() || today;
|
|
53
|
+
const endDate = opts.endDate?.trim() || startDate;
|
|
54
|
+
const startDay = ymdAtUtc(startDate, '--start-date');
|
|
55
|
+
const endDay = ymdAtUtc(endDate, '--end-date');
|
|
56
|
+
const days = Math.round((endDay - startDay) / DAY_MS) + 1;
|
|
57
|
+
if (days <= 0) {
|
|
58
|
+
throw new VertuError('--end-date 不能早于 --start-date。');
|
|
59
|
+
}
|
|
60
|
+
if (days > maxDays) {
|
|
61
|
+
throw new VertuError(`日期范围最多 ${maxDays} 天。`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
startDate,
|
|
65
|
+
endDate,
|
|
66
|
+
start: `${startDate}T00:00:00+08:00`,
|
|
67
|
+
endExclusive: `${addDays(endDate, 1)}T00:00:00+08:00`,
|
|
68
|
+
days,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function isDryRun(opts) {
|
|
72
|
+
return opts.validateOnly === true || opts.dryRun === true;
|
|
73
|
+
}
|
|
74
|
+
function baseUrl(opts, cmd) {
|
|
75
|
+
return resolveShortcutVpsServiceUrl(opts, cmd);
|
|
76
|
+
}
|
|
77
|
+
function printPreview(opts, endpoint, query) {
|
|
78
|
+
printImResult(opts.json, {
|
|
79
|
+
validateOnly: true,
|
|
80
|
+
method: 'GET',
|
|
81
|
+
endpoint,
|
|
82
|
+
query,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function printMutationPreview(opts, method, endpoint, body) {
|
|
86
|
+
printImResult(opts.json, {
|
|
87
|
+
validateOnly: true,
|
|
88
|
+
method,
|
|
89
|
+
endpoint,
|
|
90
|
+
body,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function decorateEvents(payload, scope, range, calendarId) {
|
|
94
|
+
const record = asRecord(payload) ?? {};
|
|
95
|
+
const calendars = recordArray(record.calendars);
|
|
96
|
+
const calendarNames = new Map(calendars.flatMap((calendar) => {
|
|
97
|
+
const id = typeof calendar.id === 'string' ? calendar.id : '';
|
|
98
|
+
return id ? [[id, typeof calendar.name === 'string' ? calendar.name : null]] : [];
|
|
99
|
+
}));
|
|
100
|
+
if (calendarId && !calendarNames.has(calendarId)) {
|
|
101
|
+
throw new VertuError(`日历不存在或无权查看:${calendarId}`);
|
|
102
|
+
}
|
|
103
|
+
const rows = recordArray(record.events)
|
|
104
|
+
.filter((event) => !calendarId || event.calendar_id === calendarId)
|
|
105
|
+
.map((event) => ({
|
|
106
|
+
...event,
|
|
107
|
+
calendar_name: typeof event.calendar_id === 'string'
|
|
108
|
+
? calendarNames.get(event.calendar_id) ?? null
|
|
109
|
+
: null,
|
|
110
|
+
}));
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
scope,
|
|
114
|
+
filter: {
|
|
115
|
+
start_date: range.startDate,
|
|
116
|
+
end_date: range.endDate,
|
|
117
|
+
time_zone: 'Asia/Shanghai',
|
|
118
|
+
...(calendarId ? { calendar_id: calendarId } : {}),
|
|
119
|
+
},
|
|
120
|
+
rows,
|
|
121
|
+
summary: { events: rows.length },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function positiveUserId(raw, flag) {
|
|
125
|
+
const text = raw?.trim();
|
|
126
|
+
if (!text) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (!/^\d+$/.test(text)) {
|
|
130
|
+
throw new VertuError(`${flag} 需为正整数。`);
|
|
131
|
+
}
|
|
132
|
+
const value = Number(text);
|
|
133
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
134
|
+
throw new VertuError(`${flag} 需为正整数。`);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function nonEmpty(raw, flag) {
|
|
139
|
+
const value = raw?.trim();
|
|
140
|
+
if (!value) {
|
|
141
|
+
throw new VertuError(`${flag} 为必填。`);
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
function optionalBoolean(raw, flag) {
|
|
146
|
+
if (raw === undefined) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const value = raw.trim().toLocaleLowerCase();
|
|
150
|
+
if (['true', '1', 'yes', '是'].includes(value)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (['false', '0', 'no', '否'].includes(value)) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
throw new VertuError(`${flag} 需为 true 或 false。`);
|
|
157
|
+
}
|
|
158
|
+
function optionalDateTime(raw, flag) {
|
|
159
|
+
const value = raw?.trim();
|
|
160
|
+
if (!value) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const local = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})(?::(\d{2}))?$/.exec(value);
|
|
164
|
+
const normalized = local
|
|
165
|
+
? `${local[1]}T${local[2]}:${local[3] ?? '00'}+08:00`
|
|
166
|
+
: value;
|
|
167
|
+
if (!Number.isFinite(Date.parse(normalized))) {
|
|
168
|
+
throw new VertuError(`${flag} 需为有效 ISO 时间,例如 2026-07-24T09:00:00+08:00。`);
|
|
169
|
+
}
|
|
170
|
+
return normalized;
|
|
171
|
+
}
|
|
172
|
+
function requiredDateTime(raw, flag) {
|
|
173
|
+
const value = optionalDateTime(raw, flag);
|
|
174
|
+
if (!value) {
|
|
175
|
+
throw new VertuError(`${flag} 为必填。`);
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
function optionalEventType(raw) {
|
|
180
|
+
const value = raw?.trim();
|
|
181
|
+
if (!value) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
if (value !== 'schedule' && value !== 'meeting') {
|
|
185
|
+
throw new VertuError('--event-type 需为 schedule 或 meeting。');
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
function optionalRecurrenceScope(raw) {
|
|
190
|
+
const value = raw?.trim();
|
|
191
|
+
if (!value) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
if (value !== 'current' && value !== 'series') {
|
|
195
|
+
throw new VertuError('--recurrence-scope 需为 current 或 series。');
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
function responseStatus(raw) {
|
|
200
|
+
const value = raw?.trim();
|
|
201
|
+
if (value === 'accepted' || value === 'declined' || value === 'tentative') {
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
throw new VertuError('--status 需为 accepted、declined 或 tentative。');
|
|
205
|
+
}
|
|
206
|
+
function optionalInteger(raw, flag) {
|
|
207
|
+
if (raw === undefined) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
const value = Number(raw);
|
|
211
|
+
if (!Number.isSafeInteger(value)) {
|
|
212
|
+
throw new VertuError(`${flag} 需为整数。`);
|
|
213
|
+
}
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
function optionalReminderMinutes(raw) {
|
|
217
|
+
if (raw === undefined) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const value = raw.trim().toLocaleLowerCase();
|
|
221
|
+
const tokens = value.split(/[,,\s]+/).filter(Boolean);
|
|
222
|
+
if (tokens.includes('none') || tokens.includes('无') || tokens.length === 0) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const aliases = new Map([
|
|
226
|
+
['start', 0], ['0', 0],
|
|
227
|
+
['5m', 5], ['5', 5],
|
|
228
|
+
['15m', 15], ['15', 15],
|
|
229
|
+
['1h', 60], ['60', 60],
|
|
230
|
+
['1d', 1440], ['1440', 1440],
|
|
231
|
+
]);
|
|
232
|
+
const selected = new Set();
|
|
233
|
+
for (const token of tokens) {
|
|
234
|
+
const minutes = aliases.get(token);
|
|
235
|
+
if (minutes === undefined) {
|
|
236
|
+
throw new VertuError('--reminder-minutes 仅支持 none、start、5m、15m、1h、1d,或 0/5/15/60/1440。');
|
|
237
|
+
}
|
|
238
|
+
selected.add(minutes);
|
|
239
|
+
}
|
|
240
|
+
return [0, 5, 15, 60, 1440].filter((minutes) => selected.has(minutes));
|
|
241
|
+
}
|
|
242
|
+
function putDefined(target, key, value) {
|
|
243
|
+
if (value !== undefined) {
|
|
244
|
+
target[key] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function participantsFromOptions(requiredRaw, optionalRaw) {
|
|
248
|
+
if (requiredRaw === undefined && optionalRaw === undefined) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const participants = new Map();
|
|
252
|
+
for (const userId of requiredRaw === undefined
|
|
253
|
+
? []
|
|
254
|
+
: parseUserIdList(requiredRaw, '--participant-user-ids')) {
|
|
255
|
+
participants.set(userId, { user_id: userId, optional: false });
|
|
256
|
+
}
|
|
257
|
+
for (const userId of optionalRaw === undefined
|
|
258
|
+
? []
|
|
259
|
+
: parseUserIdList(optionalRaw, '--optional-user-ids')) {
|
|
260
|
+
participants.set(userId, { user_id: userId, optional: true });
|
|
261
|
+
}
|
|
262
|
+
return [...participants.values()];
|
|
263
|
+
}
|
|
264
|
+
async function calendarMutationBody(opts) {
|
|
265
|
+
const body = {};
|
|
266
|
+
putDefined(body, 'calendar_id', opts.calendarId?.trim() || undefined);
|
|
267
|
+
putDefined(body, 'title', opts.title?.trim() || undefined);
|
|
268
|
+
putDefined(body, 'event_type', optionalEventType(opts.eventType));
|
|
269
|
+
putDefined(body, 'starts_at', optionalDateTime(opts.startsAt, '--starts-at'));
|
|
270
|
+
putDefined(body, 'ends_at', optionalDateTime(opts.endsAt, '--ends-at'));
|
|
271
|
+
putDefined(body, 'all_day', optionalBoolean(opts.allDay, '--all-day'));
|
|
272
|
+
putDefined(body, 'time_zone', opts.timeZone?.trim() || undefined);
|
|
273
|
+
putDefined(body, 'location', opts.location);
|
|
274
|
+
putDefined(body, 'description', opts.description);
|
|
275
|
+
putDefined(body, 'recurrence_rule', opts.recurrenceRule);
|
|
276
|
+
putDefined(body, 'recurrence_scope', optionalRecurrenceScope(opts.recurrenceScope));
|
|
277
|
+
putDefined(body, 'occurrence_at', optionalDateTime(opts.occurrenceAt, '--occurrence-at'));
|
|
278
|
+
putDefined(body, 'drag_origin_at', optionalDateTime(opts.dragOriginAt, '--drag-origin-at'));
|
|
279
|
+
putDefined(body, 'calendar_day_shift', optionalInteger(opts.calendarDayShift, '--calendar-day-shift'));
|
|
280
|
+
putDefined(body, 'meeting_url', opts.meetingUrl);
|
|
281
|
+
putDefined(body, 'ai_minutes_enabled', optionalBoolean(opts.aiMinutesEnabled, '--ai-minutes-enabled'));
|
|
282
|
+
putDefined(body, 'reminder_minutes', optionalReminderMinutes(opts.reminderMinutes));
|
|
283
|
+
putDefined(body, 'participants', participantsFromOptions(opts.participantUserIds, opts.optionalUserIds));
|
|
284
|
+
putDefined(body, 'push_to_participants', optionalBoolean(opts.pushToParticipants, '--push-to-participants'));
|
|
285
|
+
return mergeShortcutParams(body, opts);
|
|
286
|
+
}
|
|
287
|
+
function validateCreateBody(body) {
|
|
288
|
+
body.title = nonEmpty(typeof body.title === 'string' ? body.title : undefined, '--title');
|
|
289
|
+
body.starts_at = requiredDateTime(typeof body.starts_at === 'string' ? body.starts_at : undefined, '--starts-at');
|
|
290
|
+
body.ends_at = requiredDateTime(typeof body.ends_at === 'string' ? body.ends_at : undefined, '--ends-at');
|
|
291
|
+
if (Date.parse(String(body.ends_at)) <= Date.parse(String(body.starts_at))) {
|
|
292
|
+
throw new VertuError('--ends-at 必须晚于 --starts-at。');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function validateUpdateBody(body) {
|
|
296
|
+
if ('title' in body) {
|
|
297
|
+
body.title = nonEmpty(typeof body.title === 'string' ? body.title : undefined, '--title');
|
|
298
|
+
}
|
|
299
|
+
if ('starts_at' in body) {
|
|
300
|
+
body.starts_at = requiredDateTime(typeof body.starts_at === 'string' ? body.starts_at : undefined, '--starts-at');
|
|
301
|
+
}
|
|
302
|
+
if ('ends_at' in body) {
|
|
303
|
+
body.ends_at = requiredDateTime(typeof body.ends_at === 'string' ? body.ends_at : undefined, '--ends-at');
|
|
304
|
+
}
|
|
305
|
+
if (Object.keys(body).length === 0) {
|
|
306
|
+
throw new VertuError('至少提供一个要修改的字段。');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function attachCalendarMutationOptions(command, update) {
|
|
310
|
+
if (update) {
|
|
311
|
+
command
|
|
312
|
+
.requiredOption('--event-id <id>', '日程 UUID')
|
|
313
|
+
.option('--title <text>', '日程主题')
|
|
314
|
+
.option('--starts-at <iso>', '开始时间,例如 2026-07-24T09:00+08:00')
|
|
315
|
+
.option('--ends-at <iso>', '结束时间,例如 2026-07-24T10:00+08:00');
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
command
|
|
319
|
+
.requiredOption('--title <text>', '日程主题')
|
|
320
|
+
.requiredOption('--starts-at <iso>', '开始时间;无时区时按 Asia/Shanghai')
|
|
321
|
+
.requiredOption('--ends-at <iso>', '结束时间;无时区时按 Asia/Shanghai')
|
|
322
|
+
.option('--calendar-id <id>', '日历 UUID;省略则使用默认日历');
|
|
323
|
+
}
|
|
324
|
+
return command
|
|
325
|
+
.option('--event-type <type>', 'schedule|meeting')
|
|
326
|
+
.option('--all-day <boolean>', '是否全天,true|false')
|
|
327
|
+
.option('--time-zone <zone>', '时区,默认 Asia/Shanghai')
|
|
328
|
+
.option('--location <text>', '地点;更新时传空字符串可清空')
|
|
329
|
+
.option('--description <text>', '描述;更新时传空字符串可清空')
|
|
330
|
+
.option('--meeting-url <url>', '会议链接;更新时传空字符串可清空')
|
|
331
|
+
.option('--ai-minutes-enabled <boolean>', '是否开启 AI 纪要,true|false')
|
|
332
|
+
.option('--reminder-minutes <list>', '多选提醒:none|start|5m|15m|1h|1d,逗号分隔;none 清空其他提醒')
|
|
333
|
+
.option('--recurrence-rule <rule>', '重复规则;更新时传空字符串可清空')
|
|
334
|
+
.option('--recurrence-scope <scope>', '重复日程拖动范围:current|series')
|
|
335
|
+
.option('--occurrence-at <iso>', '重复日程当前实例的原始时间')
|
|
336
|
+
.option('--drag-origin-at <iso>', '整组拖动前的实例时间')
|
|
337
|
+
.option('--calendar-day-shift <n>', '整组重复日程移动的自然日数')
|
|
338
|
+
.option('--participant-user-ids <list>', '必选参与人 user_id,逗号分隔或 JSON 数组')
|
|
339
|
+
.option('--optional-user-ids <list>', '可选参与人 user_id,逗号分隔或 JSON 数组')
|
|
340
|
+
.option('--push-to-participants <boolean>', '是否发送日程通知,true|false');
|
|
341
|
+
}
|
|
342
|
+
function validatePersonTarget(opts) {
|
|
343
|
+
const userId = positiveUserId(opts.userId, '--user-id');
|
|
344
|
+
const name = opts.name?.trim() || undefined;
|
|
345
|
+
if (userId && name) {
|
|
346
|
+
throw new VertuError('--user-id 与 --name 只能传一个。');
|
|
347
|
+
}
|
|
348
|
+
if (!userId && !name) {
|
|
349
|
+
throw new VertuError('请传 --user-id <id> 或 --name <姓名>。');
|
|
350
|
+
}
|
|
351
|
+
return { userId, name };
|
|
352
|
+
}
|
|
353
|
+
async function directoryUsers(query, serviceBaseUrl) {
|
|
354
|
+
const payload = asRecord(await imRequest('GET', '/v1/im/org/users', {
|
|
355
|
+
baseUrl: serviceBaseUrl,
|
|
356
|
+
query,
|
|
357
|
+
})) ?? {};
|
|
358
|
+
return recordArray(payload.users).flatMap((row) => {
|
|
359
|
+
const userId = Number(row.user_id);
|
|
360
|
+
return Number.isInteger(userId) && userId > 0
|
|
361
|
+
? [{
|
|
362
|
+
user_id: userId,
|
|
363
|
+
employee_name: typeof row.employee_name === 'string' ? row.employee_name : null,
|
|
364
|
+
login: typeof row.login === 'string' ? row.login : null,
|
|
365
|
+
barcode: typeof row.barcode === 'string' ? row.barcode : null,
|
|
366
|
+
department_name: typeof row.department_name === 'string' ? row.department_name : null,
|
|
367
|
+
}]
|
|
368
|
+
: [];
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async function resolvePerson(opts, serviceBaseUrl) {
|
|
372
|
+
const { userId, name } = validatePersonTarget(opts);
|
|
373
|
+
if (userId) {
|
|
374
|
+
return (await directoryUsers({ user_ids: userId }, serviceBaseUrl))[0] ?? { user_id: userId };
|
|
375
|
+
}
|
|
376
|
+
// validatePersonTarget guarantees one selector is present.
|
|
377
|
+
const queryName = name;
|
|
378
|
+
const users = await directoryUsers({ query: queryName, limit: 20 }, serviceBaseUrl);
|
|
379
|
+
const normalized = queryName.toLocaleLowerCase();
|
|
380
|
+
const exact = users.filter((user) => [user.employee_name, user.login, user.barcode]
|
|
381
|
+
.some((value) => value?.toLocaleLowerCase() === normalized));
|
|
382
|
+
const matches = exact.length ? exact : users;
|
|
383
|
+
if (matches.length === 0) {
|
|
384
|
+
throw new VertuError(`未找到人员:${queryName}`);
|
|
385
|
+
}
|
|
386
|
+
if (matches.length > 1) {
|
|
387
|
+
const candidates = matches
|
|
388
|
+
.slice(0, 8)
|
|
389
|
+
.map((user) => `${user.employee_name ?? user.login ?? '未命名'}(${user.user_id})`)
|
|
390
|
+
.join('、');
|
|
391
|
+
throw new VertuError(`人员不唯一,请改用 --user-id:${candidates}`);
|
|
392
|
+
}
|
|
393
|
+
return matches[0];
|
|
394
|
+
}
|
|
395
|
+
async function queryPersonAvailability(user, range, serviceBaseUrl) {
|
|
396
|
+
const rows = [];
|
|
397
|
+
let cursor = Date.parse(range.start);
|
|
398
|
+
const end = Date.parse(range.endExclusive);
|
|
399
|
+
while (cursor < end) {
|
|
400
|
+
const chunkEnd = Math.min(cursor + AVAILABILITY_RANGE_DAYS * DAY_MS, end);
|
|
401
|
+
const payload = asRecord(await imRequest('GET', '/v1/calendar/availability', {
|
|
402
|
+
baseUrl: serviceBaseUrl,
|
|
403
|
+
query: {
|
|
404
|
+
user_ids: user.user_id,
|
|
405
|
+
start: new Date(cursor).toISOString(),
|
|
406
|
+
end: new Date(chunkEnd).toISOString(),
|
|
407
|
+
},
|
|
408
|
+
})) ?? {};
|
|
409
|
+
rows.push(...recordArray(payload.busy).filter((row) => Number(row.user_id) === user.user_id));
|
|
410
|
+
cursor = chunkEnd;
|
|
411
|
+
}
|
|
412
|
+
const unique = new Map();
|
|
413
|
+
for (const row of rows) {
|
|
414
|
+
unique.set(`${String(row.event_id)}:${String(row.user_id)}:${String(row.starts_at)}`, row);
|
|
415
|
+
}
|
|
416
|
+
return [...unique.values()].sort((a, b) => String(a.starts_at ?? '').localeCompare(String(b.starts_at ?? '')));
|
|
417
|
+
}
|
|
418
|
+
export function attachCalendarShortcuts(parent) {
|
|
419
|
+
attachSharedShortcutOptions(parent
|
|
420
|
+
.command('+mine')
|
|
421
|
+
.alias('+agenda')
|
|
422
|
+
.description('查看指定日期范围内我的可见日程(默认今天)')
|
|
423
|
+
.option('--start-date <ymd>', '开始日期 YYYY-MM-DD,默认今天')
|
|
424
|
+
.option('--end-date <ymd>', '结束日期 YYYY-MM-DD,默认与开始日期相同')).action(async (opts, cmd) => {
|
|
425
|
+
const range = resolveDateRange(opts);
|
|
426
|
+
const query = { start: range.start, end: range.endExclusive };
|
|
427
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
428
|
+
if (isDryRun(opts)) {
|
|
429
|
+
printPreview(opts, `${serviceBaseUrl}/v1/calendar`, query);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const payload = await imRequest('GET', '/v1/calendar', {
|
|
433
|
+
baseUrl: serviceBaseUrl,
|
|
434
|
+
query,
|
|
435
|
+
});
|
|
436
|
+
printImResult(opts.json, decorateEvents(payload, 'mine', range));
|
|
437
|
+
});
|
|
438
|
+
attachSharedShortcutOptions(parent
|
|
439
|
+
.command('+person')
|
|
440
|
+
.description('按 user_id 或姓名查看某个人的忙碌日程')
|
|
441
|
+
.option('--user-id <id>', 'VPS 用户 ID')
|
|
442
|
+
.option('--name <name>', '姓名、登录名或工号;必须唯一')
|
|
443
|
+
.option('--start-date <ymd>', '开始日期 YYYY-MM-DD,默认今天')
|
|
444
|
+
.option('--end-date <ymd>', '结束日期 YYYY-MM-DD,默认与开始日期相同')).action(async (opts, cmd) => {
|
|
445
|
+
const range = resolveDateRange(opts);
|
|
446
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
447
|
+
if (isDryRun(opts)) {
|
|
448
|
+
const { userId, name } = validatePersonTarget(opts);
|
|
449
|
+
printPreview(opts, `${serviceBaseUrl}/v1/calendar/availability`, {
|
|
450
|
+
user_ids: userId ?? `<resolve:${name}>`,
|
|
451
|
+
start: range.start,
|
|
452
|
+
end: range.endExclusive,
|
|
453
|
+
chunk_days: AVAILABILITY_RANGE_DAYS,
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const user = await resolvePerson(opts, serviceBaseUrl);
|
|
458
|
+
const rows = await queryPersonAvailability(user, range, serviceBaseUrl);
|
|
459
|
+
printImResult(opts.json, {
|
|
460
|
+
ok: true,
|
|
461
|
+
scope: 'person',
|
|
462
|
+
target: user,
|
|
463
|
+
filter: {
|
|
464
|
+
start_date: range.startDate,
|
|
465
|
+
end_date: range.endDate,
|
|
466
|
+
time_zone: 'Asia/Shanghai',
|
|
467
|
+
},
|
|
468
|
+
rows,
|
|
469
|
+
summary: { events: rows.length },
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
attachSharedShortcutOptions(parent
|
|
473
|
+
.command('+calendar')
|
|
474
|
+
.description('查看指定日历在日期范围内的日程')
|
|
475
|
+
.requiredOption('--calendar-id <id>', '日历 UUID')
|
|
476
|
+
.option('--start-date <ymd>', '开始日期 YYYY-MM-DD,默认今天')
|
|
477
|
+
.option('--end-date <ymd>', '结束日期 YYYY-MM-DD,默认与开始日期相同')).action(async (opts, cmd) => {
|
|
478
|
+
const calendarId = opts.calendarId?.trim();
|
|
479
|
+
if (!calendarId) {
|
|
480
|
+
throw new VertuError('--calendar-id 为必填。');
|
|
481
|
+
}
|
|
482
|
+
const range = resolveDateRange(opts);
|
|
483
|
+
const query = { start: range.start, end: range.endExclusive };
|
|
484
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
485
|
+
if (isDryRun(opts)) {
|
|
486
|
+
printPreview(opts, `${serviceBaseUrl}/v1/calendar`, {
|
|
487
|
+
...query,
|
|
488
|
+
client_filter_calendar_id: calendarId,
|
|
489
|
+
});
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const payload = await imRequest('GET', '/v1/calendar', {
|
|
493
|
+
baseUrl: serviceBaseUrl,
|
|
494
|
+
query,
|
|
495
|
+
});
|
|
496
|
+
printImResult(opts.json, decorateEvents(payload, 'calendar', range, calendarId));
|
|
497
|
+
});
|
|
498
|
+
attachSharedShortcutOptions(parent.command('+calendars').description('列出我有权查看的全部日历及成员')).action(async (opts, cmd) => {
|
|
499
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
500
|
+
if (isDryRun(opts)) {
|
|
501
|
+
printPreview(opts, `${serviceBaseUrl}/v1/calendar/calendars`, {});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
printImResult(opts.json, await imRequest('GET', '/v1/calendar/calendars', {
|
|
505
|
+
baseUrl: serviceBaseUrl,
|
|
506
|
+
}));
|
|
507
|
+
});
|
|
508
|
+
attachSharedShortcutOptions(attachCalendarMutationOptions(parent.command('+create').description('创建日程或会议'), false)).action(async (opts, cmd) => {
|
|
509
|
+
const body = await calendarMutationBody(opts);
|
|
510
|
+
validateCreateBody(body);
|
|
511
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
512
|
+
if (isDryRun(opts)) {
|
|
513
|
+
printMutationPreview(opts, 'POST', `${serviceBaseUrl}/v1/calendar`, body);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
printImResult(opts.json, await imRequest('POST', '/v1/calendar', {
|
|
517
|
+
baseUrl: serviceBaseUrl,
|
|
518
|
+
body,
|
|
519
|
+
}));
|
|
520
|
+
});
|
|
521
|
+
attachSharedShortcutOptions(parent
|
|
522
|
+
.command('+response')
|
|
523
|
+
.description('查看我对某个日程邀请的当前响应状态')
|
|
524
|
+
.requiredOption('--event-id <id>', '日程 UUID')).action(async (opts, cmd) => {
|
|
525
|
+
const eventId = nonEmpty(opts.eventId, '--event-id');
|
|
526
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
527
|
+
const endpoint = `/v1/calendar/${encodeURIComponent(eventId)}/response`;
|
|
528
|
+
if (isDryRun(opts)) {
|
|
529
|
+
printPreview(opts, `${serviceBaseUrl}${endpoint}`, {});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
printImResult(opts.json, await imRequest('GET', endpoint, {
|
|
533
|
+
baseUrl: serviceBaseUrl,
|
|
534
|
+
}));
|
|
535
|
+
});
|
|
536
|
+
attachSharedShortcutOptions(parent
|
|
537
|
+
.command('+respond')
|
|
538
|
+
.description('同意、拒绝或暂定一个日程邀请')
|
|
539
|
+
.requiredOption('--event-id <id>', '日程 UUID')
|
|
540
|
+
.requiredOption('--status <status>', 'accepted|declined|tentative')).action(async (opts, cmd) => {
|
|
541
|
+
const eventId = nonEmpty(opts.eventId, '--event-id');
|
|
542
|
+
const status = responseStatus(opts.status);
|
|
543
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
544
|
+
const endpoint = `/v1/calendar/${encodeURIComponent(eventId)}/response`;
|
|
545
|
+
const body = { status };
|
|
546
|
+
if (isDryRun(opts)) {
|
|
547
|
+
printMutationPreview(opts, 'PATCH', `${serviceBaseUrl}${endpoint}`, body);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
printImResult(opts.json, await imRequest('PATCH', endpoint, {
|
|
551
|
+
baseUrl: serviceBaseUrl,
|
|
552
|
+
body,
|
|
553
|
+
}));
|
|
554
|
+
});
|
|
555
|
+
attachSharedShortcutOptions(attachCalendarMutationOptions(parent.command('+update').description('局部修改日程;未提供的字段保持不变'), true)).action(async (opts, cmd) => {
|
|
556
|
+
const eventId = nonEmpty(opts.eventId, '--event-id');
|
|
557
|
+
const body = await calendarMutationBody(opts);
|
|
558
|
+
validateUpdateBody(body);
|
|
559
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
560
|
+
const endpoint = `/v1/calendar/${encodeURIComponent(eventId)}`;
|
|
561
|
+
if (isDryRun(opts)) {
|
|
562
|
+
printMutationPreview(opts, 'PATCH', `${serviceBaseUrl}${endpoint}`, body);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
printImResult(opts.json, await imRequest('PATCH', endpoint, {
|
|
566
|
+
baseUrl: serviceBaseUrl,
|
|
567
|
+
body,
|
|
568
|
+
}));
|
|
569
|
+
});
|
|
570
|
+
attachSharedShortcutOptions(parent
|
|
571
|
+
.command('+availability')
|
|
572
|
+
.description('查看多人忙闲(单次最多 50 人、8 天)')
|
|
573
|
+
.requiredOption('--user-ids <list>', 'user_id,逗号分隔或 JSON 数组')
|
|
574
|
+
.option('--start-date <ymd>', '开始日期 YYYY-MM-DD,默认今天')
|
|
575
|
+
.option('--end-date <ymd>', '结束日期 YYYY-MM-DD,默认与开始日期相同')
|
|
576
|
+
.option('--exclude-event-id <id>', '排除正在编辑的事件 UUID')).action(async (opts, cmd) => {
|
|
577
|
+
const userIds = [...new Set(parseUserIdList(opts.userIds, '--user-ids'))];
|
|
578
|
+
if (userIds.length > 50) {
|
|
579
|
+
throw new VertuError('--user-ids 最多 50 人。');
|
|
580
|
+
}
|
|
581
|
+
const range = resolveDateRange(opts, AVAILABILITY_RANGE_DAYS);
|
|
582
|
+
const query = {
|
|
583
|
+
user_ids: userIds.join(','),
|
|
584
|
+
start: range.start,
|
|
585
|
+
end: range.endExclusive,
|
|
586
|
+
exclude_event_id: opts.excludeEventId?.trim() || undefined,
|
|
587
|
+
};
|
|
588
|
+
const serviceBaseUrl = baseUrl(opts, cmd);
|
|
589
|
+
if (isDryRun(opts)) {
|
|
590
|
+
printPreview(opts, `${serviceBaseUrl}/v1/calendar/availability`, query);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
printImResult(opts.json, await imRequest('GET', '/v1/calendar/availability', {
|
|
594
|
+
baseUrl: serviceBaseUrl,
|
|
595
|
+
query,
|
|
596
|
+
}));
|
|
597
|
+
});
|
|
598
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { CLI_BIN } from '../core/cli-bin.js';
|
|
3
|
+
import { fetchExecuteCatalog } from '../core/execute-catalog-client.js';
|
|
4
|
+
import { resolveVpsServiceUrl } from '../core/sales-query-client.js';
|
|
5
|
+
/** 让 Agent 先查活目录,而不是依赖本机 --help 或过期 Skill 表。 */
|
|
6
|
+
export function buildCatalogCommand() {
|
|
7
|
+
return new Command('catalog')
|
|
8
|
+
.description('列出当前企业身份可用的命令(服务端裁剪,无需升级 CLI)')
|
|
9
|
+
.option('--domain <name>', '只看某个域,如 sales / im / hr')
|
|
10
|
+
.option('--json', '机读输出', true)
|
|
11
|
+
.option('--no-json', '人类可读表格')
|
|
12
|
+
.action(async (opts, cmd) => {
|
|
13
|
+
const merged = cmd.optsWithGlobals();
|
|
14
|
+
const catalog = await fetchExecuteCatalog(opts.baseUrl ?? merged.baseUrl ?? resolveVpsServiceUrl(), opts.domain);
|
|
15
|
+
if (opts.json !== false) {
|
|
16
|
+
console.log(JSON.stringify(catalog, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(`企业类型 : ${catalog.tenant_type}`);
|
|
20
|
+
console.log(`产品包 : ${catalog.product_profile}`);
|
|
21
|
+
console.log(`目录版本 : ${catalog.revision}`);
|
|
22
|
+
console.log('');
|
|
23
|
+
for (const item of catalog.shortcuts) {
|
|
24
|
+
const cli = item.cli ? `${CLI_BIN} ${item.cli}` : `${CLI_BIN} exec --skill ${item.skill_key} --script ${item.script_key}`;
|
|
25
|
+
console.log(`${item.domain}\t${item.script_key}\t${item.summary}\t${cli}`);
|
|
26
|
+
}
|
|
27
|
+
console.log(`\n共 ${catalog.count} 条。transport=execute 的条目用 ${CLI_BIN} exec 执行。`);
|
|
28
|
+
});
|
|
29
|
+
}
|