pdlab-cli 0.1.1 → 0.2.0

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 (3) hide show
  1. package/README.md +62 -5
  2. package/dist/index.js +436 -127
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -17,7 +17,13 @@ npm install -g pdlab-cli
17
17
  ## 快速开始
18
18
 
19
19
  ```bash
20
- # 首次使用需要告诉它服务器地址,之后会记住
20
+ pdlab login
21
+ ```
22
+
23
+ 默认连官方部署 `https://1836.online`,装完直接就能用。
24
+ 自建实例才需要指定地址,指定过一次之后会记在配置文件里:
25
+
26
+ ```bash
21
27
  pdlab login --api-url https://your-1836-instance.example.com
22
28
  ```
23
29
 
@@ -27,6 +33,7 @@ pdlab login --api-url https://your-1836-instance.example.com
27
33
  ```bash
28
34
  pdlab whoami # 看看当前是谁
29
35
  pdlab project list # 列出项目
36
+ pdlab schema # 一次性 JSON 输出全部命令、参数、权限要求、示例和退出码含义——给 agent/脚本用
30
37
  ```
31
38
 
32
39
  ## 命令
@@ -38,20 +45,36 @@ pdlab project list # 列出项目
38
45
  | `pdlab logout` | 清除本地凭证(不撤销服务端的,见下方「撤销凭证」) |
39
46
  | `pdlab whoami` | 查看当前登录账号 |
40
47
  | `pdlab config get` / `set api-url <url>` | 查看/修改本地配置 |
48
+ | `pdlab schema` | JSON 输出完整命令树 + 权限要求 + 示例 + 退出码含义 |
41
49
  | `pdlab project list \| get \| create \| update \| delete` | 项目的增删改查 |
50
+ | `pdlab project join \| leave <id>` | 报名共建 / 撤回申请或退出共建 |
51
+ | `pdlab project member review \| set-role \| remove <projectId> <userId>` | 审核报名、设置协作者角色、移除参与者(仅项目发起人本人) |
52
+ | `pdlab project revenue <id>` | 查看项目的积分分成明细(只读) |
42
53
  | `pdlab post list \| get \| create \| update \| delete` | 内容的增删改查 |
54
+ | `pdlab post review <id> --action approve\|reject` | 审核一条待审内容 |
43
55
 
44
- 任何命令加 `--help` 看具体参数。
56
+ 任何命令加 `--help` 看具体参数——示例已经直接打在 `--help` 末尾,不用等一次报错才能看到。
45
57
 
46
58
  ### 例子
47
59
 
48
60
  ```bash
49
- # 按状态筛选项目
61
+ # 按状态 / 关键词 / 分类标签筛选项目,翻页
50
62
  pdlab project list --status active
63
+ pdlab project list --q "钠电"
64
+ pdlab project list --taxonomy tax_1 --taxonomy tax_2 # 可重复传,多个取交集
65
+ pdlab project list --limit 10 --cursor <上一页返回的 nextCursor>
51
66
 
52
67
  # 创建项目(需要项目创建权限,即「1836发起人」角色)
53
68
  pdlab project create --title "钠电储能原型" --description "第一阶段验证"
54
69
 
70
+ # 报名共建 / 退出
71
+ pdlab project join proj_123
72
+ pdlab project leave proj_123
73
+
74
+ # 项目发起人审核报名、提拔协作者
75
+ pdlab project member review proj_123 user_456 --status approved
76
+ pdlab project member set-role proj_123 user_456 --role maintainer
77
+
55
78
  # 往自己参与的项目发布内容(共建者发布后进待审队列)
56
79
  pdlab post create \
57
80
  --project-id <项目 id> \
@@ -61,8 +84,40 @@ pdlab post create \
61
84
 
62
85
  # 只看某个项目下的内容
63
86
  pdlab post list --project-id <项目 id>
87
+
88
+ # 审核待审内容(需要 content:review 权限)
89
+ pdlab post review post_123 --action approve
90
+ pdlab post review post_123 --action reject --comment "缺少数据来源,请补充引用"
91
+ ```
92
+
93
+ ### 分页
94
+
95
+ `project list` / `post list` 默认单页 30 条(上限 100),响应里的 `nextCursor` 非 `null`
96
+ 就表示还有下一页,把它传给下一次调用的 `--cursor` 继续翻页;`nextCursor` 为 `null`
97
+ 表示已经是最后一页。CLI 不会替你自动拉全量——那样一次调用可能悄悄发出几十次请求。
98
+
99
+ ### 报错与退出码
100
+
101
+ 失败时,一个 JSON 对象会打到 stderr(不是人类可读的长句),方便脚本/agent 解析:
102
+
103
+ ```json
104
+ { "error": "无权限执行此操作", "code": "forbidden", "status": 403 }
64
105
  ```
65
106
 
107
+ 校验类错误还会带上 `fields`(哪个字段、什么问题)和 `hint`(对应命令的示例)。
108
+ 退出码和 `code` 一一对应,`pdlab schema` 的 `exitCodes` 字段里也有同一份:
109
+
110
+ | 退出码 | `code` | 含义 | 建议的处理 |
111
+ |---|---|---|---|
112
+ | 0 | — | 成功 | — |
113
+ | 2 | `validation` | 参数校验失败(本地或服务端) | 按 `fields`/`hint` 改参数重试 |
114
+ | 3 | `unauthenticated` | 未登录或 token 已失效 | 跑 `pdlab login` |
115
+ | 4 | `forbidden` | 无权限 | 不要重试,告知用户 |
116
+ | 5 | `not_found` | 资源不存在 | 检查 id 是否正确 |
117
+ | 6 | `conflict` | 状态冲突(如重复审核) | 不要重试,状态已经变了 |
118
+ | 7 | `network` | 连不上 API 服务器 | 检查 `--api-url`/网络后重试 |
119
+ | 1 | `unknown` / 未分类 | 其他 | 按错误信息人工排查 |
120
+
66
121
  ## 配置
67
122
 
68
123
  配置存在 `~/.pdlab/config.json`(目录 `0700`、文件 `0600`):
@@ -71,8 +126,10 @@ pdlab post list --project-id <项目 id>
71
126
  { "apiUrl": "https://your-1836-instance.example.com", "token": "pdlab_…" }
72
127
  ```
73
128
 
74
- API 地址的解析顺序:`--api-url` 参数 > `PDLAB_API_URL` 环境变量 > 配置文件。
75
- 所有命令都支持 `--api-url`,包括 `project` / `post` 这些资源命令。
129
+ API 地址的解析顺序:`--api-url` 参数 > `PDLAB_API_URL` 环境变量 > 配置文件 > 默认地址
130
+ (`https://1836.online`)。所有命令都支持 `--api-url`,
131
+ 包括 `project` / `post` 这些资源命令。`pdlab config get` 会同时打出配置文件里写了什么、
132
+ 以及这次实际会用哪个地址。
76
133
 
77
134
  读命令(`project list` / `get`、`post list` / `get`)会在**已登录时带上凭证**,
78
135
  可见范围因此按你的身份走:有审核权限才能 `post list --status pending`,
package/dist/index.js CHANGED
@@ -35,8 +35,20 @@ var import_open = __toESM(require("open"));
35
35
  var import_fs = require("fs");
36
36
  var import_os = require("os");
37
37
  var import_path = require("path");
