gaoding-cli 1.0.0-alpha.7 → 1.0.0-alpha.8
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 +20 -0
- package/README.md +18 -14
- package/dist/src/api/app-runner.js +1 -2
- package/dist/src/api/creative-client.d.ts +34 -0
- package/dist/src/api/creative-client.js +184 -0
- package/dist/src/api/direct-client.d.ts +9 -2
- package/dist/src/api/direct-client.js +45 -18
- package/dist/src/api/hub-client.d.ts +11 -8
- package/dist/src/api/hub-client.js +17 -18
- package/dist/src/api/usage-client.d.ts +4 -4
- package/dist/src/api/usage-client.js +9 -49
- package/dist/src/auth/credential-store-v2.d.ts +0 -1
- package/dist/src/auth/credential-store-v2.js +0 -6
- package/dist/src/commands/auth.js +13 -13
- package/dist/src/commands/creative.d.ts +8 -0
- package/dist/src/commands/creative.js +186 -0
- package/dist/src/commands/dam.js +27 -22
- package/dist/src/commands/org.js +20 -14
- package/dist/src/commands/skills.js +9 -4
- package/dist/src/commands/update.js +6 -2
- package/dist/src/commands/usage.d.ts +1 -1
- package/dist/src/commands/usage.js +6 -4
- package/dist/src/commands/workflows.js +31 -13
- package/dist/src/core/auth/auth-guard.d.ts +21 -0
- package/dist/src/core/auth/auth-guard.js +121 -0
- package/dist/src/core/auth/org-manager.d.ts +2 -1
- package/dist/src/core/auth/org-manager.js +61 -6
- package/dist/src/creative/contract.d.ts +1112 -0
- package/dist/src/creative/contract.js +743 -0
- package/dist/src/creative/media.d.ts +19 -0
- package/dist/src/creative/media.js +83 -0
- package/dist/src/creative/protocol.d.ts +60 -0
- package/dist/src/creative/protocol.js +337 -0
- package/dist/src/middleware/auth.d.ts +6 -2
- package/dist/src/middleware/auth.js +25 -63
- package/dist/src/middleware/error-handler.d.ts +2 -0
- package/dist/src/middleware/error-handler.js +3 -4
- package/dist/src/program.js +7 -89
- package/dist/src/registry/agent-contracts.d.ts +3 -11
- package/dist/src/registry/agent-contracts.js +5 -243
- package/dist/src/registry/direct-registry.d.ts +0 -5
- package/dist/src/registry/direct-registry.js +0 -19
- package/dist/src/registry/registry-client.d.ts +0 -21
- package/dist/src/registry/registry-client.js +1 -40
- package/dist/src/utils/config.d.ts +2 -2
- package/dist/src/utils/config.js +1 -1
- package/dist/src/utils/help-verify.d.ts +1 -3
- package/dist/src/utils/help-verify.js +1 -25
- package/package.json +3 -3
- package/skills/gd-cli/SKILL.md +10 -9
- package/skills/gd-cli/references/auth.md +6 -0
- package/skills/gd-cli/references/creative.md +47 -0
- package/skills/gd-cli/references/entitlement.md +2 -2
- package/skills/gd-cli/references/errors.md +7 -0
- package/skills/gd-cli/references/sop.md +4 -4
- package/skills/gd-cli/references/workflows.md +1 -1
- package/dist/src/commands/models.d.ts +0 -2
- package/dist/src/commands/models.js +0 -78
- package/dist/src/commands/tools.d.ts +0 -4
- package/dist/src/commands/tools.js +0 -383
- package/skills/gd-cli/references/tools.md +0 -29
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [1.0.0-alpha.8] - 2026-08-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- 新增统一 `creative` 创作入口及可发现的输入输出 Schema、调用示例和连续创作支持。
|
|
13
|
+
- 新增集中式鉴权前置层,按命令声明凭证或当前组织要求,并在请求前透明刷新当前组织授权。
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `org credits` 取代顶层 `usage`,组织绑定与刷新有效期不再作为公开概念展示。
|
|
18
|
+
- DAM 素材格式筛选统一使用 `--asset-format`,全局 `--format` 仅控制输出格式。
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- 删除公开的 `tools`、`models` 以及顶层 `usage`、`manifest` 和 `examples` 命令。
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- 升级 `brace-expansion` 补丁版本,修复 production 依赖链中的高危拒绝服务漏洞。
|
|
27
|
+
|
|
8
28
|
## [1.0.0-alpha.7] - 2026-07-23
|
|
9
29
|
|
|
10
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# gaoding-cli
|
|
2
2
|
|
|
3
|
-
GD CLI 是稿定设计能力的命令行入口,为用户、脚本、CI 和 Agent
|
|
3
|
+
GD CLI 是稿定设计能力的命令行入口,为用户、脚本、CI 和 Agent 提供登录、组织切换、统一 Creative 创作、多步骤 Workflow 和 DAM 素材管理能力。
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -36,9 +36,9 @@ gd-cli auth status
|
|
|
36
36
|
gd-cli org list
|
|
37
37
|
gd-cli org switch --org <org-id>
|
|
38
38
|
|
|
39
|
-
gd-cli
|
|
40
|
-
gd-cli
|
|
41
|
-
gd-cli
|
|
39
|
+
gd-cli creative schema --format json
|
|
40
|
+
gd-cli creative examples --format json
|
|
41
|
+
gd-cli creative run --prompt "为夏季运动鞋生成一张户外通勤广告图" --format json
|
|
42
42
|
|
|
43
43
|
gd-cli workflows list
|
|
44
44
|
gd-cli workflows run listing-gallery \
|
|
@@ -54,26 +54,30 @@ gd-cli dam upload ./product.jpg
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `auth` | 登录、退出和查看本地认证状态 |
|
|
56
56
|
| `org` | 查看和切换当前组织 |
|
|
57
|
-
| `
|
|
57
|
+
| `creative` | 统一发起图片、视频和文本创作 |
|
|
58
58
|
| `workflows` | 运行多步骤创意生产流程 |
|
|
59
59
|
| `skills` | 安装和管理 `gd-cli` Agent Skill |
|
|
60
60
|
| `dam` | 上传、查询和删除 DAM 素材 |
|
|
61
61
|
| `update` | 检查和升级全局 CLI |
|
|
62
|
-
| `
|
|
63
|
-
| `examples` | 查看常用命令示例 |
|
|
62
|
+
| `org credits` | 查询当前组织稿豆余额 |
|
|
64
63
|
|
|
65
64
|
## Agent And Script Usage
|
|
66
65
|
|
|
67
|
-
核心命令支持 `--format json`,便于脚本和 Agent 解析。
|
|
66
|
+
核心命令支持 `--format json`,便于脚本和 Agent 解析。Creative 接收目标描述、媒体、结构化约束和连续会话信息,由 GD Agent 理解并完成创作;CLI 对使用者表现为一次输入、一次最终输出。
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
Creative 的完整输入和输出结构使用本地静态 Schema 发现:
|
|
70
69
|
|
|
71
70
|
```bash
|
|
72
|
-
gd-cli
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
gd-cli creative schema --format json
|
|
72
|
+
gd-cli creative examples --format json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
纯文本请求可以直接使用 `--prompt`;包含媒体、`parameters` 或连续会话信息时使用 JSON 输入:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
gd-cli creative run --prompt "为夏季运动鞋生成三条简洁的电商标题" --format json
|
|
79
|
+
gd-cli creative run --input creative-request.json --format json
|
|
80
|
+
cat creative-request.json | gd-cli creative run --input - --format json
|
|
77
81
|
```
|
|
78
82
|
|
|
79
83
|
多步骤交付使用 `workflows run`:
|
|
@@ -752,9 +752,8 @@ function normalizeAppStepError(error, step) {
|
|
|
752
752
|
: "business",
|
|
753
753
|
message: `${step.step_name} 执行失败:${friendlyErrorMessage(rawMessage)}`,
|
|
754
754
|
raw_message: rawMessage,
|
|
755
|
-
hint: "
|
|
755
|
+
hint: "请调整图片或 brief 后重试当前 Workflow;内部步骤由 Workflow 自动编排。",
|
|
756
756
|
next_steps: [
|
|
757
|
-
`单独验证该步骤:gd-cli tools call ${step.tool}`,
|
|
758
757
|
"调整输入后重试原 workflows run 命令。",
|
|
759
758
|
],
|
|
760
759
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DamClient, type DamUploadOptions } from "./dam-client.js";
|
|
2
|
+
import { type DirectRequestOptions } from "./direct-client.js";
|
|
3
|
+
import { type CreativeAgentMessage, type CreativeAgentRequest } from "../creative/protocol.js";
|
|
4
|
+
import { type CreativeInput, type CreativeResult } from "../creative/contract.js";
|
|
5
|
+
export declare const CREATIVE_COMPLETION_PATH: "/ai-agent/v1/thread/completion";
|
|
6
|
+
export interface CreativeStreamClient {
|
|
7
|
+
postStream(path: string, body?: unknown, options?: DirectRequestOptions): Promise<Response>;
|
|
8
|
+
}
|
|
9
|
+
export interface CreativeUploadClient {
|
|
10
|
+
uploadLocalAsset(file: string, options?: DamUploadOptions): Promise<Awaited<ReturnType<DamClient["uploadLocalAsset"]>>>;
|
|
11
|
+
}
|
|
12
|
+
export interface CreativeClientDeps {
|
|
13
|
+
api?: CreativeStreamClient;
|
|
14
|
+
dam?: CreativeUploadClient;
|
|
15
|
+
idFactory?: () => string;
|
|
16
|
+
}
|
|
17
|
+
export declare class CreativeClient {
|
|
18
|
+
private readonly api;
|
|
19
|
+
private readonly dam;
|
|
20
|
+
private readonly idFactory;
|
|
21
|
+
constructor(deps?: CreativeClientDeps);
|
|
22
|
+
run(value: unknown): Promise<CreativeResult>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Consume the Editor completion body without exposing transport events to the
|
|
26
|
+
* CLI caller. The Editor currently emits JSON arrays, sometimes as adjacent
|
|
27
|
+
* JSON values and sometimes wrapped in `data:` SSE records, so both forms are
|
|
28
|
+
* accepted here.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseCreativeSse(stream: ReadableStream<Uint8Array>): Promise<CreativeAgentMessage[]>;
|
|
31
|
+
export declare function buildCreativeRequestForTest(input: CreativeInput, request: CreativeAgentRequest): {
|
|
32
|
+
input: CreativeInput;
|
|
33
|
+
request: CreativeAgentRequest;
|
|
34
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { DamClient } from "./dam-client.js";
|
|
3
|
+
import { DirectApiClient } from "./direct-client.js";
|
|
4
|
+
import { buildCreativeAgentRequest, normalizeCreativeMessages, } from "../creative/protocol.js";
|
|
5
|
+
import { validateCreativeInput } from "../creative/contract.js";
|
|
6
|
+
import { prepareCreativeMedia } from "../creative/media.js";
|
|
7
|
+
export const CREATIVE_COMPLETION_PATH = "/ai-agent/v1/thread/completion";
|
|
8
|
+
export class CreativeClient {
|
|
9
|
+
api;
|
|
10
|
+
dam;
|
|
11
|
+
idFactory;
|
|
12
|
+
constructor(deps = {}) {
|
|
13
|
+
this.api = deps.api ?? new DirectApiClient();
|
|
14
|
+
this.dam = deps.dam ?? new DamClient();
|
|
15
|
+
this.idFactory = deps.idFactory ?? randomUUID;
|
|
16
|
+
}
|
|
17
|
+
async run(value) {
|
|
18
|
+
const input = validateCreativeInput(value);
|
|
19
|
+
const uploader = (file, options) => this.dam.uploadLocalAsset(file, options);
|
|
20
|
+
const media = await prepareCreativeMedia(input.media ?? [], uploader);
|
|
21
|
+
const request = buildCreativeAgentRequest(input, media, {
|
|
22
|
+
local_thread_id: this.idFactory(),
|
|
23
|
+
local_message_id: this.idFactory(),
|
|
24
|
+
});
|
|
25
|
+
const response = await this.api.postStream(CREATIVE_COMPLETION_PATH, request, {
|
|
26
|
+
headers: { Accept: "text/event-stream" },
|
|
27
|
+
});
|
|
28
|
+
if (!response.body) {
|
|
29
|
+
throw new Error("Creative completion 没有返回流式响应");
|
|
30
|
+
}
|
|
31
|
+
const messages = await parseCreativeSse(response.body);
|
|
32
|
+
return normalizeCreativeMessages(messages, input.conversation_id ?? "");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Consume the Editor completion body without exposing transport events to the
|
|
37
|
+
* CLI caller. The Editor currently emits JSON arrays, sometimes as adjacent
|
|
38
|
+
* JSON values and sometimes wrapped in `data:` SSE records, so both forms are
|
|
39
|
+
* accepted here.
|
|
40
|
+
*/
|
|
41
|
+
export async function parseCreativeSse(stream) {
|
|
42
|
+
const reader = stream.getReader();
|
|
43
|
+
const decoder = new TextDecoder();
|
|
44
|
+
let raw = "";
|
|
45
|
+
try {
|
|
46
|
+
while (true) {
|
|
47
|
+
const result = await reader.read();
|
|
48
|
+
if (result.done)
|
|
49
|
+
break;
|
|
50
|
+
raw += decoder.decode(result.value, { stream: true });
|
|
51
|
+
}
|
|
52
|
+
raw += decoder.decode();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
}
|
|
57
|
+
const payloads = extractSsePayloads(raw);
|
|
58
|
+
const messages = [];
|
|
59
|
+
for (const payload of payloads) {
|
|
60
|
+
for (const value of parseJsonSequence(payload)) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
for (const item of value) {
|
|
63
|
+
if (isMessage(item))
|
|
64
|
+
messages.push(item);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (isMessage(value)) {
|
|
68
|
+
messages.push(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return messages;
|
|
73
|
+
}
|
|
74
|
+
function extractSsePayloads(raw) {
|
|
75
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
76
|
+
const lines = normalized.split("\n");
|
|
77
|
+
const payloads = [];
|
|
78
|
+
let dataLines = [];
|
|
79
|
+
let foundDataLine = false;
|
|
80
|
+
const flush = () => {
|
|
81
|
+
if (dataLines.length > 0) {
|
|
82
|
+
payloads.push(dataLines.join("\n"));
|
|
83
|
+
dataLines = [];
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.startsWith("data:")) {
|
|
88
|
+
foundDataLine = true;
|
|
89
|
+
dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
90
|
+
}
|
|
91
|
+
else if (line === "") {
|
|
92
|
+
flush();
|
|
93
|
+
}
|
|
94
|
+
else if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:")) {
|
|
95
|
+
// SSE metadata/heartbeats do not contain completion messages.
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
else if (!foundDataLine) {
|
|
99
|
+
// The current backend may send raw JSON arrays without an SSE prefix.
|
|
100
|
+
dataLines.push(line);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// A continuation line belongs to the current data record according to
|
|
104
|
+
// the SSE grammar.
|
|
105
|
+
dataLines.push(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
flush();
|
|
109
|
+
return payloads;
|
|
110
|
+
}
|
|
111
|
+
function parseJsonSequence(payload) {
|
|
112
|
+
const values = [];
|
|
113
|
+
let index = 0;
|
|
114
|
+
while (index < payload.length) {
|
|
115
|
+
while (index < payload.length && /[\s,]/.test(payload[index] ?? ""))
|
|
116
|
+
index += 1;
|
|
117
|
+
if (index >= payload.length)
|
|
118
|
+
break;
|
|
119
|
+
const remaining = payload.slice(index).trim();
|
|
120
|
+
if (remaining === "[DONE]")
|
|
121
|
+
break;
|
|
122
|
+
const start = findJsonStart(payload, index);
|
|
123
|
+
if (start < 0)
|
|
124
|
+
break;
|
|
125
|
+
const end = findJsonEnd(payload, start);
|
|
126
|
+
if (end < 0)
|
|
127
|
+
break;
|
|
128
|
+
const text = payload.slice(start, end + 1);
|
|
129
|
+
try {
|
|
130
|
+
values.push(JSON.parse(text));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// A malformed event is ignored here; valid later events can still be
|
|
134
|
+
// delivered and the final normalizer will report an empty completion.
|
|
135
|
+
}
|
|
136
|
+
index = end + 1;
|
|
137
|
+
}
|
|
138
|
+
return values;
|
|
139
|
+
}
|
|
140
|
+
function findJsonStart(value, from) {
|
|
141
|
+
for (let index = from; index < value.length; index += 1) {
|
|
142
|
+
if (value[index] === "{" || value[index] === "[")
|
|
143
|
+
return index;
|
|
144
|
+
}
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
function findJsonEnd(value, start) {
|
|
148
|
+
const stack = [];
|
|
149
|
+
let inString = false;
|
|
150
|
+
let escaped = false;
|
|
151
|
+
for (let index = start; index < value.length; index += 1) {
|
|
152
|
+
const char = value[index];
|
|
153
|
+
if (inString) {
|
|
154
|
+
if (escaped)
|
|
155
|
+
escaped = false;
|
|
156
|
+
else if (char === "\\")
|
|
157
|
+
escaped = true;
|
|
158
|
+
else if (char === '"')
|
|
159
|
+
inString = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (char === '"') {
|
|
163
|
+
inString = true;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (char === "{" || char === "[")
|
|
167
|
+
stack.push(char);
|
|
168
|
+
else if (char === "}" || char === "]") {
|
|
169
|
+
const opening = stack.pop();
|
|
170
|
+
if ((char === "}" && opening !== "{") || (char === "]" && opening !== "[")) {
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
if (stack.length === 0)
|
|
174
|
+
return index;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
179
|
+
function isMessage(value) {
|
|
180
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
181
|
+
}
|
|
182
|
+
export function buildCreativeRequestForTest(input, request) {
|
|
183
|
+
return { input, request };
|
|
184
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
|
+
import { type AuthGuardLike } from "../core/auth/auth-guard.js";
|
|
2
3
|
type QueryValue = string | number | boolean | readonly (string | number | boolean)[] | undefined;
|
|
3
4
|
export interface DirectRequestOptions {
|
|
4
5
|
query?: Record<string, QueryValue>;
|
|
@@ -13,10 +14,16 @@ export declare class DirectApiError extends Error {
|
|
|
13
14
|
}
|
|
14
15
|
export declare class DirectApiClient {
|
|
15
16
|
private baseUrl;
|
|
16
|
-
private
|
|
17
|
-
constructor(baseUrl?: string, storeV2?: CredentialStoreV2);
|
|
17
|
+
private authGuard;
|
|
18
|
+
constructor(baseUrl?: string, storeV2?: CredentialStoreV2, authGuard?: AuthGuardLike);
|
|
18
19
|
get<T>(path: string, options?: DirectRequestOptions): Promise<T>;
|
|
19
20
|
post<T>(path: string, body?: unknown, options?: DirectRequestOptions): Promise<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Start a streaming POST and leave the response body untouched for the
|
|
23
|
+
* caller. JSON requests use `post`; Creative uses this method for the
|
|
24
|
+
* Editor-compatible completion stream.
|
|
25
|
+
*/
|
|
26
|
+
postStream(path: string, body?: unknown, options?: DirectRequestOptions): Promise<Response>;
|
|
20
27
|
delete<T>(path: string, options?: DirectRequestOptions): Promise<T>;
|
|
21
28
|
private request;
|
|
22
29
|
private attachAuthHeaders;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
|
+
import { AuthGuard, } from "../core/auth/auth-guard.js";
|
|
3
3
|
import { signApiRequest } from "../core/auth/signature.js";
|
|
4
|
-
import { isCredentialsV2 } from "../core/auth/types.js";
|
|
5
4
|
import { CLI_USER_AGENT, resolveApiBase, resolveEndpoints } from "../utils/config.js";
|
|
6
5
|
import { readEnv } from "./direct-auth.js";
|
|
7
6
|
import { redactSensitiveText } from "../utils/redact.js";
|
|
@@ -20,10 +19,10 @@ export class DirectApiError extends Error {
|
|
|
20
19
|
}
|
|
21
20
|
export class DirectApiClient {
|
|
22
21
|
baseUrl;
|
|
23
|
-
|
|
24
|
-
constructor(baseUrl = resolveApiBase(), storeV2 = new CredentialStoreV2()) {
|
|
22
|
+
authGuard;
|
|
23
|
+
constructor(baseUrl = resolveApiBase(), storeV2 = new CredentialStoreV2(), authGuard = new AuthGuard({ store: storeV2 })) {
|
|
25
24
|
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
26
|
-
this.
|
|
25
|
+
this.authGuard = authGuard;
|
|
27
26
|
}
|
|
28
27
|
async get(path, options) {
|
|
29
28
|
return this.request("GET", path, undefined, options);
|
|
@@ -31,6 +30,39 @@ export class DirectApiClient {
|
|
|
31
30
|
async post(path, body, options) {
|
|
32
31
|
return this.request("POST", path, body, options);
|
|
33
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Start a streaming POST and leave the response body untouched for the
|
|
35
|
+
* caller. JSON requests use `post`; Creative uses this method for the
|
|
36
|
+
* Editor-compatible completion stream.
|
|
37
|
+
*/
|
|
38
|
+
async postStream(path, body, options = {}) {
|
|
39
|
+
const url = buildUrl(this.baseUrl, path, options.query);
|
|
40
|
+
const headers = new Headers({
|
|
41
|
+
Accept: "text/event-stream",
|
|
42
|
+
"User-Agent": CLI_USER_AGENT,
|
|
43
|
+
...defaultBusinessHeaders(this.baseUrl),
|
|
44
|
+
...(options.headers ?? {}),
|
|
45
|
+
});
|
|
46
|
+
if (body !== undefined) {
|
|
47
|
+
headers.set("Content-Type", "application/json");
|
|
48
|
+
}
|
|
49
|
+
if (options.auth !== false) {
|
|
50
|
+
await this.attachAuthHeaders(headers, "POST", url);
|
|
51
|
+
}
|
|
52
|
+
const response = await fetch(url.toString(), {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers,
|
|
55
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const parsed = await parseResponse(response);
|
|
59
|
+
throw new DirectApiError(response.status, parsed, url.toString());
|
|
60
|
+
}
|
|
61
|
+
if (!response.body) {
|
|
62
|
+
throw new DirectApiError(response.status, { message: "流式接口没有返回响应 body" }, url.toString());
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
34
66
|
async delete(path, options) {
|
|
35
67
|
return this.request("DELETE", path, undefined, options);
|
|
36
68
|
}
|
|
@@ -60,20 +92,15 @@ export class DirectApiClient {
|
|
|
60
92
|
return parsed;
|
|
61
93
|
}
|
|
62
94
|
async attachAuthHeaders(headers, method, url) {
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
throw new Error(chalk.yellow("当前未绑定组织:请先执行 gd-cli org switch\n"));
|
|
67
|
-
}
|
|
68
|
-
const { signature, timestamp } = signApiRequest(method, url.pathname, url.searchParams.toString(), v2.sk);
|
|
69
|
-
headers.set("X-AccessKey", v2.ak);
|
|
70
|
-
headers.set("X-Signature", signature);
|
|
71
|
-
headers.set("X-Timestamp", timestamp);
|
|
72
|
-
headers.set("X-Org-Id", v2.current_org.id);
|
|
73
|
-
return;
|
|
95
|
+
const credentials = await this.authGuard.ensure("current-org");
|
|
96
|
+
if (!credentials?.current_org?.id) {
|
|
97
|
+
throw new Error("AuthGuard 未返回当前组织凭证。");
|
|
74
98
|
}
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
const { signature, timestamp } = signApiRequest(method, url.pathname, url.searchParams.toString(), credentials.sk);
|
|
100
|
+
headers.set("X-AccessKey", credentials.ak);
|
|
101
|
+
headers.set("X-Signature", signature);
|
|
102
|
+
headers.set("X-Timestamp", timestamp);
|
|
103
|
+
headers.set("X-Org-Id", credentials.current_org.id);
|
|
77
104
|
}
|
|
78
105
|
}
|
|
79
106
|
export function unwrapEnvelope(value) {
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
|
+
import { type AuthGuardLike } from "../core/auth/auth-guard.js";
|
|
2
3
|
export declare class HubClient {
|
|
3
4
|
private baseUrl;
|
|
4
|
-
private
|
|
5
|
-
constructor(baseUrl?: string, storeV2?: CredentialStoreV2);
|
|
6
|
-
get<T>(path: string, options?:
|
|
7
|
-
|
|
8
|
-
}): Promise<T>;
|
|
9
|
-
post<T>(path: string, body?: unknown, options?: {
|
|
10
|
-
auth?: boolean;
|
|
11
|
-
}): Promise<T>;
|
|
5
|
+
private authGuard;
|
|
6
|
+
constructor(baseUrl?: string, storeV2?: CredentialStoreV2, authGuard?: AuthGuardLike);
|
|
7
|
+
get<T>(path: string, options?: HubRequestOptions): Promise<T>;
|
|
8
|
+
post<T>(path: string, body?: unknown, options?: HubRequestOptions): Promise<T>;
|
|
12
9
|
private request;
|
|
13
10
|
private attachAuthHeaders;
|
|
14
11
|
}
|
|
12
|
+
export interface HubRequestOptions {
|
|
13
|
+
auth?: boolean;
|
|
14
|
+
authRequirement?: AuthenticatedRequirement;
|
|
15
|
+
}
|
|
16
|
+
type AuthenticatedRequirement = "credential" | "current-org";
|
|
15
17
|
export declare class ApiError extends Error {
|
|
16
18
|
statusCode: number;
|
|
17
19
|
body: string;
|
|
18
20
|
path: string;
|
|
19
21
|
constructor(statusCode: number, body: string, path: string);
|
|
20
22
|
}
|
|
23
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
2
|
import { resolveApiBase, CLI_USER_AGENT } from "../utils/config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { AuthGuard, } from "../core/auth/auth-guard.js";
|
|
4
4
|
import { signApiRequest } from "../core/auth/signature.js";
|
|
5
5
|
import { redactSensitiveText } from "../utils/redact.js";
|
|
6
6
|
function collectErrorCodes(err, out) {
|
|
@@ -27,10 +27,10 @@ function fetchFailureHint(baseUrl, requestUrl) {
|
|
|
27
27
|
}
|
|
28
28
|
export class HubClient {
|
|
29
29
|
baseUrl;
|
|
30
|
-
|
|
31
|
-
constructor(baseUrl, storeV2) {
|
|
30
|
+
authGuard;
|
|
31
|
+
constructor(baseUrl, storeV2 = new CredentialStoreV2(), authGuard = new AuthGuard({ store: storeV2 })) {
|
|
32
32
|
this.baseUrl = baseUrl ?? resolveApiBase();
|
|
33
|
-
this.
|
|
33
|
+
this.authGuard = authGuard;
|
|
34
34
|
}
|
|
35
35
|
async get(path, options) {
|
|
36
36
|
return this.request("GET", path, undefined, options);
|
|
@@ -46,7 +46,7 @@ export class HubClient {
|
|
|
46
46
|
};
|
|
47
47
|
const needsAuth = options?.auth !== false;
|
|
48
48
|
if (needsAuth) {
|
|
49
|
-
await this.attachAuthHeaders(headers, method, path);
|
|
49
|
+
await this.attachAuthHeaders(headers, method, path, options?.authRequirement ?? "current-org");
|
|
50
50
|
}
|
|
51
51
|
let response;
|
|
52
52
|
try {
|
|
@@ -69,20 +69,19 @@ export class HubClient {
|
|
|
69
69
|
}
|
|
70
70
|
return response.json();
|
|
71
71
|
}
|
|
72
|
-
async attachAuthHeaders(headers, method, path) {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
async attachAuthHeaders(headers, method, path, requirement) {
|
|
73
|
+
const credentials = await this.authGuard.ensure(requirement);
|
|
74
|
+
if (!credentials) {
|
|
75
|
+
throw new Error("AuthGuard 未返回签名凭证。");
|
|
76
|
+
}
|
|
77
|
+
const url = new URL(path.replace(/^\//, ""), `${this.baseUrl.replace(/\/$/, "")}/`);
|
|
78
|
+
const { signature, timestamp } = signApiRequest(method, url.pathname, url.searchParams.toString(), credentials.sk);
|
|
79
|
+
headers["X-AccessKey"] = credentials.ak;
|
|
80
|
+
headers["X-Signature"] = signature;
|
|
81
|
+
headers["X-Timestamp"] = timestamp;
|
|
82
|
+
if (requirement === "current-org" && credentials.current_org?.id) {
|
|
83
|
+
headers["X-Org-Id"] = credentials.current_org.id;
|
|
84
84
|
}
|
|
85
|
-
throw new Error("AK/SK 模式需要先登录并绑定组织:\n gd-cli auth login\n gd-cli org switch --org <org-id>");
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
export class ApiError extends Error {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
|
+
import { type AuthGuardLike } from "../core/auth/auth-guard.js";
|
|
2
3
|
declare const RIGHTS_OVERVIEW_ENDPOINT = "/trade/rights-overview";
|
|
3
4
|
declare const GAODOU = "GAODOU";
|
|
4
5
|
export interface UsageCredits {
|
|
@@ -28,12 +29,11 @@ interface DirectGetClient {
|
|
|
28
29
|
}): Promise<T>;
|
|
29
30
|
}
|
|
30
31
|
export declare class UsageClient {
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
constructor(store?: CredentialStoreV2, client?: DirectGetClient);
|
|
32
|
+
private readonly client;
|
|
33
|
+
private readonly authGuard;
|
|
34
|
+
constructor(store?: CredentialStoreV2, client?: DirectGetClient, authGuard?: AuthGuardLike);
|
|
34
35
|
getCreditsOverview(): Promise<UsageOverview>;
|
|
35
36
|
private fetchRightsOverview;
|
|
36
|
-
private readUsableCredentials;
|
|
37
37
|
}
|
|
38
38
|
export declare function normalizeUsageCredits(response: unknown): UsageCredits;
|
|
39
39
|
export {};
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
2
|
+
import { AuthGuard, } from "../core/auth/auth-guard.js";
|
|
2
3
|
import { CliUsageError } from "../core/output/cli-error.js";
|
|
3
4
|
import { DirectApiClient, DirectApiError } from "./direct-client.js";
|
|
4
5
|
const RIGHTS_OVERVIEW_ENDPOINT = "/trade/rights-overview";
|
|
5
6
|
const GAODOU = "GAODOU";
|
|
6
7
|
export class UsageClient {
|
|
7
|
-
store;
|
|
8
8
|
client;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
9
|
+
authGuard;
|
|
10
|
+
constructor(store = new CredentialStoreV2(), client, authGuard = new AuthGuard({ store })) {
|
|
11
|
+
this.authGuard = authGuard;
|
|
12
|
+
this.client = client ?? new DirectApiClient(undefined, store, authGuard);
|
|
12
13
|
}
|
|
13
14
|
async getCreditsOverview() {
|
|
14
|
-
const credentials = this.
|
|
15
|
+
const credentials = await this.authGuard.ensure("current-org");
|
|
16
|
+
if (!credentials?.current_org?.id) {
|
|
17
|
+
throw new Error("AuthGuard 未返回当前组织凭证。");
|
|
18
|
+
}
|
|
15
19
|
const response = await this.fetchRightsOverview();
|
|
16
20
|
return {
|
|
17
21
|
status: "completed",
|
|
@@ -53,50 +57,6 @@ export class UsageClient {
|
|
|
53
57
|
throw error;
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
|
-
readUsableCredentials() {
|
|
57
|
-
const credentials = this.store.read();
|
|
58
|
-
if (!credentials) {
|
|
59
|
-
throw new CliUsageError({
|
|
60
|
-
code: "AUTH_MISSING",
|
|
61
|
-
type: "auth",
|
|
62
|
-
message: "未找到 AK/SK 凭证,请先登录。",
|
|
63
|
-
hint: "请先登录:gd-cli auth login",
|
|
64
|
-
retryable: false,
|
|
65
|
-
next_steps: ["gd-cli auth login"],
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
if (this.store.isAkExpired()) {
|
|
69
|
-
throw new CliUsageError({
|
|
70
|
-
code: "AUTH_EXPIRED",
|
|
71
|
-
type: "auth",
|
|
72
|
-
message: "AK/SK 凭证已过期,请重新登录。",
|
|
73
|
-
hint: "请重新登录:gd-cli auth login",
|
|
74
|
-
retryable: false,
|
|
75
|
-
next_steps: ["gd-cli auth login"],
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
if (!credentials.current_org?.id) {
|
|
79
|
-
throw new CliUsageError({
|
|
80
|
-
code: "ORG_REQUIRED",
|
|
81
|
-
type: "auth",
|
|
82
|
-
message: "当前未绑定组织,无法查询稿豆余额。",
|
|
83
|
-
hint: "请先切换组织:gd-cli org switch",
|
|
84
|
-
retryable: false,
|
|
85
|
-
next_steps: ["gd-cli org list", "gd-cli org switch"],
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
if (this.store.isBindExpired()) {
|
|
89
|
-
throw new CliUsageError({
|
|
90
|
-
code: "ORG_BIND_EXPIRED",
|
|
91
|
-
type: "auth",
|
|
92
|
-
message: "当前组织绑定已过期,无法查询稿豆余额。",
|
|
93
|
-
hint: "请重新切换组织:gd-cli org switch",
|
|
94
|
-
retryable: false,
|
|
95
|
-
next_steps: ["gd-cli org switch"],
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return credentials;
|
|
99
|
-
}
|
|
100
60
|
}
|
|
101
61
|
export function normalizeUsageCredits(response) {
|
|
102
62
|
const record = findGaodouRecord(response) ?? {};
|
|
@@ -50,10 +50,4 @@ export class CredentialStoreV2 {
|
|
|
50
50
|
return true;
|
|
51
51
|
return new Date(creds.expires_at) <= new Date();
|
|
52
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
53
|
}
|