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.
Files changed (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +18 -14
  3. package/dist/src/api/app-runner.js +1 -2
  4. package/dist/src/api/creative-client.d.ts +34 -0
  5. package/dist/src/api/creative-client.js +184 -0
  6. package/dist/src/api/direct-client.d.ts +9 -2
  7. package/dist/src/api/direct-client.js +45 -18
  8. package/dist/src/api/hub-client.d.ts +11 -8
  9. package/dist/src/api/hub-client.js +17 -18
  10. package/dist/src/api/usage-client.d.ts +4 -4
  11. package/dist/src/api/usage-client.js +9 -49
  12. package/dist/src/auth/credential-store-v2.d.ts +0 -1
  13. package/dist/src/auth/credential-store-v2.js +0 -6
  14. package/dist/src/commands/auth.js +13 -13
  15. package/dist/src/commands/creative.d.ts +8 -0
  16. package/dist/src/commands/creative.js +186 -0
  17. package/dist/src/commands/dam.js +27 -22
  18. package/dist/src/commands/org.js +20 -14
  19. package/dist/src/commands/skills.js +9 -4
  20. package/dist/src/commands/update.js +6 -2
  21. package/dist/src/commands/usage.d.ts +1 -1
  22. package/dist/src/commands/usage.js +6 -4
  23. package/dist/src/commands/workflows.js +31 -13
  24. package/dist/src/core/auth/auth-guard.d.ts +21 -0
  25. package/dist/src/core/auth/auth-guard.js +121 -0
  26. package/dist/src/core/auth/org-manager.d.ts +2 -1
  27. package/dist/src/core/auth/org-manager.js +61 -6
  28. package/dist/src/creative/contract.d.ts +1112 -0
  29. package/dist/src/creative/contract.js +743 -0
  30. package/dist/src/creative/media.d.ts +19 -0
  31. package/dist/src/creative/media.js +83 -0
  32. package/dist/src/creative/protocol.d.ts +60 -0
  33. package/dist/src/creative/protocol.js +337 -0
  34. package/dist/src/middleware/auth.d.ts +6 -2
  35. package/dist/src/middleware/auth.js +25 -63
  36. package/dist/src/middleware/error-handler.d.ts +2 -0
  37. package/dist/src/middleware/error-handler.js +3 -4
  38. package/dist/src/program.js +7 -89
  39. package/dist/src/registry/agent-contracts.d.ts +3 -11
  40. package/dist/src/registry/agent-contracts.js +5 -243
  41. package/dist/src/registry/direct-registry.d.ts +0 -5
  42. package/dist/src/registry/direct-registry.js +0 -19
  43. package/dist/src/registry/registry-client.d.ts +0 -21
  44. package/dist/src/registry/registry-client.js +1 -40
  45. package/dist/src/utils/config.d.ts +2 -2
  46. package/dist/src/utils/config.js +1 -1
  47. package/dist/src/utils/help-verify.d.ts +1 -3
  48. package/dist/src/utils/help-verify.js +1 -25
  49. package/package.json +3 -3
  50. package/skills/gd-cli/SKILL.md +10 -9
  51. package/skills/gd-cli/references/auth.md +6 -0
  52. package/skills/gd-cli/references/creative.md +47 -0
  53. package/skills/gd-cli/references/entitlement.md +2 -2
  54. package/skills/gd-cli/references/errors.md +7 -0
  55. package/skills/gd-cli/references/sop.md +4 -4
  56. package/skills/gd-cli/references/workflows.md +1 -1
  57. package/dist/src/commands/models.d.ts +0 -2
  58. package/dist/src/commands/models.js +0 -78
  59. package/dist/src/commands/tools.d.ts +0 -4
  60. package/dist/src/commands/tools.js +0 -383
  61. 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 提供登录、组织切换、单项 AI Tool、多步骤 Workflow 和 DAM 素材管理能力。
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 tools list
40
- gd-cli tools call image.cutout --image ./product.jpg --mode product
41
- gd-cli tools call text.title --brief "夏季运动鞋,轻量缓震,适合通勤"
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
- | `tools` | 调用单项 AI 创意能力 |
57
+ | `creative` | 统一发起图片、视频和文本创作 |
58
58
  | `workflows` | 运行多步骤创意生产流程 |
59
59
  | `skills` | 安装和管理 `gd-cli` Agent Skill |
60
60
  | `dam` | 上传、查询和删除 DAM 素材 |
61
61
  | `update` | 检查和升级全局 CLI |
62
- | `manifest` | 输出 Agent 可解析的能力清单 |
63
- | `examples` | 查看常用命令示例 |
62
+ | `org credits` | 查询当前组织稿豆余额 |
64
63
 
65
64
  ## Agent And Script Usage
66
65
 
67
- 核心命令支持 `--format json`,便于脚本和 Agent 解析。Agent 自身有语言模型时,标题、正文、标签和 prompt 草稿可优先由 Agent 生成;`gd-cli` 负责上传、抠图、生图、生视频、高清化和完整 Workflow 交付。
66
+ 核心命令支持 `--format json`,便于脚本和 Agent 解析。Creative 接收目标描述、媒体、结构化约束和连续会话信息,由 GD Agent 理解并完成创作;CLI 对使用者表现为一次输入、一次最终输出。
68
67
 
69
- 单项能力使用 `tools call`:
68
+ Creative 的完整输入和输出结构使用本地静态 Schema 发现:
70
69
 
71
70
  ```bash
72
- gd-cli tools call image.generate \
73
- --prompt "电商主图,白底,突出轻量缓震" \
74
- --image ./product.jpg \
75
- --ratio 1:1 \
76
- --format json
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: "可根据失败步骤单独调用对应 Tool 验证输入,也可以调整图片或 brief 后重试 App。",
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 storeV2;
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
- storeV2;
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.storeV2 = storeV2;
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 v2 = this.storeV2.read();
64
- if (v2 && isCredentialsV2(v2)) {
65
- if (!v2.current_org?.id) {
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
- console.log("");
76
- throw new Error(chalk.yellow("先登录并绑定组织:gd-cli auth login\n"));
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 storeV2;
5
- constructor(baseUrl?: string, storeV2?: CredentialStoreV2);
6
- get<T>(path: string, options?: {
7
- auth?: boolean;
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 { isCredentialsV2 } from "../core/auth/types.js";
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
- storeV2;
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.storeV2 = storeV2 ?? new CredentialStoreV2();
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 v2 = this.storeV2.read();
74
- if (v2 && isCredentialsV2(v2)) {
75
- const url = new URL(path.replace(/^\//, ""), `${this.baseUrl.replace(/\/$/, "")}/`);
76
- const { signature, timestamp } = signApiRequest(method, url.pathname, url.searchParams.toString(), v2.sk);
77
- headers["X-AccessKey"] = v2.ak;
78
- headers["X-Signature"] = signature;
79
- headers["X-Timestamp"] = timestamp;
80
- if (v2.current_org?.id) {
81
- headers["X-Org-Id"] = v2.current_org.id;
82
- }
83
- return;
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 store;
32
- private client;
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
- constructor(store = new CredentialStoreV2(), client = new DirectApiClient(undefined, store)) {
10
- this.store = store;
11
- this.client = client;
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.readUsableCredentials();
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) ?? {};
@@ -8,5 +8,4 @@ export declare class CredentialStoreV2 {
8
8
  write(credentials: CredentialsV2): void;
9
9
  delete(): void;
10
10
  isAkExpired(): boolean;
11
- isBindExpired(): boolean;
12
11
  }
@@ -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
  }