@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.
@@ -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`.