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,224 @@
|
|
|
1
|
+
import { SsoClient } from "../../api/sso-client.js";
|
|
2
|
+
import { HubClient } from "../../api/hub-client.js";
|
|
3
|
+
import { CredentialStoreV2 } from "../../auth/credential-store-v2.js";
|
|
4
|
+
import { GDHUB_CLI_CLIENT_ID } from "../../utils/config.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
const BIND_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const STRUCTURE_USER_ORGS_PATH = "/structure/user/orgs";
|
|
8
|
+
/**
|
|
9
|
+
* 组织列表与 bind-org。
|
|
10
|
+
* Device 模式:组织列表走 Hub 网关代理的 /structure/user/orgs;
|
|
11
|
+
* 网关不可用时退回本地 current_org 与 SSO identity。
|
|
12
|
+
*/
|
|
13
|
+
export class OrgManager {
|
|
14
|
+
v2Store;
|
|
15
|
+
sso;
|
|
16
|
+
hub;
|
|
17
|
+
constructor(v2Store, sso, hub) {
|
|
18
|
+
this.v2Store = v2Store ?? new CredentialStoreV2();
|
|
19
|
+
this.sso = sso ?? new SsoClient();
|
|
20
|
+
this.hub = hub ?? new HubClient(undefined, this.v2Store);
|
|
21
|
+
}
|
|
22
|
+
async listOrgs() {
|
|
23
|
+
const v2 = this.v2Store.read();
|
|
24
|
+
if (!v2)
|
|
25
|
+
throw new Error("未找到 AK/SK 凭证,请先执行 gd-cli auth login");
|
|
26
|
+
return (await this.listOrgsWithSource(v2)).orgs;
|
|
27
|
+
}
|
|
28
|
+
/** TTY 下选择结构接口返回的组织;接口不可用时允许手输 orgId。 */
|
|
29
|
+
async switchOrgInteractive() {
|
|
30
|
+
const orgs = await this.listOrgs();
|
|
31
|
+
const current = this.getCurrentOrgId();
|
|
32
|
+
if (orgs.length === 1) {
|
|
33
|
+
return this.switchOrg(orgs[0].id);
|
|
34
|
+
}
|
|
35
|
+
if (orgs.length > 1) {
|
|
36
|
+
const { Prompt } = await import("../prompt/index.js");
|
|
37
|
+
const selected = await Prompt.select({
|
|
38
|
+
message: "选择 CLI 当前组织",
|
|
39
|
+
choices: orgs.map((o) => ({
|
|
40
|
+
name: formatOrgChoice(o, current),
|
|
41
|
+
value: o.id,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
return this.switchOrg(selected);
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.yellow("\nSSO 未返回可切换的组织列表。请从稿定工作台获取 orgId,或使用:\n gd-cli org switch --org <org-id>\n"));
|
|
47
|
+
const { Prompt } = await import("../prompt/index.js");
|
|
48
|
+
const orgId = await Prompt.text({
|
|
49
|
+
message: "输入要绑定的组织 ID (orgId)",
|
|
50
|
+
validate: (v) => (v?.trim() ? true : "orgId 不能为空"),
|
|
51
|
+
});
|
|
52
|
+
return this.switchOrg(orgId.trim());
|
|
53
|
+
}
|
|
54
|
+
async switchOrg(orgId) {
|
|
55
|
+
const v2 = this.v2Store.read();
|
|
56
|
+
if (!v2)
|
|
57
|
+
throw new Error("未找到 AK/SK 凭证,请先执行 gd-cli auth login");
|
|
58
|
+
const normalizedOrgId = orgId.trim();
|
|
59
|
+
if (!normalizedOrgId)
|
|
60
|
+
throw new Error("orgId 不能为空");
|
|
61
|
+
const listed = await this.listOrgsWithSource(v2);
|
|
62
|
+
const selected = listed.orgs.find((o) => o.id === normalizedOrgId);
|
|
63
|
+
if (listed.source === "structure" && !selected) {
|
|
64
|
+
throw new Error(`组织 ${normalizedOrgId} 不在当前授权用户可绑定组织列表中,请先执行 gd-cli org list 确认 orgId`);
|
|
65
|
+
}
|
|
66
|
+
return this.bindOrgDevice(v2, normalizedOrgId, selected);
|
|
67
|
+
}
|
|
68
|
+
async bindAuthorizedOrg(orgId) {
|
|
69
|
+
const v2 = this.v2Store.read();
|
|
70
|
+
if (!v2)
|
|
71
|
+
throw new Error("未找到 AK/SK 凭证,请先执行 gd-cli auth login");
|
|
72
|
+
const normalizedOrgId = orgId.trim();
|
|
73
|
+
if (!normalizedOrgId)
|
|
74
|
+
throw new Error("orgId 不能为空");
|
|
75
|
+
let selected;
|
|
76
|
+
try {
|
|
77
|
+
const listed = await this.listOrgsWithSource(v2);
|
|
78
|
+
selected = listed.orgs.find((o) => o.id === normalizedOrgId);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* 授权页已给出 orgId,组织列表只用于补充名称,不阻断绑定。 */
|
|
82
|
+
}
|
|
83
|
+
return this.bindOrgDevice(v2, normalizedOrgId, selected);
|
|
84
|
+
}
|
|
85
|
+
getCurrentOrgId() {
|
|
86
|
+
const v2 = this.v2Store.read();
|
|
87
|
+
if (v2?.current_org?.id)
|
|
88
|
+
return v2.current_org.id;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
getCurrentOrg() {
|
|
92
|
+
const v2 = this.v2Store.read();
|
|
93
|
+
if (!v2?.current_org?.id)
|
|
94
|
+
return undefined;
|
|
95
|
+
return {
|
|
96
|
+
id: v2.current_org.id,
|
|
97
|
+
name: v2.current_org.name,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async listOrgsWithSource(v2) {
|
|
101
|
+
try {
|
|
102
|
+
const orgs = await this.listStructureOrgs();
|
|
103
|
+
if (orgs.length > 0)
|
|
104
|
+
return { orgs, source: "structure" };
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* Fall back to device identity when the organization endpoint is unavailable. */
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
orgs: await this.listOrgsDevice(v2.ak, v2),
|
|
111
|
+
source: "fallback",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async listStructureOrgs() {
|
|
115
|
+
const raw = await this.hub.get(STRUCTURE_USER_ORGS_PATH);
|
|
116
|
+
if (!Array.isArray(raw))
|
|
117
|
+
return [];
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const orgs = [];
|
|
120
|
+
for (const item of raw) {
|
|
121
|
+
const org = normalizeStructureOrg(item);
|
|
122
|
+
if (!org || seen.has(org.id))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(org.id);
|
|
125
|
+
orgs.push(org);
|
|
126
|
+
}
|
|
127
|
+
return orgs;
|
|
128
|
+
}
|
|
129
|
+
async listOrgsDevice(ak, creds) {
|
|
130
|
+
const items = [];
|
|
131
|
+
if (creds.current_org?.id) {
|
|
132
|
+
items.push({
|
|
133
|
+
id: creds.current_org.id,
|
|
134
|
+
name: creds.current_org.name,
|
|
135
|
+
is_default: true,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const identity = await this.sso.getIdentity(ak);
|
|
140
|
+
if (identity.orgId) {
|
|
141
|
+
const id = String(identity.orgId);
|
|
142
|
+
if (!items.some((o) => o.id === id)) {
|
|
143
|
+
items.push({
|
|
144
|
+
id,
|
|
145
|
+
name: `组织 ${id}`,
|
|
146
|
+
is_default: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* identity 不可用时仅展示本地 current_org */
|
|
153
|
+
}
|
|
154
|
+
return items;
|
|
155
|
+
}
|
|
156
|
+
async bindOrgDevice(creds, orgId, org) {
|
|
157
|
+
const result = await this.sso.bindOrg(creds.ak, creds.sk, orgId);
|
|
158
|
+
assertBindOrgSuccess(result, orgId);
|
|
159
|
+
const bindExpires = result.bind_expires_at ?? new Date(Date.now() + BIND_TTL_MS).toISOString();
|
|
160
|
+
const name = org?.name ?? (result.message?.includes("成功") ? `组织 ${orgId}` : orgId);
|
|
161
|
+
creds.current_org = {
|
|
162
|
+
id: orgId,
|
|
163
|
+
name,
|
|
164
|
+
bind_expires_at: bindExpires,
|
|
165
|
+
};
|
|
166
|
+
creds.client_id = GDHUB_CLI_CLIENT_ID;
|
|
167
|
+
this.v2Store.write(creds);
|
|
168
|
+
return {
|
|
169
|
+
...org,
|
|
170
|
+
id: creds.current_org.id,
|
|
171
|
+
name: creds.current_org.name,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function assertBindOrgSuccess(result, orgId) {
|
|
176
|
+
const code = cleanString(result.code);
|
|
177
|
+
const message = cleanString(result.message);
|
|
178
|
+
const text = [code, message].filter(Boolean).join(" ");
|
|
179
|
+
const hasSuccessSignal = /^(0|200|OK|SUCCESS)$/i.test(code ?? "") ||
|
|
180
|
+
/成功|success|ok/i.test(text);
|
|
181
|
+
const hasFailureSignal = /(失败|错误|无效|不允许|未授权|无权限|没有权限|不存在|过期|fail|error|invalid|denied|forbidden|unauthorized|not found)/i
|
|
182
|
+
.test(text);
|
|
183
|
+
if (hasFailureSignal && !hasSuccessSignal) {
|
|
184
|
+
throw new Error(`绑定组织失败:${message ?? code ?? "bind-org 返回失败"}(orgId=${orgId})`);
|
|
185
|
+
}
|
|
186
|
+
if (code && !hasSuccessSignal) {
|
|
187
|
+
throw new Error(`绑定组织失败:${message ?? `bind-org 返回 code=${code}`}(orgId=${orgId})`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function formatOrgChoice(org, currentOrgId) {
|
|
191
|
+
const product = org.product_name ? ` · ${org.product_name}` : "";
|
|
192
|
+
const current = org.id === currentOrgId ? " ← 当前" : "";
|
|
193
|
+
return `${org.name} (${org.id})${product}${current}`;
|
|
194
|
+
}
|
|
195
|
+
function normalizeStructureOrg(item) {
|
|
196
|
+
const id = cleanString(item.org_id) ?? cleanString(item.company?.id);
|
|
197
|
+
if (!id)
|
|
198
|
+
return null;
|
|
199
|
+
const productExpire = numberOrUndefined(item.product?.expire_date);
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
name: cleanString(item.company?.name) ?? `组织 ${id}`,
|
|
203
|
+
code: cleanString(item.company?.code),
|
|
204
|
+
domain: cleanString(item.company?.domain),
|
|
205
|
+
org_class: cleanString(item.company?.org_class),
|
|
206
|
+
status: cleanString(item.company?.status),
|
|
207
|
+
module_code: cleanString(item.module_code),
|
|
208
|
+
product_name: cleanString(item.product?.product_name),
|
|
209
|
+
product_type: cleanString(item.product?.product_type),
|
|
210
|
+
expire_date: numberOrUndefined(item.expire_date) ?? productExpire,
|
|
211
|
+
last_bind_org: item.last_bind_org === true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function cleanString(value) {
|
|
215
|
+
if (value === null || value === undefined)
|
|
216
|
+
return undefined;
|
|
217
|
+
const s = String(value).trim();
|
|
218
|
+
return s ? s : undefined;
|
|
219
|
+
}
|
|
220
|
+
function numberOrUndefined(value) {
|
|
221
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
222
|
+
return value;
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Unix 秒级时间戳(与 SSO / Kong 文档一致) */
|
|
2
|
+
export declare function signatureTimestamp(): string;
|
|
3
|
+
export declare function hmacSha256Hex(secret: string, signString: string): string;
|
|
4
|
+
/** bind-org uses the backend signing contract: GET@<path>@<query>@<timestamp>. */
|
|
5
|
+
export declare function signBindOrg(ak: string, sk: string, orgId: string, timestamp?: string): {
|
|
6
|
+
signature: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* 业务 API 签名。
|
|
11
|
+
* @param path 不含域名的路径,如 /hub/v1/tools/cutout/invoke
|
|
12
|
+
* @param query 已排序且 URL 编码的 query,无 query 传空串
|
|
13
|
+
*/
|
|
14
|
+
export declare function signApiRequest(method: string, path: string, query: string, sk: string, timestamp?: string): {
|
|
15
|
+
signature: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
};
|
|
18
|
+
/** query 参数按 key 排序后拼接(与网关验签一致) */
|
|
19
|
+
export declare function canonicalQuery(params: Record<string, string>): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
/** Unix 秒级时间戳(与 SSO / Kong 文档一致) */
|
|
3
|
+
export function signatureTimestamp() {
|
|
4
|
+
return String(Math.floor(Date.now() / 1000));
|
|
5
|
+
}
|
|
6
|
+
export function hmacSha256Hex(secret, signString) {
|
|
7
|
+
return crypto.createHmac("sha256", secret).update(signString).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
/** bind-org uses the backend signing contract: GET@<path>@<query>@<timestamp>. */
|
|
10
|
+
export function signBindOrg(ak, sk, orgId, timestamp) {
|
|
11
|
+
const ts = timestamp ?? signatureTimestamp();
|
|
12
|
+
const signStr = `GET@/oauth/device/bind-org@ak=${ak}&orgId=${orgId}@${ts}`;
|
|
13
|
+
return { signature: hmacSha256Hex(sk, signStr), timestamp: ts };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 业务 API 签名。
|
|
17
|
+
* @param path 不含域名的路径,如 /hub/v1/tools/cutout/invoke
|
|
18
|
+
* @param query 已排序且 URL 编码的 query,无 query 传空串
|
|
19
|
+
*/
|
|
20
|
+
export function signApiRequest(method, path, query, sk, timestamp) {
|
|
21
|
+
const ts = timestamp ?? signatureTimestamp();
|
|
22
|
+
const signStr = `${method.toUpperCase()}@${path}@${query}@${ts}`;
|
|
23
|
+
return { signature: hmacSha256Hex(sk, signStr), timestamp: ts };
|
|
24
|
+
}
|
|
25
|
+
/** query 参数按 key 排序后拼接(与网关验签一致) */
|
|
26
|
+
export function canonicalQuery(params) {
|
|
27
|
+
const keys = Object.keys(params).sort();
|
|
28
|
+
if (keys.length === 0)
|
|
29
|
+
return "";
|
|
30
|
+
return keys
|
|
31
|
+
.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
|
|
32
|
+
.join("&");
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** V2 凭证(Device Flow + bind-org) */
|
|
2
|
+
export interface OrgBinding {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
bind_expires_at?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface UserInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CredentialsV2 {
|
|
12
|
+
version: 2;
|
|
13
|
+
provider: "gdhub";
|
|
14
|
+
client_id: string;
|
|
15
|
+
ak: string;
|
|
16
|
+
sk: string;
|
|
17
|
+
scope: string[];
|
|
18
|
+
expires_at: string;
|
|
19
|
+
/** 签发时 active_env,切换环境后提示重新登录 */
|
|
20
|
+
issued_for_env?: import("../../utils/settings-store.js").GdEnvName;
|
|
21
|
+
current_org?: OrgBinding;
|
|
22
|
+
user?: UserInfo;
|
|
23
|
+
created_at: string;
|
|
24
|
+
}
|
|
25
|
+
export type StoredCredentials = CredentialsV2;
|
|
26
|
+
export declare function isCredentialsV2(c: StoredCredentials): c is CredentialsV2;
|
|
27
|
+
export interface OrgSummary {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
is_default?: boolean;
|
|
32
|
+
code?: string;
|
|
33
|
+
domain?: string;
|
|
34
|
+
org_class?: string;
|
|
35
|
+
status?: string;
|
|
36
|
+
module_code?: string;
|
|
37
|
+
product_name?: string;
|
|
38
|
+
product_type?: string;
|
|
39
|
+
expire_date?: number;
|
|
40
|
+
last_bind_org?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export type AuthMode = "aksk";
|
|
43
|
+
export interface LoginOptions {
|
|
44
|
+
orgId?: string;
|
|
45
|
+
noBrowser?: boolean;
|
|
46
|
+
scope?: string;
|
|
47
|
+
env?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface LoginResult {
|
|
50
|
+
credentials: CredentialsV2;
|
|
51
|
+
org?: OrgSummary;
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function detectOutputMode(options) {
|
|
2
|
+
if (options.format === "table")
|
|
3
|
+
return "table";
|
|
4
|
+
if (options.format === "text" || options.format === "pretty")
|
|
5
|
+
return "text";
|
|
6
|
+
if (!process.stdout.isTTY)
|
|
7
|
+
return "json";
|
|
8
|
+
if (options.format === "json")
|
|
9
|
+
return "json";
|
|
10
|
+
return "text";
|
|
11
|
+
}
|
|
12
|
+
export function isTTY() {
|
|
13
|
+
return !!process.stdout.isTTY;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { LiveDisplay } from "./live-display.js";
|
|
2
|
+
export interface ProgressEvent {
|
|
3
|
+
type: "progress";
|
|
4
|
+
current: number;
|
|
5
|
+
total: number;
|
|
6
|
+
step: string;
|
|
7
|
+
status: string;
|
|
8
|
+
percent: number;
|
|
9
|
+
}
|
|
10
|
+
interface CommandResult {
|
|
11
|
+
title?: string;
|
|
12
|
+
status?: "success" | "error" | "info";
|
|
13
|
+
data: Record<string, unknown>;
|
|
14
|
+
nextSteps?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare class OutputEngine {
|
|
17
|
+
private mode;
|
|
18
|
+
private liveDisplay;
|
|
19
|
+
constructor(options?: {
|
|
20
|
+
format?: string;
|
|
21
|
+
});
|
|
22
|
+
get live(): LiveDisplay;
|
|
23
|
+
get isTTYMode(): boolean;
|
|
24
|
+
render(result: CommandResult): void;
|
|
25
|
+
json(data: unknown): void;
|
|
26
|
+
emitProgress(data: ProgressEvent): void;
|
|
27
|
+
error(err: Error & {
|
|
28
|
+
code?: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
private renderTable;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { detectOutputMode, isTTY } from "./detector.js";
|
|
2
|
+
import { LiveDisplay } from "./live-display.js";
|
|
3
|
+
import { ResultCard } from "./result-card.js";
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
export class OutputEngine {
|
|
6
|
+
mode;
|
|
7
|
+
liveDisplay;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.mode = detectOutputMode(options);
|
|
10
|
+
this.liveDisplay = new LiveDisplay(isTTY());
|
|
11
|
+
}
|
|
12
|
+
get live() {
|
|
13
|
+
return this.liveDisplay;
|
|
14
|
+
}
|
|
15
|
+
get isTTYMode() {
|
|
16
|
+
return isTTY();
|
|
17
|
+
}
|
|
18
|
+
render(result) {
|
|
19
|
+
switch (this.mode) {
|
|
20
|
+
case "json":
|
|
21
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
22
|
+
break;
|
|
23
|
+
case "table":
|
|
24
|
+
this.renderTable(result.data);
|
|
25
|
+
break;
|
|
26
|
+
case "text":
|
|
27
|
+
ResultCard.render({
|
|
28
|
+
title: result.title,
|
|
29
|
+
status: result.status ?? "success",
|
|
30
|
+
data: result.data,
|
|
31
|
+
nextSteps: result.nextSteps,
|
|
32
|
+
});
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
json(data) {
|
|
37
|
+
console.log(JSON.stringify(data, null, 2));
|
|
38
|
+
}
|
|
39
|
+
emitProgress(data) {
|
|
40
|
+
if (isTTY())
|
|
41
|
+
return;
|
|
42
|
+
process.stderr.write(JSON.stringify(data) + "\n");
|
|
43
|
+
}
|
|
44
|
+
async error(err) {
|
|
45
|
+
this.liveDisplay.stop();
|
|
46
|
+
if (this.mode === "json") {
|
|
47
|
+
console.log(JSON.stringify({
|
|
48
|
+
error: true,
|
|
49
|
+
code: err.code ?? "UNKNOWN_ERROR",
|
|
50
|
+
message: err.message,
|
|
51
|
+
}, null, 2));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
ResultCard.error({
|
|
55
|
+
code: err.code ?? "UNKNOWN_ERROR",
|
|
56
|
+
message: err.message,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
renderTable(data) {
|
|
61
|
+
if (!Array.isArray(data)) {
|
|
62
|
+
ResultCard.render({ status: "info", data: data });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (data.length === 0) {
|
|
66
|
+
console.log("(empty)");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const keys = Object.keys(data[0]);
|
|
70
|
+
const table = new Table({ head: keys });
|
|
71
|
+
for (const row of data) {
|
|
72
|
+
const r = row;
|
|
73
|
+
table.push(keys.map((k) => String(r[k] ?? "")));
|
|
74
|
+
}
|
|
75
|
+
console.log(table.toString());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface AssetRef {
|
|
2
|
+
id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
cover_url?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
filename?: string;
|
|
7
|
+
source?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CliErrorBody {
|
|
10
|
+
code: string;
|
|
11
|
+
type: "business" | "auth" | "network" | "validation" | "unknown";
|
|
12
|
+
message: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
app_id?: string;
|
|
16
|
+
tool_id?: string;
|
|
17
|
+
missing_tools?: Array<string | Record<string, unknown>>;
|
|
18
|
+
retryable?: boolean;
|
|
19
|
+
detail?: unknown;
|
|
20
|
+
diagnostic_context?: Record<string, unknown>;
|
|
21
|
+
next_command?: string;
|
|
22
|
+
next_steps?: string[];
|
|
23
|
+
pricing_url?: string;
|
|
24
|
+
user_action_required?: string;
|
|
25
|
+
forbidden_actions?: string[];
|
|
26
|
+
allow_org_check?: boolean;
|
|
27
|
+
agent_message_template?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ResultEnvelope<T = unknown> {
|
|
30
|
+
status: "completed" | "failed" | "pending" | "submitted" | "running";
|
|
31
|
+
data?: T;
|
|
32
|
+
assets?: AssetRef[];
|
|
33
|
+
task_id?: string;
|
|
34
|
+
meta?: Record<string, unknown>;
|
|
35
|
+
error?: CliErrorBody | null;
|
|
36
|
+
}
|
|
37
|
+
export declare function wrapEnvelope<T>(payload: T, meta?: Record<string, unknown>): ResultEnvelope<T>;
|
|
38
|
+
export declare function failedEnvelope(error: CliErrorBody, meta?: Record<string, unknown>): ResultEnvelope<null>;
|
|
39
|
+
export declare function emitJson(data: unknown): void;
|
|
40
|
+
export declare function shouldEmitEnvelope(format: string, rootFormat?: string): boolean;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export function wrapEnvelope(payload, meta) {
|
|
2
|
+
if (typeof payload === "object" && payload !== null) {
|
|
3
|
+
const p = payload;
|
|
4
|
+
const status = normalizeEnvelopeStatus(p.status);
|
|
5
|
+
const assets = extractAssets(p);
|
|
6
|
+
return {
|
|
7
|
+
status,
|
|
8
|
+
data: payload,
|
|
9
|
+
assets: assets.length ? assets : undefined,
|
|
10
|
+
task_id: p.task_id != null ? String(p.task_id) : undefined,
|
|
11
|
+
meta,
|
|
12
|
+
error: status === "failed" ? normalizePayloadError(p.error) : null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return { status: "completed", data: payload, meta, error: null };
|
|
16
|
+
}
|
|
17
|
+
function normalizeEnvelopeStatus(value) {
|
|
18
|
+
if (value === "failed" ||
|
|
19
|
+
value === "pending" ||
|
|
20
|
+
value === "submitted" ||
|
|
21
|
+
value === "running") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return "completed";
|
|
25
|
+
}
|
|
26
|
+
export function failedEnvelope(error, meta) {
|
|
27
|
+
return { status: "failed", data: null, error, meta };
|
|
28
|
+
}
|
|
29
|
+
function normalizePayloadError(value) {
|
|
30
|
+
if (!value)
|
|
31
|
+
return null;
|
|
32
|
+
if (typeof value === "string") {
|
|
33
|
+
return {
|
|
34
|
+
code: "ERROR",
|
|
35
|
+
type: "unknown",
|
|
36
|
+
message: value,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
40
|
+
return {
|
|
41
|
+
code: "ERROR",
|
|
42
|
+
type: "unknown",
|
|
43
|
+
message: String(value),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const error = value;
|
|
47
|
+
const type = error.type;
|
|
48
|
+
return {
|
|
49
|
+
code: typeof error.code === "string" ? error.code : "ERROR",
|
|
50
|
+
type: type === "business" ||
|
|
51
|
+
type === "auth" ||
|
|
52
|
+
type === "network" ||
|
|
53
|
+
type === "validation" ||
|
|
54
|
+
type === "unknown"
|
|
55
|
+
? type
|
|
56
|
+
: "unknown",
|
|
57
|
+
message: typeof error.message === "string" ? error.message : JSON.stringify(error),
|
|
58
|
+
reason: typeof error.reason === "string" ? error.reason : undefined,
|
|
59
|
+
hint: typeof error.hint === "string" ? error.hint : undefined,
|
|
60
|
+
app_id: typeof error.app_id === "string" ? error.app_id : undefined,
|
|
61
|
+
tool_id: typeof error.tool_id === "string" ? error.tool_id : undefined,
|
|
62
|
+
retryable: typeof error.retryable === "boolean" ? error.retryable : undefined,
|
|
63
|
+
next_command: typeof error.next_command === "string" ? error.next_command : undefined,
|
|
64
|
+
next_steps: Array.isArray(error.next_steps) ? error.next_steps.map(String) : undefined,
|
|
65
|
+
pricing_url: typeof error.pricing_url === "string" ? error.pricing_url : undefined,
|
|
66
|
+
user_action_required: typeof error.user_action_required === "string" ? error.user_action_required : undefined,
|
|
67
|
+
forbidden_actions: Array.isArray(error.forbidden_actions) ? error.forbidden_actions.map(String) : undefined,
|
|
68
|
+
allow_org_check: typeof error.allow_org_check === "boolean" ? error.allow_org_check : undefined,
|
|
69
|
+
agent_message_template: typeof error.agent_message_template === "string" ? error.agent_message_template : undefined,
|
|
70
|
+
detail: error,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function extractAssets(obj) {
|
|
74
|
+
const out = [];
|
|
75
|
+
const push = (a) => {
|
|
76
|
+
if (!a || typeof a !== "object")
|
|
77
|
+
return;
|
|
78
|
+
const o = a;
|
|
79
|
+
const id = o.asset_id ?? o.id;
|
|
80
|
+
const url = o.url;
|
|
81
|
+
if (id && url) {
|
|
82
|
+
const asset = {
|
|
83
|
+
id: String(id),
|
|
84
|
+
url: String(url),
|
|
85
|
+
cover_url: o.cover_url != null ? String(o.cover_url) : undefined,
|
|
86
|
+
type: o.type != null ? String(o.type) : undefined,
|
|
87
|
+
filename: o.filename != null ? String(o.filename) : undefined,
|
|
88
|
+
source: o.source != null ? String(o.source) : "cli",
|
|
89
|
+
};
|
|
90
|
+
if (!out.some((item) => item.id === asset.id && item.url === asset.url)) {
|
|
91
|
+
out.push(asset);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
if (obj.asset && typeof obj.asset === "object")
|
|
96
|
+
push(obj.asset);
|
|
97
|
+
if (Array.isArray(obj.assets))
|
|
98
|
+
for (const a of obj.assets)
|
|
99
|
+
push(a);
|
|
100
|
+
if (obj.outputs && typeof obj.outputs === "object") {
|
|
101
|
+
const outputs = obj.outputs;
|
|
102
|
+
if (outputs.asset)
|
|
103
|
+
push(outputs.asset);
|
|
104
|
+
if (Array.isArray(outputs.assets))
|
|
105
|
+
for (const a of outputs.assets)
|
|
106
|
+
push(a);
|
|
107
|
+
if (typeof outputs.asset_id === "string" && typeof outputs.url === "string") {
|
|
108
|
+
push({ id: outputs.asset_id, url: outputs.url, cover_url: outputs.cover_url });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
export function emitJson(data) {
|
|
114
|
+
console.log(JSON.stringify(data, null, 2));
|
|
115
|
+
}
|
|
116
|
+
export function shouldEmitEnvelope(format, rootFormat) {
|
|
117
|
+
const f = format || rootFormat || "json";
|
|
118
|
+
return f === "json";
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class LiveDisplay {
|
|
2
|
+
private steps;
|
|
3
|
+
private frameIndex;
|
|
4
|
+
private timer;
|
|
5
|
+
private isTTY;
|
|
6
|
+
private startedAt;
|
|
7
|
+
constructor(tty?: boolean);
|
|
8
|
+
addStep(label: string): number;
|
|
9
|
+
startStep(idx: number): void;
|
|
10
|
+
completeStep(idx: number): void;
|
|
11
|
+
failStep(idx: number): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
private render;
|
|
14
|
+
private emitProgress;
|
|
15
|
+
}
|