38
+
39
+ // src/cli-error.ts
40
+ var CliError = class extends Error {
41
+ constructor(message, code = "unauthenticated") {
42
+ super(message);
43
+ this.code = code;
44
+ }
45
+ code;
46
+ };
47
+
48
+ // src/config.ts
38
49
  var CONFIG_DIR = process.env.PDLAB_CONFIG_DIR ?? (0, import_path.join)((0, import_os.homedir)(), ".pdlab");
39
50
  var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
51
+ var DEFAULT_API_URL = "https://1836.online";
40
52
  function readConfig() {
41
53
  if (!(0, import_fs.existsSync)(CONFIG_FILE)) return {};
42
54
  try {
@@ -55,11 +67,7 @@ function updateConfig(patch) {
55
67
  return next;
56
68
  }
57
69
  function resolveApiUrl(flagValue) {
58
- const apiUrl = flagValue ?? process.env.PDLAB_API_URL ?? readConfig().apiUrl;
59
- if (!apiUrl) {
60
- console.error("\u672A\u914D\u7F6E API \u5730\u5740\u3002\u8BF7\u5148\u8FD0\u884C `pdlab login` / `pdlab register`\uFF0C\u6216\u8BBE\u7F6E PDLAB_API_URL \u73AF\u5883\u53D8\u91CF\u3002");
61
- process.exit(1);
62
- }
70
+ const apiUrl = flagValue ?? process.env.PDLAB_API_URL ?? readConfig().apiUrl ?? DEFAULT_API_URL;
63
71
  return apiUrl.replace(/\/+$/, "");
64
72
  }
65
73
  function optionalToken() {
@@ -68,28 +76,53 @@ function optionalToken() {
68
76
  function requireToken() {
69
77
  const token = readConfig().token;
70
78
  if (!token) {
71
- console.error("\u5C1A\u672A\u767B\u5F55\u3002\u8BF7\u5148\u8FD0\u884C `pdlab login`\u3002");
72
- process.exit(1);
79
+ throw new CliError("\u5C1A\u672A\u767B\u5F55\u3002\u8BF7\u5148\u8FD0\u884C `pdlab login`\u3002");
73
80
  }
74
81
  return token;
75
82
  }
76
83
 
77
84
  // src/api-client.ts
85
+ function classifyStatus(status) {
86
+ switch (status) {
87
+ case 400:
88
+ case 422:
89
+ return "validation";
90
+ case 401:
91
+ return "unauthenticated";
92
+ case 403:
93
+ return "forbidden";
94
+ case 404:
95
+ return "not_found";
96
+ case 409:
97
+ return "conflict";
98
+ case 0:
99
+ return "network";
100
+ default:
101
+ return "unknown";
102
+ }
103
+ }
78
104
  var ApiRequestError = class extends Error {
79
105
  constructor(message, status, details) {
80
106
  super(message);
81
107
  this.status = status;
82
108
  this.details = details;
109
+ this.code = classifyStatus(status);
83
110
  }
84
111
  status;
85
112
  details;
113
+ code;
86
114
  };
87
115
  async function apiRequest(path, options = {}) {
88
116
  const apiUrl = resolveApiUrl(options.apiUrl);
89
117
  const url = new URL(path.replace(/^\/+/, ""), apiUrl + "/");
90
118
  if (options.query) {
91
119
  for (const [key, value] of Object.entries(options.query)) {
92
- if (value !== void 0) url.searchParams.set(key, value);
120
+ if (value === void 0) continue;
121
+ if (Array.isArray(value)) {
122
+ for (const v of value) url.searchParams.append(key, v);
123
+ } else {
124
+ url.searchParams.set(key, value);
125
+ }
93
126
  }
94
127
  }
95
128
  const headers = {};
@@ -142,7 +175,7 @@ async function pollCliAuth(code, apiUrl) {
142
175
 
143
176
  // src/commands/login.ts
144
177
  function registerLoginCommand(program2) {
145
- program2.command("login").description("\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u4E00\u6B21\u6027\u6388\u6743\uFF0C\u767B\u5F55 pdlab CLI\uFF08token \u957F\u671F\u6709\u6548\uFF0C\u76F4\u5230\u88AB\u64A4\u9500\uFF09").option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u672A\u914D\u7F6E\u8FC7\u65F6\u5FC5\u987B\u63D0\u4F9B\u4E00\u6B21").action(async (opts) => {
178
+ program2.command("login").description("\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u4E00\u6B21\u6027\u6388\u6743\uFF0C\u767B\u5F55 pdlab CLI\uFF08token \u957F\u671F\u6709\u6548\uFF0C\u76F4\u5230\u88AB\u64A4\u9500\uFF09").option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u9ED8\u8BA4\u662F\u5B98\u65B9\u90E8\u7F72\uFF0C\u81EA\u5EFA\u5B9E\u4F8B\u624D\u9700\u8981\u586B").action(async (opts) => {
146
179
  const apiUrl = resolveApiUrl(opts.apiUrl);
147
180
  const start = await apiRequest("/api/cli/auth/start", {
148
181
  method: "POST",
@@ -174,7 +207,7 @@ function registerLoginCommand(program2) {
174
207
  // src/commands/register.ts
175
208
  var import_open2 = __toESM(require("open"));
176
209
  function registerRegisterCommand(program2) {
177
- program2.command("register").description("\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u6CE8\u518C\u7533\u8BF7\uFF08\u6CE8\u518C\u540E\u9700\u7BA1\u7406\u5458\u5BA1\u6838\uFF0CCLI \u4E0D\u4F1A\u81EA\u52A8\u767B\u5F55\uFF09").option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u672A\u914D\u7F6E\u8FC7\u65F6\u5FC5\u987B\u63D0\u4F9B\u4E00\u6B21").action(async (opts) => {
210
+ program2.command("register").description("\u5728\u6D4F\u89C8\u5668\u91CC\u5B8C\u6210\u6CE8\u518C\u7533\u8BF7\uFF08\u6CE8\u518C\u540E\u9700\u7BA1\u7406\u5458\u5BA1\u6838\uFF0CCLI \u4E0D\u4F1A\u81EA\u52A8\u767B\u5F55\uFF09").option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u9ED8\u8BA4\u662F\u5B98\u65B9\u90E8\u7F72\uFF0C\u81EA\u5EFA\u5B9E\u4F8B\u624D\u9700\u8981\u586B").action(async (opts) => {
178
211
  const apiUrl = resolveApiUrl(opts.apiUrl);
179
212
  const start = await apiRequest("/api/cli/auth/start", {
180
213
  method: "POST",
@@ -227,8 +260,19 @@ function registerWhoamiCommand(program2) {
227
260
  function registerConfigCommand(program2) {
228
261
  const config = program2.command("config").description("\u67E5\u770B/\u4FEE\u6539\u672C\u5730\u914D\u7F6E\uFF08~/.pdlab/config.json\uFF09");
229
262
  config.command("get").description("\u6253\u5370\u5F53\u524D\u914D\u7F6E").action(() => {
230
- const { token, ...rest } = readConfig();
231
- console.log(JSON.stringify({ ...rest, token: token ? "***" : void 0 }, null, 2));
263
+ const { token, apiUrl } = readConfig();
264
+ console.log(
265
+ JSON.stringify(
266
+ {
267
+ apiUrl,
268
+ token: token ? "***" : void 0,
269
+ effectiveApiUrl: resolveApiUrl(),
270
+ defaultApiUrl: DEFAULT_API_URL
271
+ },
272
+ null,
273
+ 2
274
+ )
275
+ );
232
276
  });
233
277
  config.command("set <key> <value>").description("\u8BBE\u7F6E\u4E00\u9879\u914D\u7F6E\uFF0C\u76EE\u524D\u652F\u6301\uFF1Aapi-url").action((key, value) => {
234
278
  if (key !== "api-url") {
@@ -296,58 +340,134 @@ var postReviewSchema = import_zod3.z.object({
296
340
 
297
341
  // src/errors.ts
298
342
  var import_zod4 = require("zod");
299
- function printValidationError(error, examples) {
300
- console.error("\u53C2\u6570\u9519\u8BEF\uFF1A");
343
+ var EXIT_CODES = {
344
+ ok: 0,
345
+ unhandled: 1,
346
+ validation: 2,
347
+ unauthenticated: 3,
348
+ forbidden: 4,
349
+ not_found: 5,
350
+ conflict: 6,
351
+ network: 7,
352
+ unknown: 1
353
+ };
354
+ function fieldsFromZod(error) {
355
+ const fields = {};
301
356
  for (const issue of error.issues) {
302
357
  const path = issue.path.length > 0 ? issue.path.join(".") : "(\u6839)";
303
- console.error(` - ${path}: ${issue.message}`);
358
+ (fields[path] ??= []).push(issue.message);
304
359
  }
305
- printExamples(examples);
360
+ return fields;
306
361
  }
307
- function printExamples(examples) {
308
- if (examples.length === 0) return;
309
- console.error("\n\u793A\u4F8B\uFF1A");
310
- for (const example of examples) {
311
- console.error(` $ ${example.command}`);
312
- if (example.note) console.error(` ${example.note}`);
313
- }
362
+ function fieldsFromApiDetails(details) {
363
+ if (!details || typeof details !== "object") return void 0;
364
+ const flat = details;
365
+ const fields = { ...flat.fieldErrors ?? {} };
366
+ if (flat.formErrors && flat.formErrors.length > 0) fields["(\u6839)"] = flat.formErrors;
367
+ return Object.keys(fields).length > 0 ? fields : void 0;
314
368
  }
315
- function printApiError(error) {
316
- console.error(`\u9519\u8BEF\uFF1A${error.message}`);
317
- if (error.details && typeof error.details === "object") {
318
- const flat = error.details;
319
- for (const [field, messages] of Object.entries(flat.fieldErrors ?? {})) {
320
- for (const message of messages) console.error(` - ${field}: ${message}`);
321
- }
322
- for (const message of flat.formErrors ?? []) console.error(` - ${message}`);
323
- }
369
+ function exampleHint(examples) {
370
+ if (examples.length === 0) return void 0;
371
+ return examples.map((e) => e.command).join(" | ");
324
372
  }
325
- function handleCommandError(error, examples) {
373
+ function handleCommandError(error, examples = []) {
374
+ let structured;
326
375
  if (error instanceof import_zod4.ZodError) {
327
- printValidationError(error, examples);
376
+ structured = {
377
+ error: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
378
+ code: "validation",
379
+ status: null,
380
+ fields: fieldsFromZod(error),
381
+ hint: exampleHint(examples)
382
+ };
328
383
  } else if (error instanceof ApiRequestError) {
329
- printApiError(error);
384
+ structured = {
385
+ error: error.message,
386
+ code: error.code,
387
+ status: error.status || null,
388
+ fields: fieldsFromApiDetails(error.details),
389
+ hint: exampleHint(examples)
390
+ };
391
+ } else if (error instanceof CliError) {
392
+ structured = { error: error.message, code: error.code, status: null, hint: "pdlab login" };
330
393
  } else {
331
- console.error(error);
394
+ structured = {
395
+ error: error instanceof Error ? error.message : String(error),
396
+ code: "unhandled",
397
+ status: null
398
+ };
332
399
  }
333
- process.exit(1);
400
+ console.error(JSON.stringify(structured, null, 2));
401
+ process.exit(EXIT_CODES[structured.code] ?? 1);
402
+ }
403
+
404
+ // src/help-meta.ts
405
+ var META = /* @__PURE__ */ new WeakMap();
406
+ function attachMeta(command, meta) {
407
+ META.set(command, meta);
408
+ if (meta.examples && meta.examples.length > 0) {
409
+ command.addHelpText(
410
+ "after",
411
+ "\n\u793A\u4F8B\uFF1A\n" + meta.examples.map((e) => ` $ ${e.command}${e.note ? `
412
+ ${e.note}` : ""}`).join("\n")
413
+ );
414
+ }
415
+ return command;
416
+ }
417
+ function getMeta(command) {
418
+ return META.get(command) ?? {};
334
419
  }
335
420
 
336
421
  // src/commands/options.ts
337
422
  function withApiUrl(cmd) {
338
- return cmd.option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u8986\u76D6 PDLAB_API_URL \u4E0E\u914D\u7F6E\u6587\u4EF6");
423
+ return cmd.option("--api-url <url>", "API \u670D\u52A1\u5668\u5730\u5740\uFF0C\u8986\u76D6 PDLAB_API_URL\u3001\u914D\u7F6E\u6587\u4EF6\u4E0E\u9ED8\u8BA4\u5730\u5740");
339
424
  }
340
425
 
341
426
  // src/commands/project.ts
427
+ var STATUS_LIST = PROJECT_STATUS_VALUES.join(" | ");
342
428
  var CREATE_EXAMPLES = [
343
429
  {
344
430
  command: 'pdlab project create --title "\u94A0\u7535\u56DE\u6536\u5DE5\u827A\u4F18\u5316" --description "..." --status planning --taxonomy-ids tax_1,tax_2',
345
- note: "title \u5FC5\u586B\uFF0C\u5176\u4F59\u5B57\u6BB5\u53EF\u7701\u7565"
431
+ note: "title \u5FC5\u586B\uFF0C\u5176\u4F59\u5B57\u6BB5\u53EF\u7701\u7565\uFF1B\u9700\u8981\u300C1836\u53D1\u8D77\u4EBA\u300D\u89D2\u8272\uFF08project:create \u6743\u9650\uFF09"
346
432
  }
347
433
  ];
348
434
  var UPDATE_EXAMPLES = [
349
435
  { command: "pdlab project update proj_123 --progress 60 --status active", note: "\u53EA\u4F20\u8981\u6539\u7684\u5B57\u6BB5" }
350
436
  ];
437
+ var LIST_EXAMPLES = [
438
+ { command: "pdlab project list --status active", note: "\u6309\u72B6\u6001\u7B5B\u9009" },
439
+ { command: 'pdlab project list --q "\u94A0\u7535"', note: "\u6309\u6807\u9898\u5173\u952E\u8BCD\u641C\u7D22" },
440
+ { command: "pdlab project list --taxonomy tax_1 --taxonomy tax_2", note: "\u53EF\u91CD\u590D\u4F20\uFF0C\u591A\u4E2A\u6807\u7B7E\u53D6\u4EA4\u96C6" },
441
+ { command: "pdlab project list --limit 10 --cursor <\u4E0A\u4E00\u9875\u8FD4\u56DE\u7684 nextCursor>", note: "\u7FFB\u9875\uFF1BnextCursor \u4E3A null \u8868\u793A\u6CA1\u6709\u4E0B\u4E00\u9875\u4E86" }
442
+ ];
443
+ var GET_EXAMPLES = [{ command: "pdlab project get proj_123" }];
444
+ var DELETE_EXAMPLES = [
445
+ { command: "pdlab project delete proj_123", note: "\u9700\u8981\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\uFF0C\u4E14\u9879\u76EE\u4E0B\u6CA1\u6709\u5185\u5BB9/\u79EF\u5206\u8BB0\u5F55" }
446
+ ];
447
+ var JOIN_EXAMPLES = [
448
+ { command: "pdlab project join proj_123", note: "\u62A5\u540D\u5171\u5EFA\uFF0C\u8FDB\u5165\u5F85\u5BA1\u6838\uFF1B\u91CD\u590D\u8C03\u7528\u662F\u5E42\u7B49\u7684" }
449
+ ];
450
+ var LEAVE_EXAMPLES = [
451
+ { command: "pdlab project leave proj_123", note: "\u64A4\u56DE\u62A5\u540D\u7533\u8BF7\uFF0C\u6216\u9000\u51FA\u5DF2\u901A\u8FC7\u5BA1\u6838\u7684\u5171\u5EFA" }
452
+ ];
453
+ var MEMBER_REVIEW_EXAMPLES = [
454
+ {
455
+ command: "pdlab project member review proj_123 user_456 --status approved",
456
+ note: "\u5BA1\u6838\u4E00\u6761 pending \u7684\u62A5\u540D\u7533\u8BF7\uFF1B\u53EA\u6709\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA\u80FD\u8C03\u7528"
457
+ }
458
+ ];
459
+ var MEMBER_SET_ROLE_EXAMPLES = [
460
+ {
461
+ command: "pdlab project member set-role proj_123 user_456 --role maintainer",
462
+ note: "\u4EC5\u5BF9\u5DF2\u901A\u8FC7\u5BA1\u6838\uFF08approved\uFF09\u7684\u53C2\u4E0E\u8005\u751F\u6548\uFF1B\u53EA\u6709\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA\u80FD\u8C03\u7528"
463
+ }
464
+ ];
465
+ var MEMBER_REMOVE_EXAMPLES = [
466
+ { command: "pdlab project member remove proj_123 user_456", note: "\u53EA\u6709\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA\u80FD\u8C03\u7528" }
467
+ ];
468
+ var REVENUE_EXAMPLES = [
469
+ { command: "pdlab project revenue proj_123", note: "\u53EA\u8BFB\uFF0C\u9700\u8981 data:view_stats \u6743\u9650" }
470
+ ];
351
471
  function toBody(opts) {
352
472
  const body = {};
353
473
  if (opts.title !== void 0) body.title = opts.title;
@@ -362,26 +482,43 @@ function toBody(opts) {
362
482
  return body;
363
483
  }
364
484
  function withProjectFlags(cmd) {
365
- return withApiUrl(cmd).option("--title <title>", "\u9879\u76EE\u6807\u9898").option("--description <description>", "\u9879\u76EE\u63CF\u8FF0").option("--cover-image <url>", "\u5C01\u9762\u56FE http/https \u94FE\u63A5").option("--commercial-type <type>", "\u5546\u4E1A\u5C5E\u6027").option("--status <status>", "planning | active | review | completed").option("--progress <percent>", "\u8FDB\u5EA6 0-100").option("--taxonomy-ids <ids>", "\u9017\u53F7\u5206\u9694\u7684\u5206\u7C7B\u6807\u7B7E id \u5217\u8868");
485
+ return withApiUrl(cmd).option("--title <title>", "\u9879\u76EE\u6807\u9898\uFF08\u5FC5\u586B\uFF0C1-200 \u5B57\uFF09").option("--description <description>", "\u9879\u76EE\u63CF\u8FF0\uFF08\u9009\u586B\uFF0C\u6700\u591A 20000 \u5B57\uFF09").option("--cover-image <url>", "\u5C01\u9762\u56FE http/https \u94FE\u63A5\uFF08\u9009\u586B\uFF09").option("--commercial-type <type>", "\u5546\u4E1A\u5C5E\u6027\uFF08\u9009\u586B\uFF09").option("--status <status>", `\u9879\u76EE\u72B6\u6001\uFF1A${STATUS_LIST}\uFF08\u9009\u586B\uFF09`).option("--progress <percent>", "\u8FDB\u5EA6\uFF0C\u6574\u6570 0-100\uFF08\u9009\u586B\uFF09").option("--taxonomy-ids <ids>", "\u9017\u53F7\u5206\u9694\u7684\u5206\u7C7B\u6807\u7B7E id \u5217\u8868\uFF0C\u5168\u91CF\u66FF\u6362\uFF08\u9009\u586B\uFF09");
486
+ }
487
+ function collect(value, previous) {
488
+ return previous.concat([value]);
366
489
  }
367
490
  function registerProjectCommand(program2) {
368
- const project = program2.command("project").description("\u9879\u76EE\u7684\u589E\u5220\u6539\u67E5");
369
- withApiUrl(project.command("list").description("\u5217\u51FA\u9879\u76EE")).option("--status <status>", "\u6309\u72B6\u6001\u7B5B\u9009\uFF1Aplanning | active | review | completed").action(async (opts) => {
370
- const data = await apiRequest("/api/projects", {
371
- query: { status: opts.status },
372
- token: optionalToken(),
373
- apiUrl: opts.apiUrl
374
- });
375
- console.log(JSON.stringify(data, null, 2));
376
- });
377
- withApiUrl(project.command("get <id>").description("\u67E5\u770B\u9879\u76EE\u8BE6\u60C5")).action(
378
- async (id, opts) => {
379
- const data = await apiRequest(`/api/projects/${id}`, { token: optionalToken(), apiUrl: opts.apiUrl });
380
- console.log(JSON.stringify(data, null, 2));
381
- }
491
+ const project = program2.command("project").description("\u9879\u76EE\u7684\u589E\u5220\u6539\u67E5\u3001\u62A5\u540D\u5171\u5EFA\u3001\u6210\u5458\u7BA1\u7406\u3001\u6536\u76CA\u67E5\u8BE2");
492
+ attachMeta(
493
+ withApiUrl(project.command("list").description("\u5217\u51FA\u9879\u76EE\uFF08\u516C\u5F00\u53EA\u8BFB\uFF0C\u4E0D\u767B\u5F55\u4E5F\u80FD\u7528\uFF09")).option("--status <status>", `\u6309\u72B6\u6001\u7B5B\u9009\uFF1A${STATUS_LIST}`).option("--q <keyword>", "\u6309\u6807\u9898\u5173\u952E\u8BCD\u641C\u7D22").option("--taxonomy <id>", "\u6309\u5206\u7C7B\u6807\u7B7E id \u7B5B\u9009\uFF0C\u53EF\u91CD\u590D\u4F20\uFF0C\u591A\u4E2A\u53D6\u4EA4\u96C6", collect, []).option("--limit <n>", "\u5355\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 30\uFF0C\u4E0A\u9650 100").option("--cursor <token>", "\u7FFB\u9875\u6E38\u6807\uFF0C\u53D6\u4E0A\u4E00\u9875\u54CD\u5E94\u91CC\u7684 nextCursor").action(
494
+ async (opts) => {
495
+ const data = await apiRequest("/api/projects", {
496
+ query: {
497
+ status: opts.status,
498
+ q: opts.q,
499
+ taxonomy: opts.taxonomy.length > 0 ? opts.taxonomy : void 0,
500
+ limit: opts.limit,
501
+ cursor: opts.cursor
502
+ },
503
+ token: optionalToken(),
504
+ apiUrl: opts.apiUrl
505
+ });
506
+ console.log(JSON.stringify(data, null, 2));
507
+ }
508
+ ),
509
+ { examples: LIST_EXAMPLES }
382
510
  );
383
- withProjectFlags(project.command("create").description("\u521B\u5EFA\u9879\u76EE\uFF08\u9700\u8981\u9879\u76EE\u521B\u5EFA\u6743\u9650\uFF09")).action(
384
- async (opts) => {
511
+ attachMeta(
512
+ withApiUrl(project.command("get <id>").description("\u67E5\u770B\u9879\u76EE\u8BE6\u60C5\uFF08\u516C\u5F00\u53EA\u8BFB\uFF0C\u4E0D\u767B\u5F55\u4E5F\u80FD\u7528\uFF09")).action(
513
+ async (id, opts) => {
514
+ const data = await apiRequest(`/api/projects/${id}`, { token: optionalToken(), apiUrl: opts.apiUrl });
515
+ console.log(JSON.stringify(data, null, 2));
516
+ }
517
+ ),
518
+ { examples: GET_EXAMPLES }
519
+ );
520
+ attachMeta(
521
+ withProjectFlags(project.command("create").description("\u521B\u5EFA\u9879\u76EE")).action(async (opts) => {
385
522
  const token = requireToken();
386
523
  try {
387
524
  const body = projectCreateSchema.parse(toBody(opts));
@@ -390,99 +527,275 @@ function registerProjectCommand(program2) {
390
527
  } catch (error) {
391
528
  handleCommandError(error, CREATE_EXAMPLES);
392
529
  }
530
+ }),
531
+ { permission: "project:create\uFF081836\u53D1\u8D77\u4EBA\u89D2\u8272\uFF09", examples: CREATE_EXAMPLES }
532
+ );
533
+ attachMeta(
534
+ withProjectFlags(project.command("update <id>").description("\u66F4\u65B0\u9879\u76EE")).action(
535
+ async (id, opts) => {
536
+ const token = requireToken();
537
+ try {
538
+ const body = projectUpdateSchema.parse(toBody(opts));
539
+ const data = await apiRequest(`/api/projects/${id}`, { method: "PATCH", body, token, apiUrl: opts.apiUrl });
540
+ console.log(JSON.stringify(data, null, 2));
541
+ } catch (error) {
542
+ handleCommandError(error, UPDATE_EXAMPLES);
543
+ }
544
+ }
545
+ ),
546
+ {
547
+ permission: "project:update_progress\uFF08\u53D1\u8D77\u4EBA\uFF09\u6216 project:join\uFF08\u534F\u4F5C\u8005\uFF0C\u4EC5\u80FD\u6539 progress/status/description\uFF09",
548
+ examples: UPDATE_EXAMPLES
393
549
  }
394
550
  );
395
- withProjectFlags(project.command("update <id>").description("\u66F4\u65B0\u9879\u76EE")).action(
396
- async (id, opts) => {
551
+ attachMeta(
552
+ withApiUrl(
553
+ project.command("delete <id>").description("\u5220\u9664\u9879\u76EE\uFF08\u9700\u8981\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\uFF0C\u4E14\u9879\u76EE\u4E0B\u6CA1\u6709\u5185\u5BB9/\u79EF\u5206\u8BB0\u5F55\uFF09")
554
+ ).action(async (id, opts) => {
397
555
  const token = requireToken();
398
- try {
399
- const body = projectUpdateSchema.parse(toBody(opts));
400
- const data = await apiRequest(`/api/projects/${id}`, { method: "PATCH", body, token, apiUrl: opts.apiUrl });
556
+ const data = await apiRequest(`/api/projects/${id}`, { method: "DELETE", token, apiUrl: opts.apiUrl });
557
+ console.log(JSON.stringify(data, null, 2));
558
+ }),
559
+ { permission: "project:create\uFF081836\u53D1\u8D77\u4EBA\u89D2\u8272\uFF09\uFF0C\u4E14\u5FC5\u987B\u662F\u8BE5\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA", examples: DELETE_EXAMPLES }
560
+ );
561
+ attachMeta(
562
+ withApiUrl(project.command("join <id>").description("\u62A5\u540D\u5171\u5EFA\u8BE5\u9879\u76EE\uFF0C\u8FDB\u5165\u5F85\u5BA1\u6838")).action(
563
+ async (id, opts) => {
564
+ const token = requireToken();
565
+ const data = await apiRequest(`/api/projects/${id}/join`, { method: "POST", token, apiUrl: opts.apiUrl });
401
566
  console.log(JSON.stringify(data, null, 2));
402
- } catch (error) {
403
- handleCommandError(error, UPDATE_EXAMPLES);
404
567
  }
405
- }
568
+ ),
569
+ { permission: "project:join\uFF081836\u5171\u5EFA\u8005\u7B49\u89D2\u8272\uFF09", examples: JOIN_EXAMPLES }
570
+ );
571
+ attachMeta(
572
+ withApiUrl(project.command("leave <id>").description("\u64A4\u56DE\u62A5\u540D\u7533\u8BF7\uFF0C\u6216\u9000\u51FA\u5DF2\u901A\u8FC7\u5BA1\u6838\u7684\u5171\u5EFA")).action(
573
+ async (id, opts) => {
574
+ const token = requireToken();
575
+ const data = await apiRequest(`/api/projects/${id}/join`, { method: "DELETE", token, apiUrl: opts.apiUrl });
576
+ console.log(JSON.stringify(data, null, 2));
577
+ }
578
+ ),
579
+ { permission: "project:join\uFF081836\u5171\u5EFA\u8005\u7B49\u89D2\u8272\uFF09", examples: LEAVE_EXAMPLES }
580
+ );
581
+ const member = project.command("member").description("\u9879\u76EE\u53C2\u4E0E\u8005\u7684\u5BA1\u6838 / \u89D2\u8272 / \u79FB\u9664\uFF08\u4EC5\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA\u53EF\u7528\uFF09");
582
+ attachMeta(
583
+ withApiUrl(member.command("review <projectId> <userId>").description("\u5BA1\u6838\u4E00\u6761 pending \u7684\u62A5\u540D\u7533\u8BF7")).requiredOption("--status <status>", "approved | rejected").action(async (projectId, userId, opts) => {
584
+ const token = requireToken();
585
+ const data = await apiRequest(`/api/projects/${projectId}/members/${userId}`, {
586
+ method: "PATCH",
587
+ body: { status: opts.status },
588
+ token,
589
+ apiUrl: opts.apiUrl
590
+ });
591
+ console.log(JSON.stringify(data, null, 2));
592
+ }),
593
+ { permission: "project:update_progress\uFF0C\u4E14\u5FC5\u987B\u662F\u8BE5\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA", examples: MEMBER_REVIEW_EXAMPLES }
594
+ );
595
+ attachMeta(
596
+ withApiUrl(member.command("set-role <projectId> <userId>").description("\u8BBE\u7F6E\u5DF2\u901A\u8FC7\u5BA1\u6838\u7684\u53C2\u4E0E\u8005\u7684\u9879\u76EE\u5185\u89D2\u8272")).requiredOption("--role <role>", "member | maintainer").action(async (projectId, userId, opts) => {
597
+ const token = requireToken();
598
+ const data = await apiRequest(`/api/projects/${projectId}/members/${userId}`, {
599
+ method: "PATCH",
600
+ body: { role: opts.role },
601
+ token,
602
+ apiUrl: opts.apiUrl
603
+ });
604
+ console.log(JSON.stringify(data, null, 2));
605
+ }),
606
+ { permission: "project:update_progress\uFF0C\u4E14\u5FC5\u987B\u662F\u8BE5\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA", examples: MEMBER_SET_ROLE_EXAMPLES }
607
+ );
608
+ attachMeta(
609
+ withApiUrl(member.command("remove <projectId> <userId>").description("\u79FB\u9664\u4E00\u4E2A\u53C2\u4E0E\u8005")).action(
610
+ async (projectId, userId, opts) => {
611
+ const token = requireToken();
612
+ const data = await apiRequest(`/api/projects/${projectId}/members/${userId}`, {
613
+ method: "DELETE",
614
+ token,
615
+ apiUrl: opts.apiUrl
616
+ });
617
+ console.log(JSON.stringify(data, null, 2));
618
+ }
619
+ ),
620
+ { permission: "project:update_progress\uFF0C\u4E14\u5FC5\u987B\u662F\u8BE5\u9879\u76EE\u53D1\u8D77\u4EBA\u672C\u4EBA", examples: MEMBER_REMOVE_EXAMPLES }
621
+ );
622
+ attachMeta(
623
+ withApiUrl(project.command("revenue <id>").description("\u67E5\u770B\u9879\u76EE\u7684\u79EF\u5206\u5206\u6210\u660E\u7EC6\uFF08\u53EA\u8BFB\uFF09")).action(
624
+ async (id, opts) => {
625
+ const token = requireToken();
626
+ const data = await apiRequest(`/api/projects/${id}/revenue-breakdown`, { token, apiUrl: opts.apiUrl });
627
+ console.log(JSON.stringify(data, null, 2));
628
+ }
629
+ ),
630
+ { permission: "data:view_stats", examples: REVENUE_EXAMPLES }
406
631
  );
407
- withApiUrl(
408
- project.command("delete <id>").description("\u5220\u9664\u9879\u76EE\uFF08\u9700\u8981\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\uFF0C\u4E14\u9879\u76EE\u4E0B\u6CA1\u6709\u5185\u5BB9/\u79EF\u5206\u8BB0\u5F55\uFF09")
409
- ).action(async (id, opts) => {
410
- const token = requireToken();
411
- const data = await apiRequest(`/api/projects/${id}`, { method: "DELETE", token, apiUrl: opts.apiUrl });
412
- console.log(JSON.stringify(data, null, 2));
413
- });
414
632
  }
415
633
 
416
634
  // src/commands/post.ts
417
635
  var CREATE_EXAMPLES2 = [
418
636
  {
419
637
  command: 'pdlab post create --title "\u7B2C\u4E00\u5468\u8FDB\u5C55" --body "..." --project-id proj_123 --media-links https://example.com/a.png',
420
- note: "title / body / project-id \u5FC5\u586B"
638
+ note: "title / body / project-id \u5FC5\u586B\uFF1B\u9700\u8981\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\u6216\u5DF2\u62A5\u540D\u5171\u5EFA\u8BE5\u9879\u76EE"
639
+ }
640
+ ];
641
+ var UPDATE_EXAMPLES2 = [
642
+ { command: 'pdlab post update post_123 --title "..." --body "..."', note: "title \u548C body \u90FD\u5FC5\u987B\u4F20" }
643
+ ];
644
+ var LIST_EXAMPLES2 = [
645
+ { command: "pdlab post list --project-id proj_123", note: "\u53EA\u770B\u67D0\u4E2A\u9879\u76EE\u4E0B\u7684\u5185\u5BB9" },
646
+ { command: "pdlab post list --status pending", note: "\u67E5\u770B\u5F85\u5BA1\u6838\u5185\u5BB9\uFF0C\u9700\u8981 content:review \u6743\u9650\uFF0C\u5426\u5219 403" },
647
+ { command: "pdlab post list --limit 10 --cursor <\u4E0A\u4E00\u9875\u8FD4\u56DE\u7684 nextCursor>", note: "\u7FFB\u9875\uFF1BnextCursor \u4E3A null \u8868\u793A\u6CA1\u6709\u4E0B\u4E00\u9875\u4E86" }
648
+ ];
649
+ var GET_EXAMPLES2 = [{ command: "pdlab post get post_123" }];
650
+ var DELETE_EXAMPLES2 = [
651
+ { command: "pdlab post delete post_123", note: "\u4F5C\u8005\u672C\u4EBA\uFF0C\u6216\u6709\u5220\u9664\u4EFB\u610F\u5185\u5BB9\u7684\u6743\u9650" }
652
+ ];
653
+ var REVIEW_EXAMPLES = [
654
+ {
655
+ command: "pdlab post review post_123 --action approve",
656
+ note: "\u901A\u8FC7\u5BA1\u6838\uFF1B\u4F5C\u8005\u4E0D\u80FD\u5BA1\u81EA\u5DF1\u53D1\u5E03\u7684\u5185\u5BB9\uFF08403\uFF09\uFF0C\u53EA\u80FD\u5BA1 pending \u72B6\u6001\u7684\u5185\u5BB9\uFF08\u975E pending \u4F1A 409\uFF09"
657
+ },
658
+ {
659
+ command: 'pdlab post review post_123 --action reject --comment "\u7F3A\u5C11\u6570\u636E\u6765\u6E90\uFF0C\u8BF7\u8865\u5145\u5F15\u7528"',
660
+ note: "\u9A73\u56DE\u65F6\u5FC5\u987B\u586B\u5199 --comment\uFF0C\u5426\u5219 400"
421
661
  }
422
662
  ];
423
- var UPDATE_EXAMPLES2 = [{ command: 'pdlab post update post_123 --title "..." --body "..."', note: "title \u548C body \u90FD\u5FC5\u987B\u4F20" }];
424
663
  function toMediaLinks(value) {
425
664
  if (value === void 0) return void 0;
426
665
  return value.split(",").map((url) => url.trim()).filter(Boolean);
427
666
  }
428
667
  function registerPostCommand(program2) {
429
- const post = program2.command("post").description("\u5185\u5BB9\uFF08\u5E16\u5B50\uFF09\u7684\u589E\u5220\u6539\u67E5");
430
- withApiUrl(post.command("list").description("\u5217\u51FA\u5185\u5BB9")).option("--project-id <id>", "\u6309\u9879\u76EE\u7B5B\u9009").option("--status <status>", "pending | approved | rejected\uFF08\u67E5\u770B\u975E approved \u9700\u8981\u5BA1\u6838\u6743\u9650\uFF09").action(async (opts) => {
431
- const data = await apiRequest("/api/posts", {
432
- query: { projectId: opts.projectId, status: opts.status },
433
- token: optionalToken(),
434
- apiUrl: opts.apiUrl
435
- });
436
- console.log(JSON.stringify(data, null, 2));
437
- });
438
- withApiUrl(post.command("get <id>").description("\u67E5\u770B\u5185\u5BB9\u8BE6\u60C5")).action(
439
- async (id, opts) => {
440
- const data = await apiRequest(`/api/posts/${id}`, { token: optionalToken(), apiUrl: opts.apiUrl });
441
- console.log(JSON.stringify(data, null, 2));
442
- }
443
- );
444
- withApiUrl(post.command("create").description("\u53D1\u5E03\u5185\u5BB9\uFF08\u9700\u8981\u5185\u5BB9\u521B\u5EFA\u6743\u9650\uFF0C\u4E14\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\u6216\u5DF2\u62A5\u540D\u5171\u5EFA\uFF09")).option("--title <title>", "\u5185\u5BB9\u6807\u9898").option("--body <body>", "\u5185\u5BB9\u6B63\u6587").option("--project-id <id>", "\u6240\u5C5E\u9879\u76EE id").option("--media-links <urls>", "\u9017\u53F7\u5206\u9694\u7684\u5A92\u4F53\u94FE\u63A5").action(async (opts) => {
445
- const token = requireToken();
446
- try {
447
- const body = postCreateSchema.parse({
448
- title: opts.title,
449
- body: opts.body,
450
- projectId: opts.projectId,
451
- mediaLinks: toMediaLinks(opts.mediaLinks)
668
+ const post = program2.command("post").description("\u5185\u5BB9\uFF08\u5E16\u5B50\uFF09\u7684\u589E\u5220\u6539\u67E5\u4E0E\u5BA1\u6838");
669
+ attachMeta(
670
+ withApiUrl(post.command("list").description("\u5217\u51FA\u5185\u5BB9\uFF08approved \u5185\u5BB9\u516C\u5F00\u53EA\u8BFB\uFF0C\u4E0D\u767B\u5F55\u4E5F\u80FD\u7528\uFF09")).option("--project-id <id>", "\u6309\u9879\u76EE\u7B5B\u9009").option("--status <status>", "pending | approved | rejected\uFF08\u67E5\u770B\u975E approved \u9700\u8981\u5BA1\u6838\u6743\u9650\uFF09").option("--limit <n>", "\u5355\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 30\uFF0C\u4E0A\u9650 100").option("--cursor <token>", "\u7FFB\u9875\u6E38\u6807\uFF0C\u53D6\u4E0A\u4E00\u9875\u54CD\u5E94\u91CC\u7684 nextCursor").action(async (opts) => {
671
+ const data = await apiRequest("/api/posts", {
672
+ query: { projectId: opts.projectId, status: opts.status, limit: opts.limit, cursor: opts.cursor },
673
+ token: optionalToken(),
674
+ apiUrl: opts.apiUrl
452
675
  });
453
- const data = await apiRequest("/api/posts", { method: "POST", body, token, apiUrl: opts.apiUrl });
454
676
  console.log(JSON.stringify(data, null, 2));
455
- } catch (error) {
456
- handleCommandError(error, CREATE_EXAMPLES2);
457
- }
458
- });
459
- withApiUrl(post.command("update <id>").description("\u7F16\u8F91\u5185\u5BB9\uFF08\u4F5C\u8005\u672C\u4EBA\uFF0C\u6216\u6709\u7F16\u8F91\u4EFB\u610F\u5185\u5BB9\u7684\u6743\u9650\uFF09")).option("--title <title>", "\u5185\u5BB9\u6807\u9898").option("--body <body>", "\u5185\u5BB9\u6B63\u6587").option("--media-links <urls>", "\u9017\u53F7\u5206\u9694\u7684\u5A92\u4F53\u94FE\u63A5").action(async (id, opts) => {
460
- const token = requireToken();
461
- try {
462
- const body = postUpdateSchema.parse({
463
- title: opts.title,
464
- body: opts.body,
465
- mediaLinks: toMediaLinks(opts.mediaLinks)
466
- });
467
- const data = await apiRequest(`/api/posts/${id}`, { method: "PATCH", body, token, apiUrl: opts.apiUrl });
468
- console.log(JSON.stringify(data, null, 2));
469
- } catch (error) {
470
- handleCommandError(error, UPDATE_EXAMPLES2);
471
- }
472
- });
473
- withApiUrl(post.command("delete <id>").description("\u5220\u9664\u5185\u5BB9\uFF08\u4F5C\u8005\u672C\u4EBA\uFF0C\u6216\u6709\u5220\u9664\u4EFB\u610F\u5185\u5BB9\u7684\u6743\u9650\uFF09")).action(
474
- async (id, opts) => {
677
+ }),
678
+ { examples: LIST_EXAMPLES2 }
679
+ );
680
+ attachMeta(
681
+ withApiUrl(post.command("get <id>").description("\u67E5\u770B\u5185\u5BB9\u8BE6\u60C5")).action(
682
+ async (id, opts) => {
683
+ const data = await apiRequest(`/api/posts/${id}`, { token: optionalToken(), apiUrl: opts.apiUrl });
684
+ console.log(JSON.stringify(data, null, 2));
685
+ }
686
+ ),
687
+ { examples: GET_EXAMPLES2 }
688
+ );
689
+ attachMeta(
690
+ withApiUrl(post.command("create").description("\u53D1\u5E03\u5185\u5BB9")).option("--title <title>", "\u5185\u5BB9\u6807\u9898\uFF08\u5FC5\u586B\uFF09").option("--body <body>", "\u5185\u5BB9\u6B63\u6587\uFF08\u5FC5\u586B\uFF09").option("--project-id <id>", "\u6240\u5C5E\u9879\u76EE id\uFF08\u5FC5\u586B\uFF09").option("--media-links <urls>", "\u9017\u53F7\u5206\u9694\u7684\u5A92\u4F53\u94FE\u63A5\uFF08\u9009\u586B\uFF0C\u6700\u591A 20 \u6761\uFF0Chttp/https\uFF09").action(async (opts) => {
475
691
  const token = requireToken();
476
- const data = await apiRequest(`/api/posts/${id}`, { method: "DELETE", token, apiUrl: opts.apiUrl });
477
- console.log(JSON.stringify(data, null, 2));
478
- }
692
+ try {
693
+ const body = postCreateSchema.parse({
694
+ title: opts.title,
695
+ body: opts.body,
696
+ projectId: opts.projectId,
697
+ mediaLinks: toMediaLinks(opts.mediaLinks)
698
+ });
699
+ const data = await apiRequest("/api/posts", { method: "POST", body, token, apiUrl: opts.apiUrl });
700
+ console.log(JSON.stringify(data, null, 2));
701
+ } catch (error) {
702
+ handleCommandError(error, CREATE_EXAMPLES2);
703
+ }
704
+ }),
705
+ { permission: "content:create\uFF0C\u4E14\u662F\u9879\u76EE\u53D1\u8D77\u4EBA\u6216\u5DF2\u62A5\u540D\u5171\u5EFA\u8BE5\u9879\u76EE", examples: CREATE_EXAMPLES2 }
706
+ );
707
+ attachMeta(
708
+ withApiUrl(post.command("update <id>").description("\u7F16\u8F91\u5185\u5BB9\uFF08\u4F5C\u8005\u672C\u4EBA\uFF0C\u6216\u6709\u7F16\u8F91\u4EFB\u610F\u5185\u5BB9\u7684\u6743\u9650\uFF09")).option("--title <title>", "\u5185\u5BB9\u6807\u9898\uFF08\u5FC5\u586B\uFF09").option("--body <body>", "\u5185\u5BB9\u6B63\u6587\uFF08\u5FC5\u586B\uFF09").option("--media-links <urls>", "\u9017\u53F7\u5206\u9694\u7684\u5A92\u4F53\u94FE\u63A5\uFF08\u9009\u586B\uFF0C\u6700\u591A 20 \u6761\uFF0Chttp/https\uFF09").action(async (id, opts) => {
709
+ const token = requireToken();
710
+ try {
711
+ const body = postUpdateSchema.parse({
712
+ title: opts.title,
713
+ body: opts.body,
714
+ mediaLinks: toMediaLinks(opts.mediaLinks)
715
+ });
716
+ const data = await apiRequest(`/api/posts/${id}`, { method: "PATCH", body, token, apiUrl: opts.apiUrl });
717
+ console.log(JSON.stringify(data, null, 2));
718
+ } catch (error) {
719
+ handleCommandError(error, UPDATE_EXAMPLES2);
720
+ }
721
+ }),
722
+ { examples: UPDATE_EXAMPLES2 }
723
+ );
724
+ attachMeta(
725
+ withApiUrl(post.command("delete <id>").description("\u5220\u9664\u5185\u5BB9\uFF08\u4F5C\u8005\u672C\u4EBA\uFF0C\u6216\u6709\u5220\u9664\u4EFB\u610F\u5185\u5BB9\u7684\u6743\u9650\uFF09")).action(
726
+ async (id, opts) => {
727
+ const token = requireToken();
728
+ const data = await apiRequest(`/api/posts/${id}`, { method: "DELETE", token, apiUrl: opts.apiUrl });
729
+ console.log(JSON.stringify(data, null, 2));
730
+ }
731
+ ),
732
+ { examples: DELETE_EXAMPLES2 }
733
+ );
734
+ attachMeta(
735
+ withApiUrl(post.command("review <id>").description("\u5BA1\u6838\u4E00\u6761\u5F85\u5BA1\u5185\u5BB9\uFF08approve / reject\uFF09")).requiredOption("--action <action>", "approve | reject").option("--comment <text>", "\u5BA1\u6838\u610F\u89C1\uFF0C\u6700\u591A 500 \u5B57\uFF08reject \u65F6\u5FC5\u586B\uFF09").action(async (id, opts) => {
736
+ const token = requireToken();
737
+ try {
738
+ const body = postReviewSchema.parse({ action: opts.action, reviewComment: opts.comment });
739
+ const data = await apiRequest(`/api/posts/${id}/review`, { method: "POST", body, token, apiUrl: opts.apiUrl });
740
+ console.log(JSON.stringify(data, null, 2));
741
+ } catch (error) {
742
+ handleCommandError(error, REVIEW_EXAMPLES);
743
+ }
744
+ }),
745
+ { permission: "content:review\uFF0C\u4E14\u4E0D\u80FD\u5BA1\u6838\u81EA\u5DF1\u53D1\u5E03\u7684\u5185\u5BB9", examples: REVIEW_EXAMPLES }
479
746
  );
480
747
  }
481
748
 
749
+ // src/commands/schema.ts
750
+ var CODE_DESCRIPTION = {
751
+ ok: "\u6210\u529F",
752
+ unhandled: "\u672A\u5206\u7C7B\u9519\u8BEF",
753
+ validation: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF08\u672C\u5730\u6216\u670D\u52A1\u7AEF\uFF09",
754
+ unauthenticated: "\u672A\u767B\u5F55\u6216 token \u5DF2\u5931\u6548\uFF0C\u9700\u8981 pdlab login",
755
+ forbidden: "\u65E0\u6743\u9650\uFF08\u670D\u52A1\u7AEF 403\uFF09",
756
+ not_found: "\u8D44\u6E90\u4E0D\u5B58\u5728\uFF08\u670D\u52A1\u7AEF 404\uFF09",
757
+ conflict: "\u72B6\u6001\u51B2\u7A81\uFF0C\u4F8B\u5982\u91CD\u590D\u5BA1\u6838\uFF08\u670D\u52A1\u7AEF 409\uFF09",
758
+ network: "\u8FDE\u4E0D\u4E0A API \u670D\u52A1\u5668",
759
+ unknown: "\u672A\u5206\u7C7B\u7684\u670D\u52A1\u7AEF\u9519\u8BEF"
760
+ };
761
+ function exitCodeTable() {
762
+ const table = {};
763
+ for (const [code, exitStatus] of Object.entries(EXIT_CODES)) {
764
+ if (table[exitStatus] === void 0) table[exitStatus] = CODE_DESCRIPTION[code];
765
+ }
766
+ return table;
767
+ }
768
+ function serialize(command) {
769
+ const meta = getMeta(command);
770
+ return {
771
+ name: command.name(),
772
+ description: command.description(),
773
+ permission: meta.permission,
774
+ arguments: command.registeredArguments.map((a) => ({ name: a.name(), required: a.required })),
775
+ options: command.options.map((o) => ({ flags: o.flags, description: o.description, required: o.mandatory })),
776
+ examples: meta.examples ?? [],
777
+ subcommands: command.commands.map(serialize)
778
+ };
779
+ }
780
+ function registerSchemaCommand(program2) {
781
+ program2.command("schema").description("\u4EE5 JSON \u8F93\u51FA\u5B8C\u6574\u547D\u4EE4\u6811\uFF08\u542B\u53C2\u6570\u3001\u6743\u9650\u8981\u6C42\u3001\u793A\u4F8B\uFF09\u548C\u9000\u51FA\u7801\u542B\u4E49\uFF0C\u4F9B agent/\u811A\u672C\u4E00\u6B21\u6027\u8BFB\u53D6").action(() => {
782
+ console.log(
783
+ JSON.stringify(
784
+ {
785
+ exitCodes: exitCodeTable(),
786
+ commands: program2.commands.map(serialize)
787
+ },
788
+ null,
789
+ 2
790
+ )
791
+ );
792
+ });
793
+ }
794
+
482
795
  // src/index.ts
483
796
  var pkg = JSON.parse((0, import_fs2.readFileSync)((0, import_path2.join)(__dirname, "../package.json"), "utf8"));
484
797
  var program = new import_commander.Command();
485
- program.name("pdlab").description("1836 \u5F00\u6E90\u5171\u5EFA\u5E73\u53F0\u547D\u4EE4\u884C\u5DE5\u5177\u2014\u2014\u662F\u73B0\u6709 REST API \u7684\u8584\u5C01\u88C5").version(pkg.version);
798
+ program.name("pdlab").description("1836 \u5F00\u6E90\u5171\u5EFA\u5E73\u53F0\u547D\u4EE4\u884C\u5DE5\u5177\u2014\u2014\u662F\u73B0\u6709 REST API \u7684\u8584\u5C01\u88C5\u3002\u7ED9 agent/\u811A\u672C\u7528\uFF1Apdlab schema \u4E00\u6B21\u6027\u5217\u51FA\u5168\u90E8\u547D\u4EE4\u3001\u53C2\u6570\u4E0E\u9000\u51FA\u7801\u542B\u4E49\u3002").version(pkg.version);
486
799
  registerLoginCommand(program);
487
800
  registerRegisterCommand(program);
488
801
  registerLogoutCommand(program);
@@ -490,11 +803,7 @@ registerWhoamiCommand(program);
490
803
  registerConfigCommand(program);
491
804
  registerProjectCommand(program);
492
805
  registerPostCommand(program);
806
+ registerSchemaCommand(program);
493
807
  program.parseAsync(process.argv).catch((error) => {
494
- if (error instanceof ApiRequestError) {
495
- console.error(`\u9519\u8BEF\uFF1A${error.message}`);
496
- } else {
497
- console.error(error);
498
- }
499
- process.exit(1);
808
+ handleCommandError(error);
500
809
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdlab-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "1836 开源共建平台(problem-driven-lab)的命令行工具:项目、内容的增删改查,浏览器授权登录",
6
6
  "keywords": [