@mu-cabin/coding-cli 0.1.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.
- package/README.md +78 -0
- package/dist/cli.js +1181 -0
- package/generated/index.json +1 -0
- package/package.json +30 -0
- package/skills/coding/SKILL.md +188 -0
- package/skills/coding/references/admin.md +65 -0
- package/skills/coding/references/artifacts.md +138 -0
- package/skills/coding/references/cd.md +103 -0
- package/skills/coding/references/ci.md +99 -0
- package/skills/coding/references/code.md +145 -0
- package/skills/coding/references/export.md +42 -0
- package/skills/coding/references/files.md +66 -0
- package/skills/coding/references/issues.md +175 -0
- package/skills/coding/references/org.md +115 -0
- package/skills/coding/references/permissions.md +164 -0
- package/skills/coding/references/program.md +79 -0
- package/skills/coding/references/project.md +150 -0
- package/skills/coding/references/servicehook.md +112 -0
- package/skills/coding/references/test.md +143 -0
- package/skills/coding/references/wiki.md +118 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# coding-permissions
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **权限 (03)** category — the 25 authorization actions. The most
|
|
4
|
+
*conceptual* category — get the model right and the actions fall out of it.
|
|
5
|
+
|
|
6
|
+
## 1. The authorization model (RAM-style)
|
|
7
|
+
|
|
8
|
+
A **grant** is a triple: a **grant object** (the principal) is given a **policy** (a permission set)
|
|
9
|
+
**on a resource** (the target).
|
|
10
|
+
|
|
11
|
+
- **Policy (权限组)** — a named permission set (`PolicyDocument` of `Statement[]`). Id: **`PolicyId`**
|
|
12
|
+
(int64). Has a `PolicyType` (`IDENTITY` | `RESOURCE`) and a read-only `Scope` (`SYSTEM` | `CUSTOM`).
|
|
13
|
+
- **UserGroup (用户组)** — a named bag of users. Id: **`GroupId`** (int64). Just a container.
|
|
14
|
+
- **GrantObject (授权对象)** — the principal receiving a grant. Identified by **`GrantObjectId`
|
|
15
|
+
(a *string*)** + its type field **`GrantScope`** ∈ `USER` | `USER_GROUP` | `DEPARTMENT`.
|
|
16
|
+
(There is **no** `GrantObjectType` field — the type is `GrantScope`.)
|
|
17
|
+
- **Resource** — the target, a `(ResourceType, ResourceId)` pair. `ResourceType` is a free **string**
|
|
18
|
+
(e.g. `project`, `team`, `depot`), `ResourceId` is a **string**.
|
|
19
|
+
|
|
20
|
+
So a grant ≈ `{GrantObjectId, GrantScope, PolicyId}` (a **`GrantInfo`**) applied to a `{ResourceType,
|
|
21
|
+
ResourceId}` (a **`Resource`**).
|
|
22
|
+
|
|
23
|
+
> ⚠️ **Three different "resource" shapes** — pass the right one:
|
|
24
|
+
> - **`ResourceInfo`** (full: `ResourceType`,`ResourceId` + optional `ResourceScope`/`ServiceName`/`RelativeResource`)
|
|
25
|
+
> — used by the grant verbs, `DescribeGrantObjectsOnResource`, the predicate actions, `DescribeUsersOnResourceAndGrantObject`.
|
|
26
|
+
> - **`ResourceInfoOfPolicyScope`** (minimal: just `ResourceType`,`ResourceId`) — used by the
|
|
27
|
+
> resource-scope-on-policy actions and `DescribeAvailablePoliciesOnResource`.
|
|
28
|
+
> - **`RamGrantResourceInfoRequest`** (also just `ResourceType`,`ResourceId`) — **only**
|
|
29
|
+
> `DescribeGrantUsersOnResource`. Don't pass `ServiceName`/`ResourceScope` where the minimal shape is expected.
|
|
30
|
+
|
|
31
|
+
## 2. Granting & revoking on a resource — three verbs, different semantics
|
|
32
|
+
|
|
33
|
+
All three take **`Resource`** (a `ResourceInfo`) + **`Grants`** (an Array of `GrantInfo`). The difference
|
|
34
|
+
is what they do to *existing* grants:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# additive — appends the listed grants; existing ones untouched; duplicates skipped
|
|
38
|
+
coding call AttachToResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
39
|
+
"Grants":[{"GrantObjectId":"5","GrantScope":"USER","PolicyId":3001}]}' --json
|
|
40
|
+
|
|
41
|
+
# selective revoke — removes ONLY the listed grants; other grants untouched
|
|
42
|
+
coding call DetachFromResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
43
|
+
"Grants":[{"GrantObjectId":"5","GrantScope":"USER","PolicyId":3001}]}' --json
|
|
44
|
+
|
|
45
|
+
# replace-all FOR THAT PRINCIPAL — ⚠️ first revokes ALL of the grant object's existing grants on the
|
|
46
|
+
# resource, then sets exactly what you pass. Semantically destructive (see §6).
|
|
47
|
+
coding call SetGrantToResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
48
|
+
"Grants":[{"GrantObjectId":"5","GrantScope":"USER","PolicyId":3001}]}' --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Inspect who has access to a resource:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# grant OBJECTS on a resource (users / groups / departments) → Data.GrantObjectList[]
|
|
55
|
+
coding call DescribeGrantObjectsOnResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
56
|
+
"PageNumber":1,"PageSize":50}' --select 'Data.GrantObjectList[].GrantScope,Data.GrantObjectList[].GrantObjectId,Data.GrantObjectList[].GrantObjectName' --json
|
|
57
|
+
|
|
58
|
+
# effective USERS on a resource (flattens groups/departments to their users) → Data.UserDataList[]
|
|
59
|
+
# NOTE: this one uses the minimal RamGrantResourceInfoRequest resource shape.
|
|
60
|
+
coding call DescribeGrantUsersOnResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
61
|
+
"PageNumber":1,"PageSize":50}' --select 'Data.UserDataList[].Id,Data.UserDataList[].Name,Data.UserDataList[].Email' --json
|
|
62
|
+
|
|
63
|
+
# users contributed by ONE specific grant object on a resource — note singular `Grant` (object, not array)
|
|
64
|
+
coding call DescribeUsersOnResourceAndGrantObject --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},
|
|
65
|
+
"Grant":{"GrantObjectId":"7","GrantScope":"USER_GROUP"},"PageNumber":1,"PageSize":50}' --select 'Data.UserDataList[].Id,Data.UserDataList[].Name' --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 3. Permission groups (权限组 / Policy)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# list policies applicable to a resource TYPE (Filter.ResourceType required) → Data.PolicyList[]
|
|
72
|
+
coding call DescribePoliciesOnResourceType --data '{"Filter":{"ResourceType":"project"},"PageNumber":1,"PageSize":50}' \
|
|
73
|
+
--select 'Data.PolicyList[].PolicyId,Data.PolicyList[].Name,Data.PolicyList[].Alias,Data.PolicyList[].PolicyType' --json
|
|
74
|
+
|
|
75
|
+
# list policies available on a specific resource → Data.PolicyList[].
|
|
76
|
+
# ⚠️ Filter is REQUIRED here (pass {} for none; or {"PolicyAlias":"…","Visible":"true"}).
|
|
77
|
+
coding call DescribeAvailablePoliciesOnResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},"Filter":{},"PageNumber":1,"PageSize":50}' \
|
|
78
|
+
--select 'Data.PolicyList[].PolicyId,Data.PolicyList[].Alias' --json
|
|
79
|
+
|
|
80
|
+
# detail — ⚠️ result is at top-level `PolicyInfo`, NOT under Data
|
|
81
|
+
coding call DescribePolicy --param PolicyId=3001 --param AccurateActions=true --select 'PolicyInfo.Name,PolicyInfo.Alias,PolicyInfo.PolicyDocument' --json
|
|
82
|
+
|
|
83
|
+
# create: PolicyDocument is Statement[] of {Effect ALLOW|DENY, Action[], Resource[]}. ResourceType is an ARRAY here.
|
|
84
|
+
coding call CreatePolicy --data '{"Name":"depot-reader","Alias":"Depot reader","PolicyType":"RESOURCE","ResourceType":["depot"],
|
|
85
|
+
"PolicyDocument":{"Statement":[{"Effect":"ALLOW","Action":["ram:DescribeDepot"],"Resource":["*"]}]}}' --json # → PolicyId
|
|
86
|
+
|
|
87
|
+
# modify — ⚠️ the id field is `Id`, not `PolicyId`; ResourceType still an array
|
|
88
|
+
coding call ModifyPolicy --data '{"Id":3001,"ResourceType":["depot"],"PolicyDocument":{"Statement":[{"Effect":"ALLOW","Action":["ram:DescribeDepot","ram:PushDepot"],"Resource":["*"]}]}}' --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 4. User groups (用户组)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
coding call DescribeUserGroups --data '{"Filter":{"GroupName":"qa"},"PageNumber":1,"PageSize":50}' \
|
|
95
|
+
--select 'Data.UserGroupList[].Id,Data.UserGroupList[].Name,Data.UserGroupList[].Description' --json
|
|
96
|
+
coding call DescribeUsersByGroupId --param GroupId=70 --param PageNumber=1 --param PageSize=50 \
|
|
97
|
+
--select 'Data.UserDataList[].Id,Data.UserDataList[].Name,Data.UserDataList[].Email' --json
|
|
98
|
+
|
|
99
|
+
coding call CreateUserGroup --data '{"Name":"QA team","Description":"quality engineers"}' --json # → Id
|
|
100
|
+
coding call UpdateUserGroupById --data '{"GroupId":70,"Name":"QA team","Description":"quality + automation"}' --json
|
|
101
|
+
|
|
102
|
+
# add / remove specific (user,group) pairs — UserGroupUserInfos is an array of {UserId,GroupId}
|
|
103
|
+
coding call CreateUserGroupUsers --data '{"UserGroupUserInfos":[{"UserId":254,"GroupId":70}]}' --json
|
|
104
|
+
coding call DeleteUserGroupUsers --data '{"UserGroupUserInfos":[{"UserId":254,"GroupId":70}]}' --yes --json # destructive
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 5. A policy's resource scope (which resources it may be applied to)
|
|
108
|
+
|
|
109
|
+
Separate from grants: this controls the set of resources a policy is *allowed to target*.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
coding call DescribeResourceScopeListOnPolicy --param PolicyId=3001 --param PageNumber=1 --param PageSize=50 \
|
|
113
|
+
--select 'Data.PolicyResourceScopeList[].Resource.ResourceType,Data.PolicyResourceScopeList[].Resource.ResourceId' --json
|
|
114
|
+
|
|
115
|
+
# additive — `ResourceInfos` (plural, minimal shape). DetachResourceScopeOnPolicy is the selective remove.
|
|
116
|
+
coding call AttachResourceScopeToPolicy --data '{"PolicyId":3001,"ResourceInfos":[{"ResourceType":"project","ResourceId":"102"}]}' --json
|
|
117
|
+
coding call DetachResourceScopeOnPolicy --data '{"PolicyId":3001,"ResourceInfos":[{"ResourceType":"project","ResourceId":"102"}]}' --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 6. Permission-inheritance / 判定策略 (predicate policy)
|
|
121
|
+
|
|
122
|
+
Per-resource, controls how effective permissions are *computed* relative to the parent resource.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# read — ⚠️ result at top-level `PredicatePolicyOnResourceInfo`, NOT under Data
|
|
126
|
+
coding call DescribePredicatePolicyOnResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"}}' \
|
|
127
|
+
--select 'PredicatePolicyOnResourceInfo.ResourcePredicatePolicy,PredicatePolicyOnResourceInfo.HasAdministrator' --json
|
|
128
|
+
|
|
129
|
+
# set — ResourcePredicatePolicy ∈ SELF_PARENT (parent + self) | SELF_NONE (self only) | NONE_PARENT (parent only)
|
|
130
|
+
coding call SetPredicatePolicyOnResource --data '{"Resource":{"ResourceType":"project","ResourceId":"102"},"ResourcePredicatePolicy":"SELF_PARENT"}' --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 7. Destructive actions (exit-10 gated)
|
|
134
|
+
|
|
135
|
+
Four `Delete*` actions need `--yes` (preview with `--dry-run`, get explicit approval, re-run the **same
|
|
136
|
+
argv with `--yes`** — see SKILL.md's exit-10 protocol):
|
|
137
|
+
|
|
138
|
+
- **`DeletePoliciesById`** — `Id` is an **Array** of policy ids.
|
|
139
|
+
- **`DeleteUserGroupByIds`** — `GroupIds` is an **Array** of group ids.
|
|
140
|
+
- **`DeleteUserGroupUsers`** — removes specific `{UserId,GroupId}` pairs.
|
|
141
|
+
- **`DeleteAllUsersOnGroup`** — clears **all** users from one `GroupId` (irreversible membership wipe).
|
|
142
|
+
|
|
143
|
+
> ⚠️ **`SetGrantToResource` is only classified `write`, but it is semantically destructive** — it revokes
|
|
144
|
+
> all of the grant object's existing grants on the resource before applying yours. The exit-10 gate will
|
|
145
|
+
> **not** stop it, so confirm the principal + resource with the user and prefer `AttachToResource` (additive)
|
|
146
|
+
> or `DetachFromResource` (selective) unless a full replace is truly intended.
|
|
147
|
+
|
|
148
|
+
## 8. Gotchas
|
|
149
|
+
|
|
150
|
+
- **Id-field names are inconsistent.** Policy: `PolicyId` (create-result, `DescribePolicy`, inside
|
|
151
|
+
`GrantInfo`) but **`Id`** in `ModifyPolicy` and `DeletePoliciesById` (array). UserGroup: `Id`
|
|
152
|
+
(create-result) but `GroupId` in most actions and `GroupIds` (array) in `DeleteUserGroupByIds`.
|
|
153
|
+
- **`GrantObjectId` and `ResourceId` are strings**, not integers, despite numeric content. The grant-object
|
|
154
|
+
*type* is **`GrantScope`** (`USER`|`USER_GROUP`|`DEPARTMENT`), not a `GrantObjectType` field.
|
|
155
|
+
- **`ResourceType` is an Array of string only in `CreatePolicy`/`ModifyPolicy`**; everywhere else it's a
|
|
156
|
+
scalar string — nested inside a `Resource` object (the grant/scope verbs) or inside a `Filter` (the
|
|
157
|
+
`DescribePoliciesOnResourceType` / `DescribeResourceScopeListOnPolicy` reads).
|
|
158
|
+
- **`DescribeUsersOnResourceAndGrantObject` uses `Grant` (a single object)**; the Attach/Set/Detach verbs
|
|
159
|
+
use `Grants` (an array). Easy to mix up.
|
|
160
|
+
- **Two reads are NOT under `Data`**: `DescribePolicy` → `PolicyInfo`; `DescribePredicatePolicyOnResource`
|
|
161
|
+
→ `PredicatePolicyOnResourceInfo`. All the list actions wrap under `Data.<XList>[]` (`PolicyList`,
|
|
162
|
+
`UserGroupList`, `UserDataList`, `GrantObjectList`, `PolicyResourceScopeList`) with paging under `Data`.
|
|
163
|
+
- **Permission administration itself needs a high-scope token** — that's what an exit-8
|
|
164
|
+
`UnauthorizedOperation` means here (a scope gap, not a wrong token).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# coding-program
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **项目集 / Program (05)** category — 6 actions. A *program* is a portfolio
|
|
4
|
+
that groups multiple projects and carries its own membership/permissions.
|
|
5
|
+
|
|
6
|
+
## 1. Identifiers
|
|
7
|
+
|
|
8
|
+
- **`ProgramId`** (int64) is the program handle — what every program-scoped action takes. Get it from
|
|
9
|
+
`DescribePrograms` → `Data.Programs[].Id`.
|
|
10
|
+
- A program also has **`Name`** (the slug / 标识) and **`DisplayName`** (展示名) — distinct fields, same
|
|
11
|
+
as projects. Member ops use the **permissions model**: a principal is (`PrincipalType`,
|
|
12
|
+
`PrincipalId`) and policies are named (`ProgramAdmin`, `ProgramMember`, …) or policy IDs.
|
|
13
|
+
- **Everything wraps under `Data`** in this category (no top-level objects). Note: `DescribePrograms` →
|
|
14
|
+
`Data.Programs[]` (paged), but `DescribeProgramProjects` → `Data[]` is **itself the array**.
|
|
15
|
+
|
|
16
|
+
> ⚠️ **There is no Delete/Modify-Program action** in the API surface — you can `CreateProgram` and add
|
|
17
|
+
> projects/members, but renaming or deleting a program is **not** exposed here. Don't promise it.
|
|
18
|
+
|
|
19
|
+
## 2. Find & read
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# list/search programs (paging required; Keyword filters by name)
|
|
23
|
+
coding call DescribePrograms --data '{"PageNumber":1,"PageSize":20,"Keyword":""}' \
|
|
24
|
+
--select 'Data.TotalCount,Data.Programs[].Id,Data.Programs[].Name,Data.Programs[].DisplayName' --json
|
|
25
|
+
|
|
26
|
+
# the projects inside a program (Data is the array directly)
|
|
27
|
+
coding call DescribeProgramProjects --param ProgramId=5 \
|
|
28
|
+
--select 'Data[].Id,Data[].Name,Data[].DisplayName' --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. Create & populate
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# create a program — Description, DisplayName, EndDate, Name, StartDate are ALL required.
|
|
35
|
+
# Dates are "yyyy-MM-dd" strings on input (responses echo them back as epoch ints).
|
|
36
|
+
# WorkflowProgramId is optional (an existing workflow program's Id).
|
|
37
|
+
coding call CreateProgram --data '{"Name":"platform","DisplayName":"Platform Portfolio",
|
|
38
|
+
"Description":"core platform work","StartDate":"2025-01-01","EndDate":"2025-12-31"}' \
|
|
39
|
+
--select 'Data.Id,Data.Name,Data.DisplayName' --json
|
|
40
|
+
|
|
41
|
+
# add projects into the program — ProjectId is an ARRAY of numeric project ids
|
|
42
|
+
coding call CreateProgramProjects --data '{"ProgramId":5,"ProjectId":[173,341]}' --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 4. Member permission policies
|
|
46
|
+
|
|
47
|
+
A member's access to a program is a set of **policies** attached to a principal. `Create…` is additive;
|
|
48
|
+
`Delete…` removes the listed policies (see §5). Both take the same shape.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# grant policies to a user within the program (PrincipalType USER; PrincipalId is a STRING)
|
|
52
|
+
coding call CreateProgramMemberPolicy --data '{"ProgramId":5,"PrincipalType":"USER","PrincipalId":"4",
|
|
53
|
+
"Policies":["ProgramAdmin","ProgramMember"]}' --select 'Data[].PolicyId,Data[].PolicyName' --json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `PrincipalType` is the principal kind — `USER` is the documented example value (the broader RAM
|
|
57
|
+
model also defines `USER_GROUP`/`DEPARTMENT`, but only `USER` is evidenced for this action);
|
|
58
|
+
`PrincipalId` is its **string** id.
|
|
59
|
+
- `Policies` are default policy **names** (`ProgramAdmin`, `ProgramMember`, …) or policy IDs.
|
|
60
|
+
|
|
61
|
+
## 5. Destructive action (exit-10 gated)
|
|
62
|
+
|
|
63
|
+
- **`DeleteProgramMemberPolicy`** (`ProgramId`, `PrincipalType`, `PrincipalId`, `Policies[]`) — removes
|
|
64
|
+
the listed policies from the principal (a **selective** detach, not a full member removal). Gated:
|
|
65
|
+
preview with `--dry-run`, get explicit approval, re-run the **same argv with `--yes`** appended (see
|
|
66
|
+
SKILL.md's exit-10 protocol). Show the user exactly which policies will be stripped.
|
|
67
|
+
|
|
68
|
+
## 6. Gotchas
|
|
69
|
+
|
|
70
|
+
- **No Delete/Modify for the program itself** — only members (`DeleteProgramMemberPolicy`) are
|
|
71
|
+
removable via this API. Renaming/deleting a program isn't exposed.
|
|
72
|
+
- **`CreateProgram` dates are `yyyy-MM-dd` strings** on input but come back as epoch ints in reads —
|
|
73
|
+
don't assume the same type round-trips.
|
|
74
|
+
- **`ProjectId` on `CreateProgramProjects` is an array** of numeric ids; **`PrincipalId` on member ops
|
|
75
|
+
is a string**.
|
|
76
|
+
- **`DescribePrograms` → `Data.Programs[]`** (paged) vs **`DescribeProgramProjects` → `Data[]`** (the
|
|
77
|
+
array is `Data` itself). Match `--select` accordingly.
|
|
78
|
+
- **`Create…MemberPolicy` is additive, `Delete…` is selective** — neither replaces a principal's full
|
|
79
|
+
policy set; list the exact policies you intend to add/remove.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# coding-project
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **项目 (04)** category — the 27 project actions.
|
|
4
|
+
|
|
5
|
+
This is the **foundational category**: most other playbooks in `references/` (issues, code, CI, artifacts) need
|
|
6
|
+
a `ProjectId` or `ProjectName` first — this file is how you resolve one.
|
|
7
|
+
|
|
8
|
+
## 1. The three project identifiers
|
|
9
|
+
|
|
10
|
+
- **`ProjectId`** (numeric) — the primary key for most write/read actions (modify, archive, members,
|
|
11
|
+
roles, credentials, labels-create, delete).
|
|
12
|
+
- **`ProjectName`** (string) — the project's **`Name`** (the slug used in URLs, e.g. `demo-project`),
|
|
13
|
+
**not** its `DisplayName`. Used by name-based reads (`DescribeProjectByName`, announcements).
|
|
14
|
+
- **`DisplayName`** (string) — the human-friendly label shown in the UI. Distinct from `Name`; never
|
|
15
|
+
pass it where `ProjectName` is expected.
|
|
16
|
+
|
|
17
|
+
So `CreateCodingProject` takes **both** `Name` (the slug) and `DisplayName` (the label).
|
|
18
|
+
|
|
19
|
+
## 2. Find / resolve a project
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# by name → get the ProjectId (and everything else)
|
|
23
|
+
coding call DescribeProjectByName --param ProjectName=demo-project \
|
|
24
|
+
--select 'Project.Id,Project.Name,Project.DisplayName,Project.Archived' --json
|
|
25
|
+
|
|
26
|
+
# by id
|
|
27
|
+
coding call DescribeOneProject --param ProjectId=341 --json
|
|
28
|
+
|
|
29
|
+
# projects the current user has joined — UserId comes from whoami (SKILL.md / DescribeCodingCurrentUser)
|
|
30
|
+
coding call DescribeUserProjects --param UserId=254 \
|
|
31
|
+
--select 'ProjectList[].Id,ProjectList[].Name,ProjectList[].DisplayName' --json
|
|
32
|
+
|
|
33
|
+
# team-wide list (paged; optional ProjectName filter; QueryArchived to include archived)
|
|
34
|
+
coding call DescribeCodingProjects --param PageNumber=1 --param PageSize=50 --param QueryArchived=false \
|
|
35
|
+
--select 'Data.ProjectList[].Id,Data.ProjectList[].Name,Data.ProjectList[].DisplayName' --json
|
|
36
|
+
|
|
37
|
+
# projects exposing a given top-level menu/feature. MenuName is the menu LABEL as shown in the UI
|
|
38
|
+
# (e.g. "项目协同"); labels vary by deployment, so verify the actual menu name before relying on it.
|
|
39
|
+
coding call DescribeProjectsByFeature --param MenuName=项目协同 --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 3. Create, modify, archive
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Name = slug, DisplayName = label, ProjectTemplate ∈ CODE_HOST | PROJECT_MANAGE | DEV_OPS | DEMO_BEGIN,
|
|
46
|
+
# Shared = 0 (private) | 1 (shared/open-source)
|
|
47
|
+
coding call CreateCodingProject --data '{"Name":"new-svc","DisplayName":"New Service",
|
|
48
|
+
"Description":"…","ProjectTemplate":"DEV_OPS","Shared":0}' --json
|
|
49
|
+
# richer template-based create. NOTE: Label here is an integration enum (TKE|TCB|SLS|QUICKSTART|APIGW),
|
|
50
|
+
# NOT a free-form project label; Invisible hides the project from non-members.
|
|
51
|
+
coding call CreateProjectWithTemplate --data '{"Name":"new-svc","DisplayName":"New Service",
|
|
52
|
+
"ProjectTemplate":"DEV_OPS","Shared":0,"Invisible":false,"Label":"QUICKSTART"}' --json
|
|
53
|
+
|
|
54
|
+
# edit metadata (Name + DisplayName required even if unchanged; dates are "YYYY-MM-DD")
|
|
55
|
+
coding call ModifyProject --param ProjectId=341 --param Name=demo-project --param DisplayName="Demo" --json
|
|
56
|
+
|
|
57
|
+
# archive / unarchive (reversible; not a delete)
|
|
58
|
+
coding call ModifyProjectArchive --param ProjectId=341 --json
|
|
59
|
+
coding call ModifyProjectUnarchive --param ProjectId=341 --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 4. Members & roles
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# the project's role/usergroup definitions
|
|
66
|
+
coding call DescribeProjectRoles --param ProjectId=341 --select 'Roles[].RoleId,Roles[].RoleTypeName' --json
|
|
67
|
+
|
|
68
|
+
# member list — RoleId here ONLY filters the list to one role (it is NOT the add-member identifier; see below)
|
|
69
|
+
coding call DescribeProjectMembers --param ProjectId=341 --param PageNumber=1 --param PageSize=50 --param RoleId=5 \
|
|
70
|
+
--select 'Data.ProjectMembers[].Id,Data.ProjectMembers[].Name,Data.ProjectMembers[].Email' --json
|
|
71
|
+
|
|
72
|
+
# principals = members + user-groups + departments (Keyword to search)
|
|
73
|
+
coding call DescribeProjectMemberPrincipals --param ProjectId=341 --param PageNumber=1 --param PageSize=50 --json
|
|
74
|
+
|
|
75
|
+
# add principals: each is {PrincipalId(string), PrincipalType, PolicyIds:[int]} — pass via --data.
|
|
76
|
+
# PrincipalId is a STRING ("254", not 254); PrincipalType "USER" (groups/depts use other values).
|
|
77
|
+
# PolicyIds (权限组ID, the permission/role-group ids) is what grants the role — NOT the RoleId above.
|
|
78
|
+
coding call CreateProjectMemberPrincipal --data '{"ProjectId":341,"Principals":[
|
|
79
|
+
{"PrincipalId":"254","PrincipalType":"USER","PolicyIds":[12]}]}' --json
|
|
80
|
+
# DeleteProjectMemberPrincipal removes one — it is exit-10 gated (see §8)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 5. Labels
|
|
84
|
+
|
|
85
|
+
Two differently-purposed actions — don't confuse them:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# labels OF a project
|
|
89
|
+
coding call DescribeAllProjectLabels --param ProjectId=341 --select 'Labels[].Id,Labels[].Name,Labels[].Color' --json
|
|
90
|
+
# projects that HAVE a given label (cross-project lookup)
|
|
91
|
+
coding call DescribeProjectLabels --param Label=backend --json
|
|
92
|
+
|
|
93
|
+
coding call CreateProjectLabel --param ProjectId=341 --param Name=urgent --param Color="#FF0000" --json
|
|
94
|
+
# edit needs LabelId (from DescribeAllProjectLabels) plus ProjectId + Name + Color (all required)
|
|
95
|
+
coding call ModifyProjectLabel --param ProjectId=341 --param LabelId=88 --param Name=urgent --param Color="#FF8800" --json
|
|
96
|
+
# DeleteProjectLabel is exit-10 gated (see §8)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 6. Announcements
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
coding call DescribeProjectAnnouncements --param ProjectName=demo-project --param PageNumber=1 --param PageSize=20 \
|
|
103
|
+
--select 'List[].Id,List[].ContentOrigin' --json
|
|
104
|
+
coding call CreateProjectAnnouncement --param ProjectName=demo-project --param Content="Maintenance tonight" --json
|
|
105
|
+
# edit needs the announcement Id (from the list above) plus ProjectName + Content (all required)
|
|
106
|
+
coding call ModifyProjectAnnouncement --param ProjectName=demo-project --param Id=12 --param Content="Rescheduled" --json
|
|
107
|
+
# DeleteProjectAnnouncement is exit-10 gated (see §8)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 7. Credentials
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
coding call DescribeProjectCredentials --param ProjectId=341 \
|
|
114
|
+
--select 'Data.CredentialList[].CredentialId,Data.CredentialList[].Name' --json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The list returns only `CredentialId` + `Name` per credential (no secret values, no type field). Treat
|
|
118
|
+
credentials as **secrets**: refer to them by name for the user; never attempt to surface secret material.
|
|
119
|
+
|
|
120
|
+
## 8. Destructive actions (exit-10 gated)
|
|
121
|
+
|
|
122
|
+
`DeleteOneProject` (deletes the whole project), `DeleteProjectMemberPrincipal`, `DeleteProjectLabel`,
|
|
123
|
+
`DeleteProjectAnnouncement`. Follow SKILL.md's protocol: preview with `--dry-run`, get explicit
|
|
124
|
+
user approval, re-run the **same argv with `--yes`**.
|
|
125
|
+
|
|
126
|
+
> **`DeleteOneProject` is irreversible and wipes everything in the project** (repos, issues, pipelines,
|
|
127
|
+
> artifacts). To merely hide a project, use `ModifyProjectArchive` (§3) instead — it's reversible.
|
|
128
|
+
> Resolve and double-check the `ProjectId` against the project's name before approving a delete.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
coding call DeleteOneProject --param ProjectId=341 --dry-run --json # show user
|
|
132
|
+
coding call DeleteOneProject --param ProjectId=341 --yes --json # only after explicit approval
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 9. Gotchas
|
|
136
|
+
|
|
137
|
+
- **`ProjectName` is the slug (`Name`), not `DisplayName`.** Resolve once via `DescribeProjectByName` and
|
|
138
|
+
reuse the numeric `Id` for the many `ProjectId`-keyed actions.
|
|
139
|
+
- **`DescribeUserProjects` needs a `UserId`** — get it from `coding auth status` / `DescribeCodingCurrentUser`
|
|
140
|
+
(the current user's `Id`), not from a project.
|
|
141
|
+
- **Two label actions, opposite directions**: `DescribeAllProjectLabels` (labels *of* a project, by
|
|
142
|
+
`ProjectId`) vs `DescribeProjectLabels` (projects *with* a label, by `Label`).
|
|
143
|
+
- **List wrappers vary**: `DescribeUserProjects`→ top-level `ProjectList[]`; `DescribeCodingProjects`→
|
|
144
|
+
`Data.ProjectList[]`; `DescribeProjectMembers`→ `Data.ProjectMembers[]`; `DescribeProjectAnnouncements`→
|
|
145
|
+
top-level `List[]`; `DescribeProjectRoles`→ `Roles[]`; `DescribeAllProjectLabels`→ `Labels[]`; but
|
|
146
|
+
`DescribeProjectLabels`→ `ProjectList[]` (it returns projects, not labels); `DescribeProjectCredentials`→
|
|
147
|
+
`Data.CredentialList[]`. Match `--select` accordingly.
|
|
148
|
+
- **Archive ≠ delete.** `ModifyProjectArchive` is reversible; `DeleteOneProject` is not.
|
|
149
|
+
- **Member/delete ops need project-admin scope** — that's what an exit-8 `UnauthorizedOperation` means
|
|
150
|
+
here (a scope gap, not a wrong token).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# coding-servicehook
|
|
2
|
+
|
|
3
|
+
A curated playbook for the **Service Hook (06)** category — 10 actions that bind CODING events
|
|
4
|
+
(issue/MR/CI/artifact/wiki/…) to an outbound notifier (chat robot or raw webhook).
|
|
5
|
+
|
|
6
|
+
## 1. Identifiers & shape
|
|
7
|
+
|
|
8
|
+
- **`ProjectId`** (int64) scopes almost every action. `TargetType` says what that id *means*:
|
|
9
|
+
`PROJECT` (default), `SPACE_NODE` (研发空间), or `PROGRAM` (项目集). For a plain project hook,
|
|
10
|
+
`ProjectId` = the numeric project id and `TargetType` = `PROJECT`. **`DescribeServiceHooks` (list)
|
|
11
|
+
and `EnabledServiceHook` require `TargetType`**; the other 06 actions treat it as optional (default
|
|
12
|
+
`PROJECT`).
|
|
13
|
+
- **`Id`** is the hook id (a **string**). ⚠️ **It's an *array* on the batch verbs** (`DeleteServiceHook`,
|
|
14
|
+
`EnabledServiceHook`, `PingServiceHook` all take `Id: ["…"]`) but a **single string** on the
|
|
15
|
+
read/modify verbs (`DescribeServiceHook`, `DescribeServiceHookLogs`, `ModifyServiceHook`). Match the
|
|
16
|
+
shape per action or you'll get a validation/remote error.
|
|
17
|
+
- **`ServiceHookLogId`** (the per-delivery log id, a string) is what `ResendServiceHookLog` keys off —
|
|
18
|
+
**not** `Id`/`ProjectId`. Get it from `DescribeServiceHookLogs` → `Data.Log[].Id`.
|
|
19
|
+
|
|
20
|
+
> ⚠️ **Response wrapping is uneven** (match `--select` to each):
|
|
21
|
+
> - create/modify/single-read → top-level **`ServiceHook`** (object), **not** under `Data`.
|
|
22
|
+
> - list → **`Data.ServiceHook[]`**; logs → **`Data.Log[]`**; event catalog → top-level **`Event[]`**.
|
|
23
|
+
> - enable/delete/ping → top-level **`Succeed`** (bool); resend → **`Data.Success`** (bool).
|
|
24
|
+
|
|
25
|
+
## 2. Orient — events & existing hooks
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# the event catalog you can subscribe to (Name = the value you put in Event[]; Label = its display
|
|
29
|
+
# name; GroupName/GroupLabel group the events)
|
|
30
|
+
coding call DescribeEvents --select 'Event[].GroupLabel,Event[].GroupName,Event[].Label,Event[].Name' --json
|
|
31
|
+
|
|
32
|
+
# list hooks in a project (TargetType + paging are required)
|
|
33
|
+
coding call DescribeServiceHooks --data '{"ProjectId":450,"TargetType":"PROJECT","PageNumber":1,"PageSize":20}' \
|
|
34
|
+
--select 'Data.TotalCount,Data.ServiceHook[].Id,Data.ServiceHook[].Name,Data.ServiceHook[].Service,Data.ServiceHook[].Enabled,Data.ServiceHook[].Event' --json
|
|
35
|
+
|
|
36
|
+
# read one hook (single Id, not an array)
|
|
37
|
+
coding call DescribeServiceHook --param ProjectId=450 --param Id=<hook-id> \
|
|
38
|
+
--select 'ServiceHook.Id,ServiceHook.Name,ServiceHook.Event,ServiceHook.Action,ServiceHook.ActionProperties,ServiceHook.Enabled' --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Event names are enum strings like `ISSUE_CREATED`, `GIT_MR_MERGED`, `CI_JOB_FINISHED`,
|
|
42
|
+
`ARTIFACTS_VERSION_RELEASED`, `WIKI_UPDATED` (the full set is in `DescribeEvents` and `coding schema
|
|
43
|
+
CreateServiceHook`). Subscribe to a list of them via `Event: [...]`.
|
|
44
|
+
|
|
45
|
+
## 3. Create / modify a hook
|
|
46
|
+
|
|
47
|
+
The two tricky params are JSON **strings** (escaped JSON inside the field), not nested objects:
|
|
48
|
+
|
|
49
|
+
- **`ActionProperty`** — the notifier config: the robot/webhook URL (`service_url`) and message type.
|
|
50
|
+
- **`FilterProperty`** — an optional event filter (e.g. only issues of a given type/priority).
|
|
51
|
+
- **`Service`** ∈ `WebHook | WeCom | DingDing | FeiShu | Jenkins`; pick the matching **`ServiceAction`**:
|
|
52
|
+
`webhook_http_post`/`webhook_http_get`, `wecom_group_chat_robot`, `dingding_group_chat_robot`,
|
|
53
|
+
`feishu_group_chat_robot`, `jenkins_generic_build_job`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# create — notify a WeCom group robot on issue creation
|
|
57
|
+
coding call CreateServiceHook --data '{
|
|
58
|
+
"ProjectId":450,"TargetType":"PROJECT","Name":"issue→WeCom","Enabled":true,
|
|
59
|
+
"Service":"WeCom","ServiceAction":"wecom_group_chat_robot",
|
|
60
|
+
"Event":["ISSUE_CREATED"],
|
|
61
|
+
"ActionProperty":"{\"service_url\":\"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=…\",\"msg_type\":\"markdown\"}",
|
|
62
|
+
"FilterProperty":"{\"type\":\"MISSION\",\"priority\":\"1\"}"
|
|
63
|
+
}' --select 'ServiceHook.Id,ServiceHook.Name,ServiceHook.Enabled' --json
|
|
64
|
+
|
|
65
|
+
# modify — ⚠️ ModifyServiceHook requires Id + ALL of these resupplied: Event, Service, ServiceAction,
|
|
66
|
+
# ActionProperty, FilterProperty, Enabled (FilterProperty is REQUIRED here, unlike on create).
|
|
67
|
+
# Read the hook first so you don't drop fields you meant to keep.
|
|
68
|
+
coding call ModifyServiceHook --data '{"ProjectId":450,"Id":"<hook-id>","Name":"issue→WeCom (v2)",
|
|
69
|
+
"Enabled":true,"Service":"WeCom","ServiceAction":"wecom_group_chat_robot","Event":["ISSUE_CREATED","ISSUE_STATUS_UPDATED"],
|
|
70
|
+
"ActionProperty":"{\"service_url\":\"…\",\"msg_type\":\"markdown\"}","FilterProperty":"{}"}' --json
|
|
71
|
+
|
|
72
|
+
# just flip the on/off switch (batch; Id is an array)
|
|
73
|
+
coding call EnabledServiceHook --data '{"ProjectId":450,"TargetType":"PROJECT","Id":["<hook-id>"],"Enabled":false}' --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 4. Test & debug deliveries
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# fire a test delivery now (batch; Id is an array) → top-level Succeed
|
|
80
|
+
coding call PingServiceHook --data '{"ProjectId":450,"Id":["<hook-id>"]}' --select 'Succeed' --json
|
|
81
|
+
|
|
82
|
+
# paged delivery log for one hook (single Id). Status int + ResponseStatus tell you why it failed.
|
|
83
|
+
coding call DescribeServiceHookLogs --data '{"ProjectId":450,"Id":"<hook-id>","PageNumber":1,"PageSize":20}' \
|
|
84
|
+
--select 'Data.TotalCount,Data.Log[].Id,Data.Log[].Event,Data.Log[].Status,Data.Log[].ResponseStatus,Data.Log[].SendAt' --json
|
|
85
|
+
|
|
86
|
+
# resend ONE failed delivery — keyed by the log id (Data.Log[].Id above), not the hook id
|
|
87
|
+
coding call ResendServiceHookLog --param ServiceHookLogId=<log-id> --select 'Data.Success' --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 5. Destructive action (exit-10 gated)
|
|
91
|
+
|
|
92
|
+
- **`DeleteServiceHook`** (`ProjectId`, `Id: ["…"]` array, optional `TargetType`) — permanently removes
|
|
93
|
+
the hook(s). Gated: preview with `--dry-run`, get explicit approval, re-run the **same argv with
|
|
94
|
+
`--yes`** appended (see SKILL.md's exit-10 protocol). It deletes **every** id in the array, so
|
|
95
|
+
show the user the exact list first.
|
|
96
|
+
|
|
97
|
+
## 6. Gotchas
|
|
98
|
+
|
|
99
|
+
- **`Id` is array-vs-single by verb** — array on delete/enable/ping (batch), single on
|
|
100
|
+
read-one/logs/modify. Don't carry one shape across calls.
|
|
101
|
+
- **`ActionProperty` / `FilterProperty` are escaped-JSON *strings***, not objects — `service_url` +
|
|
102
|
+
`msg_type` go inside `ActionProperty`. `FilterProperty` is **optional on `CreateServiceHook` but
|
|
103
|
+
required on `ModifyServiceHook`** (use `"{}"` for no filter).
|
|
104
|
+
- **`ModifyServiceHook` requires every field resupplied** (Id, Event, Service, ServiceAction,
|
|
105
|
+
ActionProperty, FilterProperty, Enabled) — read the hook first (`DescribeServiceHook`) so you don't
|
|
106
|
+
drop a field you meant to keep.
|
|
107
|
+
- **`ResendServiceHookLog` takes only `ServiceHookLogId`** — no `ProjectId`, no hook `Id`.
|
|
108
|
+
- **`DescribeServiceHooks` requires `TargetType`** (and `PageNumber`/`PageSize`); the single-read and
|
|
109
|
+
most writes default `TargetType` to `PROJECT`.
|
|
110
|
+
- **Uneven wrapping**: create/modify/single-read → top-level `ServiceHook`; list → `Data.ServiceHook[]`;
|
|
111
|
+
logs → `Data.Log[]`; events → top-level `Event[]`; enable/delete/ping → top-level `Succeed`; resend →
|
|
112
|
+
`Data.Success`.
|