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,88 @@
|
|
|
1
|
+
import { VertuError } from '../core/errors.js';
|
|
2
|
+
import { resolveVpsServiceUrl, tryVpsServiceExecute } from '../core/sales-query-client.js';
|
|
3
|
+
import { resolveParamsArg } from './shortcut-params.js';
|
|
4
|
+
export async function mergeShortcutParams(base, opts) {
|
|
5
|
+
const extra = await resolveParamsArg(opts);
|
|
6
|
+
return { ...base, ...extra };
|
|
7
|
+
}
|
|
8
|
+
export function isValidateOnly(opts) {
|
|
9
|
+
return opts.validateOnly === true || opts.dryRun === true;
|
|
10
|
+
}
|
|
11
|
+
function printShortcutResult(opts, result) {
|
|
12
|
+
if (opts.json !== false) {
|
|
13
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(typeof result === 'string' ? result : JSON.stringify(result));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function previewShortcutRequest(opts, cmd, method, path, payload = {}) {
|
|
20
|
+
if (!isValidateOnly(opts)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd).replace(/\/$/, '');
|
|
24
|
+
printShortcutResult(opts, {
|
|
25
|
+
validateOnly: true,
|
|
26
|
+
method,
|
|
27
|
+
endpoint: `${baseUrl}${path}`,
|
|
28
|
+
...payload,
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/** 域快捷命令上的 `--base-url` 始终指向 VPS 服务。 */
|
|
33
|
+
export function resolveShortcutVpsServiceUrl(opts, cmd) {
|
|
34
|
+
const merged = cmd.optsWithGlobals();
|
|
35
|
+
return resolveVpsServiceUrl(opts.baseUrl ?? merged.baseUrl);
|
|
36
|
+
}
|
|
37
|
+
export async function runSkillShortcutExecute(skillKey, scriptKey, baseParams, opts, cmd) {
|
|
38
|
+
const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
39
|
+
const fileParams = await resolveParamsArg(opts);
|
|
40
|
+
const params = { ...baseParams, ...fileParams };
|
|
41
|
+
if (isValidateOnly(opts)) {
|
|
42
|
+
printShortcutResult(opts, {
|
|
43
|
+
validateOnly: true,
|
|
44
|
+
source: 'vps-service-execute',
|
|
45
|
+
endpoint: `${serviceBaseUrl}/v1/execute`,
|
|
46
|
+
skill_key: skillKey,
|
|
47
|
+
script_key: scriptKey,
|
|
48
|
+
params,
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// 统一走 Vertu 服务(支持账号会话 / 智能体长效 key / 服务 API Key)。
|
|
53
|
+
const result = await tryVpsServiceExecute(skillKey, scriptKey, params, serviceBaseUrl);
|
|
54
|
+
if (result === null) {
|
|
55
|
+
throw new VertuError(`Vertu 服务未提供该查询脚本:${scriptKey}`);
|
|
56
|
+
}
|
|
57
|
+
printShortcutResult(opts, result);
|
|
58
|
+
}
|
|
59
|
+
export function parseOptionalFloat(raw, flag) {
|
|
60
|
+
if (raw === undefined || raw.trim() === '') {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const n = Number(raw);
|
|
64
|
+
if (!Number.isFinite(n)) {
|
|
65
|
+
throw new VertuError(`Invalid ${flag}: expected a number.`);
|
|
66
|
+
}
|
|
67
|
+
return n;
|
|
68
|
+
}
|
|
69
|
+
export function parseOptionalInt(raw, flag) {
|
|
70
|
+
if (raw === undefined || raw.trim() === '') {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const n = Number(raw);
|
|
74
|
+
if (!Number.isInteger(n)) {
|
|
75
|
+
throw new VertuError(`Invalid ${flag}: expected an integer.`);
|
|
76
|
+
}
|
|
77
|
+
return n;
|
|
78
|
+
}
|
|
79
|
+
export function attachSharedShortcutOptions(cmd) {
|
|
80
|
+
return cmd
|
|
81
|
+
.option('--base-url <url>', 'Override Vertu service URL')
|
|
82
|
+
.option('--endpoint <alias>', 'Target endpoint alias (e.g. `im`)')
|
|
83
|
+
.option('--params <json>', 'JSON object merged into defaults. Also @path or @- for stdin.')
|
|
84
|
+
.option('--params-file <path>', 'Read --params from file (mutually exclusive with --params)')
|
|
85
|
+
.option('--validate-only', 'Preview request payload without executing/querying', false)
|
|
86
|
+
.option('--dry-run', 'Alias for --validate-only', false)
|
|
87
|
+
.option('--no-json', 'Print plain text instead of formatted JSON');
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VertuError } from '../core/errors.js';
|
|
2
|
+
import { expandAtFileToken } from '../utils/at-file.js';
|
|
3
|
+
import { parseJsonArg } from '../utils/parse-json-arg.js';
|
|
4
|
+
export async function resolveParamsArg(opts) {
|
|
5
|
+
if (opts.params !== undefined && opts.paramsFile !== undefined) {
|
|
6
|
+
throw new VertuError('--params and --params-file are mutually exclusive; pass only one.');
|
|
7
|
+
}
|
|
8
|
+
let rawJson;
|
|
9
|
+
if (opts.paramsFile !== undefined) {
|
|
10
|
+
rawJson = await expandAtFileToken(`@${opts.paramsFile}`, '--params-file');
|
|
11
|
+
}
|
|
12
|
+
else if (opts.params !== undefined) {
|
|
13
|
+
rawJson = await expandAtFileToken(opts.params, '--params');
|
|
14
|
+
}
|
|
15
|
+
if (rawJson === undefined || rawJson.trim().length === 0) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
return parseJsonArg(rawJson, '--params', {
|
|
19
|
+
requireObject: true,
|
|
20
|
+
fileFlagSuggestion: '--params-file',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { VertuError } from '../../core/errors.js';
|
|
3
|
+
import { AGENT_TARGETS, copySkillTo, detectInstalledAgents, loadPublicSkills, removeSkillFrom, resolveAgentTargets, resolveDestinations, } from '../../core/skill-catalog.js';
|
|
4
|
+
import { addPersonalSkillCommands } from './personal.js';
|
|
5
|
+
function collectDir(value, previous) {
|
|
6
|
+
return [...previous, value];
|
|
7
|
+
}
|
|
8
|
+
function pickSkills(all, keys, installAll) {
|
|
9
|
+
if (installAll || keys.length === 0) {
|
|
10
|
+
return all;
|
|
11
|
+
}
|
|
12
|
+
const byKey = new Map(all.map((s) => [s.key, s]));
|
|
13
|
+
const picked = [];
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
const hit = byKey.get(key);
|
|
16
|
+
if (!hit) {
|
|
17
|
+
throw new VertuError(`未知 skill "${key}"。用 \`vps-work skill list\` 查看全部公开 skill。`);
|
|
18
|
+
}
|
|
19
|
+
if (!picked.some((s) => s.key === hit.key)) {
|
|
20
|
+
picked.push(hit);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return picked;
|
|
24
|
+
}
|
|
25
|
+
async function resolveInstallTargets(opts) {
|
|
26
|
+
if (opts.agent?.trim()) {
|
|
27
|
+
return resolveAgentTargets(opts.agent);
|
|
28
|
+
}
|
|
29
|
+
if (opts.dir?.length) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const detected = await detectInstalledAgents();
|
|
33
|
+
if (detected.length === 0) {
|
|
34
|
+
throw new VertuError('未检测到任何 Agent 目录(~/.cursor、~/.claude、~/.hermes 等)。' +
|
|
35
|
+
'请用 --agent <id> 或 --dir <path> 指定安装位置。');
|
|
36
|
+
}
|
|
37
|
+
return detected;
|
|
38
|
+
}
|
|
39
|
+
export function buildSkillCommand() {
|
|
40
|
+
const skill = new Command('skill').description('安装随包公开 skill,或用当前身份新建、更新和发布自己的个人技能。');
|
|
41
|
+
skill
|
|
42
|
+
.command('list')
|
|
43
|
+
.description('列出随包分发的公开 skill')
|
|
44
|
+
.option('--json', '机读输出', false)
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
const skills = await loadPublicSkills();
|
|
47
|
+
if (opts.json) {
|
|
48
|
+
console.log(JSON.stringify(skills.map((s) => ({ key: s.key, description: s.description })), null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const s of skills) {
|
|
52
|
+
console.log(`${s.key}\n ${s.description || '(无描述)'}`);
|
|
53
|
+
}
|
|
54
|
+
console.log(`\n共 ${skills.length} 个公开 skill。`);
|
|
55
|
+
});
|
|
56
|
+
skill
|
|
57
|
+
.command('install [keys...]')
|
|
58
|
+
.description('安装公开 skill 到 Agent 目录;不传 keys 等价 --all')
|
|
59
|
+
.option('--agent <ids>', 'cursor / claude-code / trae / trae-cn / openclaw / hermes / all(逗号分隔)')
|
|
60
|
+
.option('--dir <path>', '额外安装到自定义目录,可重复', collectDir, [])
|
|
61
|
+
.option('--all', '安装全部公开 skill', false)
|
|
62
|
+
.action(async (keys, opts) => {
|
|
63
|
+
const all = await loadPublicSkills();
|
|
64
|
+
const skills = pickSkills(all, keys, opts.all === true);
|
|
65
|
+
const targets = await resolveInstallTargets(opts);
|
|
66
|
+
const dests = resolveDestinations(targets, opts.dir ?? []);
|
|
67
|
+
if (dests.length === 0) {
|
|
68
|
+
throw new VertuError('没有可写入的目标目录。');
|
|
69
|
+
}
|
|
70
|
+
for (const dest of dests) {
|
|
71
|
+
for (const s of skills) {
|
|
72
|
+
const written = await copySkillTo(s, dest.skillsRoot);
|
|
73
|
+
console.log(`[${dest.label}] ${s.key} → ${written}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.log(`\n完成:${skills.length} 个 skill × ${dests.length} 个目标。`);
|
|
77
|
+
});
|
|
78
|
+
skill
|
|
79
|
+
.command('update')
|
|
80
|
+
.description('把公开 skill 重新同步(覆盖式)到全部已检测到的 Agent 目录')
|
|
81
|
+
.option('--agent <ids>', '只同步指定 Agent;默认全部已检测到的')
|
|
82
|
+
.option('--dir <path>', '额外同步到自定义目录,可重复', collectDir, [])
|
|
83
|
+
.action(async (opts) => {
|
|
84
|
+
const all = await loadPublicSkills();
|
|
85
|
+
const targets = opts.agent?.trim()
|
|
86
|
+
? resolveAgentTargets(opts.agent)
|
|
87
|
+
: await detectInstalledAgents();
|
|
88
|
+
const dests = resolveDestinations(targets, opts.dir ?? []);
|
|
89
|
+
if (dests.length === 0) {
|
|
90
|
+
throw new VertuError('未检测到 Agent 目录,也未指定 --agent / --dir。');
|
|
91
|
+
}
|
|
92
|
+
// 清理已弃用的旧引导 skill:实验名 vps-cli、改名前的 vps-work-cli,以及 vertu-cli 时代的 vertu-cli / vertu-onboard。
|
|
93
|
+
// 业务 skill(vps-im / vps-hr …)目录名与旧包相同,覆盖式同步即完成替换。
|
|
94
|
+
const obsoleteKeys = ['vps-cli', 'vps-work-cli', 'vertu-cli', 'vertu-onboard'];
|
|
95
|
+
for (const dest of dests) {
|
|
96
|
+
for (const s of all) {
|
|
97
|
+
await copySkillTo(s, dest.skillsRoot);
|
|
98
|
+
}
|
|
99
|
+
for (const key of obsoleteKeys) {
|
|
100
|
+
if (await removeSkillFrom(key, dest.skillsRoot)) {
|
|
101
|
+
console.log(`[${dest.label}] removed obsolete ${key}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
console.log(`[${dest.label}] 已同步 ${all.length} 个 skill`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
skill
|
|
108
|
+
.command('uninstall [keys...]')
|
|
109
|
+
.description('从 Agent 目录移除指定公开 skill;不传 keys 移除全部公开 skill')
|
|
110
|
+
.option('--agent <ids>', '指定 Agent;默认全部已检测到的')
|
|
111
|
+
.option('--dir <path>', '额外从自定义目录移除,可重复', collectDir, [])
|
|
112
|
+
.action(async (keys, opts) => {
|
|
113
|
+
const all = await loadPublicSkills();
|
|
114
|
+
const removeKeys = keys.length ? keys : all.map((s) => s.key);
|
|
115
|
+
const targets = opts.agent?.trim()
|
|
116
|
+
? resolveAgentTargets(opts.agent)
|
|
117
|
+
: await detectInstalledAgents();
|
|
118
|
+
const dests = resolveDestinations(targets, opts.dir ?? []);
|
|
119
|
+
if (dests.length === 0) {
|
|
120
|
+
throw new VertuError('未检测到 Agent 目录,也未指定 --agent / --dir。');
|
|
121
|
+
}
|
|
122
|
+
let removed = 0;
|
|
123
|
+
for (const dest of dests) {
|
|
124
|
+
for (const key of removeKeys) {
|
|
125
|
+
if (await removeSkillFrom(key, dest.skillsRoot)) {
|
|
126
|
+
console.log(`[${dest.label}] removed ${key}`);
|
|
127
|
+
removed += 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
console.log(`\n移除 ${removed} 项。`);
|
|
132
|
+
});
|
|
133
|
+
skill.addHelpText('after', `\n支持的 --agent:${AGENT_TARGETS.map((t) => t.id).join(' / ')}(all 表示全部)。`);
|
|
134
|
+
addPersonalSkillCommands(skill);
|
|
135
|
+
return skill;
|
|
136
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { VertuError } from '../../core/errors.js';
|
|
2
|
+
import { hermesRequest } from '../../core/hermes-client.js';
|
|
3
|
+
import { expandAtFileToken } from '../../utils/at-file.js';
|
|
4
|
+
import { parseFrontmatter, stringifyFrontmatter } from '../../utils/frontmatter.js';
|
|
5
|
+
const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
|
+
export function normalizePersonalSkillSlug(value) {
|
|
7
|
+
const slug = value.trim().toLowerCase();
|
|
8
|
+
if (!SKILL_SLUG_RE.test(slug)) {
|
|
9
|
+
throw new VertuError('--slug 仅允许小写字母、数字和单连字符,例如 sales-follow-up。');
|
|
10
|
+
}
|
|
11
|
+
return slug;
|
|
12
|
+
}
|
|
13
|
+
export function ensurePersonalSkillFrontmatter(markdown, input) {
|
|
14
|
+
const slug = normalizePersonalSkillSlug(input.slug);
|
|
15
|
+
const parsed = parseFrontmatter(markdown.trim());
|
|
16
|
+
const description = input.description?.trim() ||
|
|
17
|
+
(typeof parsed.data.description === 'string' ? parsed.data.description.trim() : '') ||
|
|
18
|
+
input.title?.trim() ||
|
|
19
|
+
slug;
|
|
20
|
+
const body = parsed.body.trim() || `# ${input.title?.trim() || slug}`;
|
|
21
|
+
return `${stringifyFrontmatter({ ...parsed.data, name: slug, description }, body).trim()}\n`;
|
|
22
|
+
}
|
|
23
|
+
function print(value) {
|
|
24
|
+
console.log(JSON.stringify(value, null, 2));
|
|
25
|
+
}
|
|
26
|
+
function asRecord(value) {
|
|
27
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
28
|
+
? value
|
|
29
|
+
: {};
|
|
30
|
+
}
|
|
31
|
+
async function currentUserId(baseUrl) {
|
|
32
|
+
const session = asRecord(await hermesRequest('GET', '/hermes-console/api/vertu/session', { baseUrl }));
|
|
33
|
+
const userId = Number(session.active_odoo_user_id || session.vertu_uid || 0);
|
|
34
|
+
if (!Number.isInteger(userId) || userId <= 0) {
|
|
35
|
+
throw new VertuError('未识别到当前企业用户,请检查宿主注入的 app_key 或人工登录会话。');
|
|
36
|
+
}
|
|
37
|
+
return userId;
|
|
38
|
+
}
|
|
39
|
+
async function expandOptional(value, source) {
|
|
40
|
+
return value === undefined ? undefined : expandAtFileToken(value, source);
|
|
41
|
+
}
|
|
42
|
+
async function createPersonalSkill(opts) {
|
|
43
|
+
const slug = normalizePersonalSkillSlug(opts.slug);
|
|
44
|
+
const title = opts.title?.trim();
|
|
45
|
+
if (!title)
|
|
46
|
+
throw new VertuError('--title is required');
|
|
47
|
+
const rawSkillMd = await expandOptional(opts.skillMd, '--skill-md');
|
|
48
|
+
const instructions = await expandOptional(opts.instructions, '--instructions');
|
|
49
|
+
if (rawSkillMd !== undefined && instructions !== undefined) {
|
|
50
|
+
throw new VertuError('--skill-md 与 --instructions 不能同时使用。');
|
|
51
|
+
}
|
|
52
|
+
const body = rawSkillMd ?? (instructions ? `# ${title}\n\n${instructions.trim()}` : '');
|
|
53
|
+
if (!body.trim())
|
|
54
|
+
throw new VertuError('必须提供 --skill-md 或 --instructions。');
|
|
55
|
+
const skillMd = ensurePersonalSkillFrontmatter(body, {
|
|
56
|
+
slug,
|
|
57
|
+
title,
|
|
58
|
+
description: opts.description,
|
|
59
|
+
});
|
|
60
|
+
const result = await hermesRequest('POST', '/hermes-console/api/skills/import-personal', {
|
|
61
|
+
baseUrl: opts.hermesBaseUrl,
|
|
62
|
+
body: {
|
|
63
|
+
skill_slug: slug,
|
|
64
|
+
title,
|
|
65
|
+
description: opts.description?.trim(),
|
|
66
|
+
category_key: opts.category?.trim(),
|
|
67
|
+
skill_md: skillMd,
|
|
68
|
+
note: opts.note?.trim() || '由 vps-work Agent 命令新建',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
print(result);
|
|
72
|
+
}
|
|
73
|
+
async function updatePersonalSkill(opts) {
|
|
74
|
+
const slug = normalizePersonalSkillSlug(opts.slug);
|
|
75
|
+
const raw = await expandOptional(opts.skillMd, '--skill-md');
|
|
76
|
+
const instructions = await expandOptional(opts.instructions, '--instructions');
|
|
77
|
+
if (raw !== undefined && instructions !== undefined) {
|
|
78
|
+
throw new VertuError('--skill-md 与 --instructions 不能同时使用。');
|
|
79
|
+
}
|
|
80
|
+
if (raw === undefined && instructions === undefined) {
|
|
81
|
+
throw new VertuError('必须提供 --skill-md 或 --instructions。');
|
|
82
|
+
}
|
|
83
|
+
const detail = asRecord(await hermesRequest('GET', `/hermes-console/api/skills/${encodeURIComponent(slug)}/detail?scope=registry`, { baseUrl: opts.hermesBaseUrl }));
|
|
84
|
+
const existingMarkdown = String(detail.markdown || detail.definition_md || '');
|
|
85
|
+
const existing = parseFrontmatter(existingMarkdown);
|
|
86
|
+
const title = opts.title?.trim() || String(detail.title || '').trim() || slug;
|
|
87
|
+
const body = raw ?? `# ${title}\n\n${instructions?.trim() || ''}`;
|
|
88
|
+
const skillMd = ensurePersonalSkillFrontmatter(body, {
|
|
89
|
+
slug,
|
|
90
|
+
title,
|
|
91
|
+
description: opts.description?.trim() ||
|
|
92
|
+
String(detail.description || '').trim() ||
|
|
93
|
+
(typeof existing.data.description === 'string' ? existing.data.description : ''),
|
|
94
|
+
});
|
|
95
|
+
const result = await hermesRequest('POST', '/hermes-console/api/skills/import-personal', {
|
|
96
|
+
baseUrl: opts.hermesBaseUrl,
|
|
97
|
+
body: {
|
|
98
|
+
skill_slug: slug,
|
|
99
|
+
title,
|
|
100
|
+
description: opts.description?.trim() || String(detail.description || '').trim(),
|
|
101
|
+
category_key: opts.category?.trim(),
|
|
102
|
+
skill_md: skillMd,
|
|
103
|
+
note: opts.note?.trim() || '由 vps-work Agent 命令更新',
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
print(result);
|
|
107
|
+
}
|
|
108
|
+
function addHermesBaseUrl(command) {
|
|
109
|
+
return command.option('--hermes-base-url <url>', 'Hermes 管理服务地址;默认读 VERTU_HERMES_BASE_URL / HERMES_AGENT_ADMIN_BASE_URL');
|
|
110
|
+
}
|
|
111
|
+
export function addPersonalSkillCommands(skill) {
|
|
112
|
+
addHermesBaseUrl(skill
|
|
113
|
+
.command('+create-personal')
|
|
114
|
+
.description('新建自己的个人技能;自动生成并校正 SKILL.md YAML frontmatter')
|
|
115
|
+
.requiredOption('--slug <slug>', '机器名,例如 sales-follow-up')
|
|
116
|
+
.requiredOption('--title <text>', '技能显示名称')
|
|
117
|
+
.option('--description <text>', '技能用途说明')
|
|
118
|
+
.option('--skill-md <markdown|@file>', '完整 SKILL.md;可用 @file / @-')
|
|
119
|
+
.option('--instructions <text|@file>', '技能正文;CLI 自动生成标题与 frontmatter')
|
|
120
|
+
.option('--category <key>', '初始分类 key')
|
|
121
|
+
.option('--note <text>', '版本说明')).action(createPersonalSkill);
|
|
122
|
+
addHermesBaseUrl(skill
|
|
123
|
+
.command('+update-personal')
|
|
124
|
+
.description('更新自己的个人技能并生成新版本;自动补齐/校正 YAML frontmatter')
|
|
125
|
+
.requiredOption('--slug <slug>', '自己的个人技能机器名')
|
|
126
|
+
.option('--title <text>', '新的显示名称')
|
|
127
|
+
.option('--description <text>', '新的用途说明')
|
|
128
|
+
.option('--skill-md <markdown|@file>', '新的完整 SKILL.md;可用 @file / @-')
|
|
129
|
+
.option('--instructions <text|@file>', '新的技能正文')
|
|
130
|
+
.option('--category <key>', '同时更新技能分类 key')
|
|
131
|
+
.option('--note <text>', '版本说明')).action(updatePersonalSkill);
|
|
132
|
+
addHermesBaseUrl(skill
|
|
133
|
+
.command('+request-publication')
|
|
134
|
+
.description('申请将自己创建的个人技能公开到企业技能库')
|
|
135
|
+
.requiredOption('--slug <slug>', '自己的个人技能机器名')
|
|
136
|
+
.option('--note <text>', '提交审核说明')).action(async (opts) => {
|
|
137
|
+
const slug = normalizePersonalSkillSlug(opts.slug);
|
|
138
|
+
print(await hermesRequest('POST', '/hermes-console/api/skills/publish', {
|
|
139
|
+
baseUrl: opts.hermesBaseUrl,
|
|
140
|
+
body: { skill_slug: slug, submit_note: opts.note?.trim() || '' },
|
|
141
|
+
}));
|
|
142
|
+
});
|
|
143
|
+
addHermesBaseUrl(skill
|
|
144
|
+
.command('+delete-personal')
|
|
145
|
+
.description('删除自己创建且尚未公开的个人技能(移入回收站)')
|
|
146
|
+
.requiredOption('--slug <slug>', '自己的个人技能机器名')).action(async (opts) => {
|
|
147
|
+
const slug = normalizePersonalSkillSlug(opts.slug);
|
|
148
|
+
const userId = await currentUserId(opts.hermesBaseUrl);
|
|
149
|
+
print(await hermesRequest('DELETE', `/hermes-console/api/skills/${encodeURIComponent(slug)}?odoo_user_id=${userId}`, { baseUrl: opts.hermesBaseUrl }));
|
|
150
|
+
});
|
|
151
|
+
addHermesBaseUrl(skill
|
|
152
|
+
.command('+change-category')
|
|
153
|
+
.description('修改自己创建的个人技能分类')
|
|
154
|
+
.requiredOption('--slug <slug>', '自己的个人技能机器名')
|
|
155
|
+
.requiredOption('--category <key>', '目标分类 key')
|
|
156
|
+
.option('--category-name <text>', '目标分类显示名称;省略时由服务端查询')).action(async (opts) => {
|
|
157
|
+
const slug = normalizePersonalSkillSlug(opts.slug);
|
|
158
|
+
print(await hermesRequest('POST', '/hermes-console/api/skills/category', {
|
|
159
|
+
baseUrl: opts.hermesBaseUrl,
|
|
160
|
+
body: {
|
|
161
|
+
skill_slug: slug,
|
|
162
|
+
category_key: opts.category.trim(),
|
|
163
|
+
category_name: opts.categoryName?.trim(),
|
|
164
|
+
},
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachSharedShortcutOptions, parseOptionalInt, runSkillShortcutExecute, } from '../shortcut-common.js';
|
|
3
|
+
import { buildSalesPeriodParams } from '../sales/period-params.js';
|
|
4
|
+
const STORE_MANAGEMENT_SKILL = 'vps-store-management';
|
|
5
|
+
export function buildStoreCommand() {
|
|
6
|
+
const store = new Command('store')
|
|
7
|
+
.description('门店管理:Walk-in 客流、门店接待和库存查询。');
|
|
8
|
+
attachSharedShortcutOptions(store
|
|
9
|
+
.command('+reception')
|
|
10
|
+
.alias('+store-reception')
|
|
11
|
+
.description('门店接待情况:进店、加微、有效客户、触摸、试用与成交(默认本月)')
|
|
12
|
+
.option('--period <name>', 'today | this_week | last_week | this_month | last_month | last_n_days | YYYY-MM-DD')
|
|
13
|
+
.option('--start-date <date>', 'YYYY-MM-DD;自定义开始日期')
|
|
14
|
+
.option('--end-date <date>', 'YYYY-MM-DD;自定义结束日期')
|
|
15
|
+
.option('--days <n>', 'period=last_n_days 时的天数')
|
|
16
|
+
.option('--region <name>', '区域:东区/西区/北区/南区')
|
|
17
|
+
.option('--store <name>', '门店名称,精确匹配')
|
|
18
|
+
.option('--department-id <id>', '真实部门 ID')
|
|
19
|
+
.option('--employee <name>', '接待员工姓名,模糊匹配')
|
|
20
|
+
.option('--source <scope>', '数据来源:store(默认)| dealer | all')).action(async (opts, cmd) => {
|
|
21
|
+
const params = buildSalesPeriodParams(opts, 'this_month');
|
|
22
|
+
if (opts.region)
|
|
23
|
+
params.region = opts.region;
|
|
24
|
+
if (opts.store)
|
|
25
|
+
params.store = opts.store;
|
|
26
|
+
if (opts.employee)
|
|
27
|
+
params.employee_name = opts.employee;
|
|
28
|
+
if (opts.source)
|
|
29
|
+
params.source = opts.source;
|
|
30
|
+
const departmentId = parseOptionalInt(opts.departmentId, '--department-id');
|
|
31
|
+
if (departmentId !== undefined)
|
|
32
|
+
params.department_id = departmentId;
|
|
33
|
+
await runSkillShortcutExecute(STORE_MANAGEMENT_SKILL, 'store_reception_summary', params, opts, cmd);
|
|
34
|
+
});
|
|
35
|
+
attachSharedShortcutOptions(store.command('+walk-in').alias('+walkin')
|
|
36
|
+
.description('Walk-in 客流:按部门、接待日期筛选和分组(默认本月)')
|
|
37
|
+
.option('--period <name>', 'today | this_week | this_month | last_month | YYYY-MM-DD')
|
|
38
|
+
.option('--start-date <date>', 'YYYY-MM-DD').option('--end-date <date>', 'YYYY-MM-DD')
|
|
39
|
+
.option('--days <n>', 'last_n_days 天数')
|
|
40
|
+
.option('--department <name>', '接待部门名称,精确匹配').option('--department-id <id>', 'ERP 接待部门 ID')
|
|
41
|
+
.option('--store <name>', '表单门店名称,精确匹配').option('--source <scope>', 'store(默认)| dealer | all')
|
|
42
|
+
.option('--group-by <dimensions>', 'department,date,store 的逗号组合,或 all')
|
|
43
|
+
.option('--date-grain <grain>', 'day(默认)| week | month')
|
|
44
|
+
.option('--limit <n>', '每页分组数,默认 100,最多 1000').option('--offset <n>', '分组偏移量,默认 0')).action(async (opts, cmd) => {
|
|
45
|
+
const params = buildSalesPeriodParams(opts, 'this_month');
|
|
46
|
+
for (const key of ['department', 'store', 'source'])
|
|
47
|
+
if (opts[key] !== undefined)
|
|
48
|
+
params[key] = opts[key];
|
|
49
|
+
if (opts.groupBy !== undefined)
|
|
50
|
+
params.group_by = opts.groupBy;
|
|
51
|
+
if (opts.dateGrain !== undefined)
|
|
52
|
+
params.date_grain = opts.dateGrain;
|
|
53
|
+
for (const [flag, key] of [['departmentId', 'department_id'], ['limit', 'limit'], ['offset', 'offset']]) {
|
|
54
|
+
const value = parseOptionalInt(opts[flag], `--${key.replaceAll('_', '-')}`);
|
|
55
|
+
if (value !== undefined)
|
|
56
|
+
params[key] = value;
|
|
57
|
+
}
|
|
58
|
+
await runSkillShortcutExecute(STORE_MANAGEMENT_SKILL, 'store_walkin_summary', params, opts, cmd);
|
|
59
|
+
});
|
|
60
|
+
attachSharedShortcutOptions(store.command('+inventory')
|
|
61
|
+
.description('当前门店库存:按库存报表部门、门店、商品筛选和分组')
|
|
62
|
+
.option('--department <name>', '库存报表部门名称,精确匹配').option('--store <name>', '库位路径首段门店,精确匹配')
|
|
63
|
+
.option('--product <name>', '商品名称或编码包含匹配').option('--product-code <code>', '商品编码精确匹配')
|
|
64
|
+
.option('--group-by <dimensions>', 'department,store,product 的逗号组合,或 all')
|
|
65
|
+
.option('--limit <n>', '每页分组数,默认 100,最多 1000').option('--offset <n>', '分组偏移量,默认 0')).action(async (opts, cmd) => {
|
|
66
|
+
const params = {};
|
|
67
|
+
for (const key of ['department', 'store', 'product'])
|
|
68
|
+
if (opts[key] !== undefined)
|
|
69
|
+
params[key] = opts[key];
|
|
70
|
+
if (opts.groupBy !== undefined)
|
|
71
|
+
params.group_by = opts.groupBy;
|
|
72
|
+
if (opts.productCode !== undefined)
|
|
73
|
+
params.product_code = opts.productCode;
|
|
74
|
+
for (const key of ['limit', 'offset']) {
|
|
75
|
+
const value = parseOptionalInt(opts[key], `--${key}`);
|
|
76
|
+
if (value !== undefined)
|
|
77
|
+
params[key] = value;
|
|
78
|
+
}
|
|
79
|
+
await runSkillShortcutExecute(STORE_MANAGEMENT_SKILL, 'store_inventory_summary', params, opts, cmd);
|
|
80
|
+
});
|
|
81
|
+
return store;
|
|
82
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { attachTaskCenterShortcuts } from './task-center.js';
|
|
3
|
+
export function buildTaskCommand() {
|
|
4
|
+
const task = new Command('task').description('R&D 任务/待办/项目:任务中心 +tc-*(REST)与 OKR +my-okr。');
|
|
5
|
+
attachTaskCenterShortcuts(task);
|
|
6
|
+
return task;
|
|
7
|
+
}
|