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,70 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { CLI_BIN, CLI_PACKAGE } from '../core/cli-bin.js';
|
|
4
|
+
import { VertuError } from '../core/errors.js';
|
|
5
|
+
import { copySkillTo, detectInstalledAgents, loadPublicSkills, resolveDestinations, } from '../core/skill-catalog.js';
|
|
6
|
+
function assertNodeEngine() {
|
|
7
|
+
const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10);
|
|
8
|
+
if (major < 20) {
|
|
9
|
+
throw new VertuError(`Node.js 20+ is required (current: ${process.versions.node}). Upgrade Node, then re-run \`${CLI_BIN} install\`.`, { exitCode: 1 });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function runNpmInstallGlobal(spec) {
|
|
13
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
14
|
+
const result = spawnSync(npmCmd, ['install', '-g', spec], {
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
env: process.env,
|
|
17
|
+
});
|
|
18
|
+
if (result.error) {
|
|
19
|
+
throw new VertuError(`Failed to run npm: ${result.error.message}`, { exitCode: 1 });
|
|
20
|
+
}
|
|
21
|
+
if (result.status !== 0) {
|
|
22
|
+
process.exit(result.status ?? 1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 把随包分发的公开 skill 自动铺到本机已检测到的 Agent 目录(lark-cli 式开箱即用)。
|
|
27
|
+
* 无需登录、无远端依赖;未检测到任何 Agent 时静默跳过。
|
|
28
|
+
*/
|
|
29
|
+
async function autoInstallPublicSkills() {
|
|
30
|
+
const detected = await detectInstalledAgents();
|
|
31
|
+
if (detected.length === 0) {
|
|
32
|
+
process.stdout.write(`\n未检测到 Agent 目录(~/.cursor 等)。可稍后运行 \`${CLI_BIN} skill install --agent <id>\` 安装公开 skill。\n`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const skills = await loadPublicSkills();
|
|
36
|
+
const dests = resolveDestinations(detected, []);
|
|
37
|
+
for (const dest of dests) {
|
|
38
|
+
for (const s of skills) {
|
|
39
|
+
await copySkillTo(s, dest.skillsRoot);
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write(` ${dest.label} → ${dest.skillsRoot} (${skills.length} skills)\n`);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write(`公开 skill 已铺到 ${dests.length} 个 Agent 目录。\n`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 全局安装 CLI(`npx vps-work@latest install` 的入口)。
|
|
47
|
+
*/
|
|
48
|
+
export function buildCliInstallCommand() {
|
|
49
|
+
return new Command('install')
|
|
50
|
+
.description(`Install ${CLI_PACKAGE} globally via npm (e.g. npx ${CLI_PACKAGE}@latest install)`)
|
|
51
|
+
.option('--tag <dist-tag>', 'npm dist-tag for the package', 'latest')
|
|
52
|
+
.option('--no-skills', '安装后不自动铺设公开 skill')
|
|
53
|
+
.action(async (opts) => {
|
|
54
|
+
assertNodeEngine();
|
|
55
|
+
const tag = opts.tag?.trim() || 'latest';
|
|
56
|
+
const spec = `${CLI_PACKAGE}@${tag}`;
|
|
57
|
+
process.stdout.write(`Installing ${spec} globally…\n`);
|
|
58
|
+
runNpmInstallGlobal(spec);
|
|
59
|
+
if (opts.skills !== false) {
|
|
60
|
+
process.stdout.write('\n铺设公开 skill…\n');
|
|
61
|
+
try {
|
|
62
|
+
await autoInstallPublicSkills();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
process.stdout.write(`公开 skill 铺设跳过:${err.message}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write(`\nDone. Next:\n ${CLI_BIN} auth status\n\nHuman interactive login only: ${CLI_BIN} auth login\nAgent/Bot/Hermes must use host-injected app_key credentials.\n`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { executeOdooAiTool } from './odoo-invoke.js';
|
|
2
|
+
export function resolveInvokeOpts(opts, cmd, defaultEndpoint) {
|
|
3
|
+
const merged = cmd.optsWithGlobals();
|
|
4
|
+
const endpoint = opts.endpoint ?? merged.endpoint ?? defaultEndpoint;
|
|
5
|
+
// 域快捷命令上的 `--base-url` 留给 vps-service;Odoo 走 login 登记的 defaultBaseUrl。
|
|
6
|
+
return { endpoint };
|
|
7
|
+
}
|
|
8
|
+
export async function callOdooLeaf(leaf, args, opts, cmd) {
|
|
9
|
+
const invokeOpts = resolveInvokeOpts(opts, cmd, leaf.defaultEndpoint);
|
|
10
|
+
return executeOdooAiTool(leaf.invokeKey, leaf.argv, args, invokeOpts);
|
|
11
|
+
}
|
|
12
|
+
export function printShortcutJson(opts, out) {
|
|
13
|
+
if (opts.json !== false) {
|
|
14
|
+
console.log(JSON.stringify(out, null, 2));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.log(typeof out === 'string' ? out : JSON.stringify(out));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { downloadCorporateAttachment, runCorporateQuery } from '../../core/corporate-client.js';
|
|
3
|
+
import { VertuError } from '../../core/errors.js';
|
|
4
|
+
import { attachSharedShortcutOptions, parseOptionalInt, resolveShortcutVpsServiceUrl, } from '../shortcut-common.js';
|
|
5
|
+
const SEAL_KINDS = {
|
|
6
|
+
contract: 'seal-contract',
|
|
7
|
+
creator: 'seal-creator',
|
|
8
|
+
procurement: 'seal-procurement',
|
|
9
|
+
'non-contract': 'seal-non-contract',
|
|
10
|
+
'certificate-loan': 'seal-certificate-loan',
|
|
11
|
+
hr: 'seal-hr',
|
|
12
|
+
};
|
|
13
|
+
const BUSINESS_KINDS = {
|
|
14
|
+
'mall-activity': 'business-mall-activity',
|
|
15
|
+
'mall-entry': 'business-mall-entry',
|
|
16
|
+
'agent-contract': 'business-agent-contract',
|
|
17
|
+
'online-store': 'business-online-store',
|
|
18
|
+
'subject-change': 'business-subject-change',
|
|
19
|
+
'air-transport': 'business-air-transport',
|
|
20
|
+
'business-policy': 'business-policy',
|
|
21
|
+
};
|
|
22
|
+
function print(opts, value) {
|
|
23
|
+
console.log(opts.json === false && typeof value === 'string' ? value : JSON.stringify(value, null, 2));
|
|
24
|
+
}
|
|
25
|
+
function positiveId(raw, flag) {
|
|
26
|
+
const value = parseOptionalInt(raw, flag);
|
|
27
|
+
if (!value || value <= 0)
|
|
28
|
+
throw new VertuError(`${flag} must be a positive integer.`);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function kind(map, raw, flag) {
|
|
32
|
+
const value = raw?.trim();
|
|
33
|
+
if (!value || !map[value]) {
|
|
34
|
+
throw new VertuError(`${flag} must be one of: ${Object.keys(map).join(', ')}`);
|
|
35
|
+
}
|
|
36
|
+
return map[value];
|
|
37
|
+
}
|
|
38
|
+
async function query(cmd, opts, payload) {
|
|
39
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
40
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
41
|
+
print(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/corporate/query`, payload });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
print(opts, await runCorporateQuery(baseUrl, payload));
|
|
45
|
+
}
|
|
46
|
+
function recordRows(value) {
|
|
47
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
48
|
+
return [];
|
|
49
|
+
const result = value.result;
|
|
50
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
51
|
+
return [];
|
|
52
|
+
const rows = result.rows;
|
|
53
|
+
return Array.isArray(rows)
|
|
54
|
+
? rows.filter((row) => Boolean(row && typeof row === 'object' && !Array.isArray(row)))
|
|
55
|
+
: [];
|
|
56
|
+
}
|
|
57
|
+
function appliedFilter(value) {
|
|
58
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
59
|
+
return {};
|
|
60
|
+
const result = value.result;
|
|
61
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
62
|
+
return {};
|
|
63
|
+
const filter = result.filter;
|
|
64
|
+
return filter && typeof filter === 'object' && !Array.isArray(filter)
|
|
65
|
+
? filter : {};
|
|
66
|
+
}
|
|
67
|
+
function assertDateFilterApplied(value, payload) {
|
|
68
|
+
const filter = appliedFilter(value);
|
|
69
|
+
for (const key of ['end_date_from', 'end_date_to']) {
|
|
70
|
+
if (payload[key] !== undefined && filter[key] !== payload[key]) {
|
|
71
|
+
throw new VertuError('当前 vps-service 尚未确认应用合同结束日期筛选,已拒绝返回可能未过滤的结果;请先部署新版 vps-service。');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function queryContractList(cmd, opts, payload) {
|
|
76
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
77
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
78
|
+
print(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/corporate/query`, payload, all: opts.all === true });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const requestedLimit = Number(payload.limit) || 50;
|
|
82
|
+
const pageSize = opts.all ? 200 : requestedLimit;
|
|
83
|
+
let offset = Number(payload.offset) || 0;
|
|
84
|
+
const rows = [];
|
|
85
|
+
let first;
|
|
86
|
+
do {
|
|
87
|
+
const response = await runCorporateQuery(baseUrl, { ...payload, limit: pageSize, offset });
|
|
88
|
+
assertDateFilterApplied(response, payload);
|
|
89
|
+
first ??= response;
|
|
90
|
+
const pageRows = recordRows(response);
|
|
91
|
+
rows.push(...pageRows);
|
|
92
|
+
if (!opts.all || pageRows.length < pageSize)
|
|
93
|
+
break;
|
|
94
|
+
offset += pageSize;
|
|
95
|
+
} while (true);
|
|
96
|
+
if (!opts.all) {
|
|
97
|
+
print(opts, first);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
print(opts, {
|
|
101
|
+
ok: true,
|
|
102
|
+
result: { rows, limit: pageSize, offset: Number(payload.offset) || 0, all: true, filter: appliedFilter(first) },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function listPayload(recordType, opts) {
|
|
106
|
+
return {
|
|
107
|
+
operation: 'list', record_type: recordType,
|
|
108
|
+
...(opts.search?.trim() ? { search: opts.search.trim() } : {}),
|
|
109
|
+
...(opts.status?.trim() ? { status: opts.status.trim() } : {}),
|
|
110
|
+
...(opts.endDateFrom?.trim() ? { end_date_from: opts.endDateFrom.trim() } : {}),
|
|
111
|
+
...(opts.endDateTo?.trim() ? { end_date_to: opts.endDateTo.trim() } : {}),
|
|
112
|
+
...(parseOptionalInt(opts.limit, '--limit') ? { limit: parseOptionalInt(opts.limit, '--limit') } : {}),
|
|
113
|
+
...(parseOptionalInt(opts.offset, '--offset') !== undefined ? { offset: parseOptionalInt(opts.offset, '--offset') } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function attachListOptions(command, withKind = false) {
|
|
117
|
+
if (withKind)
|
|
118
|
+
command.option('--kind <kind>', '业务子类型');
|
|
119
|
+
return attachSharedShortcutOptions(command
|
|
120
|
+
.option('--search <text>', '按名称模糊查询')
|
|
121
|
+
.option('--status <status>', '按状态筛选')
|
|
122
|
+
.option('--limit <n>', '返回条数,最大 200', '50')
|
|
123
|
+
.option('--offset <n>', '分页偏移', '0'));
|
|
124
|
+
}
|
|
125
|
+
function attachDetailOptions(command, withKind = false) {
|
|
126
|
+
if (withKind)
|
|
127
|
+
command.option('--kind <kind>', '业务子类型');
|
|
128
|
+
return attachSharedShortcutOptions(command.option('--id <id>', '记录 ID'));
|
|
129
|
+
}
|
|
130
|
+
/** 企业综合管理 — 合作企业档案、用印、业务管理和规章制度。 */
|
|
131
|
+
export function buildCorporateCommand() {
|
|
132
|
+
const corporate = new Command('corporate').description('企业综合管理只读查询:合作企业档案、文件用印、业务管理、规章制度及授权附件。');
|
|
133
|
+
attachListOptions(corporate.command('+partner-list').description('查询合作企业档案')).action(async (opts, cmd) => query(cmd, opts, listPayload('partner', opts)));
|
|
134
|
+
attachDetailOptions(corporate.command('+partner-detail').description('查看合作企业档案详情')).action(async (opts, cmd) => query(cmd, opts, {
|
|
135
|
+
operation: 'detail', record_type: 'partner', record_id: positiveId(opts.id, '--id'),
|
|
136
|
+
}));
|
|
137
|
+
attachListOptions(corporate.command('+seal-list').description('查询文件用印及印章记录'), true)
|
|
138
|
+
.option('--end-date-from <date>', '合同结束日期起始,YYYY-MM-DD;仅合同类 kind')
|
|
139
|
+
.option('--end-date-to <date>', '合同结束日期截止,YYYY-MM-DD;仅合同类 kind')
|
|
140
|
+
.option('--all', '自动翻页返回全部匹配记录', false)
|
|
141
|
+
.action(async (opts, cmd) => {
|
|
142
|
+
const recordType = kind(SEAL_KINDS, opts.kind, '--kind');
|
|
143
|
+
if ((opts.endDateFrom || opts.endDateTo) && !['seal-contract', 'seal-creator', 'seal-procurement'].includes(recordType)) {
|
|
144
|
+
throw new VertuError('--end-date-from/--end-date-to 仅适用于 contract、creator、procurement。');
|
|
145
|
+
}
|
|
146
|
+
const payload = listPayload(recordType, opts);
|
|
147
|
+
if (opts.endDateFrom || opts.endDateTo || opts.all) {
|
|
148
|
+
await queryContractList(cmd, opts, payload);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await query(cmd, opts, payload);
|
|
152
|
+
});
|
|
153
|
+
attachDetailOptions(corporate.command('+seal-detail').description('查看文件用印及印章详情'), true).action(async (opts, cmd) => query(cmd, opts, {
|
|
154
|
+
operation: 'detail', record_type: kind(SEAL_KINDS, opts.kind, '--kind'), record_id: positiveId(opts.id, '--id'),
|
|
155
|
+
}));
|
|
156
|
+
attachListOptions(corporate.command('+business-list').description('查询业务管理审批'), true).action(async (opts, cmd) => query(cmd, opts, listPayload(kind(BUSINESS_KINDS, opts.kind, '--kind'), opts)));
|
|
157
|
+
attachDetailOptions(corporate.command('+business-detail').description('查看业务管理审批详情'), true).action(async (opts, cmd) => query(cmd, opts, {
|
|
158
|
+
operation: 'detail', record_type: kind(BUSINESS_KINDS, opts.kind, '--kind'), record_id: positiveId(opts.id, '--id'),
|
|
159
|
+
}));
|
|
160
|
+
attachListOptions(corporate.command('+regulation-list').description('查询适用的规章制度')).action(async (opts, cmd) => query(cmd, opts, listPayload('regulation', opts)));
|
|
161
|
+
attachDetailOptions(corporate.command('+regulation-detail').description('查看规章制度详情')).action(async (opts, cmd) => query(cmd, opts, {
|
|
162
|
+
operation: 'detail', record_type: 'regulation', record_id: positiveId(opts.id, '--id'),
|
|
163
|
+
}));
|
|
164
|
+
attachSharedShortcutOptions(corporate.command('+attachments')
|
|
165
|
+
.description('列出已授权记录的附件元数据和下载选择器')
|
|
166
|
+
.requiredOption('--record-type <type>', '服务返回的 record_type')
|
|
167
|
+
.requiredOption('--record-id <id>', '记录 ID'))
|
|
168
|
+
.action(async (opts, cmd) => query(cmd, opts, {
|
|
169
|
+
operation: 'attachments', record_type: opts.recordType,
|
|
170
|
+
record_id: positiveId(opts.recordId, '--record-id'),
|
|
171
|
+
}));
|
|
172
|
+
attachSharedShortcutOptions(corporate.command('+attachment-download')
|
|
173
|
+
.description('下载已授权的企业综合管理附件,不覆盖已有文件')
|
|
174
|
+
.requiredOption('--record-type <type>', '附件列表返回的 record_type')
|
|
175
|
+
.requiredOption('--record-id <id>', '记录 ID')
|
|
176
|
+
.requiredOption('--field <field>', '附件列表返回的 field')
|
|
177
|
+
.requiredOption('--index <index>', '附件列表返回的 index')
|
|
178
|
+
.requiredOption('--output <path>', '本地输出文件'))
|
|
179
|
+
.action(async (opts, cmd) => {
|
|
180
|
+
const selector = {
|
|
181
|
+
record_type: opts.recordType,
|
|
182
|
+
record_id: positiveId(opts.recordId, '--record-id'),
|
|
183
|
+
field: opts.field,
|
|
184
|
+
index: parseOptionalInt(opts.index, '--index'),
|
|
185
|
+
};
|
|
186
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
187
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
188
|
+
print(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/corporate/attachment`, selector, output: opts.output });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (selector.index === undefined || selector.index < 0)
|
|
192
|
+
throw new VertuError('--index must be zero or greater.');
|
|
193
|
+
if (!opts.output?.trim())
|
|
194
|
+
throw new VertuError('--output is required.');
|
|
195
|
+
print(opts, await downloadCorporateAttachment(baseUrl, selector, opts.output));
|
|
196
|
+
});
|
|
197
|
+
corporate.addHelpText('after', [
|
|
198
|
+
'',
|
|
199
|
+
`Seal kinds: ${Object.keys(SEAL_KINDS).join(', ')}`,
|
|
200
|
+
`Business kinds: ${Object.keys(BUSINESS_KINDS).join(', ')}`,
|
|
201
|
+
'附件下载前先运行 +attachments,严格使用返回的 record_type / field / index。',
|
|
202
|
+
].join('\n'));
|
|
203
|
+
return corporate;
|
|
204
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { VertuError } from '../../core/errors.js';
|
|
3
|
+
import { vpsServiceGet, vpsServicePost } from '../../core/sales-query-client.js';
|
|
4
|
+
import { creativeDownloadPath, downloadCreativeAttachment } from '../../core/creative-attachment-download.js';
|
|
5
|
+
import { attachSharedShortcutOptions, parseOptionalFloat, parseOptionalInt, resolveShortcutVpsServiceUrl, } from '../shortcut-common.js';
|
|
6
|
+
/** Features · 内容创作 — `vps-work creative +…`(AI 生图 / 生视频 / 生音频)。 */
|
|
7
|
+
export function buildCreativeCommand() {
|
|
8
|
+
const creative = new Command('creative')
|
|
9
|
+
.description('内容创作:AI 生图、生视频、生音频(TTS/音乐)与模型列表,返回可下载附件链接。');
|
|
10
|
+
attachCreativeShortcuts(creative);
|
|
11
|
+
return creative;
|
|
12
|
+
}
|
|
13
|
+
function printResult(opts, result) {
|
|
14
|
+
if (opts.json !== false) {
|
|
15
|
+
console.log(JSON.stringify(result, null, 2));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log(typeof result === 'string' ? result : JSON.stringify(result));
|
|
19
|
+
}
|
|
20
|
+
/** 从站内受控 OSS 路径提取 key(`/api/oss/object?key=...`)。 */
|
|
21
|
+
function ossKeyFromUrl(url) {
|
|
22
|
+
try {
|
|
23
|
+
return new URL(url, 'http://creative-studio.invalid').searchParams.get('key');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 把创意工坊返回的站内受控附件路径 `/api/oss/object?key=...` 重写为
|
|
31
|
+
* vps-service 的可下载地址 `/v1/creative/oss?key=...`(复用 CLI 会话鉴权)。
|
|
32
|
+
* 递归处理对象/数组,覆盖 image_url / url / video_url / original_video_url 等字段。
|
|
33
|
+
*/
|
|
34
|
+
function rewriteOssUrls(value, baseUrl) {
|
|
35
|
+
if (typeof value === 'string' && value.startsWith('/api/oss/object')) {
|
|
36
|
+
const key = ossKeyFromUrl(value);
|
|
37
|
+
return key ? `${baseUrl}/v1/creative/oss?key=${encodeURIComponent(key)}` : value;
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value.map((item) => rewriteOssUrls(item, baseUrl));
|
|
41
|
+
}
|
|
42
|
+
if (value && typeof value === 'object') {
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [k, v] of Object.entries(value)) {
|
|
45
|
+
out[k] = rewriteOssUrls(v, baseUrl);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
async function printCreativeResult(opts, result, baseUrl) {
|
|
52
|
+
const publicBaseUrl = new URL(baseUrl).hostname === 'tenant-api' ? 'https://vps-service.vertu.cn' : baseUrl;
|
|
53
|
+
const rewritten = rewriteOssUrls(result, publicBaseUrl);
|
|
54
|
+
const keys = new Set();
|
|
55
|
+
const collect = (value) => {
|
|
56
|
+
if (typeof value === 'string' && value.startsWith(`${publicBaseUrl}/v1/creative/oss?`)) {
|
|
57
|
+
const key = ossKeyFromUrl(value);
|
|
58
|
+
if (key)
|
|
59
|
+
keys.add(key);
|
|
60
|
+
}
|
|
61
|
+
else if (Array.isArray(value))
|
|
62
|
+
value.forEach(collect);
|
|
63
|
+
else if (value && typeof value === 'object')
|
|
64
|
+
Object.values(value).forEach(collect);
|
|
65
|
+
};
|
|
66
|
+
collect(rewritten);
|
|
67
|
+
const attachments = [];
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
try {
|
|
70
|
+
const file = await vpsServiceGet('/v1/creative/oss', { key, mode: 'sign' }, baseUrl, '获取素材临时下载链接');
|
|
71
|
+
const name = key.split('/').pop() || 'attachment';
|
|
72
|
+
const ext = name.split('.').pop()?.toLowerCase() || '';
|
|
73
|
+
const mime = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif', mp4: 'video/mp4', mp3: 'audio/mpeg' }[ext];
|
|
74
|
+
attachments.push({ ...file, name, mime_type: mime, attachment_type: mime?.split('/')[0] || 'file', url: `${publicBaseUrl}/v1/creative/oss?key=${encodeURIComponent(key)}`, download_command: ['vps-work', 'creative', '+download', '--storage-key', key, '--output', name] });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Preserve successful generation even if the signing service is unavailable.
|
|
78
|
+
attachments.push({ storage_key: key, url: `${publicBaseUrl}/v1/creative/oss?key=${encodeURIComponent(key)}`, download_command: ['vps-work', 'creative', '+download', '--storage-key', key, '--output', key.split('/').pop() || 'attachment'], error: '临时链接获取失败,可通过 download_command 使用当前登录身份下载' });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
printResult(opts, attachments.length ? { ...(rewritten && typeof rewritten === 'object' && !Array.isArray(rewritten) ? rewritten : { result: rewritten }), attachments } : rewritten);
|
|
82
|
+
}
|
|
83
|
+
function splitCsv(raw) {
|
|
84
|
+
if (raw === undefined || raw.trim() === '') {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
88
|
+
return parts.length ? parts : undefined;
|
|
89
|
+
}
|
|
90
|
+
function splitCsvNumbers(raw) {
|
|
91
|
+
const parts = splitCsv(raw);
|
|
92
|
+
if (!parts) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return parts.map((p, i) => {
|
|
96
|
+
const n = Number(p);
|
|
97
|
+
if (!Number.isFinite(n)) {
|
|
98
|
+
throw new VertuError(`Invalid numeric list at index ${i}: "${p}"`);
|
|
99
|
+
}
|
|
100
|
+
return n;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function num(raw, flag) {
|
|
104
|
+
return parseOptionalFloat(raw, flag);
|
|
105
|
+
}
|
|
106
|
+
function int(raw, flag) {
|
|
107
|
+
return parseOptionalInt(raw, flag);
|
|
108
|
+
}
|
|
109
|
+
async function postCreative(opts, cmd, path, body, label) {
|
|
110
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
111
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
112
|
+
printResult(opts, {
|
|
113
|
+
validateOnly: true,
|
|
114
|
+
endpoint: `${baseUrl}${path}`,
|
|
115
|
+
payload: body,
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
await printCreativeResult(opts, await vpsServicePost(path, body, baseUrl, label), baseUrl);
|
|
120
|
+
}
|
|
121
|
+
async function pollVideoTask(opts, baseUrl, model, taskId) {
|
|
122
|
+
const timeoutSec = parseOptionalInt(opts.waitTimeout, '--wait-timeout') ?? 600;
|
|
123
|
+
const intervalSec = parseOptionalInt(opts.pollInterval, '--poll-interval') ?? 5;
|
|
124
|
+
const deadline = Date.now() + timeoutSec * 1000;
|
|
125
|
+
let last = '';
|
|
126
|
+
for (;;) {
|
|
127
|
+
const status = await vpsServiceGet('/v1/creative/video', { model, task_id: taskId }, baseUrl, '创意工坊生视频状态');
|
|
128
|
+
const s = String(status.status ?? 'pending');
|
|
129
|
+
if (s !== last) {
|
|
130
|
+
process.stderr.write(`[creative] video ${taskId}: ${s}\n`);
|
|
131
|
+
last = s;
|
|
132
|
+
}
|
|
133
|
+
if (s === 'succeeded' || s === 'failed' || s === 'error') {
|
|
134
|
+
return status;
|
|
135
|
+
}
|
|
136
|
+
if (Date.now() >= deadline) {
|
|
137
|
+
throw new VertuError(`生视频任务 ${taskId} 在 ${timeoutSec}s 内未完成,可用 creative +video-status --model ${model} --task-id ${taskId} 继续查询`);
|
|
138
|
+
}
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, intervalSec * 1000));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function attachCreativeShortcuts(parent) {
|
|
143
|
+
attachSharedShortcutOptions(parent.command('+download')
|
|
144
|
+
.description('通过当前账号下载创意素材到工作区,云电脑无需公网 DNS')
|
|
145
|
+
.option('--url <url>', 'VPS 创意素材地址')
|
|
146
|
+
.option('--storage-key <key>', '生成结果中的 storage_key')
|
|
147
|
+
.requiredOption('--output <path>', '保存路径,不覆盖已有文件'))
|
|
148
|
+
.action(async (opts, cmd) => {
|
|
149
|
+
const base = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
150
|
+
const path = creativeDownloadPath(base, opts);
|
|
151
|
+
if (opts.validateOnly || opts.dryRun) {
|
|
152
|
+
printResult(opts, { validateOnly: true, endpoint: new URL(path, base).href, output: opts.output });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
printResult(opts, await downloadCreativeAttachment(base, opts));
|
|
156
|
+
});
|
|
157
|
+
attachSharedShortcutOptions(parent
|
|
158
|
+
.command('+models')
|
|
159
|
+
.description('模型列表:按能力类型筛选(image / video / audio / tts)')
|
|
160
|
+
.option('--type <type>', '能力类型:image|video|audio|tts|text')).action(async (opts, cmd) => {
|
|
161
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
162
|
+
const query = opts.type ? { type: opts.type } : {};
|
|
163
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
164
|
+
printResult(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/creative/models`, query });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
printResult(opts, await vpsServiceGet('/v1/creative/models', query, baseUrl, '创意工坊模型'));
|
|
168
|
+
});
|
|
169
|
+
attachSharedShortcutOptions(parent
|
|
170
|
+
.command('+image')
|
|
171
|
+
.description('AI 生图 / 改图,返回 image_url 与计费(POST /api/aigc/image-edit)')
|
|
172
|
+
.option('--model <id>', '模型 id 或 model_id(必填)')
|
|
173
|
+
.option('--prompt <text>', '画面提示词(必填)')
|
|
174
|
+
.option('--source-image-url <url>', '参考/底图 URL')
|
|
175
|
+
.option('--reference-images <urls>', '参考图 URL 列表(逗号分隔)')
|
|
176
|
+
.option('--size <size>', '输出尺寸,如 1024x1024 / auto')
|
|
177
|
+
.option('--quality <q>', '质量:auto|low|medium|high|xhigh|max(后两档仅 gpt-image-2.5-sunburst)')
|
|
178
|
+
.option('--resolution <r>', '分辨率档:512|1K|2K|4K')
|
|
179
|
+
.option('--aspect-ratio <ratio>', '宽高比,如 1:1 / 16:9')
|
|
180
|
+
.option('--background <text>', '背景:auto|opaque|transparent(transparent 仅 gpt-image-2.5-sunburst)')
|
|
181
|
+
.option('--output-compression <n>', '输出压缩级别(整数)')
|
|
182
|
+
.option('--n <n>', '生成张数(当前接口固定单图)')).action(async (opts, cmd) => {
|
|
183
|
+
if (!opts.model || !opts.prompt) {
|
|
184
|
+
throw new VertuError('生图需要 --model 与 --prompt');
|
|
185
|
+
}
|
|
186
|
+
const body = { model: opts.model, prompt: opts.prompt };
|
|
187
|
+
if (opts.sourceImageUrl)
|
|
188
|
+
body.source_image_url = opts.sourceImageUrl;
|
|
189
|
+
const refs = splitCsv(opts.referenceImages);
|
|
190
|
+
if (refs)
|
|
191
|
+
body.reference_images = refs;
|
|
192
|
+
if (opts.size)
|
|
193
|
+
body.size = opts.size;
|
|
194
|
+
if (opts.quality)
|
|
195
|
+
body.quality = opts.quality;
|
|
196
|
+
if (opts.resolution)
|
|
197
|
+
body.resolution = opts.resolution;
|
|
198
|
+
if (opts.aspectRatio)
|
|
199
|
+
body.aspect_ratio = opts.aspectRatio;
|
|
200
|
+
if (opts.background)
|
|
201
|
+
body.background = opts.background;
|
|
202
|
+
const compression = int(opts.outputCompression, '--output-compression');
|
|
203
|
+
if (compression !== undefined)
|
|
204
|
+
body.output_compression = compression;
|
|
205
|
+
const count = int(opts.n, '--n');
|
|
206
|
+
if (count !== undefined)
|
|
207
|
+
body.n = count;
|
|
208
|
+
await postCreative(opts, cmd, '/v1/creative/image', body, '创意工坊生图');
|
|
209
|
+
});
|
|
210
|
+
attachSharedShortcutOptions(parent
|
|
211
|
+
.command('+video')
|
|
212
|
+
.description('AI 生视频(异步):提交后返回 task_id;加 --wait 轮询到完成')
|
|
213
|
+
.option('--model <id>', '模型 id 或 model_id(必填)')
|
|
214
|
+
.option('--prompt <text>', '文本描述')
|
|
215
|
+
.option('--reference-images <urls>', '参考图 URL 列表(逗号分隔)')
|
|
216
|
+
.option('--reference-videos <urls>', '参考视频 URL 列表(逗号分隔)')
|
|
217
|
+
.option('--reference-audios <urls>', '参考音频 URL 列表(逗号分隔)')
|
|
218
|
+
.option('--reference-audio-durations-sec <secs>', '各参考音频时长秒数(逗号分隔)')
|
|
219
|
+
.option('--first-frame-image <url>', '首帧图片 URL(Seedance 首尾帧)')
|
|
220
|
+
.option('--last-frame-image <url>', '尾帧图片 URL(Seedance 首尾帧)')
|
|
221
|
+
.option('--ratio <ratio>', '画幅比:16:9 / 9:16 / 1:1 等')
|
|
222
|
+
.option('--resolution <r>', '分辨率档')
|
|
223
|
+
.option('--duration <sec>', '视频时长(秒)')
|
|
224
|
+
.option('--generate-audio', '是否同时生成音频', false)
|
|
225
|
+
.option('--watermark', '是否加水印', false)
|
|
226
|
+
.option('--wait', '轮询到任务成功/失败后返回', false)
|
|
227
|
+
.option('--wait-timeout <sec>', '--wait 最长等待秒数,默认 600')
|
|
228
|
+
.option('--poll-interval <sec>', '--wait 轮询间隔秒数,默认 5')).action(async (opts, cmd) => {
|
|
229
|
+
if (!opts.model) {
|
|
230
|
+
throw new VertuError('生视频需要 --model');
|
|
231
|
+
}
|
|
232
|
+
const body = { model: opts.model };
|
|
233
|
+
if (opts.prompt)
|
|
234
|
+
body.prompt = opts.prompt;
|
|
235
|
+
const imgs = splitCsv(opts.referenceImages);
|
|
236
|
+
if (imgs)
|
|
237
|
+
body.reference_images = imgs;
|
|
238
|
+
const vids = splitCsv(opts.referenceVideos);
|
|
239
|
+
if (vids)
|
|
240
|
+
body.reference_videos = vids;
|
|
241
|
+
const auds = splitCsv(opts.referenceAudios);
|
|
242
|
+
if (auds)
|
|
243
|
+
body.reference_audios = auds;
|
|
244
|
+
const durs = splitCsvNumbers(opts.referenceAudioDurationsSec);
|
|
245
|
+
if (durs)
|
|
246
|
+
body.reference_audio_durations_sec = durs;
|
|
247
|
+
if (opts.firstFrameImage)
|
|
248
|
+
body.first_frame_image = opts.firstFrameImage;
|
|
249
|
+
if (opts.lastFrameImage)
|
|
250
|
+
body.last_frame_image = opts.lastFrameImage;
|
|
251
|
+
if (opts.ratio)
|
|
252
|
+
body.ratio = opts.ratio;
|
|
253
|
+
if (opts.resolution)
|
|
254
|
+
body.resolution = opts.resolution;
|
|
255
|
+
const duration = num(opts.duration, '--duration');
|
|
256
|
+
if (duration !== undefined)
|
|
257
|
+
body.duration = duration;
|
|
258
|
+
if (opts.generateAudio === true)
|
|
259
|
+
body.generate_audio = true;
|
|
260
|
+
if (opts.watermark === true)
|
|
261
|
+
body.watermark = true;
|
|
262
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
263
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
264
|
+
printResult(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/creative/video`, payload: body });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const result = await vpsServicePost('/v1/creative/video', body, baseUrl, '创意工坊生视频');
|
|
268
|
+
const taskId = typeof result.task_id === 'string'
|
|
269
|
+
? result.task_id
|
|
270
|
+
: '';
|
|
271
|
+
if (!taskId) {
|
|
272
|
+
await printCreativeResult(opts, result, baseUrl);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (opts.wait === true) {
|
|
276
|
+
await printCreativeResult(opts, await pollVideoTask(opts, baseUrl, opts.model, taskId), baseUrl);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
await printCreativeResult(opts, result, baseUrl);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
attachSharedShortcutOptions(parent
|
|
283
|
+
.command('+video-status')
|
|
284
|
+
.description('查询视频生成任务状态,返回 status 与 video_url')
|
|
285
|
+
.option('--model <id>', '模型 id(必填)')
|
|
286
|
+
.option('--task-id <id>', '任务 id(必填)')).action(async (opts, cmd) => {
|
|
287
|
+
if (!opts.model || !opts.taskId) {
|
|
288
|
+
throw new VertuError('需要 --model 与 --task-id');
|
|
289
|
+
}
|
|
290
|
+
const baseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
|
|
291
|
+
const query = { model: opts.model, task_id: opts.taskId };
|
|
292
|
+
if (opts.validateOnly === true || opts.dryRun === true) {
|
|
293
|
+
printResult(opts, { validateOnly: true, endpoint: `${baseUrl}/v1/creative/video`, query });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
await printCreativeResult(opts, await vpsServiceGet('/v1/creative/video', query, baseUrl, '创意工坊生视频状态'), baseUrl);
|
|
297
|
+
});
|
|
298
|
+
attachSharedShortcutOptions(parent
|
|
299
|
+
.command('+tts')
|
|
300
|
+
.description('文字转语音,返回音频 url(POST /api/aigc/audio-tts)')
|
|
301
|
+
.option('--model <id>', '模型 id 或 model_id(必填)')
|
|
302
|
+
.option('--text <text>', '待合成文本(必填)')
|
|
303
|
+
.option('--voice-id <id>', '音色 id')
|
|
304
|
+
.option('--speed <n>', '语速')
|
|
305
|
+
.option('--vol <n>', '音量')
|
|
306
|
+
.option('--pitch <n>', '音调')
|
|
307
|
+
.option('--emotion <e>', '情感')
|
|
308
|
+
.option('--format <fmt>', 'mp3|wav|flac|pcm')
|
|
309
|
+
.option('--sample-rate <hz>', '采样率')
|
|
310
|
+
.option('--bitrate <kbps>', '码率')).action(async (opts, cmd) => {
|
|
311
|
+
if (!opts.model || !opts.text) {
|
|
312
|
+
throw new VertuError('语音合成需要 --model 与 --text');
|
|
313
|
+
}
|
|
314
|
+
const body = { model: opts.model, text: opts.text };
|
|
315
|
+
if (opts.voiceId)
|
|
316
|
+
body.voice_id = opts.voiceId;
|
|
317
|
+
const speed = num(opts.speed, '--speed');
|
|
318
|
+
if (speed !== undefined)
|
|
319
|
+
body.speed = speed;
|
|
320
|
+
const vol = num(opts.vol, '--vol');
|
|
321
|
+
if (vol !== undefined)
|
|
322
|
+
body.vol = vol;
|
|
323
|
+
const pitch = num(opts.pitch, '--pitch');
|
|
324
|
+
if (pitch !== undefined)
|
|
325
|
+
body.pitch = pitch;
|
|
326
|
+
if (opts.emotion)
|
|
327
|
+
body.emotion = opts.emotion;
|
|
328
|
+
if (opts.format)
|
|
329
|
+
body.format = opts.format;
|
|
330
|
+
const sampleRate = int(opts.sampleRate, '--sample-rate');
|
|
331
|
+
if (sampleRate !== undefined)
|
|
332
|
+
body.sample_rate = sampleRate;
|
|
333
|
+
const bitrate = int(opts.bitrate, '--bitrate');
|
|
334
|
+
if (bitrate !== undefined)
|
|
335
|
+
body.bitrate = bitrate;
|
|
336
|
+
await postCreative(opts, cmd, '/v1/creative/audio/tts', body, '创意工坊语音合成');
|
|
337
|
+
});
|
|
338
|
+
attachSharedShortcutOptions(parent
|
|
339
|
+
.command('+music')
|
|
340
|
+
.description('AI 音乐生成,返回音频 url(POST /api/aigc/audio-music)')
|
|
341
|
+
.option('--model <id>', '模型 id 或 model_id(必填)')
|
|
342
|
+
.option('--prompt <text>', '风格/情绪/场景描述(必填)')
|
|
343
|
+
.option('--lyrics <text>', '歌词;不传走纯音乐')
|
|
344
|
+
.option('--instrumental', '纯音乐(无歌词)', false)
|
|
345
|
+
.option('--lyrics-optimizer', '开启歌词优化', false)
|
|
346
|
+
.option('--format <fmt>', 'mp3|wav|pcm')
|
|
347
|
+
.option('--sample-rate <hz>', '采样率')
|
|
348
|
+
.option('--bitrate <kbps>', '码率')).action(async (opts, cmd) => {
|
|
349
|
+
if (!opts.model || !opts.prompt) {
|
|
350
|
+
throw new VertuError('音乐生成需要 --model 与 --prompt');
|
|
351
|
+
}
|
|
352
|
+
const body = { model: opts.model, prompt: opts.prompt };
|
|
353
|
+
if (opts.lyrics)
|
|
354
|
+
body.lyrics = opts.lyrics;
|
|
355
|
+
if (opts.instrumental === true)
|
|
356
|
+
body.is_instrumental = true;
|
|
357
|
+
if (opts.lyricsOptimizer === true)
|
|
358
|
+
body.lyrics_optimizer = true;
|
|
359
|
+
if (opts.format)
|
|
360
|
+
body.format = opts.format;
|
|
361
|
+
const sampleRate = int(opts.sampleRate, '--sample-rate');
|
|
362
|
+
if (sampleRate !== undefined)
|
|
363
|
+
body.sample_rate = sampleRate;
|
|
364
|
+
const bitrate = int(opts.bitrate, '--bitrate');
|
|
365
|
+
if (bitrate !== undefined)
|
|
366
|
+
body.bitrate = bitrate;
|
|
367
|
+
await postCreative(opts, cmd, '/v1/creative/audio/music', body, '创意工坊音乐生成');
|
|
368
|
+
});
|
|
369
|
+
}
|