gaoding-cli 1.0.0-alpha.5
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/CHANGELOG.md +88 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/SECURITY.md +34 -0
- package/dist/bin/gd-cli.d.ts +2 -0
- package/dist/bin/gd-cli.js +10 -0
- package/dist/src/api/app-runner.d.ts +33 -0
- package/dist/src/api/app-runner.js +1177 -0
- package/dist/src/api/dam-client.d.ts +111 -0
- package/dist/src/api/dam-client.js +897 -0
- package/dist/src/api/direct-auth.d.ts +3 -0
- package/dist/src/api/direct-auth.js +11 -0
- package/dist/src/api/direct-client.d.ts +27 -0
- package/dist/src/api/direct-client.js +151 -0
- package/dist/src/api/direct-tools.d.ts +43 -0
- package/dist/src/api/direct-tools.js +1155 -0
- package/dist/src/api/hub-client.d.ts +20 -0
- package/dist/src/api/hub-client.js +99 -0
- package/dist/src/api/sso-client.d.ts +60 -0
- package/dist/src/api/sso-client.js +140 -0
- package/dist/src/api/task-poller.d.ts +38 -0
- package/dist/src/api/task-poller.js +72 -0
- package/dist/src/api/tool-invoker.d.ts +12 -0
- package/dist/src/api/tool-invoker.js +11 -0
- package/dist/src/api/usage-client.d.ts +39 -0
- package/dist/src/api/usage-client.js +179 -0
- package/dist/src/auth/credential-store-v2.d.ts +12 -0
- package/dist/src/auth/credential-store-v2.js +59 -0
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +191 -0
- package/dist/src/commands/dam.d.ts +2 -0
- package/dist/src/commands/dam.js +293 -0
- package/dist/src/commands/models.d.ts +2 -0
- package/dist/src/commands/models.js +78 -0
- package/dist/src/commands/org.d.ts +2 -0
- package/dist/src/commands/org.js +137 -0
- package/dist/src/commands/skills.d.ts +2 -0
- package/dist/src/commands/skills.js +213 -0
- package/dist/src/commands/tools.d.ts +4 -0
- package/dist/src/commands/tools.js +383 -0
- package/dist/src/commands/update.d.ts +2 -0
- package/dist/src/commands/update.js +167 -0
- package/dist/src/commands/usage.d.ts +7 -0
- package/dist/src/commands/usage.js +43 -0
- package/dist/src/commands/workflows.d.ts +3 -0
- package/dist/src/commands/workflows.js +398 -0
- package/dist/src/core/auth/auth-provider.d.ts +8 -0
- package/dist/src/core/auth/auth-provider.js +12 -0
- package/dist/src/core/auth/device-oauth-provider.d.ts +12 -0
- package/dist/src/core/auth/device-oauth-provider.js +124 -0
- package/dist/src/core/auth/org-manager.d.ts +26 -0
- package/dist/src/core/auth/org-manager.js +224 -0
- package/dist/src/core/auth/signature.d.ts +19 -0
- package/dist/src/core/auth/signature.js +33 -0
- package/dist/src/core/auth/types.d.ts +52 -0
- package/dist/src/core/auth/types.js +3 -0
- package/dist/src/core/output/cli-error.d.ts +5 -0
- package/dist/src/core/output/cli-error.js +8 -0
- package/dist/src/core/output/detector.d.ts +5 -0
- package/dist/src/core/output/detector.js +14 -0
- package/dist/src/core/output/engine.d.ts +32 -0
- package/dist/src/core/output/engine.js +77 -0
- package/dist/src/core/output/envelope.d.ts +40 -0
- package/dist/src/core/output/envelope.js +119 -0
- package/dist/src/core/output/index.d.ts +4 -0
- package/dist/src/core/output/index.js +4 -0
- package/dist/src/core/output/live-display.d.ts +15 -0
- package/dist/src/core/output/live-display.js +111 -0
- package/dist/src/core/output/result-card.d.ts +18 -0
- package/dist/src/core/output/result-card.js +39 -0
- package/dist/src/core/prompt/index.d.ts +31 -0
- package/dist/src/core/prompt/index.js +36 -0
- package/dist/src/io.d.ts +14 -0
- package/dist/src/io.js +31 -0
- package/dist/src/middleware/auth.d.ts +3 -0
- package/dist/src/middleware/auth.js +74 -0
- package/dist/src/middleware/error-handler.d.ts +3 -0
- package/dist/src/middleware/error-handler.js +431 -0
- package/dist/src/middleware/interactive-params.d.ts +14 -0
- package/dist/src/middleware/interactive-params.js +53 -0
- package/dist/src/middleware/output.d.ts +14 -0
- package/dist/src/middleware/output.js +546 -0
- package/dist/src/models/model-catalog.d.ts +26 -0
- package/dist/src/models/model-catalog.js +216 -0
- package/dist/src/program.d.ts +2 -0
- package/dist/src/program.js +152 -0
- package/dist/src/registry/agent-contracts.d.ts +43 -0
- package/dist/src/registry/agent-contracts.js +483 -0
- package/dist/src/registry/app-loader.d.ts +2 -0
- package/dist/src/registry/app-loader.js +70 -0
- package/dist/src/registry/direct-registry.d.ts +11 -0
- package/dist/src/registry/direct-registry.js +234 -0
- package/dist/src/registry/registry-cache.d.ts +10 -0
- package/dist/src/registry/registry-cache.js +52 -0
- package/dist/src/registry/registry-client.d.ts +28 -0
- package/dist/src/registry/registry-client.js +50 -0
- package/dist/src/registry/types.d.ts +69 -0
- package/dist/src/registry/types.js +1 -0
- package/dist/src/tty/auth-presenter.d.ts +3 -0
- package/dist/src/tty/auth-presenter.js +21 -0
- package/dist/src/update/install-source.d.ts +19 -0
- package/dist/src/update/install-source.js +139 -0
- package/dist/src/update/notifier.d.ts +2 -0
- package/dist/src/update/notifier.js +59 -0
- package/dist/src/update/update-checker.d.ts +22 -0
- package/dist/src/update/update-checker.js +110 -0
- package/dist/src/update/version.d.ts +9 -0
- package/dist/src/update/version.js +52 -0
- package/dist/src/utils/agent-skill-installer.d.ts +57 -0
- package/dist/src/utils/agent-skill-installer.js +389 -0
- package/dist/src/utils/capability-metadata.d.ts +18 -0
- package/dist/src/utils/capability-metadata.js +88 -0
- package/dist/src/utils/capability-presenter.d.ts +4 -0
- package/dist/src/utils/capability-presenter.js +190 -0
- package/dist/src/utils/config.d.ts +16 -0
- package/dist/src/utils/config.js +54 -0
- package/dist/src/utils/credential-permissions.d.ts +2 -0
- package/dist/src/utils/credential-permissions.js +22 -0
- package/dist/src/utils/ecommerce-input-guidance.d.ts +15 -0
- package/dist/src/utils/ecommerce-input-guidance.js +85 -0
- package/dist/src/utils/endpoints.d.ts +52 -0
- package/dist/src/utils/endpoints.js +258 -0
- package/dist/src/utils/enrich-upload-params.d.ts +6 -0
- package/dist/src/utils/enrich-upload-params.js +11 -0
- package/dist/src/utils/entitlement.d.ts +17 -0
- package/dist/src/utils/entitlement.js +146 -0
- package/dist/src/utils/gd-home.d.ts +7 -0
- package/dist/src/utils/gd-home.js +26 -0
- package/dist/src/utils/help-verify.d.ts +9 -0
- package/dist/src/utils/help-verify.js +41 -0
- package/dist/src/utils/input-json.d.ts +3 -0
- package/dist/src/utils/input-json.js +61 -0
- package/dist/src/utils/list-output-format.d.ts +10 -0
- package/dist/src/utils/list-output-format.js +34 -0
- package/dist/src/utils/logger.d.ts +6 -0
- package/dist/src/utils/logger.js +19 -0
- package/dist/src/utils/matting-compose.d.ts +10 -0
- package/dist/src/utils/matting-compose.js +45 -0
- package/dist/src/utils/open-browser.d.ts +1 -0
- package/dist/src/utils/open-browser.js +4 -0
- package/dist/src/utils/output-path.d.ts +5 -0
- package/dist/src/utils/output-path.js +70 -0
- package/dist/src/utils/redact.d.ts +3 -0
- package/dist/src/utils/redact.js +96 -0
- package/dist/src/utils/reference-image.d.ts +8 -0
- package/dist/src/utils/reference-image.js +43 -0
- package/dist/src/utils/resolve-app-local-images.d.ts +8 -0
- package/dist/src/utils/resolve-app-local-images.js +84 -0
- package/dist/src/utils/sensitive-path.d.ts +6 -0
- package/dist/src/utils/sensitive-path.js +69 -0
- package/dist/src/utils/settings-store.d.ts +42 -0
- package/dist/src/utils/settings-store.js +50 -0
- package/dist/src/utils/sleep.d.ts +1 -0
- package/dist/src/utils/sleep.js +3 -0
- package/dist/src/utils/spinner.d.ts +2 -0
- package/dist/src/utils/spinner.js +4 -0
- package/dist/src/utils/static-help.d.ts +2 -0
- package/dist/src/utils/static-help.js +3 -0
- package/dist/src/utils/tool-display.d.ts +10 -0
- package/dist/src/utils/tool-display.js +55 -0
- package/dist/src/utils/transient-network-error.d.ts +4 -0
- package/dist/src/utils/transient-network-error.js +40 -0
- package/dist/src/utils/url-safety.d.ts +6 -0
- package/dist/src/utils/url-safety.js +67 -0
- package/dist/src/utils/write-app-deliverables.d.ts +18 -0
- package/dist/src/utils/write-app-deliverables.js +386 -0
- package/package.json +91 -0
- package/skills/gd-cli/SKILL.md +57 -0
- package/skills/gd-cli/references/auth.md +31 -0
- package/skills/gd-cli/references/deliverables.md +22 -0
- package/skills/gd-cli/references/entitlement.md +21 -0
- package/skills/gd-cli/references/errors.md +19 -0
- package/skills/gd-cli/references/org.md +18 -0
- package/skills/gd-cli/references/sop.md +25 -0
- package/skills/gd-cli/references/tools.md +29 -0
- package/skills/gd-cli/references/update.md +16 -0
- package/skills/gd-cli/references/workflows.md +22 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { GD_DIR, CREDENTIALS_FILE, migrateCredentialsIfNeeded } from "../utils/config.js";
|
|
4
|
+
import { isCredentialsV2 } from "../core/auth/types.js";
|
|
5
|
+
import { ensureCredentialDir, ensureCredentialFile } from "../utils/credential-permissions.js";
|
|
6
|
+
export class CredentialStoreV2 {
|
|
7
|
+
dir;
|
|
8
|
+
file;
|
|
9
|
+
constructor(customDir) {
|
|
10
|
+
if (!customDir)
|
|
11
|
+
migrateCredentialsIfNeeded();
|
|
12
|
+
this.dir = customDir ?? GD_DIR;
|
|
13
|
+
this.file = customDir
|
|
14
|
+
? path.join(customDir, "credentials.json")
|
|
15
|
+
: CREDENTIALS_FILE;
|
|
16
|
+
}
|
|
17
|
+
read() {
|
|
18
|
+
const raw = this.readAny();
|
|
19
|
+
if (!raw || !isCredentialsV2(raw))
|
|
20
|
+
return null;
|
|
21
|
+
return raw;
|
|
22
|
+
}
|
|
23
|
+
readAny() {
|
|
24
|
+
if (!fs.existsSync(this.file))
|
|
25
|
+
return null;
|
|
26
|
+
ensureCredentialDir(this.dir);
|
|
27
|
+
ensureCredentialFile(this.file);
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(this.file, "utf-8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
write(credentials) {
|
|
36
|
+
ensureCredentialDir(this.dir);
|
|
37
|
+
const tmp = `${this.file}.tmp`;
|
|
38
|
+
fs.writeFileSync(tmp, JSON.stringify(credentials, null, 2), { mode: 0o600 });
|
|
39
|
+
ensureCredentialFile(tmp);
|
|
40
|
+
fs.renameSync(tmp, this.file);
|
|
41
|
+
ensureCredentialFile(this.file);
|
|
42
|
+
}
|
|
43
|
+
delete() {
|
|
44
|
+
if (fs.existsSync(this.file))
|
|
45
|
+
fs.unlinkSync(this.file);
|
|
46
|
+
}
|
|
47
|
+
isAkExpired() {
|
|
48
|
+
const creds = this.read();
|
|
49
|
+
if (!creds)
|
|
50
|
+
return true;
|
|
51
|
+
return new Date(creds.expires_at) <= new Date();
|
|
52
|
+
}
|
|
53
|
+
isBindExpired() {
|
|
54
|
+
const creds = this.read();
|
|
55
|
+
if (!creds?.current_org?.bind_expires_at)
|
|
56
|
+
return false;
|
|
57
|
+
return new Date(creds.current_org.bind_expires_at) <= new Date();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { withErrorHandler } from "../middleware/error-handler.js";
|
|
3
|
+
import { createAuthProvider } from "../core/auth/auth-provider.js";
|
|
4
|
+
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
5
|
+
import { resolveEndpoints, isVerbose, isDebug } from "../utils/config.js";
|
|
6
|
+
import { getIO } from "../io.js";
|
|
7
|
+
import { emitJson, wrapEnvelope } from "../core/output/envelope.js";
|
|
8
|
+
import { setStaticHelp } from "../utils/static-help.js";
|
|
9
|
+
import { resolveInvokeOutputFormat } from "../utils/list-output-format.js";
|
|
10
|
+
const loginAction = withErrorHandler(async (options) => {
|
|
11
|
+
if (isVerbose() || isDebug()) {
|
|
12
|
+
const ep = resolveEndpoints();
|
|
13
|
+
console.error(chalk.dim(`[gd-cli] env=${ep.active_env} hub=${ep.hub_api_base.value} sso=${ep.sso_api_base.value} auth_page=${ep.auth_page_origin.value}`));
|
|
14
|
+
}
|
|
15
|
+
const provider = await createAuthProvider();
|
|
16
|
+
const result = await provider.login({
|
|
17
|
+
orgId: options.org,
|
|
18
|
+
noBrowser: !!options.noBrowser,
|
|
19
|
+
scope: options.scope,
|
|
20
|
+
env: options.env,
|
|
21
|
+
});
|
|
22
|
+
const io = getIO();
|
|
23
|
+
if (io.prefersJsonOutput()) {
|
|
24
|
+
emitJson(wrapEnvelope({
|
|
25
|
+
mode: provider.mode,
|
|
26
|
+
logged_in: true,
|
|
27
|
+
user: result.credentials.user,
|
|
28
|
+
org: result.org,
|
|
29
|
+
next_steps: result.org ? [] : ["gd-cli org switch"],
|
|
30
|
+
}, { command: "auth login" }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk.green("\n✓ 授权成功,可以开始使用稿定能力进行创意生产\n"));
|
|
34
|
+
if (!result.org) {
|
|
35
|
+
console.log(chalk.yellow(" 未获取到授权团队,请执行 gd-cli org switch 后再调用 Workflows / Tools / DAM。"));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const statusAction = withErrorHandler(async (_opts, command) => {
|
|
39
|
+
const cmd = command;
|
|
40
|
+
const format = resolveInvokeOutputFormat(cmd);
|
|
41
|
+
const io = getIO();
|
|
42
|
+
const store = new CredentialStoreV2();
|
|
43
|
+
const creds = store.read();
|
|
44
|
+
const ep = resolveEndpoints();
|
|
45
|
+
if (!creds) {
|
|
46
|
+
if (io.prefersJsonOutput(format)) {
|
|
47
|
+
emitJson(wrapEnvelope({
|
|
48
|
+
logged_in: false,
|
|
49
|
+
environment: { active: ep.active_env },
|
|
50
|
+
next_steps: ["gd-cli auth login"],
|
|
51
|
+
}, { command: "auth status" }));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(chalk.yellow("未授权"));
|
|
55
|
+
console.log(" 登录: gd-cli auth login");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const payload = buildStatusPayload(creds, store);
|
|
59
|
+
if (io.prefersJsonOutput(format)) {
|
|
60
|
+
emitJson(wrapEnvelope(payload, { command: "auth status" }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
printStatusHuman(payload);
|
|
64
|
+
});
|
|
65
|
+
const logoutAction = withErrorHandler(async () => {
|
|
66
|
+
const provider = await createAuthProvider();
|
|
67
|
+
await provider.logout();
|
|
68
|
+
new CredentialStoreV2().delete();
|
|
69
|
+
const io = getIO();
|
|
70
|
+
if (io.prefersJsonOutput()) {
|
|
71
|
+
emitJson(wrapEnvelope({ logged_out: true }, { command: "auth logout" }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk.green("✓ 已取消授权,可以重新执行 gd-cli auth login 进行授权。\n"));
|
|
75
|
+
});
|
|
76
|
+
export function registerAuthCommand(program) {
|
|
77
|
+
const auth = program
|
|
78
|
+
.command("auth")
|
|
79
|
+
.description("登录、退出、查看登录状态");
|
|
80
|
+
setStaticHelp(auth, AUTH_HELP);
|
|
81
|
+
auth
|
|
82
|
+
.command("login")
|
|
83
|
+
.description("浏览器/扫码授权登录")
|
|
84
|
+
.option("--org <org-id>", "登录后直接绑定该组织")
|
|
85
|
+
.option("--env <env>", "环境标识 cursor / claude / ci")
|
|
86
|
+
.option("--scope <scope>", "授权范围,逗号分隔")
|
|
87
|
+
.option("--no-browser", "不自动打开浏览器")
|
|
88
|
+
.action(loginAction);
|
|
89
|
+
auth
|
|
90
|
+
.command("logout")
|
|
91
|
+
.description("取消授权")
|
|
92
|
+
.action(logoutAction);
|
|
93
|
+
auth.command("status").description("查看授权状态").action(statusAction);
|
|
94
|
+
}
|
|
95
|
+
const AUTH_HELP = `
|
|
96
|
+
Usage: gd-cli auth [options] [command]
|
|
97
|
+
|
|
98
|
+
登录、退出、查看登录状态
|
|
99
|
+
|
|
100
|
+
Commands:
|
|
101
|
+
login 浏览器/扫码授权登录
|
|
102
|
+
logout 取消授权
|
|
103
|
+
status 查看授权状态
|
|
104
|
+
`;
|
|
105
|
+
function buildStatusPayload(creds, store) {
|
|
106
|
+
const ep = resolveEndpoints();
|
|
107
|
+
const credentialExpired = store.isAkExpired();
|
|
108
|
+
const orgBindExpired = store.isBindExpired();
|
|
109
|
+
const envMatches = !creds.issued_for_env || creds.issued_for_env === ep.active_env;
|
|
110
|
+
const nextSteps = [];
|
|
111
|
+
if (credentialExpired)
|
|
112
|
+
nextSteps.push("gd-cli auth login");
|
|
113
|
+
if (!creds.current_org?.id)
|
|
114
|
+
nextSteps.push("gd-cli org list", "gd-cli org switch");
|
|
115
|
+
if (orgBindExpired)
|
|
116
|
+
nextSteps.push("gd-cli org switch");
|
|
117
|
+
if (!envMatches)
|
|
118
|
+
nextSteps.push("gd-cli auth login");
|
|
119
|
+
const payload = {
|
|
120
|
+
logged_in: true,
|
|
121
|
+
environment: {
|
|
122
|
+
active: ep.active_env,
|
|
123
|
+
issued_for: creds.issued_for_env ?? null,
|
|
124
|
+
matches_credentials: envMatches,
|
|
125
|
+
},
|
|
126
|
+
account: {
|
|
127
|
+
user_id: creds.user?.id ?? null,
|
|
128
|
+
name: creds.user?.name ?? null,
|
|
129
|
+
},
|
|
130
|
+
organization: creds.current_org
|
|
131
|
+
? {
|
|
132
|
+
id: creds.current_org.id,
|
|
133
|
+
name: creds.current_org.name,
|
|
134
|
+
bind_expires_at: creds.current_org.bind_expires_at ?? null,
|
|
135
|
+
bind_valid: !orgBindExpired,
|
|
136
|
+
}
|
|
137
|
+
: null,
|
|
138
|
+
credential: {
|
|
139
|
+
mode: "aksk",
|
|
140
|
+
ak_preview: `${creds.ak.slice(0, 8)}***`,
|
|
141
|
+
expires_at: creds.expires_at,
|
|
142
|
+
valid: !credentialExpired,
|
|
143
|
+
scope: creds.scope,
|
|
144
|
+
},
|
|
145
|
+
next_steps: nextSteps,
|
|
146
|
+
};
|
|
147
|
+
if (isVerbose() || isDebug()) {
|
|
148
|
+
payload.debug = {
|
|
149
|
+
hub_api_base: ep.hub_api_base.value,
|
|
150
|
+
sso_api_base: ep.sso_api_base.value,
|
|
151
|
+
auth_page_origin: ep.auth_page_origin.value,
|
|
152
|
+
web_origin: ep.web_origin.value,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return payload;
|
|
156
|
+
}
|
|
157
|
+
function printStatusHuman(payload) {
|
|
158
|
+
const environment = payload.environment;
|
|
159
|
+
const account = payload.account;
|
|
160
|
+
const organization = payload.organization;
|
|
161
|
+
const credential = payload.credential;
|
|
162
|
+
const nextSteps = payload.next_steps;
|
|
163
|
+
console.log(chalk.bold("gd-cli 授权状态"));
|
|
164
|
+
console.log(` 状态: ${payload.logged_in ? "已登录" : "未登录"}`);
|
|
165
|
+
// console.log(` 环境: ${environment.active}`);
|
|
166
|
+
if (environment.matches_credentials === false) {
|
|
167
|
+
console.log(chalk.yellow(` 提醒: 凭证属于 ${environment.issued_for},当前环境为 ${environment.active}`));
|
|
168
|
+
}
|
|
169
|
+
if (account.user_id) {
|
|
170
|
+
console.log(` 用户: ${account.name ?? account.user_id}`);
|
|
171
|
+
}
|
|
172
|
+
if (organization) {
|
|
173
|
+
const valid = organization.bind_valid ? "有效" : "已过期";
|
|
174
|
+
console.log(` 当前组织: ${organization.name} (${organization.id})`);
|
|
175
|
+
console.log(` 组织绑定: ${valid}${organization.bind_expires_at ? `,至 ${organization.bind_expires_at}` : ""}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log(chalk.yellow(" 当前组织: 未绑定"));
|
|
179
|
+
}
|
|
180
|
+
console.log(` 凭证: AK/SK ${credential.ak_preview},${credential.valid ? "有效" : "已过期"}`);
|
|
181
|
+
console.log(` 过期时间: ${credential.expires_at}`);
|
|
182
|
+
const scope = Array.isArray(credential.scope) ? credential.scope.join(", ") : "";
|
|
183
|
+
if (scope)
|
|
184
|
+
console.log(` 权限范围: ${scope}`);
|
|
185
|
+
if (nextSteps.length > 0) {
|
|
186
|
+
console.log("\n下一步:");
|
|
187
|
+
for (const step of nextSteps)
|
|
188
|
+
console.log(` ${step}`);
|
|
189
|
+
}
|
|
190
|
+
console.log("");
|
|
191
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { withAuth } from "../middleware/auth.js";
|
|
4
|
+
import { withErrorHandler } from "../middleware/error-handler.js";
|
|
5
|
+
import { DamClient } from "../api/dam-client.js";
|
|
6
|
+
import { formatOutput } from "../middleware/output.js";
|
|
7
|
+
import { resolveInvokeOutputFormat, resolveListOutputFormat, } from "../utils/list-output-format.js";
|
|
8
|
+
import { createSpinner } from "../utils/spinner.js";
|
|
9
|
+
import { emitJson, wrapEnvelope, shouldEmitEnvelope } from "../core/output/envelope.js";
|
|
10
|
+
import { setStaticHelp } from "../utils/static-help.js";
|
|
11
|
+
export function registerDamCommand(program) {
|
|
12
|
+
const dam = program
|
|
13
|
+
.command("dam")
|
|
14
|
+
.usage("<command> [options]")
|
|
15
|
+
.description("DAM 素材管理")
|
|
16
|
+
.configureHelp({ showGlobalOptions: true });
|
|
17
|
+
setStaticHelp(dam, DAM_HELP);
|
|
18
|
+
dam
|
|
19
|
+
.command("upload <file>")
|
|
20
|
+
.description("上传本地文件并提交 DAM 入库,返回 DAM asset_id 与 CDN URL")
|
|
21
|
+
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
22
|
+
.option("--name <name>", "指定素材名称")
|
|
23
|
+
.option("--folder-id <id>", "DAM 文件夹 ID,MVP 默认 0", "0")
|
|
24
|
+
.option("--folder <name>", "文件夹名称")
|
|
25
|
+
.option("--tags <list>", "标签,逗号分隔")
|
|
26
|
+
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
27
|
+
.option("--storage-only", "只上传对象存储,不提交 DAM 入库", false)
|
|
28
|
+
.option("--allow-sensitive-path", "确认上传默认受保护的本地路径", false)
|
|
29
|
+
.option("--wait-analysis", "等待 DAM 后端分析完成并返回封面", false)
|
|
30
|
+
.option("--analysis-timeout <ms>", "等待分析超时时间(毫秒)", parsePositiveInteger, 180000)
|
|
31
|
+
.option("--analysis-interval <ms>", "分析轮询间隔(毫秒)", parsePositiveInteger, 3000)
|
|
32
|
+
.action(withErrorHandler(withAuth(async (file, options, command) => {
|
|
33
|
+
const opts = options;
|
|
34
|
+
const cmd = command;
|
|
35
|
+
const p = resolve(process.cwd(), String(file));
|
|
36
|
+
if (!existsSync(p) || !statSync(p).isFile()) {
|
|
37
|
+
throw new Error(`文件不存在: ${p}`);
|
|
38
|
+
}
|
|
39
|
+
const spinner = createSpinner("上传素材…");
|
|
40
|
+
if (process.stderr.isTTY)
|
|
41
|
+
spinner.start();
|
|
42
|
+
const damClient = new DamClient();
|
|
43
|
+
const uploaded = await damClient.uploadLocalAsset(p, {
|
|
44
|
+
register: !opts.storageOnly,
|
|
45
|
+
allowSensitivePath: opts.allowSensitivePath === true,
|
|
46
|
+
repositoryId: optionalString(opts.repositoryId),
|
|
47
|
+
folderId: optionalString(opts.folderId) ?? "0",
|
|
48
|
+
title: optionalString(opts.name),
|
|
49
|
+
tagIds: splitList(opts.tagIds),
|
|
50
|
+
waitAnalysis: Boolean(opts.waitAnalysis) && !opts.storageOnly,
|
|
51
|
+
analysisTimeoutMs: numberOption(opts.analysisTimeout),
|
|
52
|
+
analysisIntervalMs: numberOption(opts.analysisInterval),
|
|
53
|
+
});
|
|
54
|
+
if (process.stderr.isTTY)
|
|
55
|
+
spinner.succeed("上传完成");
|
|
56
|
+
const asset = {
|
|
57
|
+
id: uploaded.id,
|
|
58
|
+
url: uploaded.url,
|
|
59
|
+
type: uploaded.mime_type,
|
|
60
|
+
filename: basename(p),
|
|
61
|
+
source: "cli",
|
|
62
|
+
cover_url: uploaded.cover_url,
|
|
63
|
+
};
|
|
64
|
+
const payload = {
|
|
65
|
+
status: "completed",
|
|
66
|
+
asset,
|
|
67
|
+
outputs: {
|
|
68
|
+
asset_id: uploaded.id,
|
|
69
|
+
id: uploaded.id,
|
|
70
|
+
storage_id: uploaded.storage_id,
|
|
71
|
+
registered_asset_id: uploaded.registered_asset_id,
|
|
72
|
+
registered: uploaded.registered ?? false,
|
|
73
|
+
url: uploaded.url,
|
|
74
|
+
cover_url: uploaded.cover_url,
|
|
75
|
+
cover: uploaded.cover,
|
|
76
|
+
asset_info: uploaded.asset_info,
|
|
77
|
+
analysis_status: uploaded.analysis_status,
|
|
78
|
+
analysis_fail_msg: uploaded.analysis_fail_msg,
|
|
79
|
+
analysis_timed_out: uploaded.analysis_timed_out,
|
|
80
|
+
filename: uploaded.filename,
|
|
81
|
+
format: uploaded.format,
|
|
82
|
+
mime_type: uploaded.mime_type,
|
|
83
|
+
object_name: uploaded.object_name,
|
|
84
|
+
auth_key: uploaded.auth_key,
|
|
85
|
+
registration: uploaded.registration,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const format = resolveInvokeOutputFormat(cmd);
|
|
89
|
+
if (shouldEmitEnvelope(format)) {
|
|
90
|
+
emitJson(wrapEnvelope(payload, { command: "dam upload" }));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
formatOutput(payload, format);
|
|
94
|
+
}
|
|
95
|
+
})));
|
|
96
|
+
dam
|
|
97
|
+
.command("list")
|
|
98
|
+
.description("查看最近上传素材")
|
|
99
|
+
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
100
|
+
.option("--all-repositories", "搜索当前组织下所有资源库", false)
|
|
101
|
+
.option("--folder-id <id>", "限定文件夹")
|
|
102
|
+
.option("--include-child-folder", "包含子文件夹", true)
|
|
103
|
+
.option("--no-include-child-folder", "不包含子文件夹")
|
|
104
|
+
.option("--type <type>", "image/video/audio/font/template/file")
|
|
105
|
+
.option("--format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
|
|
106
|
+
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
107
|
+
.option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
|
|
108
|
+
.option("--page <n>", "页码", parsePositiveInteger, 1)
|
|
109
|
+
.option("--cursor <id>", "分页 cursor/query_id")
|
|
110
|
+
.option("--raw", "JSON 输出包含后端原始数据", false)
|
|
111
|
+
.action(withErrorHandler(withAuth(async (options, command) => {
|
|
112
|
+
const opts = options;
|
|
113
|
+
const cmd = command;
|
|
114
|
+
const damClient = new DamClient();
|
|
115
|
+
const data = await damClient.searchAssets("", {
|
|
116
|
+
...buildDamSearchOptions(opts),
|
|
117
|
+
allowEmptyKeyword: true,
|
|
118
|
+
});
|
|
119
|
+
outputDamSearch(data, resolveListOutputFormat(cmd), "list");
|
|
120
|
+
})));
|
|
121
|
+
dam
|
|
122
|
+
.command("search <keyword>")
|
|
123
|
+
.description("搜索素材")
|
|
124
|
+
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
125
|
+
.option("--all-repositories", "搜索当前组织下所有资源库", false)
|
|
126
|
+
.option("--folder-id <id>", "限定文件夹")
|
|
127
|
+
.option("--include-child-folder", "包含子文件夹", true)
|
|
128
|
+
.option("--no-include-child-folder", "不包含子文件夹")
|
|
129
|
+
.option("--type <type>", "image/video/audio/font/template/file")
|
|
130
|
+
.option("--format <list>", "素材格式,逗号分隔,例如 png,jpg,mp4")
|
|
131
|
+
.option("--tag-ids <list>", "标签 ID,逗号分隔")
|
|
132
|
+
.option("--page-size <n>", "每页数量,最大 100", parsePositiveInteger, 20)
|
|
133
|
+
.option("--page <n>", "页码", parsePositiveInteger, 1)
|
|
134
|
+
.option("--cursor <id>", "分页 cursor/query_id")
|
|
135
|
+
.option("--raw", "JSON 输出包含后端原始数据", false)
|
|
136
|
+
.action(withErrorHandler(withAuth(async (keyword, options, command) => {
|
|
137
|
+
const opts = options;
|
|
138
|
+
const cmd = command;
|
|
139
|
+
const damClient = new DamClient();
|
|
140
|
+
const data = await damClient.searchAssets(String(keyword), buildDamSearchOptions(opts));
|
|
141
|
+
outputDamSearch(data, resolveListOutputFormat(cmd), "search");
|
|
142
|
+
})));
|
|
143
|
+
dam
|
|
144
|
+
.command("get <id-or-url>")
|
|
145
|
+
.description("按 asset_id、CDN URL 或文件名查询素材(依赖 Hub DAM API)")
|
|
146
|
+
.action(withErrorHandler(withAuth(async (idOrUrl, _opts, command) => {
|
|
147
|
+
const cmd = command;
|
|
148
|
+
const key = String(idOrUrl);
|
|
149
|
+
const damClient = new DamClient();
|
|
150
|
+
const data = await damClient.getAsset(key);
|
|
151
|
+
const format = resolveInvokeOutputFormat(cmd);
|
|
152
|
+
if (shouldEmitEnvelope(format)) {
|
|
153
|
+
emitJson(wrapEnvelope(data, { command: "dam get" }));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
formatOutput(data, format);
|
|
157
|
+
}
|
|
158
|
+
})));
|
|
159
|
+
dam
|
|
160
|
+
.command("delete <asset-id>")
|
|
161
|
+
.description("删除 DAM 素材")
|
|
162
|
+
.option("--repository-id <id>", "DAM 资源库 ID,默认取个人库或 GD_DAM_REPOSITORY_ID")
|
|
163
|
+
.option("--permanent", "进入回收站后继续永久删除", false)
|
|
164
|
+
.action(withErrorHandler(withAuth(async (assetId, options, command) => {
|
|
165
|
+
const cmd = command;
|
|
166
|
+
const opts = options;
|
|
167
|
+
const id = String(assetId);
|
|
168
|
+
const damClient = new DamClient();
|
|
169
|
+
const data = await damClient.deleteAsset(id, {
|
|
170
|
+
repositoryId: optionalString(opts.repositoryId),
|
|
171
|
+
permanent: Boolean(opts.permanent),
|
|
172
|
+
});
|
|
173
|
+
const format = resolveInvokeOutputFormat(cmd);
|
|
174
|
+
if (shouldEmitEnvelope(format)) {
|
|
175
|
+
emitJson(wrapEnvelope(data, { command: "dam delete" }));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
formatOutput(data, format);
|
|
179
|
+
}
|
|
180
|
+
})));
|
|
181
|
+
}
|
|
182
|
+
function buildDamSearchOptions(opts) {
|
|
183
|
+
return {
|
|
184
|
+
repositoryId: optionalString(opts.repositoryId),
|
|
185
|
+
allRepositories: Boolean(opts.allRepositories),
|
|
186
|
+
folderId: optionalString(opts.folderId),
|
|
187
|
+
includeChildFolder: opts.includeChildFolder !== false,
|
|
188
|
+
type: optionalString(opts.type),
|
|
189
|
+
format: splitList(opts.format),
|
|
190
|
+
tagIds: splitList(opts.tagIds),
|
|
191
|
+
pageSize: numberOption(opts.pageSize),
|
|
192
|
+
page: numberOption(opts.page),
|
|
193
|
+
cursor: optionalString(opts.cursor),
|
|
194
|
+
raw: Boolean(opts.raw),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function outputDamSearch(data, format, mode) {
|
|
198
|
+
if (format === "json") {
|
|
199
|
+
formatOutput(data, "json");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const repo = data.repository_ids.join(", ") || "default";
|
|
203
|
+
if (mode === "list") {
|
|
204
|
+
console.log(`Found ${data.total} recent assets in repo ${repo}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.log(`Found ${data.total} assets for "${data.query}" in repo ${repo}`);
|
|
208
|
+
}
|
|
209
|
+
console.log("");
|
|
210
|
+
formatOutput(rowsForDamSearch(data), format === "human" ? "table" : format);
|
|
211
|
+
if (data.next_cursor) {
|
|
212
|
+
const base = mode === "list"
|
|
213
|
+
? "gd-cli dam list"
|
|
214
|
+
: `gd-cli dam search ${quoteShell(data.query)}`;
|
|
215
|
+
console.log(`\nNext:\n ${base} --cursor ${data.next_cursor}`);
|
|
216
|
+
}
|
|
217
|
+
if (data.assets.length === 1) {
|
|
218
|
+
console.log(`\nUse:\n gd-cli dam get ${data.assets[0].asset_id}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function rowsForDamSearch(data) {
|
|
222
|
+
return data.assets.map((asset, index) => ({
|
|
223
|
+
"#": String(index + 1),
|
|
224
|
+
"Asset ID": asset.asset_id,
|
|
225
|
+
Title: asset.title ?? "",
|
|
226
|
+
Type: asset.type ?? "",
|
|
227
|
+
Format: asset.format ?? "",
|
|
228
|
+
Size: asset.size === undefined ? "" : formatBytes(asset.size),
|
|
229
|
+
Folder: asset.folder_name ?? asset.folder_id ?? "",
|
|
230
|
+
Cover: asset.cover_url ?? asset.url ?? "",
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
function optionalString(value) {
|
|
234
|
+
if (typeof value !== "string")
|
|
235
|
+
return undefined;
|
|
236
|
+
const trimmed = value.trim();
|
|
237
|
+
return trimmed ? trimmed : undefined;
|
|
238
|
+
}
|
|
239
|
+
function splitList(value) {
|
|
240
|
+
const raw = optionalString(value);
|
|
241
|
+
if (!raw)
|
|
242
|
+
return undefined;
|
|
243
|
+
return raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
244
|
+
}
|
|
245
|
+
function parsePositiveInteger(value) {
|
|
246
|
+
const parsed = Number(value);
|
|
247
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
248
|
+
throw new Error(`必须是正整数: ${value}`);
|
|
249
|
+
}
|
|
250
|
+
return parsed;
|
|
251
|
+
}
|
|
252
|
+
function numberOption(value) {
|
|
253
|
+
return typeof value === "number" ? value : undefined;
|
|
254
|
+
}
|
|
255
|
+
function formatBytes(value) {
|
|
256
|
+
if (!Number.isFinite(value))
|
|
257
|
+
return "";
|
|
258
|
+
if (value < 1024)
|
|
259
|
+
return `${value} B`;
|
|
260
|
+
if (value < 1024 * 1024)
|
|
261
|
+
return `${Math.round(value / 102.4) / 10} KB`;
|
|
262
|
+
if (value < 1024 * 1024 * 1024)
|
|
263
|
+
return `${Math.round(value / 104857.6) / 10} MB`;
|
|
264
|
+
return `${Math.round(value / 107374182.4) / 10} GB`;
|
|
265
|
+
}
|
|
266
|
+
function quoteShell(value) {
|
|
267
|
+
return /^[\w./:=,-]+$/.test(value) ? value : `"${value.replace(/"/g, '\\"')}"`;
|
|
268
|
+
}
|
|
269
|
+
const DAM_HELP = `Usage: gd-cli dam <command> [options]
|
|
270
|
+
|
|
271
|
+
用于把本地文件上传到稿定素材库,获得可复用的 asset_id 和 CDN URL。
|
|
272
|
+
适合在 Workflows / Tools / Agent 工作流中反复引用同一批商品图、Logo、参考图和交付文件。
|
|
273
|
+
|
|
274
|
+
常用命令:
|
|
275
|
+
upload <file> 上传文件并入库,返回 asset_id 与 CDN URL
|
|
276
|
+
get <id-or-url> 按 asset_id、CDN URL 或文件名查询素材
|
|
277
|
+
list 查看最近上传素材
|
|
278
|
+
search <keyword> 搜索素材
|
|
279
|
+
delete <asset-id> 删除素材
|
|
280
|
+
|
|
281
|
+
常用选项:
|
|
282
|
+
--name <name> 指定素材名称
|
|
283
|
+
--folder <folder> 指定 DAM 文件夹
|
|
284
|
+
--tags <tags> 添加标签,逗号分隔
|
|
285
|
+
--format <list> 在 dam search/list 中表示素材格式过滤,例如 png,jpg
|
|
286
|
+
--json 输出机器可读 JSON
|
|
287
|
+
|
|
288
|
+
示例:
|
|
289
|
+
gd-cli dam upload ./sku-001.png --tags product,shoe
|
|
290
|
+
gd-cli dam get ast_123
|
|
291
|
+
gd-cli dam search "white sneaker" --type image --format png,jpg
|
|
292
|
+
gd-cli dam delete ast_123 --yes
|
|
293
|
+
`;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { formatOutput } from "../middleware/output.js";
|
|
2
|
+
import { withErrorHandler } from "../middleware/error-handler.js";
|
|
3
|
+
import { Prompt } from "../core/prompt/index.js";
|
|
4
|
+
import { resolveInvokeOutputFormat, resolveListOutputFormat } from "../utils/list-output-format.js";
|
|
5
|
+
import { getConfiguredModel, getDefaultModelOrder, listModels, modelEntriesForType, parseModelType, publicModelByCode, resolveModelOrder, setConfiguredModel, } from "../models/model-catalog.js";
|
|
6
|
+
export function registerModelsCommand(program) {
|
|
7
|
+
const models = program
|
|
8
|
+
.command("models", { hidden: true })
|
|
9
|
+
.description("模型选择(Agent 入口)")
|
|
10
|
+
.configureHelp({ showGlobalOptions: true });
|
|
11
|
+
models
|
|
12
|
+
.command("list")
|
|
13
|
+
.description("列出可选图像/视频模型")
|
|
14
|
+
.requiredOption("--type <type>", "image / video")
|
|
15
|
+
.action(withErrorHandler(async (options, command) => {
|
|
16
|
+
const cmd = command;
|
|
17
|
+
const type = parseModelType(options.type);
|
|
18
|
+
const selected = getConfiguredModel(type);
|
|
19
|
+
const payload = {
|
|
20
|
+
type,
|
|
21
|
+
current: {
|
|
22
|
+
mode: selected === "auto" ? "auto" : "manual",
|
|
23
|
+
model: selected === "auto" ? undefined : publicModelByCode(type, selected),
|
|
24
|
+
effective_order: resolveModelOrder(type),
|
|
25
|
+
},
|
|
26
|
+
default_order: getDefaultModelOrder(type),
|
|
27
|
+
models: listModels(type),
|
|
28
|
+
};
|
|
29
|
+
const format = resolveListOutputFormat(cmd);
|
|
30
|
+
if (format === "json") {
|
|
31
|
+
formatOutput(payload, "json");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
formatOutput(payload.models, format);
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
models
|
|
38
|
+
.command("switch")
|
|
39
|
+
.description("切换默认图像/视频模型")
|
|
40
|
+
.requiredOption("--type <type>", "image / video")
|
|
41
|
+
.option("--model <model>", "模型 code / 名称 / auto")
|
|
42
|
+
.action(withErrorHandler(async (options, command) => {
|
|
43
|
+
const cmd = command;
|
|
44
|
+
const opts = options;
|
|
45
|
+
const type = parseModelType(opts.type);
|
|
46
|
+
const model = typeof opts.model === "string" && opts.model.trim()
|
|
47
|
+
? opts.model.trim()
|
|
48
|
+
: await promptModel(type);
|
|
49
|
+
const selected = setConfiguredModel(type, model);
|
|
50
|
+
const payload = {
|
|
51
|
+
type,
|
|
52
|
+
selected: selected === "auto"
|
|
53
|
+
? { mode: "auto" }
|
|
54
|
+
: { mode: "manual", model: publicModelByCode(type, selected) },
|
|
55
|
+
effective_order: resolveModelOrder(type),
|
|
56
|
+
};
|
|
57
|
+
formatOutput(payload, resolveInvokeOutputFormat(cmd));
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
async function promptModel(type) {
|
|
61
|
+
const current = getConfiguredModel(type);
|
|
62
|
+
const choices = [
|
|
63
|
+
{
|
|
64
|
+
name: current === "auto" ? "auto 当前" : "auto",
|
|
65
|
+
value: "auto",
|
|
66
|
+
description: "使用 Web 默认返回顺序和失败兜底",
|
|
67
|
+
},
|
|
68
|
+
...modelEntriesForType(type).map((model) => ({
|
|
69
|
+
name: current === model.code ? `${model.name} 当前` : model.name,
|
|
70
|
+
value: model.code,
|
|
71
|
+
description: `${model.description} / ${model.code} / 消耗${model.cost_level}`,
|
|
72
|
+
})),
|
|
73
|
+
];
|
|
74
|
+
return Prompt.select({
|
|
75
|
+
message: `选择默认${type === "image" ? "图像" : "视频"}模型`,
|
|
76
|
+
choices,
|
|
77
|
+
});
|
|
78
|
+
}
|