@super-coding/cli 0.2.1 → 0.2.3

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.
@@ -1,8 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdirSync } from "node:fs";
2
+ import { mkdirSync, symlinkSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { AgentError, prepareAgent, restoreAgentLaunch, validateBusinessAgent } from "./agent/index.js";
6
+ import { applyRemotePingcodeConfig, maskSecret } from "../pingcode/index.js";
7
+ import { inspectLocalPath } from "./local-path.js";
6
8
  import { promptWithSkills, syncRunSkills } from "./run-skills.js";
7
9
  /** 合并多份子进程环境变量;后者覆盖前者(PingCode 注入的 PC_* 必须压过用户配置) */
8
10
  function mergeEnv(...parts) {
@@ -15,6 +17,43 @@ function mergeEnv(...parts) {
15
17
  }
16
18
  /** 会话目录默认基底(持久位置:目录保留不删,不用 /tmp) */
17
19
  const DEFAULT_WORKDIR_BASE = join(homedir(), ".super-coding", "workspaces");
20
+ /**
21
+ * project_repo 由浏览器按项目设置解析后下发,不能全信:
22
+ * local 必须在本机真实存在且是目录(否则让前端去问用户),repo 至少要有地址。
23
+ */
24
+ function resolveProjectRepo(repo) {
25
+ if (!repo)
26
+ return { ok: true };
27
+ if (repo.type === "local") {
28
+ const dir = typeof repo.dir === "string" ? repo.dir.trim() : "";
29
+ if (!dir)
30
+ return { ok: false, code: "project_repo_invalid", reason: "项目配置的本地目录为空" };
31
+ const state = inspectLocalPath(dir);
32
+ if (!state.exists) {
33
+ return {
34
+ ok: false,
35
+ code: "local_dir_missing",
36
+ reason: `本地目录不存在:${dir}(请确认本机路径,或在项目设置里改用远程仓储)`,
37
+ };
38
+ }
39
+ if (!state.is_directory) {
40
+ return { ok: false, code: "local_dir_missing", reason: `本地目录不是文件夹:${dir}` };
41
+ }
42
+ return { ok: true, repo: { type: "local", dir: state.resolved } };
43
+ }
44
+ if (repo.type === "repo") {
45
+ const url = typeof repo.url === "string" ? repo.url.trim() : "";
46
+ if (!url)
47
+ return { ok: false, code: "project_repo_invalid", reason: "项目配置的远程仓储地址为空" };
48
+ const branch = typeof repo.branch === "string" ? repo.branch.trim() : "";
49
+ return { ok: true, repo: { type: "repo", url, ...(branch ? { branch } : {}) } };
50
+ }
51
+ return {
52
+ ok: false,
53
+ code: "project_repo_invalid",
54
+ reason: `不支持的项目仓储类型:${String(repo.type)}`,
55
+ };
56
+ }
18
57
  /**
19
58
  * 任务编排:transport ⇄ adapter ⇄ store 接线。
20
59
  * 事件一律先落盘(分配 seq)再发 server——断线后 server 按 last_seq 请求补发。
@@ -82,10 +121,53 @@ export class Orchestrator {
82
121
  case "resume":
83
122
  this.handleResume(msg);
84
123
  break;
124
+ case "app:config":
125
+ this.applyAppConfig(msg);
126
+ break;
85
127
  default:
86
128
  break; // ping 由 transport 层处理
87
129
  }
88
130
  }
131
+ /** 同一种「跳过」结果只打一次日志:重连很频繁,否则 daemon.log 会被刷满 */
132
+ lastAppConfigOutcome;
133
+ /**
134
+ * 平台下发的应用级凭据(manifest environment → webhook → remote → 这里)。
135
+ * 写入策略见 pingcode/remote-config.ts:只在本机缺失时落盘,绝不覆盖用户配置。
136
+ * 写入后无需通知 bridge——它每轮 prepareRun 都会重读 pingcode.json。
137
+ */
138
+ applyAppConfig(msg) {
139
+ const pingcode = msg.pingcode;
140
+ const result = applyRemotePingcodeConfig(pingcode);
141
+ if (result.applied !== "written" && result.applied === this.lastAppConfigOutcome)
142
+ return;
143
+ this.lastAppConfigOutcome = result.applied;
144
+ switch (result.applied) {
145
+ case "written":
146
+ console.log(`[daemon] 已写入平台下发的 PingCode 凭据(client_id=${pingcode?.client_id ?? "-"}, ` +
147
+ `secret=${maskSecret(pingcode?.client_secret)}, ` +
148
+ `base_url=${pingcode?.base_url ?? "默认"})→ ${result.path};下一轮任务生效`);
149
+ return;
150
+ case "exists":
151
+ console.log(`[daemon] 本机已有 PingCode 配置,忽略平台下发(${result.path})`);
152
+ return;
153
+ case "skipped_invalid":
154
+ console.warn(`[daemon][warn] 本机 PingCode 配置无法解析,未覆盖平台下发值(${result.path}:${result.error});` +
155
+ "可运行 scode pingcode connect 修复");
156
+ return;
157
+ case "skipped_bad_base_url":
158
+ console.warn(`[daemon][warn] 平台下发的 PC_OPEN_API_BASE_URL 不是 https,已忽略:${result.base_url}`);
159
+ return;
160
+ case "skipped_incomplete":
161
+ console.warn(`[daemon][warn] 平台只下发了半个 PingCode 凭据(缺 ${result.missing}),已忽略`);
162
+ return;
163
+ case "failed":
164
+ console.warn(`[daemon][warn] 写入平台下发的 PingCode 凭据失败:${result.error}`);
165
+ return;
166
+ default:
167
+ // skipped_empty(平台未配置)/ skipped_disabled(开关关闭):保持静默
168
+ return;
169
+ }
170
+ }
89
171
  /** hello 先复位本连接同步状态;后续每个 sync 响应驱动下一批,覆盖终态运行 */
90
172
  handleResume(msg) {
91
173
  if (!msg.sync_id) {
@@ -170,12 +252,34 @@ export class Orchestrator {
170
252
  return;
171
253
  }
172
254
  }
255
+ // 项目仓储(项目设置里的「代码仓库」):local 必须本机真实存在,否则明确拒绝,
256
+ // 让前端去问用户——绝不静默退化成「空目录 + 不克隆」让 agent 在错误前提下开工。
257
+ const repoCheck = resolveProjectRepo(msg.project_repo);
258
+ if (!repoCheck.ok) {
259
+ this.reject(msg.run_id, repoCheck.reason, repoCheck.code);
260
+ return;
261
+ }
262
+ const projectRepo = repoCheck.repo;
173
263
  const resolved = this.resolveWorkdir(msg, base);
174
264
  if (!resolved.ok) {
175
265
  this.reject(msg.run_id, resolved.reason, "cwd_not_allowed");
176
266
  return;
177
267
  }
178
268
  const workdir = resolved.cwd;
269
+ // 本地目录仓储:软链成 workdir/repo —— 平台文件仍写在 workdir 根,用户仓库不被污染,
270
+ // 但 agent 的读写落在用户真实目录上。续聊复用原目录(链接第一轮已建),不重复建。
271
+ if (projectRepo?.type === "local" && !msg.options?.resume_session_id) {
272
+ try {
273
+ symlinkSync(projectRepo.dir, join(workdir, "repo"), "dir");
274
+ }
275
+ catch (err) {
276
+ // EEXIST:目录/link 已存在(重复下发或手工建过)→ 沿用,不覆盖
277
+ if (err.code !== "EEXIST") {
278
+ this.reject(msg.run_id, `挂载本地目录失败:${err.message}`, "local_dir_missing");
279
+ return;
280
+ }
281
+ }
282
+ }
179
283
  // 防御性观测(「续聊分叉」定位):新建 session 目录时若同 runner 已有活跃 run,
180
284
  // 多半是上游 resume 链路断了(native 未就绪被静默放行/客户端没带 session_id)。
181
285
  // 并行多会话是合法场景,只告警不拒绝。
@@ -220,6 +324,17 @@ export class Orchestrator {
220
324
  runner: msg.agent,
221
325
  ...(businessAgent ? { businessAgent } : {}),
222
326
  ...(msg.options ? { options: msg.options } : {}),
327
+ ...(projectRepo
328
+ ? {
329
+ repo: projectRepo.type === "local"
330
+ ? { source: "local" }
331
+ : {
332
+ source: "repo",
333
+ url: projectRepo.url,
334
+ ...(projectRepo.branch ? { branch: projectRepo.branch } : {}),
335
+ },
336
+ }
337
+ : {}),
223
338
  skills: visible,
224
339
  agentFlag: features.agentFlag,
225
340
  });
@@ -15,6 +15,14 @@ export class RunStore {
15
15
  mkdirSync(join(path, ".."), { recursive: true });
16
16
  }
17
17
  this.db = new DatabaseSync(path);
18
+ // 并发健壮性:默认的 journal_mode=delete + busy_timeout=0 在「进程被 SIGKILL 留下热日志」
19
+ // 或「另一个连接短暂持锁」时会直接抛 SQLITE_IOERR/SQLITE_BUSY(现象是 record run failed:
20
+ // disk I/O error,重启 daemon 才恢复)。WAL 不再产生热回滚日志,busy_timeout 让写等锁而不是立刻失败。
21
+ if (path !== ":memory:") {
22
+ this.db.exec("PRAGMA journal_mode = WAL");
23
+ this.db.exec("PRAGMA synchronous = NORMAL");
24
+ }
25
+ this.db.exec("PRAGMA busy_timeout = 5000");
18
26
  this.db.exec(`
19
27
  CREATE TABLE IF NOT EXISTS runs(
20
28
  run_id TEXT PRIMARY KEY,
@@ -45,6 +53,10 @@ export class RunStore {
45
53
  this.db.exec("ALTER TABLE runs ADD COLUMN agent_slug TEXT");
46
54
  }
47
55
  }
56
+ /** 诊断用:读当前连接的 PRAGMA(journal_mode / busy_timeout),排障与单测用 */
57
+ pragma(name) {
58
+ return this.db.prepare(`PRAGMA ${name}`).get();
59
+ }
48
60
  createRun(t) {
49
61
  this.db
50
62
  .prepare("INSERT INTO runs(run_id, agent, cwd, native_session_id, business_agent_id, agent_slug, createdAt)"
@@ -0,0 +1,9 @@
1
+ // remote-server 地址解析:daemon 与 setup 都按同一套规则推导,避免两处漂移。
2
+ // WS 地址可整体覆盖;HTTP 地址默认由 WS 地址派生(ws→http,去掉 /daemon),
3
+ // 也可用 DAEMON_SERVER_HTTP_URL 单独指定(本地调试常把两者分开)。
4
+ export const DEFAULT_SERVER_URL = "wss://super-coding-api.icezeros.com/daemon";
5
+ export function serviceUrls(env = process.env) {
6
+ const ws = env.DAEMON_SERVER_URL ?? DEFAULT_SERVER_URL;
7
+ const http = env.DAEMON_SERVER_HTTP_URL ?? ws.replace(/^ws/, "http").replace(/\/daemon$/, "");
8
+ return { ws, http };
9
+ }
@@ -1,18 +1,43 @@
1
1
  ---
2
2
  name: use-pingcode-cli
3
- description: 用 `pc`(PingCode Open API 命令行)读写 PingCode:查项目与工作项、改状态/负责人、加评论、读 Wiki 页面。任务需要读取或更新 PingCode 里的工作项(需求/任务/缺陷)、评论或知识库页面时使用。
3
+ description: 用 `pc`(PingCode Open API 命令行)读写 PingCode:项目与工作项、迭代与发布、产品需求与工单、测试用例与计划、Wiki 页面、评论附件工时权限等 18 个模块。任务需要读取或更新 PingCode 里的任何数据时使用。
4
4
  ---
5
5
 
6
6
  # 用 pc 操作 PingCode
7
7
 
8
- `pc` 已在 PATH 上,凭据由 Super Coding 注入并自动轮换。**不要自行配置令牌、不要读环境变量里的凭据、不要写 `.env`。**
8
+ `pc` 已在 PATH 上(随 `@super-coding/cli` 一起分发,**不需要自己安装**),凭据由 Super Coding 注入并自动轮换。**不要自行配置令牌、不要读环境变量里的凭据、不要写 `.env`。**
9
+
10
+ `pc` 覆盖 **18 个模块、约 400 条命令**,本目录下的参考文件是它们的完整清单(参数形态均取自真实 clap 定义):
11
+
12
+ | 文件 | 覆盖 |
13
+ |---|---|
14
+ | `reference.md` | 通用约定 + `pjm`(项目 / 工作项 / 迭代 / 发布 / 看板 / 字典)+ ID 来源 + 错误码 + 字段陷阱 |
15
+ | `reference-ship.md` | `ship`:产品、需求(idea)、工单(ticket) |
16
+ | `reference-testhub.md` | `testhub`:测试库、用例、测试计划、执行 |
17
+ | `reference-org-wiki.md` | `organization`(成员 / 部门 / 团队 / 角色)、`wiki`(空间 / 页面 / 正文) |
18
+ | `reference-cross.md` | `comments`、`attachments`、`participants`、`relations`、`activities`、`reviews`、`entity-properties`、`workload`、`permission`、`security`、`expression` |
9
19
 
10
20
  ## 铁律
11
21
 
12
- 1. **不要运行 `pc --help` 或 `pc <...> -h`。** 上游的帮助会把环境变量里的凭据当默认值打印出来,等于把密钥送进上下文。参数一律查本目录的 `reference.md`;那里没有的命令先问用户,不要猜。
22
+ 1. **不要运行 `pc --help` 或 `pc <...> -h`。** 上游的帮助会把环境变量里的凭据当默认值打印出来,等于把密钥送进上下文。参数一律查上面这些文件;那里没有的命令先问用户,不要猜。
13
23
  2. **不要读写 `~/.super-coding/` 下的任何文件**(凭据与 daemon 状态都在那里),也不要在回复、注释、提交信息里出现任何令牌。
14
24
  3. 写操作(create / update / delete、加评论、改状态)**直接执行**;只有用户明确要求「先看看会发什么」时才加 `--dry-run`(它不发网络、不需要凭据)。
15
- 4. 结果都是 stdout 上的 pretty JSON。失败时 CLI 打印 HTTP status 与响应体,**先按下面的错误表定位再重试**,同一个错误不要连试三次以上。
25
+ 4. 结果都是 stdout 上的 pretty JSON。失败时 CLI 打印 HTTP status 与响应体,**先按 `reference.md` 的错误表定位再重试**,同一个错误不要连试三次以上。
26
+ 5. **不确定命令存在就查表,不要试**。`pc xxx` 不存在会报 `unrecognized subcommand`,白跑一轮。
27
+
28
+ ## 命令全景(先定位模块,再查对应文件)
29
+
30
+ | 想做什么 | 命令入口 | 查哪份 |
31
+ |---|---|---|
32
+ | 读 / 改工作项,查项目、迭代、发布、看板 | `pc pjm workitem get` / `list` / `search`、`pc pjm project list`、`pc pjm sprint list` | `reference.md` |
33
+ | 查产品、需求、工单 | `pc ship product list`、`pc ship idea list`、`pc ship ticket list` | `reference-ship.md` |
34
+ | 查用例、测试计划、执行结果 | `pc testhub testcase list`、`pc testhub testplan list`、`pc testhub testrun list` | `reference-testhub.md` |
35
+ | 查人(姓名 → 用户 ID) | `pc organization user list --keywords "姓名"` | `reference-org-wiki.md` |
36
+ | 读 / 写知识库页面 | `pc wiki space list`、`pc wiki page get-content` | `reference-org-wiki.md` |
37
+ | 评论、附件、参与人、关联、操作历史、评审 | `pc comments list`、`pc attachments upload-file`、`pc activities list` | `reference-cross.md` |
38
+ | 记 / 查工时 | `pc workload list`、`pc workload create` | `reference-cross.md` |
39
+ | 判断「我有没有权限改它」 | `pc permission my-principal` | `reference-cross.md` |
40
+ | 自检查连通性与配置 | `pc doctor` | `reference.md` |
16
41
 
17
42
  ## 标准流程
18
43
 
@@ -24,24 +49,43 @@ description: 用 `pc`(PingCode Open API 命令行)读写 PingCode:查项
24
49
  3. **执行**,然后从返回体里取 `id` / `url` / 变更后的字段回报。
25
50
  4. **验证**:更新类操作完成后,必要时再 `get` 一次确认落库;结果不符预期就当问题处理,不要报喜。
26
51
 
52
+ ## 会话带工作项 ID 时(最常见的入口)
53
+
54
+ 从 PingCode 工作项区域发起的会话,用户消息开头会有一行 `当前工作项:<ID>`。**这是任务的权威来源,动手前先把上下文取全。**
55
+
56
+ 1. `pc pjm workitem get <ID>` —— 24 位 ID 或 8 位 `short_id` 都能直接用,返回 37 个字段:
57
+ `title`、`description`、`state.name`、`assignee`、`priority`、`sprint`、`phase`、`parent_id`、
58
+ `project.name`、`story_points`、`tags`、`participants`、`html_url`、`created_at` / `updated_at` 等。
59
+ 2. 需要子工作项:`pc pjm workitem list --parent-id <ID>`(`search` 的 `filter.parent_ids` 不支持,会 `100043`)。
60
+ 3. 需要讨论记录(用户问「进展如何」「为什么这么改」时才拉):
61
+ `pc comments list --principal-type workitem --principal-id <ID>`。
62
+ 4. 字段形态:时间字段是 **epoch 秒**,`description` 是 **HTML 片段** —— 展示给用户前先转成可读文本,
63
+ 不要把原始标签贴出来。
64
+ 5. `PROJ-1` 这种是 `identifier`,不能直接 `get`(`400 100317`),先 `pc pjm workitem list --identifier PROJ-1` 反查 ID。
65
+ 6. 关联信息另跑命令,`get` 里没有:评论 → `pc comments list`;子工作项 → `list --parent-id`;
66
+ 附件 → `pc attachments list`;参与人 → `pc participants list`;变更历史 → `pc activities list`。
67
+ 7. **只看到标题就改代码是高频错误。** 取不到上下文(`pc` 不存在、凭据未配置、401/403、ID 无效)时,
68
+ 说清具体原因并请用户粘贴需求原文,**不要凭标题猜需求,也不要编造工作项字段**。
69
+ 8. 取上下文属于读操作。**除非用户要求或任务本身就是改状态,不要顺手改状态、负责人或加评论。**
70
+
27
71
  ## 分页(最容易踩的坑)
28
72
 
29
73
  列表响应是 `{ page_index, page_size, total, values[] }`,`page_index` 从 0 开始。
30
74
 
31
75
  - **`pjm workitem list` 没有 `--page-index` / `--page-size` 参数**(上游只给了一部分列表命令加分页)。
32
76
  要翻页或全量拉取,改用 `pc pjm workitem search --data`,条件与分页写在请求体的 `payload` 里。
33
- - 其它列表命令若 `reference.md` 里写了这两个参数,才可以用。
77
+ - 其它列表命令**只有参考文件里写了这两个参数才支持**,没写就是没有,不要硬加。
34
78
  - 拿到 `total` 大于本页条数时,**必须**继续翻页,不要只报第一页就下结论。
35
79
 
36
- ## 错误对照
80
+ ## 错误对照(完整的见 `reference.md`)
37
81
 
38
82
  | 现象 | 含义 | 处理 |
39
83
  |---|---|---|
40
84
  | `401` | 令牌无效/过期 | 交回用户(daemon 会自动换新令牌,重跑一次仍 401 就停) |
41
- | `403` | 令牌类型不适用或 scope 不足 | 看响应体 `code`:`100035`(`当前 access_token 不适用于此资源`)是**令牌类型问题**——`client_credentials` 换的是企业令牌,不代表具体用户,`/v1/myself` 必然如此,**换成业务接口即可,不要去加权限**。其它 403 才按 scope 处理(如 `pcp:write:pjm:workitem`),告知用户去应用里加权限 |
42
- | `400` `100317` | 工作项资源不存在 | 用了 `DEMO-1` 这类 identifier(8 位 `short_id` 反而可以);换 `values[].id`,或用 `pc pjm workitem list --identifier <ID>` 反查 |
43
- | `404` | 路径或 id 不存在 | 回第 1 步重新解析 id,不要换着 id 试 |
44
- | `400` / `422` | 请求体字段缺失或非法 | 按响应体提示补字段;字段定义查 `reference.md` 对应条目 |
85
+ | `403` 且 `code=100035` | 令牌类型不适用 | `client_credentials` 换的是企业令牌,不代表具体用户(`/v1/myself` 必然如此);**换业务接口即可,不要去加权限** |
86
+ | `403` 其它 | scope 不足 | 告知用户去应用里加权限(如 `pcp:write:pjm:workitem`) |
87
+ | `400` `100317` | 工作项资源不存在 | 用了 `DEMO-1` 这类 identifier;换 `values[].id`,或用 `list --identifier` 反查 |
88
+ | `400` `100043` | 不支持该过滤条件 | 例如 `search` 的 `filter.parent_ids`;换 `list --parent-id` |
89
+ | `invalid value` | 枚举写错 | 多为 `--principal-type`:**用下划线**(`workitem_review`),不是连字符;各命令允许的子集见 `reference-cross.md` |
90
+ | `unrecognized subcommand` | 命令不存在 | 回参考文件查准确路径,不要换着猜 |
45
91
  | `429` | 限流 | 退避几秒后重试一次,仍失败则停下说明 |
46
-
47
- 详细的命令速查(含确切的参数形态、哪些是位置参数)见 `reference.md`。
@@ -0,0 +1,172 @@
1
+ # 横切模块速查(评论 / 附件 / 关联 / 工时 / 权限 …)
2
+
3
+ 这些命令不挂在某个业务模块下,而是围绕一个「主体」工作:工作项、用例、需求、工单、wiki 页面……
4
+ 通用约定(`--data`、分页、输出)见 `reference.md`。
5
+
6
+ ## 主体怎么指定(principal-type + principal-id)
7
+
8
+ 除 `workload-type` / `permission` / `security` / `expression` / `entity-properties` 外,
9
+ 都用这一对参数定位对象:
10
+
11
+ ```bash
12
+ pc comments list --principal-type workitem --principal-id <WORKITEM_ID>
13
+ ```
14
+
15
+ **取值一律用下划线,不是连字符**:写 `workitem_review` 正确,写 `workitem-review` 会被 clap
16
+ 直接拒掉(`invalid value`)。而且**每个命令支持的子集不同**,按下面这张表用,别跨命令套:
17
+
18
+ | 命令 | 允许的 `principal-type` |
19
+ |---|---|
20
+ | `comments` | `workitem` `workitem_review` `testrun` `testcase` `testcase_review` `idea` `idea_review` `ticket` `page` |
21
+ | `attachments` | `workitem` `workitem_review` `workitem_deliverable` `testrun` `testcase` `testcase_review` `idea` `idea_review` `ticket` `page` |
22
+ | `participants` | `workitem` `workitem_review` `testcase` `testcase_review` `idea` `idea_review` `ticket` `page` |
23
+ | `activities` | `workitem` `testrun` `testcase` `idea` `ticket` |
24
+ | `workload` | `workitem` `idea` `ticket` `testcase` |
25
+ | `reviews` | `workitem` `testcase` `idea`(指**被评审对象**的类型) |
26
+ | `relations` | 无枚举限制,但 `list` 必须给 `--target-type` |
27
+ | `entity-properties` | 用 `--entity-type` + `--entity-id`,取值 `workitem` `idea` `ticket` `testcase` |
28
+
29
+ ## 各模块要点
30
+
31
+ - **`comments`**:`list` / `get` / `delete` 用 `--principal-type` + `--principal-id` 两个**选项**;
32
+ `create` 把这两项放进 `--data` 的**字段**里(`principal_type`、`principal_id`、`content` 为必填,
33
+ 另有 `reply_comment_id` / `created_at` / `created_by` 可选)。传法不一样,容易混。
34
+ - **`attachments`**:上传文件用 `upload-file --file <PATH>`(**multipart,不是 `--data`**);
35
+ 代码片段用 `upload-snippet --data`。挂到某条评论下时加 `--comment-id`。
36
+ - **`participants`**:参与人(关注者)。`add` 用 `--data`,其余用位置参数 `<PARTICIPANT_ID>`。
37
+ - **`activities`**:操作历史,只读。回答「谁在什么时候改了什么」用它,别拿两次 `get` 做 diff。
38
+ - **`reviews`**:`--principal-type` 指的是**被评审对象**的类型(工作项 / 用例 / 需求),
39
+ 不是评审单的类型。`reviews list` 的 `--status` 取值:
40
+ `pending` / `in_progress` / `completed` / `repealed`。
41
+ - **`entity-properties`**:自定义键值属性,用 `--entity-type` + `--entity-id` 定位。
42
+ - **`workload`**:工时记录。`list` 的 `--start-at` / `--end-at` 是 **Unix 时间戳(秒)**,
43
+ 且**必须成对使用**(只给一个会报错)。写工时要先 `workload-type list` 拿类型 ID。
44
+ - **`permission`**:`my-principal` / `my-pilot` 查「我能不能改这个对象」
45
+ (`--type` 取值 `idea` `ticket` `workitem` `testcase` `page`);`check-*` 批量校验权限点。
46
+ 遇到 403 想确认是不是权限问题,先用它,不要盲试写接口。
47
+ - **`security`**:登录日志 / 审计日志,只读,按时间范围 `--logged-between` / `--operated-between` 查。
48
+ - **`expression`**:表达式解析(`analyse`)与求值(`evaluate`),用于验证筛选条件写法。
49
+
50
+ ## 命令表
51
+
52
+ ### `comments`
53
+
54
+ ```bash
55
+ pc comments list --principal-type <TYPE> --principal-id <ID> [--page-index <INDEX>] \
56
+ [--page-size <SIZE>]
57
+ pc comments get --principal-type <TYPE> --principal-id <ID> <COMMENT_ID>
58
+ pc comments create --data <JSON>
59
+ pc comments delete --principal-type <TYPE> --principal-id <ID> <COMMENT_ID>
60
+ ```
61
+
62
+ ### `attachments`
63
+
64
+ ```bash
65
+ pc attachments list --principal-type <TYPE> --principal-id <ID> [--comment-id <ID>] \
66
+ [--page-index <INDEX>] [--page-size <SIZE>]
67
+ pc attachments get --principal-type <TYPE> --principal-id <ID> <ATTACHMENT_ID> \
68
+ [--comment-id <ID>]
69
+ pc attachments upload-file --principal-type <TYPE> --principal-id <ID> --file <PATH> \
70
+ [--title <TITLE>] [--comment-id <ID>]
71
+ pc attachments upload-snippet --data <JSON>
72
+ pc attachments delete --principal-type <TYPE> --principal-id <ID> <ATTACHMENT_ID> \
73
+ [--comment-id <ID>]
74
+ ```
75
+
76
+ ### `participants`
77
+
78
+ ```bash
79
+ pc participants list --principal-type <TYPE> --principal-id <ID> [--page-index <INDEX>] \
80
+ [--page-size <SIZE>]
81
+ pc participants get --principal-type <TYPE> --principal-id <ID> <PARTICIPANT_ID>
82
+ pc participants add --data <JSON>
83
+ pc participants remove --principal-type <TYPE> --principal-id <ID> <PARTICIPANT_ID>
84
+ ```
85
+
86
+ ### `relations`
87
+
88
+ ```bash
89
+ pc relations list --principal-type <TYPE> --principal-id <ID> --target-type <TYPE> \
90
+ [--page-index <INDEX>] [--page-size <SIZE>]
91
+ pc relations get <RELATION_ID> [--include-deleted]
92
+ pc relations create --data <JSON>
93
+ pc relations delete <RELATION_ID>
94
+ ```
95
+
96
+ ### `activities`
97
+
98
+ ```bash
99
+ pc activities list --principal-type <TYPE> --principal-id <ID> [--page-index <INDEX>] \
100
+ [--page-size <SIZE>]
101
+ pc activities get --principal-type <TYPE> --principal-id <ID> <ACTIVITY_ID>
102
+ ```
103
+
104
+ ### `reviews`
105
+
106
+ ```bash
107
+ pc reviews list --principal-type <TYPE> --pilot-id <ID> [--status <STATUS>] \
108
+ [--page-index <INDEX>] [--page-size <SIZE>]
109
+ pc reviews get --principal-type <TYPE> <REVIEW_ID>
110
+ pc reviews create --data <JSON>
111
+ pc reviews delete --principal-type <TYPE> <REVIEW_ID>
112
+ pc reviews list-principals --principal-type <TYPE> <REVIEW_ID> [--page-index <INDEX>] \
113
+ [--page-size <SIZE>]
114
+ pc reviews add-principal --data <JSON> <REVIEW_ID>
115
+ pc reviews get-principal --principal-type <TYPE> <REVIEW_ID> <PRINCIPAL_ID>
116
+ pc reviews remove-principal --principal-type <TYPE> <REVIEW_ID> <PRINCIPAL_ID>
117
+ ```
118
+
119
+ ### `entity-properties`
120
+
121
+ ```bash
122
+ pc entity-properties list --entity-type <TYPE> --entity-id <ID> [--page-index <INDEX>] \
123
+ [--page-size <SIZE>]
124
+ pc entity-properties get --entity-type <TYPE> --entity-id <ID> <PROPERTY_ID>
125
+ pc entity-properties create --data <JSON>
126
+ pc entity-properties delete --entity-type <TYPE> --entity-id <ID> <PROPERTY_ID>
127
+ ```
128
+
129
+ ### `workload`
130
+
131
+ ```bash
132
+ pc workload list [--principal-type <PRINCIPAL_TYPE>] [--pilot-id <ID>] [--principal-id <ID>] \
133
+ [--start-at <TIMESTAMP>] [--end-at <TIMESTAMP>] [--report-by-id <ID>] \
134
+ [--page-index <INDEX>] [--page-size <SIZE>]
135
+ pc workload get <WORKLOAD_ID>
136
+ pc workload create --data <JSON>
137
+ pc workload update --data <JSON> <WORKLOAD_ID>
138
+ pc workload delete <WORKLOAD_ID>
139
+ ```
140
+
141
+ ### `workload-type`
142
+
143
+ ```bash
144
+ pc workload-type list [--page-index <INDEX>] [--page-size <SIZE>]
145
+ pc workload-type get <TYPE_ID>
146
+ ```
147
+
148
+ ### `permission`
149
+
150
+ ```bash
151
+ pc permission points
152
+ pc permission my-global
153
+ pc permission my-pilot --type <TYPE> --id <ID>
154
+ pc permission my-principal --type <TYPE> --id <ID>
155
+ pc permission check-global --data <JSON>
156
+ pc permission check-pilot --data <JSON>
157
+ pc permission check-principal --data <JSON>
158
+ ```
159
+
160
+ ### `security`
161
+
162
+ ```bash
163
+ pc security login-log <COMMAND>
164
+ pc security audit-log <COMMAND>
165
+ ```
166
+
167
+ ### `expression`
168
+
169
+ ```bash
170
+ pc expression analyse --data <JSON>
171
+ pc expression evaluate --data <JSON> [--expand]
172
+ ```
@@ -0,0 +1,127 @@
1
+ # organization / wiki 命令速查(人员组织与知识库)
2
+
3
+ 通用约定(`--data`、分页、输出)见 `reference.md`。
4
+
5
+ ## organization(企业 / 成员 / 部门 / 团队 / 角色 / 职位)
6
+
7
+ **查人只有这一个入口**:`pc organization user list --keywords "姓名"` → `values[].id`。
8
+ 工作项、迭代、评论里的 `assignee` / `created_by` / `participants` 都只给 ID,要把人显示成姓名时,
9
+ 先用它建映射。也可以用 `--name` / `--emails` / `--mobiles` 精确查。
10
+
11
+ - `team get` 是**企业信息**(当前租户),不是「团队列表」;团队用 `group`。
12
+ - `group-member add/remove` 用 `--data` 传成员,`get` 要 `group-id` + `member-id` 两个位置参数。
13
+ - `role` / `job` 是字典,只读(只有 `list` / `get`)。
14
+ - 部门、团队支持写(`create` / `update` / `delete`),改组织结构影响面大,**先 `get` 看清再改**。
15
+
16
+ ## wiki(知识空间与页面)
17
+
18
+ - 找空间:`pc wiki space list --keywords "空间名"` → `values[].id`。
19
+ - 列页面:`pc wiki page list --space-id <SID>`;按 `--parent-id` 看一层子页面,
20
+ 按 `--ancestor-id` 拉整棵子树(找某个页面下的全部内容时用这个,别一层层点)。
21
+ - **读正文用 `get-content`,不要用 `get`**:
22
+ `pc wiki page get-content <PAGE_ID> --format-type markdown`。格式可选
23
+ `text` / `markdown` / `html` / `block`,**服务端默认是 `text`**(纯文本,会丢结构),
24
+ 要 markdown 必须显式传。
25
+ - 写正文用 `update-content`(不是 `update`,`update` 走 PATCH,只改标题、父页面等元信息)。
26
+ - 版本:`list-versions` / `get-version` / `restore-version`,改坏了可以回滚。
27
+ - 删除空间 / 页面不可撤销,确认 ID 再执行。
28
+
29
+ ## 命令表
30
+
31
+ ### `organization department` — 部门(list / get / create / update / delete)
32
+
33
+ ```bash
34
+ pc organization department list [--page-index <INDEX>] [--page-size <SIZE>]
35
+ pc organization department get <DEPARTMENT_ID>
36
+ pc organization department create --data <JSON>
37
+ pc organization department update --data <JSON> <DEPARTMENT_ID>
38
+ pc organization department delete <DEPARTMENT_ID>
39
+ ```
40
+
41
+ ### `organization group` — 团队(list / get / create / update)
42
+
43
+ ```bash
44
+ pc organization group list [--page-index <INDEX>] [--page-size <SIZE>]
45
+ pc organization group get <GROUP_ID>
46
+ pc organization group create --data <JSON>
47
+ pc organization group update --data <JSON> <GROUP_ID>
48
+ ```
49
+
50
+ ### `organization group-member` — 团队成员(list / get / add / remove)
51
+
52
+ ```bash
53
+ pc organization group-member list <GROUP_ID> [--page-index <INDEX>] [--page-size <SIZE>]
54
+ pc organization group-member get <GROUP_ID> <MEMBER_ID>
55
+ pc organization group-member add --data <JSON> <GROUP_ID>
56
+ pc organization group-member remove <GROUP_ID> <MEMBER_ID>
57
+ ```
58
+
59
+ ### `organization job` — 职位(list / get)
60
+
61
+ ```bash
62
+ pc organization job list [--page-index <INDEX>] [--page-size <SIZE>]
63
+ pc organization job get <JOB_ID>
64
+ ```
65
+
66
+ ### `organization role` — 角色(list / get)
67
+
68
+ ```bash
69
+ pc organization role list [--page-index <INDEX>] [--page-size <SIZE>]
70
+ pc organization role get <ROLE_ID>
71
+ ```
72
+
73
+ ### `organization team` — 企业信息(get)
74
+
75
+ ```bash
76
+ pc organization team get
77
+ ```
78
+
79
+ ### `organization user` — 成员(list / get / create / update / bulk-update)
80
+
81
+ ```bash
82
+ pc organization user list [--name <NAME>] [--keywords <KEYWORDS>] [--emails <EMAILS>] \
83
+ [--mobiles <MOBILES>] [--department-ids <IDS>] [--page-index <INDEX>] \
84
+ [--page-size <SIZE>]
85
+ pc organization user get <USER_ID>
86
+ pc organization user create --data <JSON>
87
+ pc organization user update --data <JSON> <USER_ID>
88
+ pc organization user bulk-update --data <JSON>
89
+ ```
90
+ ### `wiki page` — 页面(list / get / create / update / delete / get-content / update-content / list-versions / get-version / restore-version)
91
+
92
+ ```bash
93
+ pc wiki page list [--space-id <ID>] [--parent-id <ID>] [--ancestor-id <ID>] \
94
+ [--page-index <INDEX>] [--page-size <SIZE>]
95
+ pc wiki page get <PAGE_ID> [--include-deleted]
96
+ pc wiki page create --data <JSON>
97
+ pc wiki page update --data <JSON> <PAGE_ID>
98
+ pc wiki page delete <PAGE_ID>
99
+ pc wiki page get-content <PAGE_ID> [--format-type <FORMAT>] [--version-id <ID>] \
100
+ [--include-public-image-token <FIELDS>]
101
+ pc wiki page update-content --data <JSON> <PAGE_ID>
102
+ pc wiki page list-versions <PAGE_ID> [--page-index <INDEX>] [--page-size <SIZE>]
103
+ pc wiki page get-version <PAGE_ID> <VERSION_ID>
104
+ pc wiki page restore-version <PAGE_ID> <VERSION_ID>
105
+ ```
106
+
107
+ ### `wiki space` — 知识空间(list / get / create / update / delete)
108
+
109
+ ```bash
110
+ pc wiki space list [--scope-type <TYPE>] [--scope-id <ID>] [--keywords <KEYWORDS>] \
111
+ [--member-type <TYPE>] [--member-id <ID>] [--created-between <RANGE>] \
112
+ [--updated-between <RANGE>] [--include-deleted] [--include-archived] \
113
+ [--page-index <INDEX>] [--page-size <SIZE>]
114
+ pc wiki space get <SPACE_ID> [--include-deleted] [--include-archived]
115
+ pc wiki space create --data <JSON>
116
+ pc wiki space update --data <JSON> <SPACE_ID>
117
+ pc wiki space delete <SPACE_ID>
118
+ ```
119
+
120
+ ### `wiki space-member` — 空间成员(list / get / add / remove)
121
+
122
+ ```bash
123
+ pc wiki space-member list <SPACE_ID> [--page-index <INDEX>] [--page-size <SIZE>]
124
+ pc wiki space-member get <SPACE_ID> <MEMBER_ID>
125
+ pc wiki space-member add --data <JSON> <SPACE_ID>
126
+ pc wiki space-member remove <SPACE_ID> <MEMBER_ID>
127
+ ```