pingcode-cli-unofficial 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +943 -0
- package/dist/bin/pingcode.js +17001 -0
- package/dist/bin/pingcode.js.map +1 -0
- package/package.json +51 -0
- package/skills/pingcode/SKILL.md +431 -0
- package/skills/pingcode/modules/api.md +116 -0
- package/skills/pingcode/modules/cicd.md +222 -0
- package/skills/pingcode/modules/crosscutting.md +168 -0
- package/skills/pingcode/modules/pjm.md +385 -0
- package/skills/pingcode/modules/scm.md +432 -0
- package/skills/pingcode/modules/ship.md +197 -0
- package/skills/pingcode/modules/testhub.md +386 -0
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pingcode-cli-unofficial",
|
|
3
|
+
"version": "1.7.1",
|
|
4
|
+
"description": "Command-line client for the PingCode Open API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/anine09/pingcode-cli-unofficial.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"pingcode": "dist/bin/pingcode.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"skills",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"catalog:sync": "node --experimental-strip-types scripts/catalog-sync.ts",
|
|
26
|
+
"catalog:check": "node --experimental-strip-types scripts/catalog-sync.ts --check",
|
|
27
|
+
"skill:install": "node --experimental-strip-types scripts/install-skill.ts",
|
|
28
|
+
"scan:secrets": "node --experimental-strip-types scripts/scan-secrets.ts",
|
|
29
|
+
"check:commits": "node --experimental-strip-types scripts/check-commits.ts",
|
|
30
|
+
"package:release": "node --experimental-strip-types scripts/package-release.ts",
|
|
31
|
+
"hooks:install": "node scripts/install-hooks.mjs",
|
|
32
|
+
"prepare": "node scripts/install-hooks.mjs",
|
|
33
|
+
"install:cli": "node scripts/install.mjs"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^12.1.0",
|
|
37
|
+
"picocolors": "^1.1.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.19.0",
|
|
41
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
42
|
+
"tsup": "^8.5.0",
|
|
43
|
+
"typescript": "^5.9.0",
|
|
44
|
+
"vitest": "^2.1.9"
|
|
45
|
+
},
|
|
46
|
+
"allowScripts": {
|
|
47
|
+
"esbuild@0.27.7": true,
|
|
48
|
+
"esbuild@0.21.5": true,
|
|
49
|
+
"fsevents@2.3.3": true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pingcode
|
|
3
|
+
description: >-
|
|
4
|
+
Use the `pingcode` CLI to work with PingCode (研发管理): 敏捷项目管理 (pjm) projects and work items —
|
|
5
|
+
list and search work items (工作项), read a story/task/bug (需求/任务/缺陷), create one, update fields,
|
|
6
|
+
move it to another state (状态流转) — 产品管理 (ship) products, requirements (需求 / idea) and
|
|
7
|
+
tickets (工单): search, read, create, update and transition them — and 测试管理 (testhub) test
|
|
8
|
+
libraries (测试库), test cases (用例), test plans (测试计划) and runs (执行用例): create a library,
|
|
9
|
+
search cases, read one, create and update a case, create a plan, record a run result, and bulk
|
|
10
|
+
add/update/delete the runs of a plan. Also resolves project-, product- and library-scoped ids and
|
|
11
|
+
organisation members (项目/产品/测试库/迭代/成员). Triggers: pingcode, PingCode 工作项,
|
|
12
|
+
创建任务, 更新状态, 迭代 sprint,
|
|
13
|
+
产品 需求 工单, 测试用例 测试计划 执行用例 测试库, SCR-5 or SLC-1 style identifiers, a work-item or
|
|
14
|
+
idea URL from a PingCode instance. Do NOT use for Wiki pages, customers or external users, the
|
|
15
|
+
org chart beyond a member lookup, Insight/Goals/Flow, or webhooks — none of those are covered by
|
|
16
|
+
this CLI, and webhooks cannot be managed through the PingCode API at all (they live in PingCode
|
|
17
|
+
Flow's UI).
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# PingCode CLI
|
|
21
|
+
|
|
22
|
+
`pingcode` is a command-line client for the PingCode Open API. Its top level mirrors PingCode's own
|
|
23
|
+
GUI modules: **`product`** (产品管理 / ship — products, requirements/需求, tickets/工单),
|
|
24
|
+
**`project`** (项目管理 / pjm — projects, work items, sprints, releases), **`testhub`** (测试管理 —
|
|
25
|
+
test libraries, cases, plans and runs), **`scm`** (源码管理 — code hosting data), **`build`** and
|
|
26
|
+
**`release`** (构建与部署 — CI build and deployment records), **`settings`** (后台设置 — the
|
|
27
|
+
organisation directory) and **`auth`** (the CLI's own local credentials).
|
|
28
|
+
|
|
29
|
+
`scm`, `build` and `release` are the DevOps **write-back** surface: a CI/CD job tells PingCode what
|
|
30
|
+
happened, and PingCode links it to work items. None of the three reads your git server or your
|
|
31
|
+
pipeline.
|
|
32
|
+
|
|
33
|
+
**Two layers, and knowing which one you are on saves you a retry loop.** Every one of the **459**
|
|
34
|
+
documented `/v1` endpoints is callable through **`pingcode api`** — same auth, paging, `--dry-run`,
|
|
35
|
+
redaction and exit codes as anything else. **158** of them also have a named command with flags,
|
|
36
|
+
name→id resolution and a table. So: if a named command exists, use it; if it does not, the answer is
|
|
37
|
+
almost never "the CLI cannot do that" — it is `pingcode api list --search <word>` and then
|
|
38
|
+
`pingcode api <METHOD> <path>`. Only 7 endpoints are genuinely out of reach (they need a user token;
|
|
39
|
+
see [`modules/api.md`](modules/api.md)).
|
|
40
|
+
|
|
41
|
+
Each business module owns its resources *and* its id lookups, so a module's whole surface is one
|
|
42
|
+
`--help` away:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
auth login status logout
|
|
46
|
+
api GET|POST|PATCH|PUT|DELETE <path> · list · describe
|
|
47
|
+
resolve list · <kind> <name>
|
|
48
|
+
product list get · idea … · ticket … · plan … · meta …
|
|
49
|
+
project list get create update progress · work-item … · sprint … · version … · member … · meta …
|
|
50
|
+
testhub libraries … · cases … · plans … · runs … · meta …
|
|
51
|
+
scm platform … · platform-user … · repo … · branch … · commit … · ref … · pr … · review …
|
|
52
|
+
build list get create update delete
|
|
53
|
+
release env … · deploy …
|
|
54
|
+
settings users
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Four cross-object families — `relation`, `comment`, `attachment`, `activity` — are mounted under
|
|
58
|
+
five entities (`product idea`, `product ticket`, `project work-item`, `testhub cases`,
|
|
59
|
+
`testhub runs`), so e.g. `pingcode project work-item comment add SCR-5 --text "…"`. See
|
|
60
|
+
[`modules/crosscutting.md`](modules/crosscutting.md).
|
|
61
|
+
|
|
62
|
+
Everything below assumes `pingcode` is on `PATH` (`npm run build` produces `dist/bin/pingcode.js`).
|
|
63
|
+
|
|
64
|
+
## 1. Authentication gate — do this first
|
|
65
|
+
|
|
66
|
+
If any command fails with exit code 3, or `pingcode auth status` reports no token, stop and get
|
|
67
|
+
credentials before retrying.
|
|
68
|
+
|
|
69
|
+
Credentials come from a PingCode application, not from a user account:
|
|
70
|
+
|
|
71
|
+
1. In the PingCode enterprise console, open **后台管理 → 凭据管理** (Credential Management) and create an
|
|
72
|
+
application.
|
|
73
|
+
2. Set 鉴权方式 (grant type) to **Client Credentials**.
|
|
74
|
+
3. Grant these scopes:
|
|
75
|
+
- `pcp:read:pjm:project` — projects
|
|
76
|
+
- `pcp:write:pjm:project` — `project create` / `project update` / `project member add`. Grant
|
|
77
|
+
this one deliberately: **a project can never be deleted or archived** through this API, so
|
|
78
|
+
`project create` is irreversible
|
|
79
|
+
- `pcp:read:pjm:workitem` — work items, types, states, priorities, tags, relation types
|
|
80
|
+
- `pcp:write:pjm:workitem` — create/update/**delete** work items, plus links, tags and
|
|
81
|
+
`bulk-update`
|
|
82
|
+
- `pcp:read:global:team` — `pingcode settings users`
|
|
83
|
+
- `pcp:read:pjm:sprint` / `pcp:write:pjm:sprint` — `pingcode project meta sprints` (the sprint
|
|
84
|
+
list) and `project sprint get|create|update|bulk-create`. Grant the write half only if you intend to
|
|
85
|
+
plan sprints: **a sprint cannot be deleted**, by the API or by anyone
|
|
86
|
+
- `pcp:read:pjm:release` / `pcp:write:pjm:release` — `pingcode project version …` (发布, a
|
|
87
|
+
project release). Note the mismatch: the scope says *release*, the resource and the command say
|
|
88
|
+
*version*
|
|
89
|
+
|
|
90
|
+
For the ship (产品管理) commands, add:
|
|
91
|
+
- `pcp:read:ship:product` — `product list` / `product get` / `product meta members` /
|
|
92
|
+
`product plan list|get`, and every product name lookup, which every other ship command starts
|
|
93
|
+
with
|
|
94
|
+
- `pcp:read:ship:idea` — `product idea list` / `get` / `history list|get`, and `product meta idea-states|idea-priorities|idea-suites|idea-properties|idea-plans`
|
|
95
|
+
- `pcp:write:ship:idea` — `product idea create` / `update`
|
|
96
|
+
- `pcp:read:ship:ticket` — `product ticket list` / `get`, and `product meta ticket-states|ticket-priorities|ticket-types|ticket-channels|ticket-properties`
|
|
97
|
+
- `pcp:write:ship:ticket` — `product ticket create` / `update` / `transition`
|
|
98
|
+
- `pcp:read:ship:configuration` — **optional**, and only for `product ticket transition`: it is what
|
|
99
|
+
makes the state-plan pre-check possible. Without it the CLI warns on stderr and lets the
|
|
100
|
+
server judge the transition, so tickets stay movable either way.
|
|
101
|
+
|
|
102
|
+
The product-scoped metadata endpoints (`/v1/ship/idea/*`, `/v1/ship/ticket/*`) sit under the
|
|
103
|
+
ordinary read scopes above, **not** under `configuration` — only the ticket state plans and
|
|
104
|
+
flows need `configuration`.
|
|
105
|
+
|
|
106
|
+
For the testhub (测试管理) commands, add:
|
|
107
|
+
- `pcp:read:testhub:library` — `pingcode testhub libraries list` / `get`, `testhub meta suites`,
|
|
108
|
+
and the case-module (模块) tree behind `--suite`. Every other testhub command starts by
|
|
109
|
+
resolving a library, so this one is not optional
|
|
110
|
+
- `pcp:write:testhub:library` — `testhub libraries create`, and nothing else. Grant it only if
|
|
111
|
+
you intend to create libraries: they cannot be deleted afterwards
|
|
112
|
+
- `pcp:read:testhub:testcase` — `testhub cases list` / `get`, and `testhub meta case-types`
|
|
113
|
+
- `pcp:write:testhub:testcase` — `testhub cases create` / `update`
|
|
114
|
+
- `pcp:read:testhub:testplan` — `testhub plans list` / `get`, `testhub meta plan-types`, and
|
|
115
|
+
`testhub runs list`
|
|
116
|
+
- `pcp:write:testhub:testplan` — `testhub plans create`, `testhub runs update` / `bulk`
|
|
117
|
+
- `pcp:read:testhub:configuration` — **not optional, despite the name**: `testhub meta
|
|
118
|
+
case-states`, `testhub meta run-statuses` and `testhub meta important-levels` all sit behind
|
|
119
|
+
it, and they are the only source of a `state_id`, a `status_id` and an `important_level_id`.
|
|
120
|
+
Without it those three return a bare 403 while their sibling `case-types` keeps working, and
|
|
121
|
+
since `PATCH /runs/{id}` requires `status_id`, a token without this scope **cannot write a
|
|
122
|
+
run at all**. The CLI adds that explanation to the 403 for all three of those lookups.
|
|
123
|
+
|
|
124
|
+
`--executor` on a run and `--assignee` on a plan both resolve through the organisation
|
|
125
|
+
directory, so they also need `pcp:read:global:team`.
|
|
126
|
+
|
|
127
|
+
For the scm (源码管理) commands, add:
|
|
128
|
+
- `pcp:read:devops:code` — `pingcode scm platform|platform-user|repo list` / `get`, and every
|
|
129
|
+
platform/repository name lookup
|
|
130
|
+
- `pcp:write:devops:code` — `scm platform|platform-user|repo create` / `update`
|
|
131
|
+
|
|
132
|
+
The whole scm area is **企业令牌 only**, which is what `client_credentials` gives you, so no
|
|
133
|
+
extra grant type is needed — only the two scopes above.
|
|
134
|
+
|
|
135
|
+
For the build and deploy write-back commands, add:
|
|
136
|
+
- `pcp:read:devops:build` / `pcp:write:devops:build` — `pingcode build list|get` and
|
|
137
|
+
`build create|update|delete`. **Separate from `devops:code`**: a token that can write
|
|
138
|
+
commits cannot write builds, and the only symptom is exit 4
|
|
139
|
+
- `pcp:read:devops:deploy` / `pcp:write:devops:deploy` — `pingcode release env …` **and**
|
|
140
|
+
`pingcode release deploy …`; one pair covers both subgroups, there is no separate
|
|
141
|
+
environment scope
|
|
142
|
+
|
|
143
|
+
These two areas are 企业令牌 only as well, so again no extra grant type.
|
|
144
|
+
4. Copy the `client_id` and `client_secret`.
|
|
145
|
+
|
|
146
|
+
Then:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pingcode auth login --client-id <id> --client-secret <secret> --save
|
|
150
|
+
pingcode auth status --check
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- `--save` writes the client id/secret to `~/.pingcode/config.json` (mode `0600`). Without `--save`
|
|
154
|
+
only the token is stored, and a new login is needed once it expires.
|
|
155
|
+
- Credentials can also arrive as `PINGCODE_CLIENT_ID` / `PINGCODE_CLIENT_SECRET`, or interactively
|
|
156
|
+
when a terminal is attached.
|
|
157
|
+
- Self-hosted instances need `--host https://pingcode.example.com` (or `PINGCODE_HOST`); the API is
|
|
158
|
+
served from `<host>/open` there.
|
|
159
|
+
- `pingcode auth logout` removes the token, the stored credentials and the metadata cache.
|
|
160
|
+
|
|
161
|
+
**Security:** a Client Credentials token has organisation-wide system-administrator authority and is
|
|
162
|
+
valid for 30 days. It is not tied to a user. Never print it, never copy it into a file in a
|
|
163
|
+
repository, and treat `~/.pingcode/config.json` as a secret. The CLI redacts the secret and the token
|
|
164
|
+
from every URL, log line, dry-run plan and error message it emits.
|
|
165
|
+
|
|
166
|
+
## 2. Output contract
|
|
167
|
+
|
|
168
|
+
- `--json` makes **stdout carry JSON only**. Logs, warnings, tables and notes go to stderr.
|
|
169
|
+
- In `--json` mode, timestamps stay raw unix **seconds**. Human mode renders local time.
|
|
170
|
+
- Three list shapes, by command family:
|
|
171
|
+
- `project list` / `project work-item list` (one page) → `{"page_index":0,"page_size":30,"total":123,"values":[…]}`,
|
|
172
|
+
and the same for `product`/`testhub` one-page lists
|
|
173
|
+
- any list with `--all` → `{"values":[…],"count":42,"all":true}`
|
|
174
|
+
- every `meta` lookup — `pingcode product meta …`, `pingcode project meta …`,
|
|
175
|
+
`pingcode testhub meta …`, and `pingcode settings users` (which still accepts
|
|
176
|
+
`--page`/`--page-size`) → `{"values":[…],"count":20}`
|
|
177
|
+
- Single-resource commands (`get`, `create`, `update`, `transition`) print the resource object.
|
|
178
|
+
- **Read keys defensively: an absent key means null or empty.** The CLI normalises `null` and `""`
|
|
179
|
+
to "not present", so a field the API returned as `null` (an unset `plan_at`, `score`, `solution`)
|
|
180
|
+
or as an empty string (a blank `description`) is simply missing from the JSON. It does **not**
|
|
181
|
+
distinguish the two, and a missing key is never evidence that the field does not exist. Use
|
|
182
|
+
`x?.y ?? fallback`, not `'y' in x`.
|
|
183
|
+
- `--dry-run` on a mutating command prints `{"dry_run":true,"request":{…}}` to stdout and exits 0
|
|
184
|
+
without sending anything. Read requests still run, so ids are really resolved.
|
|
185
|
+
- Errors in `--json` mode go to **stderr** as `{"error":{"kind":…,"message":…,"code":…,"exit":…}}`.
|
|
186
|
+
- **`pingcode api` is the exception to the shapes above**: stdout is the API's raw JSON, so `--json`
|
|
187
|
+
is a no-op on its five verbs and no key is normalised away. Its own `api list` / `api describe` are
|
|
188
|
+
local catalog views and do honour `--json`.
|
|
189
|
+
- Global flags (`--host`, `--json`, `--dry-run`, `--no-cache`, `--verbose`) may appear before or
|
|
190
|
+
after the subcommand.
|
|
191
|
+
|
|
192
|
+
### Exit codes
|
|
193
|
+
|
|
194
|
+
| Exit | Meaning |
|
|
195
|
+
|---|---|
|
|
196
|
+
| 0 | success (including a printed dry-run plan) |
|
|
197
|
+
| 1 | unexpected internal error |
|
|
198
|
+
| 2 | usage error: bad flags, missing input, ambiguous or unresolvable name, empty update |
|
|
199
|
+
| 3 | authentication: no or invalid credentials |
|
|
200
|
+
| 4 | permission: 403, or a scope the app was not granted |
|
|
201
|
+
| 5 | not found: the work item, state or other resource does not exist |
|
|
202
|
+
| 6 | rate limited: 429 (200 requests/minute per token) |
|
|
203
|
+
| 7 | other API error, carrying the API's `{code, message}` |
|
|
204
|
+
| 8 | transport failure: DNS, TCP, TLS, timeout, unparseable body |
|
|
205
|
+
|
|
206
|
+
The API answers HTTP 400 for both "not found" and "bad credentials", so the CLI maps a few known API
|
|
207
|
+
codes onto exits 5 and 3 rather than trusting the status. Unknown codes stay on exit 7 with the raw
|
|
208
|
+
`code` in the error payload — read it before concluding anything.
|
|
209
|
+
|
|
210
|
+
## 3. Commands
|
|
211
|
+
|
|
212
|
+
### Where each module's commands are documented
|
|
213
|
+
|
|
214
|
+
The per-module surface — every flag, every id lookup, and the traps specific to that module — lives
|
|
215
|
+
one file per module, so a module can be revised without touching this one:
|
|
216
|
+
|
|
217
|
+
| Module | File | Covers |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| 项目管理 pjm | [`modules/pjm.md`](modules/pjm.md) | `project list/get/create/update/progress`, `project meta …`, `project work-item …` (incl. `link` / `tag` / `history`), `project sprint …`, `project version …`, `project member …`, and the pjm-only rules |
|
|
220
|
+
| 产品管理 ship | [`modules/ship.md`](modules/ship.md) | `product list/get`, `product meta …`, `product idea …`, `product ticket …`, `product plan …` (需求排期, read-only), and the ship-only traps |
|
|
221
|
+
| 测试管理 testhub | [`modules/testhub.md`](modules/testhub.md) | `testhub libraries/cases/plans/runs/meta …`, and the testhub-only traps |
|
|
222
|
+
| 源码管理 scm | [`modules/scm.md`](modules/scm.md) | `scm platform …`, `platform-user …`, `repo …`, `branch …`, `commit …`, `ref …`, `pr …`, `review …` — the DevOps write-back surface, 企业令牌 only |
|
|
223
|
+
| 构建与部署 | [`modules/cicd.md`](modules/cicd.md) | `build …` (CI build records) and `release env …` / `release deploy …` (deploy targets and deployments), 企业令牌 only |
|
|
224
|
+
| 跨对象资源 | [`modules/crosscutting.md`](modules/crosscutting.md) | `relation` / `comment` / `attachment` / `activity`, mounted under work items, ideas, tickets, cases and runs |
|
|
225
|
+
| 通用逃生舱 | [`modules/api.md`](modules/api.md) | `api GET/POST/PATCH/PUT/DELETE <path>` for every documented endpoint, plus `api list` / `api describe` |
|
|
226
|
+
|
|
227
|
+
A file marked *reserved* describes commands that **do not exist yet** — there are none at the
|
|
228
|
+
moment, every module document above is real. Do not suggest a command you have not seen in
|
|
229
|
+
`pingcode --help`; run it if you need to know what is actually installed.
|
|
230
|
+
|
|
231
|
+
`auth` and `settings` are documented here rather than in a module file: they are the CLI's own
|
|
232
|
+
credentials and a single directory lookup, not a business module.
|
|
233
|
+
|
|
234
|
+
### Auth
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
pingcode auth login --client-id <id> --client-secret <secret> --save
|
|
238
|
+
pingcode auth status
|
|
239
|
+
pingcode auth status --check # adds one live API call: GET /v1/pjm/projects?page_size=1
|
|
240
|
+
pingcode auth logout
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Settings — 后台设置
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pingcode settings users --keywords wang --json
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The organisation directory (`/v1/directory/users`, scope `pcp:read:global:team`) belongs to no
|
|
250
|
+
business module, which is why it sits here rather than under `product` or `project`. It is the
|
|
251
|
+
candidate set for pjm's `--assignee`; ship's `--assignee` uses `product meta members` instead.
|
|
252
|
+
|
|
253
|
+
### Name → id resolution
|
|
254
|
+
|
|
255
|
+
`pingcode api` takes **ids only** — it understands no business names on purpose. `resolve` is the
|
|
256
|
+
missing half: one lookup, one id on stdout, nothing else.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pingcode resolve list --json # every kind and the parent it needs
|
|
260
|
+
pingcode resolve project "移动端 App" --json # {"kind":"project","id":"5f2a…",…}
|
|
261
|
+
pingcode resolve ship-product SLC --json # identifier works as an alias
|
|
262
|
+
pingcode resolve ship-idea-state 已评审 --parent <product_id> --json
|
|
263
|
+
pingcode resolve testhub-library "研发测试库" --json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- stdout under `--json` is the resolution itself, so it composes:
|
|
267
|
+
`pingcode api GET /v1/ship/idea/states --query product_id=$(pingcode resolve ship-product "智能客服" --json | jq -r .id)`.
|
|
268
|
+
- An **id is passed through** after being verified; a **name must match exactly** (case-insensitively)
|
|
269
|
+
and exactly once. Zero or several matches is exit 2 listing the candidates — it never picks one.
|
|
270
|
+
- `--parent` takes an **id**, not a name: chain a second `resolve` if you only have the name.
|
|
271
|
+
- Answers are cached for 24 h per (host, `client_id`, parent, kind); `--no-cache` bypasses it.
|
|
272
|
+
- Ticket state plans and their flows are absent from `resolve list`, because no name addresses them.
|
|
273
|
+
|
|
274
|
+
The refined commands (`project meta …`, `product meta …`, `testhub meta …`) already accept names
|
|
275
|
+
directly, so `resolve` is mainly for feeding the generic layer.
|
|
276
|
+
|
|
277
|
+
### The two flag shapes, and how to tell which one a leaf uses
|
|
278
|
+
|
|
279
|
+
Every field that could be given either as a business name or as an id is spelled in **one of two
|
|
280
|
+
ways**, and the split is by module, not by field. It is not documented anywhere upstream, so read
|
|
281
|
+
`--help` rather than guessing — but these are the rules the CLI follows.
|
|
282
|
+
|
|
283
|
+
| Shape | Modules | Example |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| **Paired** — `--x <name\|id>` **plus** `--x-id <id>` | `testhub`, `scm`, `release` | `--library` / `--library-id`, `--platform` / `--platform-id`, `--repo` / `--repo-id`, `--env` / `--env-id` |
|
|
286
|
+
| **Single** — one `--x <name\|id>` that accepts both | `pjm` (`project`), `ship` (`product`) | `--project`, `--sprint`, `--release`, `--product` |
|
|
287
|
+
|
|
288
|
+
Both shapes accept a name or an id. What differs is only whether you can **force** the no-lookup
|
|
289
|
+
path:
|
|
290
|
+
|
|
291
|
+
- With a **pair**, `--x` triggers a name lookup (one extra GET, cached 24 h) and `--x-id` is sent
|
|
292
|
+
verbatim with no lookup at all. The two are mutually exclusive — passing both is exit 2. Reach for
|
|
293
|
+
`--x-id` when you already hold ids (it is faster, and it cannot be tripped by two objects sharing
|
|
294
|
+
a name).
|
|
295
|
+
- With a **single** flag, the CLI decides: it passes a value through if it looks like an id and
|
|
296
|
+
looks it up otherwise. There is no way to say "skip the lookup". It never validates an id's shape
|
|
297
|
+
— ids in this API are 24-hex, 32-hex *or* a bare slug like `task` / `story` / `bug`.
|
|
298
|
+
|
|
299
|
+
Three deliberate gaps in that scheme, so you do not read them as bugs:
|
|
300
|
+
|
|
301
|
+
1. **`testhub runs list --case-id` has no `--case` twin**, although `runs create`,
|
|
302
|
+
`runs bulk-create` and `cases bulk-update` all pair on the same resource. The value goes straight
|
|
303
|
+
into the search filter, so a `short_id` arrives unresolved and the API answers 400 `100044`
|
|
304
|
+
(`'property_value'值不合法`). Pass a full case id, or get one from `testhub cases list`.
|
|
305
|
+
2. **`scm branch|commit|pr list --work-item-id` accepts a 24-hex ObjectId only** — *not* an
|
|
306
|
+
identifier like `PLM-001`, which returns 400 `100003`. Note the asymmetry within one group: the
|
|
307
|
+
*write* paths' `--work-item` in the same commands **do** take `PLM-001`. Each leaf's `--help`
|
|
308
|
+
says which.
|
|
309
|
+
3. **`project update --state-id` has no `--state`**, because there is no project-state resolver kind:
|
|
310
|
+
the lookup lives at `/v1/pjm/project/states`, which no `resolve` kind covers. Read the ids with
|
|
311
|
+
`pingcode api GET /v1/pjm/project/states --query project_id=<id>`.
|
|
312
|
+
## 4. Rules that will bite you
|
|
313
|
+
|
|
314
|
+
These hold in **every** module. The rules that apply to one module only — pjm's
|
|
315
|
+
`--state` needs `--type`, ship's product scoping, testhub's library scoping, scm's platform hop —
|
|
316
|
+
live in that module's file, because there is no way to state them once and have them be true.
|
|
317
|
+
|
|
318
|
+
1. **Resolve ids under the right parent first.** Ids are scoped: to a **project** in pjm, a
|
|
319
|
+
**product** in ship, a **test library** in testhub, a **hosting platform** in scm. The same state
|
|
320
|
+
name is a different id under a different parent, so run that module's `meta` lookup (or
|
|
321
|
+
`pingcode resolve <kind> <name> --parent <id>`) before a write. Never validate an id's shape: they
|
|
322
|
+
are 24-hex, 32-hex **or** bare slugs (`task`, `story`, `bug`).
|
|
323
|
+
2. **`update` replaces, it does not merge.** Every field you pass overwrites the stored value, and
|
|
324
|
+
array fields and `properties` objects are replaced wholesale rather than merged. Read the item
|
|
325
|
+
first if you need to preserve anything.
|
|
326
|
+
3. **There is no way to clear a field.** Only the fields you pass are sent; the CLI has no
|
|
327
|
+
`--clear-<field>`.
|
|
328
|
+
4. **An update with no fields is exit 2**, not a no-op.
|
|
329
|
+
5. **A 200 is not proof the field landed.** This API accepts unknown body fields, several read-only
|
|
330
|
+
fields, and whole unsupported properties, with a 200 and no warning. Where the CLI knows about a
|
|
331
|
+
specific case it refuses or warns on stderr; otherwise read the object back.
|
|
332
|
+
6. **Every `delete` needs `--yes`, and every `PUT` needs `pingcode api`.** No named command issues a
|
|
333
|
+
`PUT`: it is full replacement and this API never documents what an omitted field does. See
|
|
334
|
+
[`modules/api.md`](modules/api.md).
|
|
335
|
+
7. **No endpoint supports sorting.** Neither the CLI nor the API can order results. Sort what you
|
|
336
|
+
collected yourself, and remember that offset paging over unordered, changing data can duplicate or
|
|
337
|
+
skip rows.
|
|
338
|
+
8. **`--all` is best effort, not a consistent snapshot.** It walks pages, de-duplicates by id, stops
|
|
339
|
+
at `--limit` (default 500), and gives up if the API stops honouring `page_index`.
|
|
340
|
+
9. **Rate limit is 200 requests per minute per token.** Prefer one `--page-size 100` call over many
|
|
341
|
+
small ones, let the metadata cache work, and do not loop `--all` over large projects casually.
|
|
342
|
+
10. **Timestamps are unix seconds.** Date flags accept `1730000000` or a calendar date — but read the
|
|
343
|
+
flag's own `--help`, because the families differ on purpose: `project --start-at` stores the
|
|
344
|
+
instant verbatim, while `project sprint` / `project version` / `testhub plans` snap `--start` to
|
|
345
|
+
`00:00:00` and `--end` to `23:59:59` of the date given.
|
|
346
|
+
|
|
347
|
+
## 5. Agent workflow
|
|
348
|
+
|
|
349
|
+
0. **Decide which layer you are on.** If the task maps onto a named command, use it. If it does not,
|
|
350
|
+
do not conclude the CLI cannot help: `pingcode api list --search <word> --json` finds the endpoint,
|
|
351
|
+
`pingcode api describe <id>` prints its fields, scope and warnings, and `pingcode resolve <kind>
|
|
352
|
+
<name> --json` turns a name into the id `api` needs. Never invent a command path — if you are
|
|
353
|
+
unsure it exists, run its `--help`.
|
|
354
|
+
1. `pingcode auth status` — if it reports no token, ask the user for credentials (§1) instead of
|
|
355
|
+
guessing.
|
|
356
|
+
2. Resolve the parent scope: `pingcode project list --json` for work items,
|
|
357
|
+
`pingcode product list --json` for ideas and tickets, `pingcode testhub libraries list --json`
|
|
358
|
+
for cases, plans and runs, or `pingcode scm platform list --json` for anything under `scm`.
|
|
359
|
+
3. Resolve ids — each module's file has the full list; the short version:
|
|
360
|
+
- pjm: `pingcode project meta types --project <p> --json`, then
|
|
361
|
+
`project meta states --project <p> --type <t> --json`. Keep the type around: you need it again for
|
|
362
|
+
`--state <name>` on any write.
|
|
363
|
+
- ship: `pingcode product meta idea-states --product <p> --json` (or the `ticket-*` equivalents), plus
|
|
364
|
+
`product meta members --product <p> --json` before setting an assignee, and
|
|
365
|
+
`product meta ticket-types --product <p> --json` before creating a ticket — `--type` is required there.
|
|
366
|
+
- testhub: `pingcode testhub meta case-states --library <l> --json`, `case-types`,
|
|
367
|
+
`run-statuses` (the source of `--status`, needed by every run write), `suites` (the `PATH`
|
|
368
|
+
column is the spelling `--suite` takes) and the org-wide `important-levels`. Find the plan with
|
|
369
|
+
`pingcode testhub plans list --library <l> --json`, or create one — `testhub meta plan-types
|
|
370
|
+
--library <l> --json` first, since `--type` on `plans create` comes from there. If the tenant
|
|
371
|
+
has no usable library at all, `pingcode testhub libraries create` is the bootstrap; read
|
|
372
|
+
[`modules/testhub.md`](modules/testhub.md) §4c rule 15 first, because it cannot be undone.
|
|
373
|
+
- scm / CI: resolve the platform, then the repo (`pingcode scm repo list --platform <p> --json`);
|
|
374
|
+
branch ids are needed as *ids* by `scm pr create`, and work items are linked by
|
|
375
|
+
**identifier** (`PLM-1`), never by id.
|
|
376
|
+
4. Read before writing: `pingcode project work-item get <ref> --json`, `pingcode product idea get <ref> --json`,
|
|
377
|
+
`pingcode product ticket get <ref> --json` or `pingcode testhub cases get <ref> --json` — and for a
|
|
378
|
+
step-level run edit, read the run's steps first, because `--step` must cover all of them.
|
|
379
|
+
5. For any mutation, run it with `--dry-run --json` first, show the plan, get confirmation, then run
|
|
380
|
+
it again without `--dry-run`. Say what cannot be undone *before* creating it: **nothing in ship can
|
|
381
|
+
be deleted**, a project or sprint can never be deleted, a test library cannot be deleted, and
|
|
382
|
+
`scm` has no DELETE except branches.
|
|
383
|
+
6. Always pass `--json` and parse stdout only; read stderr for warnings. Remember that
|
|
384
|
+
`pingcode api` prints the API's raw JSON regardless, and that a missing key means null *or* empty.
|
|
385
|
+
7. On exit 2 read the message — it names the flag, the ambiguous name, the states a ticket can
|
|
386
|
+
actually move to, or the nearest documented paths. On exit 3 re-authenticate. On exit 4 name the
|
|
387
|
+
scope from `api describe`. On exit 6 wait a minute rather than retrying immediately.
|
|
388
|
+
|
|
389
|
+
## 6. What has no named command
|
|
390
|
+
|
|
391
|
+
**Nothing on this list is out of reach** — it has no *refined* command, which means the answer is
|
|
392
|
+
`pingcode api`, not "the CLI cannot". Say that to the user rather than declaring a limitation.
|
|
393
|
+
|
|
394
|
+
| Area | Named commands | How to reach it |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| Wiki spaces and pages (19 endpoints) | none | `pingcode api list --module wiki`, then `api GET/POST/PATCH`. **`DELETE /v1/wiki/pages/{id}` has no recovery path** — never run it speculatively |
|
|
397
|
+
| 组织架构 beyond `settings users`: departments, teams, roles, jobs (22) | none | `pingcode api GET /v1/directory/departments` etc. |
|
|
398
|
+
| 工时 worklogs and worklog types (7) | none | `pingcode api GET /v1/workloads` |
|
|
399
|
+
| 评审 reviews (8) and 关注人 participants/followers (4) | none | `pingcode api GET /v1/reviews`, `api GET /v1/participants` — note `scm review` is a *different* resource |
|
|
400
|
+
| 权限 permission views (7) | none | 6 of the 7 need a user token and are refused; `pingcode api GET /v1/permission/points` works |
|
|
401
|
+
| 安全日志 login / audit logs (2) | none | `pingcode api GET /v1/security/audit_logs` |
|
|
402
|
+
| Nexus / CES app storage (5) | none | `pingcode api list --module nexus` |
|
|
403
|
+
| pjm 工作项配置 schemes (42) and 看板 boards (15) | only the project-scoped read lookups, as `project meta …` | `pingcode api list --module pjm --search 工作项配置` |
|
|
404
|
+
| ship customers, external users, product tags, suite and member writes | none | `pingcode api list --module ship` |
|
|
405
|
+
| all 10 `PUT` endpoints | none, on purpose | `pingcode api PUT …` — read the warning in [`modules/api.md`](modules/api.md) first |
|
|
406
|
+
|
|
407
|
+
Genuinely unavailable, as opposed to merely unrefined:
|
|
408
|
+
|
|
409
|
+
- **Webhooks.** PingCode has no REST API for them at all; they are configured in PingCode Flow's UI.
|
|
410
|
+
- **Insight / Goals / Flow.** No API surface.
|
|
411
|
+
- **The 7 user-token endpoints** (`/v1/myself`, `/v1/permission/my/*`, `/v1/permission/check/*`) —
|
|
412
|
+
they need the OAuth2 authorization-code flow, which this CLI does not implement. They are refused
|
|
413
|
+
before any request, with an explanation.
|
|
414
|
+
- **Uploading a file as an attachment.** Only the JSON code-snippet form of `POST /v1/attachments`
|
|
415
|
+
exists; the `multipart/form-data` form is not expressible in either layer. See
|
|
416
|
+
[`modules/crosscutting.md`](modules/crosscutting.md).
|
|
417
|
+
|
|
418
|
+
A module file also records the omissions *inside* that module and how to reach them — testhub's are a
|
|
419
|
+
section of its own in [`modules/testhub.md`](modules/testhub.md) §5, and every other module states
|
|
420
|
+
them inline next to the family they belong to. If a module document does not mention something, check
|
|
421
|
+
`pingcode api list --module <m>` before telling the user it is impossible.
|
|
422
|
+
|
|
423
|
+
## 7. Installing this skill elsewhere
|
|
424
|
+
|
|
425
|
+
From a checkout of the CLI repository:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
npm run skill:install -- --dry-run # show the destinations
|
|
429
|
+
npm run skill:install # copy to ~/.claude/skills and ./.opencode/skills
|
|
430
|
+
npm run skill:install -- --force # overwrite existing copies
|
|
431
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# 通用逃生舱 (the generic executor) — `api`
|
|
2
|
+
|
|
3
|
+
> Part of the `pingcode` skill. Read [`../SKILL.md`](../SKILL.md) first — it carries the
|
|
4
|
+
> authentication gate, the stdout/stderr contract, the exit-code table and the rules that
|
|
5
|
+
> apply to every module. This file is only the generic executor and its discovery commands.
|
|
6
|
+
|
|
7
|
+
`pingcode api` calls **any** of the 459 documented `/v1` endpoints directly. Use it whenever the
|
|
8
|
+
refined command groups do not cover what you need — that is most of the API, and it is not a
|
|
9
|
+
second-class path: the request goes through the same transport, the same auth, the same
|
|
10
|
+
`--dry-run` gate and the same exit codes as every other command.
|
|
11
|
+
|
|
12
|
+
What it deliberately does **not** do: no name→id resolution, no response shaping, no
|
|
13
|
+
per-endpoint flags. Ids go in as ids, JSON comes out as JSON.
|
|
14
|
+
|
|
15
|
+
## 1. The two rules that surprise people
|
|
16
|
+
|
|
17
|
+
1. **stdout is always the API's raw JSON, so `--json` is a no-op on the five verbs.** There is no
|
|
18
|
+
table, no localised time, no column clipping — nothing to switch off. (`api list` and
|
|
19
|
+
`api describe` are local catalog views and *do* honour `--json`.)
|
|
20
|
+
2. **`DELETE` requires `--yes`.** Without it: exit 2, nothing sent, and the message shows what
|
|
21
|
+
would have been sent. `pingcode api list --method DELETE` enumerates all 49 deletable
|
|
22
|
+
endpoints; two of them have no recovery path at all — a wiki page and a code branch.
|
|
23
|
+
|
|
24
|
+
## 2. Find the endpoint first
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pingcode api list --search commit # substring over path, title and group
|
|
28
|
+
pingcode api list --module scm # 36 endpoints
|
|
29
|
+
pingcode api list --token ENT # 61 endpoints only a machine identity may call
|
|
30
|
+
pingcode api list --method DELETE # the auditable danger surface, 49 rows
|
|
31
|
+
pingcode api describe scm.commits.get # every documented field, scope, token, paging
|
|
32
|
+
pingcode api describe GET /v1/scm/commits/{commit_id_or_sha}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Both are **local**: they read a bundled catalog and never make a request. Prefer them over
|
|
36
|
+
guessing a path, and prefer them over the documentation site (which is a client-rendered SPA
|
|
37
|
+
whose per-endpoint anchors are ~200 characters of percent-encoding).
|
|
38
|
+
|
|
39
|
+
`api describe` also prints the warnings that matter for that one endpoint: that a `PUT` is a
|
|
40
|
+
full replacement, that a `DELETE` is irreversible, that an endpoint needs a user token, or that
|
|
41
|
+
the docs declare no scope for it.
|
|
42
|
+
|
|
43
|
+
## 3. Call it
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pingcode api GET /v1/scm/commits/9f3c1ab
|
|
47
|
+
pingcode api GET /v1/relations --query principal_type=work_item --query principal_id=<id>
|
|
48
|
+
pingcode api GET /v1/directory/users --page 2 --page-size 100
|
|
49
|
+
pingcode api GET /v1/directory/users --all --limit 200
|
|
50
|
+
pingcode api POST /v1/comments --set principal_type=work_item --set principal_id=<id> --set content="CI #123 failed"
|
|
51
|
+
pingcode api PATCH /v1/build/builds/<id> --set status=success
|
|
52
|
+
pingcode api POST /v1/wiki/pages --body-file page.json
|
|
53
|
+
pingcode api POST /v1/pjm/work_items/search --body '{"mode":"query","payload":{"keywords":"login"}}'
|
|
54
|
+
pingcode api DELETE /v1/pjm/projects/<id>/versions/<id> --yes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- the path carries **substituted ids**, never `{placeholders}` and never a `?query` — a query
|
|
58
|
+
string in the path is exit 2, because silently dropping it would be worse;
|
|
59
|
+
- `--query k=v` is repeatable; repeating a key sends it as CSV, which is this API's convention;
|
|
60
|
+
- body: `--set k=v` (repeatable, flat, values sent **verbatim**), `--body-file <path>`,
|
|
61
|
+
`--body '<json>'`, or `--body -` to read stdin. The four are mutually exclusive;
|
|
62
|
+
- `--set` values are never type-guessed: a select-typed field wants the option's `_id`, not its
|
|
63
|
+
display text;
|
|
64
|
+
- `--set` is **scalar-only**. Its value is sent verbatim as a JSON *string*, so an array or object
|
|
65
|
+
field needs `--body` / `--body-file`. A few fields are genuinely type-checked upstream —
|
|
66
|
+
`--set version_ids='["<id>"]'` on a work item is `100006 'version_ids'不是有效的数组`, exit 7 —
|
|
67
|
+
but do not count on that everywhere: this API's default is to accept a wrong-typed field with 200
|
|
68
|
+
and store nothing;
|
|
69
|
+
- paging: `--page` / `--page-size` (max 100) are forwarded **only when you pass them**; `--all`
|
|
70
|
+
walks the pages and prints `{"values":[…],"count":N,"all":true}`. On an endpoint that is not a
|
|
71
|
+
collection, any paging flag is exit 2 rather than silently ignored.
|
|
72
|
+
|
|
73
|
+
### The five `POST …/search` endpoints
|
|
74
|
+
|
|
75
|
+
`pjm/work_items`, `ship/ideas`, `ship/tickets`, `testhub/cases`, `testhub/runs`. They are **reads
|
|
76
|
+
wearing a mutating verb**, so: they execute even under `--dry-run`, their body is
|
|
77
|
+
`{"mode":"query","payload":{…}}` (`mode` has exactly one legal value), and the paging flags are
|
|
78
|
+
written into `payload.page_index` / `payload.page_size` for you. stdout is the page envelope.
|
|
79
|
+
|
|
80
|
+
### `PUT` — prefer `PATCH`
|
|
81
|
+
|
|
82
|
+
All 10 `PUT` endpoints are reachable **only** here, on purpose. `PUT` replaces the whole object
|
|
83
|
+
and this API never documents what an omitted field does; one module was measured clearing a
|
|
84
|
+
field that its `PATCH` sibling preserves. Unless you truly mean "replace everything", use
|
|
85
|
+
`PATCH`.
|
|
86
|
+
|
|
87
|
+
## 4. What fails before anything is sent (all exit 2, zero requests)
|
|
88
|
+
|
|
89
|
+
| Situation | What you get |
|
|
90
|
+
|---|---|
|
|
91
|
+
| path not in the catalog | the three nearest documented paths ("did you mean …") |
|
|
92
|
+
| method the path does not support | the methods that path *does* support |
|
|
93
|
+
| a required query/body field missing | the field named, by kind |
|
|
94
|
+
| `{placeholder}` or `?query` still in the path | told which one |
|
|
95
|
+
| `DELETE` without `--yes` | the request it refused to send |
|
|
96
|
+
| paging flags on a non-collection | told that the endpoint does not page |
|
|
97
|
+
| `/oauth2/authorize` | told it is the browser authorization page, not a REST endpoint |
|
|
98
|
+
| a **user-token-only** endpoint | told this CLI holds an enterprise token |
|
|
99
|
+
|
|
100
|
+
The seven user-token endpoints — `/v1/myself`, `/v1/permission/my/*` (3) and
|
|
101
|
+
`/v1/permission/check/*` (3) — are the only part of the API this CLI cannot reach at all: they
|
|
102
|
+
need the OAuth2 authorization-code flow, which is not implemented. Note that
|
|
103
|
+
`GET /v1/permission/points` **does** work. Everything else, including all 61 enterprise-token-only
|
|
104
|
+
DevOps endpoints, is reachable with the credentials from `pingcode auth login`.
|
|
105
|
+
|
|
106
|
+
## 5. Everything else behaves exactly as it does elsewhere
|
|
107
|
+
|
|
108
|
+
`--dry-run` halts every write (search reads excepted, above) and prints the full request plan
|
|
109
|
+
with the `Authorization` header and any `client_secret` redacted. A `401` re-acquires the token
|
|
110
|
+
and replays once. A `429` honours `x-pc-retry-after` once. Any `2xx` is success. The exit-code
|
|
111
|
+
table in [`../SKILL.md`](../SKILL.md) applies unchanged; on a `403` the scope the docs declare
|
|
112
|
+
for that endpoint is printed to stderr (or an honest "the docs declare no scope" when they
|
|
113
|
+
declare none).
|
|
114
|
+
|
|
115
|
+
The rate limit is shared with everything else, so a `--all` sweep over a large collection spends
|
|
116
|
+
the same budget any other command would.
|