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,36 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachSharedShortcutOptions, runSkillShortcutExecute, } from '../shortcut-common.js';
|
|
3
|
+
const SKILL = 'vps-knowledge';
|
|
4
|
+
function action(parent, name, description, script, configure) {
|
|
5
|
+
let command = parent.command(name).description(description);
|
|
6
|
+
if (configure)
|
|
7
|
+
command = configure(command);
|
|
8
|
+
attachSharedShortcutOptions(command).action(async (opts, cmd) => {
|
|
9
|
+
await runSkillShortcutExecute(SKILL, script, {
|
|
10
|
+
...(opts.baseId ? { base_id: opts.baseId } : {}),
|
|
11
|
+
...(opts.name ? { name: opts.name } : {}),
|
|
12
|
+
...(opts.description !== undefined ? { description: opts.description } : {}),
|
|
13
|
+
...(opts.visibility ? { visibility: opts.visibility } : {}),
|
|
14
|
+
...(opts.vDriveItemId ? { v_drive_item_id: opts.vDriveItemId } : {}),
|
|
15
|
+
...(opts.question ? { question: opts.question } : {}),
|
|
16
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
17
|
+
...(opts.node ? { node: opts.node } : {}),
|
|
18
|
+
...(opts.keywords ? { keywords: opts.keywords } : {}),
|
|
19
|
+
...(opts.topN ? { top_n: Number(opts.topN) } : {}),
|
|
20
|
+
}, opts, cmd);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function buildKnowledgeCommand() {
|
|
24
|
+
const command = new Command('knowledge').description('VPS 企业知识库、资料检索、问答与知识图谱。');
|
|
25
|
+
action(command, '+list', '列出有权访问的知识库', 'list_bases');
|
|
26
|
+
action(command, '+create', '创建知识库', 'create_base', (cmd) => cmd.requiredOption('--name <name>').option('--description <text>').option('--visibility <value>', 'private|tenant'));
|
|
27
|
+
action(command, '+update', '更新知识库', 'update_base', (cmd) => cmd.requiredOption('--base-id <id>').option('--name <name>').option('--description <text>').option('--visibility <value>', 'private|tenant'));
|
|
28
|
+
action(command, '+delete', '删除知识库', 'delete_base', (cmd) => cmd.requiredOption('--base-id <id>'));
|
|
29
|
+
action(command, '+documents', '列出知识库资料', 'list_documents', (cmd) => cmd.requiredOption('--base-id <id>'));
|
|
30
|
+
action(command, '+import-vdrive', '从 V盘导入资料', 'import_vdrive', (cmd) => cmd.requiredOption('--base-id <id>').requiredOption('--v-drive-item-id <id>'));
|
|
31
|
+
action(command, '+search', '检索知识库', 'search', (cmd) => cmd.requiredOption('--base-id <id>').requiredOption('--question <text>'));
|
|
32
|
+
action(command, '+ask', '基于知识库回答问题', 'ask', (cmd) => cmd.requiredOption('--base-id <id>').requiredOption('--question <text>').option('--model <name>'));
|
|
33
|
+
action(command, '+graph', '读取知识星图', 'graph', (cmd) => cmd.requiredOption('--base-id <id>').option('--node <id>').option('--keywords <text>').option('--top-n <n>'));
|
|
34
|
+
action(command, '+build-graph', '生成知识图谱', 'build_graph', (cmd) => cmd.requiredOption('--base-id <id>'));
|
|
35
|
+
return command;
|
|
36
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { promptSecret, promptText, normalizeBaseUrl } from '../utils/prompt.js';
|
|
3
|
+
import { bindVpsServiceAgentCredential, defaultVpsServiceLoginUrl, loginVpsServiceWithPassword, persistVpsServiceAgentCredential, persistVpsServiceSession, pickVpsServiceUserInfo, } from '../core/vps-service-auth.js';
|
|
4
|
+
import { DEFAULT_AGENT_APP_ID, VERTU_APP_ID_ENV } from '../core/vertu-agent-env.js';
|
|
5
|
+
import { readVpsServiceConfig } from '../core/vps-service-config.js';
|
|
6
|
+
import { resolveVpsServiceUrl } from '../core/sales-query-client.js';
|
|
7
|
+
export function buildLoginCommand() {
|
|
8
|
+
return new Command('login')
|
|
9
|
+
.description('人工交互使用 Vertu 账号密码登录;Agent/Bot/Hermes 禁止使用此命令,应使用宿主注入的 app_key。')
|
|
10
|
+
.option('--service-url <url>', 'Vertu 服务地址(默认正式环境,可用 --base-url 同义覆盖)')
|
|
11
|
+
.option('--base-url <url>', '同 --service-url(兼容旧参数名)')
|
|
12
|
+
.option('--login <user>', 'VPS 登录名(邮箱或用户名)')
|
|
13
|
+
.option('--password <secret>', '人工登录密码(Agent/Bot/Hermes 禁止传入)')
|
|
14
|
+
.option('--app-id <id>', `智能体 app_id(cursor / openclaw / hermes,默认 ${DEFAULT_AGENT_APP_ID})`)
|
|
15
|
+
.option('--no-agent-bind', '登录后不签发 Agent app_key')
|
|
16
|
+
.action(async (opts, cmd) => {
|
|
17
|
+
const merged = cmd.optsWithGlobals();
|
|
18
|
+
const existingVps = await readVpsServiceConfig();
|
|
19
|
+
const serviceUrlOverride = opts.serviceUrl ?? opts.baseUrl ?? merged.baseUrl ?? merged.serviceUrl;
|
|
20
|
+
const serviceUrlInput = serviceUrlOverride ??
|
|
21
|
+
existingVps.baseUrl ??
|
|
22
|
+
resolveVpsServiceUrl() ??
|
|
23
|
+
(await promptText('Vertu 服务地址', defaultVpsServiceLoginUrl()));
|
|
24
|
+
const serviceUrl = normalizeBaseUrl(serviceUrlInput);
|
|
25
|
+
const login = opts.login ?? existingVps.login ?? (await promptText('账号(邮箱 / 用户名)'));
|
|
26
|
+
const password = opts.password ?? (await promptSecret('密码'));
|
|
27
|
+
const agentAppId = opts.appId?.trim() ??
|
|
28
|
+
process.env[VERTU_APP_ID_ENV]?.trim() ??
|
|
29
|
+
DEFAULT_AGENT_APP_ID;
|
|
30
|
+
const session = await loginVpsServiceWithPassword(serviceUrl, login, password);
|
|
31
|
+
await persistVpsServiceSession(serviceUrl, session);
|
|
32
|
+
const userInfo = pickVpsServiceUserInfo(session.user);
|
|
33
|
+
console.log(`已登录:${session.login}${userInfo.employeeName ? `(${userInfo.employeeName})` : ''}`);
|
|
34
|
+
if (userInfo.departmentPath ?? userInfo.departmentName) {
|
|
35
|
+
console.log(`部门:${userInfo.departmentPath ?? userInfo.departmentName}`);
|
|
36
|
+
}
|
|
37
|
+
console.log(`服务:${serviceUrl}`);
|
|
38
|
+
console.log('会话:已保存到本机配置');
|
|
39
|
+
const shouldBindAgent = opts.agentBind !== false;
|
|
40
|
+
if (shouldBindAgent) {
|
|
41
|
+
const appId = agentAppId;
|
|
42
|
+
try {
|
|
43
|
+
const bound = await bindVpsServiceAgentCredential(serviceUrl, session.token, appId);
|
|
44
|
+
await persistVpsServiceAgentCredential(serviceUrl, bound);
|
|
45
|
+
console.log(`Agent:app_id=${bound.appId}(宿主执行 \`vps-work agent env\` 获取环境变量)`);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.warn(`Agent 绑定跳过:${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clearVpsServiceSession, logoutVpsServiceRemote } from '../core/vps-service-auth.js';
|
|
3
|
+
export function buildLogoutCommand() {
|
|
4
|
+
return new Command('logout')
|
|
5
|
+
.description('退出 VPS 登录并清除本机会话。')
|
|
6
|
+
.option('--forget', '同时移除本地保存的登录名')
|
|
7
|
+
.action(async (opts) => {
|
|
8
|
+
await logoutVpsServiceRemote();
|
|
9
|
+
await clearVpsServiceSession(opts.forget === true);
|
|
10
|
+
console.log('已退出 VPS 登录(会话已清除)。');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { VertuError } from '../../core/errors.js';
|
|
3
|
+
import { printImResult } from '../../core/im-client.js';
|
|
4
|
+
import { downloadMailFile, inspectMailFile, mailRequest, mailUrl, uploadMailFile } from '../../core/mail-client.js';
|
|
5
|
+
const collect = (value, previous) => [...previous, value];
|
|
6
|
+
function addresses(values = []) {
|
|
7
|
+
return values.flatMap((value) => value.split(',')).map((value) => {
|
|
8
|
+
const email = value.trim();
|
|
9
|
+
if (!/^[^\s<>@,]+@[^\s<>@,]+\.[^\s<>@,]+$/.test(email))
|
|
10
|
+
throw new VertuError(`邮箱地址无效:${email}`);
|
|
11
|
+
return { email };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function preview(opts, method, path, body, query) {
|
|
15
|
+
if (!opts.dryRun && !opts.validateOnly)
|
|
16
|
+
return false;
|
|
17
|
+
printImResult(opts.json, { validateOnly: true, method, endpoint: mailUrl(path, opts, query), ...(body ? { body } : {}) });
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
export function attachEmailCommands(parent) {
|
|
21
|
+
const command = (name, description) => parent.command(name).description(description)
|
|
22
|
+
.option('--mailbox-id <uuid>', '部门邮箱 UUID;省略时使用本人个人邮箱')
|
|
23
|
+
.option('--base-url <url>', 'VPS 服务地址')
|
|
24
|
+
.option('--dry-run', '仅预览,不发起请求')
|
|
25
|
+
.option('--validate-only', '同 --dry-run')
|
|
26
|
+
.option('--no-json', '紧凑 JSON 输出');
|
|
27
|
+
const options = (cmd) => {
|
|
28
|
+
const opts = cmd.optsWithGlobals();
|
|
29
|
+
if (opts.mailboxId && !/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i.test(opts.mailboxId)) {
|
|
30
|
+
throw new VertuError('--mailbox-id 必须是部门邮箱 UUID。');
|
|
31
|
+
}
|
|
32
|
+
return opts;
|
|
33
|
+
};
|
|
34
|
+
for (const [name, path, description] of [
|
|
35
|
+
['+account', '/account', '查看当前邮箱及权限'],
|
|
36
|
+
['+mailboxes', '/shared-mailboxes', '列出可见的部门邮箱及成员角色'],
|
|
37
|
+
['+folders', '/folders', '列出文件夹、邮件数与未读数'],
|
|
38
|
+
['+identities', '/identities', '列出当前邮箱允许使用的发件身份'],
|
|
39
|
+
]) {
|
|
40
|
+
command(name, description).action(async (_, cmd) => {
|
|
41
|
+
const opts = options(cmd);
|
|
42
|
+
if (preview(opts, 'GET', path))
|
|
43
|
+
return;
|
|
44
|
+
printImResult(opts.json, await mailRequest('GET', path, opts));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
command('+messages', '查询已收取的邮件,支持搜索、文件夹与游标翻页')
|
|
48
|
+
.option('--folder-id <id>', '文件夹 ID(见 +folders)').option('--search <text>', '搜索文字')
|
|
49
|
+
.option('--limit <n>', '每页数量,1–100', '50').option('--cursor <cursor>', '上一页 next_cursor')
|
|
50
|
+
.option('--unread-only', '只查询未读邮件')
|
|
51
|
+
.action(async (_, cmd) => {
|
|
52
|
+
const opts = options(cmd), limit = Number(opts.limit);
|
|
53
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
54
|
+
throw new VertuError('--limit 必须是 1–100 的整数。');
|
|
55
|
+
const query = { folder_id: opts.folderId, search: opts.search, limit, cursor: opts.cursor, unread: opts.unreadOnly ? true : undefined };
|
|
56
|
+
if (preview(opts, 'GET', '/messages', undefined, query))
|
|
57
|
+
return;
|
|
58
|
+
printImResult(opts.json, await mailRequest('GET', '/messages', opts, undefined, query));
|
|
59
|
+
});
|
|
60
|
+
for (const [name, resource, description] of [
|
|
61
|
+
['+read', 'messages', '读取邮件正文与附件信息(不自动标记已读)'],
|
|
62
|
+
['+submission', 'submissions', '查询发送提交状态'],
|
|
63
|
+
]) {
|
|
64
|
+
command(name, description).requiredOption('--id <id>', '邮件或发送提交 ID')
|
|
65
|
+
.action(async (_, cmd) => {
|
|
66
|
+
const opts = options(cmd), path = `/${resource}/${encodeURIComponent(opts.id)}`;
|
|
67
|
+
if (preview(opts, 'GET', path))
|
|
68
|
+
return;
|
|
69
|
+
printImResult(opts.json, await mailRequest('GET', path, opts));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
command('+mark-read', '标记邮件已读(只读成员不可用)').requiredOption('--id <id>', '邮件 ID')
|
|
73
|
+
.action(async (_, cmd) => {
|
|
74
|
+
const opts = options(cmd), path = `/messages/${encodeURIComponent(opts.id)}`;
|
|
75
|
+
if (preview(opts, 'PATCH', path, { unread: false }))
|
|
76
|
+
return;
|
|
77
|
+
printImResult(opts.json, await mailRequest('PATCH', path, opts, { unread: false }));
|
|
78
|
+
});
|
|
79
|
+
for (const draft of [false, true]) {
|
|
80
|
+
const c = command(draft ? '+draft' : '+send-email', draft ? '保存邮件草稿,不发送' : '发送真实电子邮件(保留 +send 的 IM 私信兼容行为)')
|
|
81
|
+
.requiredOption('--to <email>', '收件地址,可重复或逗号分隔', collect, [])
|
|
82
|
+
.option('--cc <email>', '抄送地址,可重复', collect, []).option('--bcc <email>', '密送地址,可重复', collect, [])
|
|
83
|
+
.requiredOption('--subject <text>', '邮件主题').option('--body <text>', '纯文本正文')
|
|
84
|
+
.option('--body-file <path>', '从 UTF-8 文件读取正文(与 --body 互斥)')
|
|
85
|
+
.option('--body-html-file <path>', '从 UTF-8 文件读取 HTML 正文,保留纯文本正文作为后备')
|
|
86
|
+
.option('--attachment-id <id>', '+upload 返回的附件 ID,可重复;须属于同一邮箱', collect, []);
|
|
87
|
+
if (!draft)
|
|
88
|
+
c.requiredOption('--request-id <id>', '8–128 字符的发送请求编号;中断重试必须复用同一编号和内容')
|
|
89
|
+
.option('--identity-id <id>', '+identities 返回的发件身份 ID')
|
|
90
|
+
.option('--reply-to <message-id>', '回复原邮件 ID,保留邮件会话引用');
|
|
91
|
+
c.action(async (_, cmd) => {
|
|
92
|
+
const opts = options(cmd);
|
|
93
|
+
if (opts.body !== undefined && opts.bodyFile)
|
|
94
|
+
throw new VertuError('--body 与 --body-file 不能同时使用。');
|
|
95
|
+
if (opts.body === undefined && !opts.bodyFile)
|
|
96
|
+
throw new VertuError('请提供 --body 或 --body-file。');
|
|
97
|
+
if (!draft && (!opts.requestId || opts.requestId.length < 8 || opts.requestId.length > 128))
|
|
98
|
+
throw new VertuError('--request-id 长度必须为 8–128。');
|
|
99
|
+
const body = { to: addresses(opts.to), cc: addresses(opts.cc), bcc: addresses(opts.bcc), subject: opts.subject,
|
|
100
|
+
body_text: opts.bodyFile ? await readFile(opts.bodyFile, 'utf8') : opts.body,
|
|
101
|
+
...(opts.bodyHtmlFile ? { body_html: await readFile(opts.bodyHtmlFile, 'utf8') } : {}),
|
|
102
|
+
attachment_ids: opts.attachmentId,
|
|
103
|
+
...(!draft ? { client_request_id: opts.requestId, identity_id: opts.identityId, reply_to_message_id: opts.replyTo } : {}),
|
|
104
|
+
};
|
|
105
|
+
const path = draft ? '/drafts' : '/messages/send';
|
|
106
|
+
if (preview(opts, 'POST', path, body))
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
printImResult(opts.json, await mailRequest('POST', path, opts, body));
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
throw new VertuError(`${e instanceof Error ? e.message : String(e)}${draft ? '' : `;发送结果可能待确认,请使用原 request-id ${opts.requestId} 和相同内容重试,不要改用新编号。`}`);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
command('+upload', '流式上传邮件附件,返回供草稿/发送使用的附件 ID')
|
|
117
|
+
.requiredOption('--file <path>', '本地附件路径,最大 1 GiB')
|
|
118
|
+
.action(async (_, cmd) => {
|
|
119
|
+
const opts = options(cmd), info = await inspectMailFile(opts.file);
|
|
120
|
+
if (preview(opts, 'POST', '/attachments', info))
|
|
121
|
+
return;
|
|
122
|
+
printImResult(opts.json, await uploadMailFile(opts.file, opts));
|
|
123
|
+
});
|
|
124
|
+
command('+download', '流式下载邮件附件;目标文件已存在时拒绝覆盖')
|
|
125
|
+
.requiredOption('--id <blob-id>', '+read 返回的附件 blob_id').requiredOption('--output <path>', '本地输出路径')
|
|
126
|
+
.action(async (_, cmd) => {
|
|
127
|
+
const opts = options(cmd), path = `/attachments/${encodeURIComponent(opts.id)}`;
|
|
128
|
+
if (preview(opts, 'GET', path, { output: opts.output }))
|
|
129
|
+
return;
|
|
130
|
+
printImResult(opts.json, await downloadMailFile(opts.id, opts.output, opts));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachEmailCommands } from './email.js';
|
|
3
|
+
import { attachMailShortcuts } from './shortcuts.js';
|
|
4
|
+
export function buildMailCommand() {
|
|
5
|
+
const mail = new Command('mail').description('企业邮箱收发、附件与部门邮箱;+send 保留旧 IM 私信兼容。');
|
|
6
|
+
attachMailShortcuts(mail);
|
|
7
|
+
attachEmailCommands(mail);
|
|
8
|
+
return mail;
|
|
9
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { VertuError } from '../../core/errors.js';
|
|
2
|
+
import { imRequest, printImResult } from '../../core/im-client.js';
|
|
3
|
+
import { attachSharedShortcutOptions, mergeShortcutParams, parseOptionalInt, } from '../shortcut-common.js';
|
|
4
|
+
import { buildImMessagePayload } from '../im/im-message-payload.js';
|
|
5
|
+
function collectAttach(value, previous) {
|
|
6
|
+
return [...previous, value];
|
|
7
|
+
}
|
|
8
|
+
export function attachMailShortcuts(parent) {
|
|
9
|
+
attachSharedShortcutOptions(parent
|
|
10
|
+
.command('+send')
|
|
11
|
+
.description('发送一对一私信(Vertu IM,自动创建/复用私聊)')
|
|
12
|
+
.requiredOption('--user-id <id>', '收件人 res.users id')
|
|
13
|
+
.option('--body <text>', '消息正文')
|
|
14
|
+
.option('--body-file <path>', '从文件读正文')
|
|
15
|
+
.option('--attach <path>', '附件路径,可重复', collectAttach, [])
|
|
16
|
+
.option('--attachments-json <json>', '附件 JSON 数组')).action(async (opts, cmd) => {
|
|
17
|
+
const userId = parseOptionalInt(opts.userId, '--user-id');
|
|
18
|
+
if (userId === undefined) {
|
|
19
|
+
throw new VertuError('Invalid --user-id.');
|
|
20
|
+
}
|
|
21
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
22
|
+
const serviceBaseUrl = opts.baseUrl ?? globalOpts.baseUrl;
|
|
23
|
+
const message = await buildImMessagePayload({ ...opts, baseUrl: serviceBaseUrl });
|
|
24
|
+
const merged = await mergeShortcutParams({
|
|
25
|
+
body: message.body,
|
|
26
|
+
attachments: message.attachments,
|
|
27
|
+
}, opts);
|
|
28
|
+
const created = await imRequest('POST', '/v1/im/channels', {
|
|
29
|
+
baseUrl: serviceBaseUrl,
|
|
30
|
+
body: { channel_type: 'direct', user_id: userId },
|
|
31
|
+
});
|
|
32
|
+
const channel = typeof created === 'object' && created !== null && 'channel' in created
|
|
33
|
+
? created.channel
|
|
34
|
+
: null;
|
|
35
|
+
if (!channel || typeof channel.id !== 'string') {
|
|
36
|
+
throw new VertuError('Vertu IM 未返回 channel.id。');
|
|
37
|
+
}
|
|
38
|
+
printImResult(opts.json, await imRequest('POST', `/v1/im/channels/${encodeURIComponent(channel.id)}/messages`, {
|
|
39
|
+
baseUrl: serviceBaseUrl,
|
|
40
|
+
body: merged,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachMeetingShortcuts } from './shortcuts.js';
|
|
3
|
+
export function buildMeetingCommand() {
|
|
4
|
+
const meeting = new Command('meeting').description('Meeting center: recordings, transcripts, minutes, summaries, action items and statistics.');
|
|
5
|
+
attachMeetingShortcuts(meeting);
|
|
6
|
+
return meeting;
|
|
7
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { VertuError } from '../../core/errors.js';
|
|
2
|
+
import { vpsServiceGet } from '../../core/sales-query-client.js';
|
|
3
|
+
import { attachSharedShortcutOptions, resolveShortcutVpsServiceUrl, } from '../shortcut-common.js';
|
|
4
|
+
function assertYmd(raw, flag) {
|
|
5
|
+
const value = raw?.trim();
|
|
6
|
+
if (!value)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
9
|
+
throw new VertuError(`${flag} 需为 YYYY-MM-DD。`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function resolveScope(raw) {
|
|
14
|
+
const value = raw?.trim().toLowerCase() || 'mine';
|
|
15
|
+
if (value === 'team')
|
|
16
|
+
return 'subordinates';
|
|
17
|
+
if (value === 'mine' || value === 'subordinates' || value === 'all' || value === 'selection') {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
throw new VertuError('--scope 仅支持 mine、subordinates、all 或 selection。');
|
|
21
|
+
}
|
|
22
|
+
function csvIds(raw, flag) {
|
|
23
|
+
if (!raw?.trim())
|
|
24
|
+
return undefined;
|
|
25
|
+
const values = raw.split(',').map((value) => Number(value.trim()));
|
|
26
|
+
if (values.some((value) => !Number.isInteger(value) || value <= 0)) {
|
|
27
|
+
throw new VertuError(`${flag} 需为逗号分隔的正整数。`);
|
|
28
|
+
}
|
|
29
|
+
return [...new Set(values)].join(',');
|
|
30
|
+
}
|
|
31
|
+
function positiveInt(raw, flag) {
|
|
32
|
+
if (!raw?.trim())
|
|
33
|
+
return undefined;
|
|
34
|
+
const value = Number(raw);
|
|
35
|
+
if (!Number.isInteger(value) || value <= 0)
|
|
36
|
+
throw new VertuError(`${flag} 需为正整数。`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function statisticsGroupBy(raw) {
|
|
40
|
+
const value = raw?.trim().toLowerCase() || 'department';
|
|
41
|
+
if (value === 'department' ||
|
|
42
|
+
value === 'template' ||
|
|
43
|
+
value === 'department_template') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
throw new VertuError('--group-by 仅支持 department、template 或 department_template。');
|
|
47
|
+
}
|
|
48
|
+
export function attachMeetingShortcuts(parent) {
|
|
49
|
+
attachSharedShortcutOptions(parent
|
|
50
|
+
.command('+list')
|
|
51
|
+
.alias('+meetings')
|
|
52
|
+
.description('查询轻量会议列表;逐字稿、纪要和待办请使用 meeting +detail')
|
|
53
|
+
.option('--scope <scope>', 'mine(本人)、subordinates(递归下属)、all(全域)或 selection(指定范围)', 'mine')
|
|
54
|
+
.option('--employee-name <name>', '按员工姓名精确查询(仍受会议权限限制)')
|
|
55
|
+
.option('--user-ids <ids>', '指定人员 user_id,逗号分隔;配合 selection')
|
|
56
|
+
.option('--department-ids <ids>', '指定部门 ID,逗号分隔并包含子部门;配合 selection')
|
|
57
|
+
.option('--start-date <ymd>', 'YYYY-MM-DD,省略=近 30 天')
|
|
58
|
+
.option('--end-date <ymd>', 'YYYY-MM-DD,省略=今天')
|
|
59
|
+
.option('--template-type <type>', '按纪要模版类型精确筛选(如:团队内部会议)')
|
|
60
|
+
.option('--keyword <text>', '搜索会议 ID、主题或摘要')
|
|
61
|
+
.option('--page <number>', '页码', '1')
|
|
62
|
+
.option('--page-size <number>', '每页数量(1-50)', '20')).action(async (opts, cmd) => {
|
|
63
|
+
const scope = resolveScope(opts.scope);
|
|
64
|
+
const userIds = csvIds(opts.userIds, '--user-ids');
|
|
65
|
+
const departmentIds = csvIds(opts.departmentIds, '--department-ids');
|
|
66
|
+
if (scope === 'selection' && !opts.employeeName?.trim() && !userIds && !departmentIds) {
|
|
67
|
+
throw new VertuError('selection 范围需要 --user-ids 或 --department-ids。');
|
|
68
|
+
}
|
|
69
|
+
const pageSize = positiveInt(opts.pageSize, '--page-size');
|
|
70
|
+
if (pageSize && pageSize > 50)
|
|
71
|
+
throw new VertuError('--page-size 不能超过 50。');
|
|
72
|
+
const result = await vpsServiceGet('/v1/meeting-center/meetings', {
|
|
73
|
+
scope,
|
|
74
|
+
employee_name: opts.employeeName?.trim() || undefined,
|
|
75
|
+
user_ids: userIds,
|
|
76
|
+
department_ids: departmentIds,
|
|
77
|
+
start_date: assertYmd(opts.startDate, '--start-date'),
|
|
78
|
+
end_date: assertYmd(opts.endDate, '--end-date'),
|
|
79
|
+
template_type: opts.templateType?.trim() || undefined,
|
|
80
|
+
keyword: opts.keyword?.trim() || undefined,
|
|
81
|
+
page: positiveInt(opts.page, '--page'),
|
|
82
|
+
page_size: pageSize,
|
|
83
|
+
}, resolveShortcutVpsServiceUrl(opts, cmd), 'Vertu 会议中心');
|
|
84
|
+
console.log(JSON.stringify(result, null, opts.json === false ? 0 : 2));
|
|
85
|
+
});
|
|
86
|
+
attachSharedShortcutOptions(parent
|
|
87
|
+
.command('+statistics')
|
|
88
|
+
.description('按部门或会议模版统计会议、待办闭环和日程来源指标')
|
|
89
|
+
.option('--department-ids <ids>', '指定部门 ID,逗号分隔并包含子部门')
|
|
90
|
+
.option('--all-departments', '查询全部门;分组维度由 --group-by 决定', false)
|
|
91
|
+
.option('--group-by <dimension>', 'department、template 或 department_template', 'department')
|
|
92
|
+
.option('--start-date <ymd>', 'YYYY-MM-DD,省略=近 30 天')
|
|
93
|
+
.option('--end-date <ymd>', 'YYYY-MM-DD,省略=今天')
|
|
94
|
+
.option('--template-type <type>', '仅统计指定会议模版')).action(async (opts, cmd) => {
|
|
95
|
+
const departmentIds = csvIds(opts.departmentIds, '--department-ids');
|
|
96
|
+
if (opts.allDepartments === Boolean(departmentIds)) {
|
|
97
|
+
throw new VertuError('--all-departments 与 --department-ids 必须且只能指定一项。');
|
|
98
|
+
}
|
|
99
|
+
const groupBy = statisticsGroupBy(opts.groupBy);
|
|
100
|
+
const result = await vpsServiceGet('/v1/meeting-center/statistics', {
|
|
101
|
+
department_ids: departmentIds,
|
|
102
|
+
all_departments: opts.allDepartments ? 'true' : undefined,
|
|
103
|
+
group_by: groupBy,
|
|
104
|
+
start_date: assertYmd(opts.startDate, '--start-date'),
|
|
105
|
+
end_date: assertYmd(opts.endDate, '--end-date'),
|
|
106
|
+
template_type: opts.templateType?.trim() || undefined,
|
|
107
|
+
}, resolveShortcutVpsServiceUrl(opts, cmd), 'Vertu 会议统计');
|
|
108
|
+
console.log(JSON.stringify(result, null, opts.json === false ? 0 : 2));
|
|
109
|
+
});
|
|
110
|
+
attachSharedShortcutOptions(parent
|
|
111
|
+
.command('+detail')
|
|
112
|
+
.description('查询会议详情(逐字稿、手写笔记、总结和待办)')
|
|
113
|
+
.requiredOption('--meeting-id <id>', '会议列表返回的 id 或 minutes_id')).action(async (opts, cmd) => {
|
|
114
|
+
const meetingId = opts.meetingId.trim();
|
|
115
|
+
if (!meetingId)
|
|
116
|
+
throw new VertuError('--meeting-id 不能为空。');
|
|
117
|
+
const result = await vpsServiceGet(`/v1/meeting-center/meetings/${encodeURIComponent(meetingId)}`, {}, resolveShortcutVpsServiceUrl(opts, cmd), 'Vertu 会议详情');
|
|
118
|
+
console.log(JSON.stringify(result, null, opts.json === false ? 0 : 2));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { buildClientFromOpts } from '../core/auth.js';
|
|
2
|
+
import { ODOO_CLI_PROGRAM_SLUG } from '../core/cli-bin.js';
|
|
3
|
+
import { unwrapExecuteEnvelope } from '../utils/unwrap-envelope.js';
|
|
4
|
+
/**
|
|
5
|
+
* 调用 capabilities 中 `invoke_kind=ai_tool` 的叶子(与 `vps-work odoo data search` 等价)。
|
|
6
|
+
*/
|
|
7
|
+
export async function executeOdooAiTool(invokeKey, argvPrefix, args, opts) {
|
|
8
|
+
const { client } = await buildClientFromOpts({
|
|
9
|
+
baseUrlOverride: opts.baseUrl,
|
|
10
|
+
endpointOverride: opts.endpoint,
|
|
11
|
+
requireAuth: true,
|
|
12
|
+
});
|
|
13
|
+
const { result } = await client.jsonRpc('/ai/cli/v1/execute', {
|
|
14
|
+
program: ODOO_CLI_PROGRAM_SLUG,
|
|
15
|
+
invoke_key: invokeKey,
|
|
16
|
+
argv: [...argvPrefix],
|
|
17
|
+
payload: { arguments: args },
|
|
18
|
+
});
|
|
19
|
+
return unwrapExecuteEnvelope(result);
|
|
20
|
+
}
|
|
21
|
+
export { APPROVAL_LOGS, ODOO_ME, ODOO_SEARCH, } from './odoo-leaf-registry.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 遗留 Odoo `/ai/cli/v1/execute` 叶子 `invoke_key` / `argv_prefix` 对齐表(仅 --legacy-odoo-cli 路径使用)。 */
|
|
2
|
+
export const ODOO_SEARCH = {
|
|
3
|
+
invokeKey: 'odoo_search',
|
|
4
|
+
argv: ['odoo', 'data', 'search'],
|
|
5
|
+
};
|
|
6
|
+
export const APPROVAL_LOGS = {
|
|
7
|
+
invokeKey: 'odoo_approval_logs',
|
|
8
|
+
argv: ['odoo', 'approval', 'logs'],
|
|
9
|
+
};
|
|
10
|
+
export const ODOO_ME = {
|
|
11
|
+
invokeKey: 'odoo_me',
|
|
12
|
+
argv: ['odoo', 'me'],
|
|
13
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { readUtf8File, readStdinText } from '../../utils/read-text.js';
|
|
4
|
+
import { applyOfficeFile, createOfficeFile, officeRuntime, readOfficeFile } from '../../core/office-files.js';
|
|
5
|
+
import { officeRequest } from '../../core/office-client.js';
|
|
6
|
+
const print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
7
|
+
async function jsonFile(path) {
|
|
8
|
+
return JSON.parse(path === '-' ? await readStdinText() : await readUtf8File(path));
|
|
9
|
+
}
|
|
10
|
+
export function buildOfficeCommand() {
|
|
11
|
+
const office = new Command('office').description('VPS Office:本地文件生成、结构化读写和桌面编辑会话');
|
|
12
|
+
office.command('+schema').description('获取格式能力、操作说明和生成示例').action(() => print(officeRuntime().capabilities()));
|
|
13
|
+
office.command('+create').description('生成 Office 文件(目标必须不存在)')
|
|
14
|
+
.requiredOption('--output <path>').requiredOption('--spec-file <path>', 'JSON 文件;- 表示 stdin')
|
|
15
|
+
.action(async (opts) => print(await createOfficeFile(opts.output, await jsonFile(opts.specFile))));
|
|
16
|
+
office.command('+read').description('读取文件结构和 SHA-256 版本').requiredOption('--file <path>')
|
|
17
|
+
.action(async (opts) => print(await readOfficeFile(opts.file)));
|
|
18
|
+
office.command('+apply').description('校验版本并整批修改文件;失败不写入')
|
|
19
|
+
.requiredOption('--file <path>').requiredOption('--ops-file <path>').requiredOption('--expected-version <sha256>')
|
|
20
|
+
.option('--output <path>', '另存为新文件').option('--dry-run', '计算修改结果但不写入')
|
|
21
|
+
.action(async (opts) => print(await applyOfficeFile(opts.file, await jsonFile(opts.opsFile), opts.expectedVersion, opts.output, opts.dryRun)));
|
|
22
|
+
const connected = (command) => command.option('--connection-file <path>', '桌面端生成的连接凭据文件');
|
|
23
|
+
connected(office.command('+new').description('在 VPS Office 桌面端新建空白文档').requiredOption('--type <type>', 'docx / xlsx / pptx / md / pdf'))
|
|
24
|
+
.action(async (opts) => {
|
|
25
|
+
if (!['docx', 'xlsx', 'pptx', 'md', 'pdf'].includes(opts.type))
|
|
26
|
+
throw new Error('--type 支持 docx / xlsx / pptx / md / pdf');
|
|
27
|
+
print(await officeRequest('open', { kind: opts.type }, opts.connectionFile));
|
|
28
|
+
});
|
|
29
|
+
connected(office.command('+status').description('检查桌面 Office 桥接状态')).action(async (opts) => print(await officeRequest('status', {}, opts.connectionFile)));
|
|
30
|
+
connected(office.command('+open').description('在 VPS Office 中打开本地文件或 V盘项目')
|
|
31
|
+
.option('--file <path>').option('--item-id <id>')).action(async (opts) => {
|
|
32
|
+
if (Boolean(opts.file) === Boolean(opts.itemId))
|
|
33
|
+
throw new Error('请只指定 --file 或 --item-id。');
|
|
34
|
+
print(await officeRequest('open', opts.file ? { filePath: resolve(opts.file) } : { itemId: opts.itemId }, opts.connectionFile));
|
|
35
|
+
});
|
|
36
|
+
connected(office.command('+sessions').description('列出正在编辑的 Office 文档')).action(async (opts) => print(await officeRequest('sessions', {}, opts.connectionFile)));
|
|
37
|
+
connected(office.command('+tools').description('发现当前编辑器的原生工具和参数 schema').requiredOption('--session-id <id>'))
|
|
38
|
+
.action(async (opts) => print(await officeRequest('session-tools', { sessionId: opts.sessionId }, opts.connectionFile)));
|
|
39
|
+
connected(office.command('+call').description('调用桌面编辑器原生工具;先用 +tools 发现参数')
|
|
40
|
+
.requiredOption('--session-id <id>').requiredOption('--tool <name>').requiredOption('--args-file <path>')
|
|
41
|
+
.requiredOption('--expected-version <version>')).action(async (opts) => {
|
|
42
|
+
const result = await officeRequest('session-apply', { sessionId: opts.sessionId, expectedVersion: opts.expectedVersion,
|
|
43
|
+
changes: { tool: opts.tool, arguments: await jsonFile(opts.argsFile) } }, opts.connectionFile);
|
|
44
|
+
print(result);
|
|
45
|
+
if (result.result?.isError)
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
});
|
|
48
|
+
connected(office.command('+session-read').description('读取正在编辑的文档,包括未保存内容与会话版本').requiredOption('--session-id <id>')).action(async (opts) => print(await officeRequest('session-read', { sessionId: opts.sessionId }, opts.connectionFile)));
|
|
49
|
+
connected(office.command('+session-apply').description('执行 JSON {tool,arguments} 原生工具调用').requiredOption('--session-id <id>').requiredOption('--expected-version <version>').requiredOption('--ops-file <path>'))
|
|
50
|
+
.action(async (opts) => {
|
|
51
|
+
const result = await officeRequest('session-apply', { sessionId: opts.sessionId, expectedVersion: opts.expectedVersion, changes: await jsonFile(opts.opsFile) }, opts.connectionFile);
|
|
52
|
+
print(result);
|
|
53
|
+
if (result.result?.isError)
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
});
|
|
56
|
+
connected(office.command('+session-save').description('通过桌面编辑器保存当前文档').requiredOption('--session-id <id>').requiredOption('--expected-version <version>'))
|
|
57
|
+
.action(async (opts) => print(await officeRequest('session-save', { sessionId: opts.sessionId, expectedVersion: opts.expectedVersion }, opts.connectionFile)));
|
|
58
|
+
return office;
|
|
59
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachOkrShortcuts } from './shortcuts.js';
|
|
3
|
+
export function buildOkrCommand() {
|
|
4
|
+
const okr = new Command('okr').description('OKR(按周期读取正式关系表或历史表,含组织树):+dept-okr、+employee-okr 等。');
|
|
5
|
+
attachOkrShortcuts(okr);
|
|
6
|
+
return okr;
|
|
7
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { VertuError } from '../../core/errors.js';
|
|
2
|
+
import { attachSharedShortcutOptions, parseOptionalInt, runSkillShortcutExecute, } from '../shortcut-common.js';
|
|
3
|
+
const OKR_SKILL = 'vps-okr-management-assistant';
|
|
4
|
+
function requireOkrPeriod(opts) {
|
|
5
|
+
const p = opts.okrPeriod?.trim();
|
|
6
|
+
if (!p || !/^\d{4}-(0[1-9]|1[0-2])$/.test(p)) {
|
|
7
|
+
throw new VertuError('--okr-period must be a valid YYYY-MM.');
|
|
8
|
+
}
|
|
9
|
+
return p;
|
|
10
|
+
}
|
|
11
|
+
async function runStructuredOkr(scriptKey, opts, cmd) {
|
|
12
|
+
const params = {
|
|
13
|
+
okr_period: requireOkrPeriod(opts),
|
|
14
|
+
};
|
|
15
|
+
if (opts.employeeName?.trim()) {
|
|
16
|
+
params.employee_name = opts.employeeName.trim();
|
|
17
|
+
}
|
|
18
|
+
const employeeId = parseOptionalInt(opts.employeeId, '--employee-id');
|
|
19
|
+
if (employeeId !== undefined) {
|
|
20
|
+
params.employee_id = employeeId;
|
|
21
|
+
}
|
|
22
|
+
if (opts.departmentName?.trim()) {
|
|
23
|
+
params.department_name = opts.departmentName.trim();
|
|
24
|
+
}
|
|
25
|
+
const departmentId = parseOptionalInt(opts.departmentId, '--department-id');
|
|
26
|
+
if (departmentId !== undefined) {
|
|
27
|
+
params.department_id = departmentId;
|
|
28
|
+
}
|
|
29
|
+
if (opts.matchDept?.trim()) {
|
|
30
|
+
params.match_dept = opts.matchDept.trim();
|
|
31
|
+
}
|
|
32
|
+
const rowLimit = parseOptionalInt(opts.rowLimit, '--row-limit');
|
|
33
|
+
if (rowLimit !== undefined) {
|
|
34
|
+
params.limit = rowLimit;
|
|
35
|
+
}
|
|
36
|
+
await runSkillShortcutExecute(OKR_SKILL, scriptKey, params, opts, cmd);
|
|
37
|
+
}
|
|
38
|
+
function attachDepartmentStructured(cmd) {
|
|
39
|
+
return cmd
|
|
40
|
+
.requiredOption('--okr-period <ym>', 'YYYY-MM')
|
|
41
|
+
.option('--department-name <text>', '部门模糊')
|
|
42
|
+
.option('--department-id <id>', 'hr.department id')
|
|
43
|
+
.option('--match-dept <text>', '部门名 / 管辖部门模糊')
|
|
44
|
+
.option('--row-limit <n>', '条数上限(默认不限)');
|
|
45
|
+
}
|
|
46
|
+
function attachPersonalStructured(cmd) {
|
|
47
|
+
return cmd
|
|
48
|
+
.requiredOption('--okr-period <ym>', 'YYYY-MM')
|
|
49
|
+
.option('--employee-name <text>', '姓名模糊')
|
|
50
|
+
.option('--employee-id <id>', 'res.users id')
|
|
51
|
+
.option('--department-name <text>', '部门模糊')
|
|
52
|
+
.option('--row-limit <n>', '条数上限(默认不限)');
|
|
53
|
+
}
|
|
54
|
+
export function attachOkrShortcuts(parent) {
|
|
55
|
+
attachSharedShortcutOptions(attachDepartmentStructured(parent
|
|
56
|
+
.command('+dept-okr')
|
|
57
|
+
.description('部门 OKR(2026-07 起读取正式 OKR,含组织树)'))).action(async (opts, cmd) => {
|
|
58
|
+
await runStructuredOkr('department_okr_structured', opts, cmd);
|
|
59
|
+
});
|
|
60
|
+
attachSharedShortcutOptions(attachPersonalStructured(parent
|
|
61
|
+
.command('+employee-okr')
|
|
62
|
+
.description('员工 OKR(2026-07 起读取正式 OKR,含组织树)'))).action(async (opts, cmd) => {
|
|
63
|
+
await runStructuredOkr('personal_okr_structured', opts, cmd);
|
|
64
|
+
});
|
|
65
|
+
attachSharedShortcutOptions(attachDepartmentStructured(parent
|
|
66
|
+
.command('+department-structured')
|
|
67
|
+
.description('同 +dept-okr(别名)'))).action(async (opts, cmd) => {
|
|
68
|
+
await runStructuredOkr('department_okr_structured', opts, cmd);
|
|
69
|
+
});
|
|
70
|
+
attachSharedShortcutOptions(attachPersonalStructured(parent
|
|
71
|
+
.command('+personal-structured')
|
|
72
|
+
.description('同 +employee-okr(别名)'))).action(async (opts, cmd) => {
|
|
73
|
+
await runStructuredOkr('personal_okr_structured', opts, cmd);
|
|
74
|
+
});
|
|
75
|
+
}
|