@seliseblocks/cli-os 0.2.7 → 0.2.8

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CLI for SELISE Blocks Cloud.
4
4
 
5
- - Package: `@seliseblocks/cli-os`
5
+ - Package: [`@seliseblocks/cli-os`](https://www.npmjs.com/package/@seliseblocks/cli-os)
6
6
  - Binary: `blocks`
7
7
 
8
8
  ## Setup
@@ -1,49 +1,49 @@
1
- ---
2
- name: blocks-iam-access-control
3
- description: "Work with SELISE Blocks RBAC (roles & permissions) via `blocks iam roles/permissions *` (CLI, project-scoped) or `blocksClient.iam.*` (SDK), never raw fetch/curl. Two facets: read-only feature-gating by the current user's own roles/permissions (common, safe) vs. creating/editing role and permission definitions (sensitive, human-confirmed only — CLI `--dry-run`→`--yes` or an in-app admin screen). OIDC/identity-provider provisioning stays portal-only, a different concern. Use for permission-gated UI, role/permission pickers, or building/scripting role & permission admin ('gate this button by permission', 'create a role and grant permissions', 'list permissions by severity')."
4
- ---
5
-
6
- # Blocks IAM — Access Control (Permissions & Roles)
7
-
8
- This skill covers **permission and role definitions** in SELISE Blocks — the RBAC model itself, not who has which role (that's the blocks-iam-users skill). Everything goes through either `blocks iam roles/permissions *` (CLI) or `blocksClient.iam.*` from **`@seliseblocks/client`**, the single SDK instance every `blocks new web` scaffold wires up at `src/lib/blocks/client.ts` and exports as `blocksClient`. No raw `fetch`/`curl` for either surface.
9
-
10
- ```ts
11
- import { blocksClient } from "../../lib/blocks/client";
12
- ```
13
-
14
- ## The platform boundary — read this before writing any code
15
-
16
- Role and permission administration is **not** portal-only or app-UI-only — `blocks` has a full, working CLI surface for it too. There are two equally real surfaces for the same operations, and the choice is about *where the human is*, not which one is "allowed" — see [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md) for the full command reference and the CLI-vs-SDK decision.
17
-
18
- Identity-provider/OIDC client provisioning is the one piece that really is **portal-only, human-driven**, at `https://os.seliseblocks.com` — unrelated to roles/permissions, don't bolt it onto this skill.
19
-
20
- Keep the two facets below (read-only feature-gating vs. sensitive admin mutations) separate in your head (and in your code) — they have very different risk profiles regardless of which surface (CLI or SDK) you're using.
21
-
22
- ## Facet 1 — Feature-gating a frontend by the user's own permissions (common, low risk)
23
-
24
- Read-only, scoped to whoever is signed in, needs no special confirmation. `useCurrentUser()` + `iam.resources.features()` + `iam.roles.assignable()`.
25
-
26
- → Full walkthrough: [flows/feature-gating.md](flows/feature-gating.md)
27
-
28
- ## Facet 2 — Creating/editing roles & permissions (sensitive)
29
-
30
- Legitimate only in direct response to a human's explicit, in-the-moment instruction — CLI (`--dry-run` reviewed, then `--yes`) or a signed-in admin's own in-app screen. Never something an agent decides to invoke on its own initiative.
31
-
32
- → Full command reference, SDK methods, and confirm-before-mutating pattern: [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md)
33
-
34
- ## Gotchas
35
-
36
- - **CLI mutations are project-scoped, not account-scoped** — they run against the impersonated-project token; `blocks iam me` is the one IAM command that uses the account token instead.
37
- - **Role hierarchy and permission assignment key off `slug`**, not `itemId`.
38
- - **Never fire a create/update/assign-permissions call — CLI or SDK — without a human confirming that specific change first.** See [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md) for the full discipline.
39
- - **OIDC/identity-provider client provisioning is always portal-only**, independent of everything above.
40
-
41
- ## Example trigger prompts
42
-
43
- - "Only show the delete button to users who have the `order::delete` permission." → Facet 1
44
- - "Hide this whole admin section unless the signed-in user has an admin role." → Facet 1
45
- - "What roles am I allowed to assign to other users?" → Facet 1
46
- - "Show me permissions grouped by severity in a settings panel." → Facet 1
47
- - "Build an admin page where I can create a role and pick which permissions it gets." → Facet 2
48
- - "Create a `content-editor` role from the CLI with these permissions." → Facet 2
49
- - "Can you just set up a few default roles for my project?" → confirm the exact list with the human first (in chat, or via a reviewed `--dry-run`), then run each `blocks iam roles create`/`assign-permissions` with `--yes` only after they say go — don't auto-provision without that per-change confirmation.
1
+ ---
2
+ name: blocks-iam-access-control
3
+ description: "Work with SELISE Blocks RBAC (roles & permissions) via `blocks iam roles/permissions *` (CLI, project-scoped) or `blocksClient.iam.*` (SDK), never raw fetch/curl. Two facets: read-only feature-gating by the current user's own roles/permissions (common, safe) vs. creating/editing role and permission definitions (sensitive, human-confirmed only — CLI `--dry-run`→`--yes` or an in-app admin screen). OIDC/identity-provider provisioning stays portal-only, a different concern. Use for permission-gated UI, role/permission pickers, or building/scripting role & permission admin ('gate this button by permission', 'create a role and grant permissions', 'list permissions by severity')."
4
+ ---
5
+
6
+ # Blocks IAM — Access Control (Permissions & Roles)
7
+
8
+ This skill covers **permission and role definitions** in SELISE Blocks — the RBAC model itself, not who has which role (that's the blocks-iam-users skill). Everything goes through either `blocks iam roles/permissions *` (CLI) or `blocksClient.iam.*` from **`@seliseblocks/client`**, the single SDK instance every `blocks new web` scaffold wires up at `src/lib/blocks/client.ts` and exports as `blocksClient`. No raw `fetch`/`curl` for either surface.
9
+
10
+ ```ts
11
+ import { blocksClient } from "../../lib/blocks/client";
12
+ ```
13
+
14
+ ## The platform boundary — read this before writing any code
15
+
16
+ Role and permission administration is **not** portal-only or app-UI-only — `blocks` has a full, working CLI surface for it too. There are two equally real surfaces for the same operations, and the choice is about *where the human is*, not which one is "allowed" — see [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md) for the full command reference and the CLI-vs-SDK decision.
17
+
18
+ Identity-provider/OIDC client provisioning is the one piece that really is **portal-only, human-driven**, at `https://os.seliseblocks.com` — unrelated to roles/permissions, don't bolt it onto this skill.
19
+
20
+ Keep the two facets below (read-only feature-gating vs. sensitive admin mutations) separate in your head (and in your code) — they have very different risk profiles regardless of which surface (CLI or SDK) you're using.
21
+
22
+ ## Facet 1 — Feature-gating a frontend by the user's own permissions (common, low risk)
23
+
24
+ Read-only, scoped to whoever is signed in, needs no special confirmation. `useCurrentUser()` + `iam.resources.features()` + `iam.roles.assignable()`.
25
+
26
+ → Full walkthrough: [flows/feature-gating.md](flows/feature-gating.md)
27
+
28
+ ## Facet 2 — Creating/editing roles & permissions (sensitive)
29
+
30
+ Legitimate only in direct response to a human's explicit, in-the-moment instruction — CLI (`--dry-run` reviewed, then `--yes`) or a signed-in admin's own in-app screen. Never something an agent decides to invoke on its own initiative.
31
+
32
+ → Full command reference, SDK methods, and confirm-before-mutating pattern: [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md)
33
+
34
+ ## Gotchas
35
+
36
+ - **CLI mutations are project-scoped, not account-scoped** — they run against the impersonated-project token; `blocks iam me` is the one IAM command that uses the account token instead.
37
+ - **Role hierarchy and permission assignment key off `slug`**, not `itemId`.
38
+ - **Never fire a create/update/assign-permissions call — CLI or SDK — without a human confirming that specific change first.** See [flows/manage-roles-permissions.md](flows/manage-roles-permissions.md) for the full discipline.
39
+ - **OIDC/identity-provider client provisioning is always portal-only**, independent of everything above.
40
+
41
+ ## Example trigger prompts
42
+
43
+ - "Only show the delete button to users who have the `order::delete` permission." → Facet 1
44
+ - "Hide this whole admin section unless the signed-in user has an admin role." → Facet 1
45
+ - "What roles am I allowed to assign to other users?" → Facet 1
46
+ - "Show me permissions grouped by severity in a settings panel." → Facet 1
47
+ - "Build an admin page where I can create a role and pick which permissions it gets." → Facet 2
48
+ - "Create a `content-editor` role from the CLI with these permissions." → Facet 2
49
+ - "Can you just set up a few default roles for my project?" → confirm the exact list with the human first (in chat, or via a reviewed `--dry-run`), then run each `blocks iam roles create`/`assign-permissions` with `--yes` only after they say go — don't auto-provision without that per-change confirmation.
@@ -1,38 +1,38 @@
1
- # Flow: Feature-gating a frontend by the user's own permissions (common, low risk)
2
-
3
- This is read-only against IAM and scoped to whoever is signed in, so it needs no special confirmation — build it the same way you'd build any other data-fetching feature.
4
-
5
- The scaffold already gives you a `useCurrentUser()` hook (`src/features/profile/useCurrentUser.ts`) wrapping `blocksClient.iam.me()` with TanStack Query; `me()` returns `{ data: { itemId, email, firstName, lastName, roles: string[], permissions: string[], ... } }`. Reuse it instead of re-fetching:
6
-
7
- ```ts
8
- // src/features/access/usePermission.ts
9
- import { useCurrentUser } from "../profile/useCurrentUser";
10
-
11
- export function useHasPermission(permission: string): boolean {
12
- const me = useCurrentUser();
13
- return me.data?.data?.permissions?.includes(permission) ?? false;
14
- }
15
-
16
- export function useHasRole(role: string): boolean {
17
- const me = useCurrentUser();
18
- return me.data?.data?.roles?.includes(role) ?? false;
19
- }
20
- ```
21
-
22
- ```tsx
23
- // src/shared/ui/RequirePermission.tsx
24
- import type { ReactNode } from "react";
25
- import { useHasPermission } from "../../features/access/usePermission";
26
-
27
- export function RequirePermission({ permission, children }: { permission: string; children: ReactNode }) {
28
- if (!useHasPermission(permission)) return null;
29
- return <>{children}</>;
30
- }
31
- ```
32
-
33
- Two more read methods round this out:
34
-
35
- - `blocksClient.iam.resources.features(query?)` — feature/resource flags for the active user context; use this to drive nav items or feature flags that are more granular than a flat permission string.
36
- - `blocksClient.iam.roles.assignable()` — lists roles the **current caller** is allowed to assign. If you're building a "grant this user a role" picker, populate it from `assignable()`, not from `roles.list()` — don't assume every role in the system is one this particular admin may hand out.
37
-
38
- There is also a CLI read path for the same data, useful outside an app (scripting/inspection): `blocks iam roles list/get/assignable` and `blocks iam permissions list/get/by-severity` — see [manage-roles-permissions.md](manage-roles-permissions.md) for the full CLI command reference (it covers both reads and mutations).
1
+ # Flow: Feature-gating a frontend by the user's own permissions (common, low risk)
2
+
3
+ This is read-only against IAM and scoped to whoever is signed in, so it needs no special confirmation — build it the same way you'd build any other data-fetching feature.
4
+
5
+ The scaffold already gives you a `useCurrentUser()` hook (`src/features/profile/useCurrentUser.ts`) wrapping `blocksClient.iam.me()` with TanStack Query; `me()` returns `{ data: { itemId, email, firstName, lastName, roles: string[], permissions: string[], ... } }`. Reuse it instead of re-fetching:
6
+
7
+ ```ts
8
+ // src/features/access/usePermission.ts
9
+ import { useCurrentUser } from "../profile/useCurrentUser";
10
+
11
+ export function useHasPermission(permission: string): boolean {
12
+ const me = useCurrentUser();
13
+ return me.data?.data?.permissions?.includes(permission) ?? false;
14
+ }
15
+
16
+ export function useHasRole(role: string): boolean {
17
+ const me = useCurrentUser();
18
+ return me.data?.data?.roles?.includes(role) ?? false;
19
+ }
20
+ ```
21
+
22
+ ```tsx
23
+ // src/shared/ui/RequirePermission.tsx
24
+ import type { ReactNode } from "react";
25
+ import { useHasPermission } from "../../features/access/usePermission";
26
+
27
+ export function RequirePermission({ permission, children }: { permission: string; children: ReactNode }) {
28
+ if (!useHasPermission(permission)) return null;
29
+ return <>{children}</>;
30
+ }
31
+ ```
32
+
33
+ Two more read methods round this out:
34
+
35
+ - `blocksClient.iam.resources.features(query?)` — feature/resource flags for the active user context; use this to drive nav items or feature flags that are more granular than a flat permission string.
36
+ - `blocksClient.iam.roles.assignable()` — lists roles the **current caller** is allowed to assign. If you're building a "grant this user a role" picker, populate it from `assignable()`, not from `roles.list()` — don't assume every role in the system is one this particular admin may hand out.
37
+
38
+ There is also a CLI read path for the same data, useful outside an app (scripting/inspection): `blocks iam roles list/get/assignable` and `blocks iam permissions list/get/by-severity` — see [manage-roles-permissions.md](manage-roles-permissions.md) for the full CLI command reference (it covers both reads and mutations).
@@ -1,110 +1,110 @@
1
- # Flow: Creating/editing roles & permissions (sensitive)
2
-
3
- This is the write side, and it's legitimate **only in direct response to a human's explicit, in-the-moment instruction** — either that human running `blocks iam roles/permissions create|update|assign-permissions` themselves (with a `--dry-run` reviewed first), or a signed-in admin operating their own in-app admin screen. Never something an agent decides to invoke on its own initiative.
4
-
5
- ## CLI command reference
6
-
7
- All project-scoped — every command resolves the active project (`blocks use <tenantId>` or an explicit `--project <tenantId>`) and runs on an impersonated project token, not the account token `iam me` uses.
8
-
9
- ```
10
- blocks iam roles list [--page] [--page-size] [--search] [--slugs a,b] [--organization-id] [--filter '<json>'] [--json]
11
- blocks iam roles get <id> [--json]
12
- blocks iam roles create --name <n> [--slug] [--description] [--parent-role-slug] [--can-create-own] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
13
- blocks iam roles update <itemId> [--name] [--description] [--parent-role-slug] [--propagate-to-other-org] [--can-create-own] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
14
- blocks iam roles assign-permissions <slug> [--add-permissions a,b] [--remove-permissions a,b] [--organization-id] [--dry-run] [--yes] [--json]
15
- blocks iam roles assignable [--json]
16
-
17
- blocks iam permissions list [--page] [--page-size] [--search] [--type <0-3>] [--severity <0-4>] [--resource-group] [--tags a,b] [--resources a,b] [--is-built-in] [--is-archived] [--roles a,b] [--organization-id] [--filter '<json>'] [--json]
18
- blocks iam permissions get <id> [--json]
19
- blocks iam permissions create --name <n> [--type] [--description] [--resource] [--resource-group] [--tags a,b] [--severity] [--is-built-in] [--dependent-permissions a,b] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
20
- blocks iam permissions update <id> [same flags as create, plus --is-archived] [--dry-run] [--yes] [--json]
21
- blocks iam permissions by-severity [--json]
22
- ```
23
-
24
- Mutating commands (`create`, `update`, `assign-permissions`) follow the same discipline as every other mutating command in this CLI: pass `--dry-run` first to see the exact request body and endpoint with no network call, then re-run with `--yes` (or you'll be prompted to confirm) to actually send it.
25
-
26
- ## Two equally real surfaces
27
-
28
- The choice is about *where the human is*, not which one is "allowed":
29
-
30
- - **`blocks iam roles/permissions *` from a terminal** — a human (or an agent acting on that human's explicit, in-the-moment instruction) runs the CLI directly. `--dry-run` is the CLI's own review step; treat a clean dry-run plus the human's go-ahead as the confirmation, then run with `--yes`.
31
- - **`blocksClient.iam.roles`/`iam.permissions` from inside an app** — for building an admin settings screen. If you're building that user their own admin screen, and *they* click "Create role" through that screen, calling `iam.roles.create()` from the app's code is exactly what the SDK method is for.
32
-
33
- The actual line is not "CLI vs SDK" — it's:
34
-
35
- - **Fine:** a command run because a human just asked for this specific change (terminal or in-app), bounded by that human's own IAM permissions.
36
- - **Not fine:** an agent deciding on its own to call `create`/`update`/`assignPermissions` (CLI or SDK) — whether to "help out," to fix something it noticed, or as part of a larger task the user didn't ask it to take this specific action for.
37
-
38
- ## SDK methods (write side)
39
-
40
- All from `blocksClient.iam`, per `iam-client.ts`:
41
-
42
- - `permissions.create(request)` / `permissions.update(id, request)` — define or edit a permission.
43
- - `permissions.list(request)` — paged/filtered search.
44
- - `permissions.bySeverity()` — permissions grouped by severity, handy for a categorized picker.
45
- - `permissions.get(id)` — one permission's detail.
46
- - `roles.create(request)` / `roles.update(request)` — define or edit a role.
47
- - `roles.list(request)` / `roles.get(id)` — search / fetch one role.
48
- - `roles.assignPermissions(request)` — attach/detach permissions on a role.
49
- - `roles.assignable()` — same read method as feature-gating; also useful here to limit which roles this admin's screen lets them touch at all.
50
- - `resources.groups()` — metadata for grouping permissions by resource in the UI (e.g. a permissions picker organized by resource/module).
51
-
52
- The SDK deliberately leaves these request bodies as open `Record<string, unknown>` rather than locking you to a fixed shape — confirm exact field names against the portal or a `list()`/`get()` response before hardcoding new ones. Two fields the SDK's own types do pin down: a role's `slug` (`BlocksRole.slug`) is its stable key — use it, not `itemId`, anywhere the API expects a role reference (e.g. `assignPermissions`); a permission's `resource` and `roles[]` (`BlocksPermission`) tell you what it's scoped to and which roles already hold it.
53
-
54
- ## Confirm before mutating
55
-
56
- **Before calling any of `permissions.create`, `permissions.update`, `roles.create`, `roles.update`, or `roles.assignPermissions` from your admin screen's code, the screen itself must get an explicit, in-the-moment confirmation from the admin operating it** — mirror the discipline `blocks iam roles/permissions create|update|assign-permissions` enforces natively with `--dry-run` before `--yes`. Concretely, that means the screen should:
57
-
58
- 1. Let the admin build up the change in the UI (pick a role, check/uncheck permissions, edit a name) without calling anything yet.
59
- 2. Show a clear summary of exactly what will change — "Grant `Editor` role: + `content::publish`, − `content::archive`" — before any network call.
60
- 3. Only fire the `create`/`update`/`assignPermissions` call after the admin clicks an explicit confirm ("Save changes", "Create role") for *that specific* change.
61
-
62
- Don't collapse steps 2–3 into an auto-save on every checkbox click, and don't have an agent call these methods proactively (e.g. as part of "let me clean up your roles" or "I'll just add the permission you mentioned needing") — only in direct response to the human's own confirmed action through the screen.
63
-
64
- ## Examples
65
-
66
- SDK, wired to a confirm step:
67
-
68
- ```tsx
69
- // src/features/admin/roles/useAssignPermissions.ts
70
- import { useMutation, useQueryClient } from "@tanstack/react-query";
71
- import { blocksClient } from "../../../lib/blocks/client";
72
-
73
- export function useAssignPermissions() {
74
- const queryClient = useQueryClient();
75
- return useMutation({
76
- mutationFn: (delta: { slug: string; addPermissions: string[]; removePermissions: string[] }) =>
77
- blocksClient.iam.roles.assignPermissions(delta),
78
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["iam", "roles"] })
79
- });
80
- }
81
- ```
82
-
83
- ```tsx
84
- // in the role editor component, only after the admin reviews a diff and clicks "Save":
85
- const assign = useAssignPermissions();
86
- const onConfirmSave = () =>
87
- assign.mutate({ slug: role.slug, addPermissions: pendingAdds, removePermissions: pendingRemoves });
88
- ```
89
-
90
- The equivalent from a terminal, doing the same review-then-confirm dance the CLI enforces natively:
91
-
92
- ```
93
- blocks use my-tenant-id # or pass --project my-tenant-id on every call below
94
- blocks iam roles create --name Editor --description "Content editors" --dry-run
95
- # review the printed request body, then:
96
- blocks iam roles create --name Editor --description "Content editors" --yes
97
-
98
- blocks iam roles assign-permissions editor --add-permissions content::publish --dry-run
99
- blocks iam roles assign-permissions editor --add-permissions content::publish --yes
100
- ```
101
-
102
- ## Gotchas
103
-
104
- - **CLI mutations are project-scoped, not account-scoped** — `blocks iam roles create/update/assign-permissions` and `blocks iam permissions create/update` all require a selected project (`blocks use <tenantId>` or `--project <tenantId>`) and run against the impersonated-project token; `blocks iam me` is the one IAM command that uses the account token instead, so don't expect `iam me`'s auth context to carry over to these.
105
- - **Role hierarchy and permission assignment key off `slug`**, not `itemId` — grab it from `roles.list()`/`roles.get()` (or `blocks iam roles list/get`) before calling `assignPermissions`.
106
- - **Permission assignment ultimately uses permission `itemId`s** — the CLI resolves `resource` strings like `content::publish` before mutation; SDK/backend callers should pass permission ids directly in `addPermissions` / `removePermissions`.
107
- - **`roles.assignPermissions` is additive/subtractive** (`addPermissions[]` / `removePermissions[]` in one call), not a full-set replace — compute the delta from what's checked/unchecked, don't resend the entire permission list as "adds."
108
- - **`roles.assignable()` scopes to the caller** — always populate role pickers from it rather than `roles.list()`, so an admin can't be shown (or attempt to grant) a role above their own authority.
109
- - **Never fire a create/update/assign-permissions call — CLI or SDK — without a human confirming that specific change first** (a reviewed `--dry-run` plus explicit go-ahead on the CLI, an explicit in-UI confirm for the SDK) — no auto-provisioning "default roles," no agent-initiated cleanup of permissions, no batch edits without a per-change confirm.
110
- - **OIDC/identity-provider client provisioning is always portal-only**, independent of everything above — if a request drifts into "create an OIDC client" or "add an identity provider," that's a different skill's territory (or no skill's — send the user to the portal), not something to bolt onto this one.
1
+ # Flow: Creating/editing roles & permissions (sensitive)
2
+
3
+ This is the write side, and it's legitimate **only in direct response to a human's explicit, in-the-moment instruction** — either that human running `blocks iam roles/permissions create|update|assign-permissions` themselves (with a `--dry-run` reviewed first), or a signed-in admin operating their own in-app admin screen. Never something an agent decides to invoke on its own initiative.
4
+
5
+ ## CLI command reference
6
+
7
+ All project-scoped — every command resolves the active project (`blocks use <tenantId>` or an explicit `--project <tenantId>`) and runs on an impersonated project token, not the account token `iam me` uses.
8
+
9
+ ```
10
+ blocks iam roles list [--page] [--page-size] [--search] [--slugs a,b] [--organization-id] [--filter '<json>'] [--json]
11
+ blocks iam roles get <id> [--json]
12
+ blocks iam roles create --name <n> [--slug] [--description] [--parent-role-slug] [--can-create-own] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
13
+ blocks iam roles update <itemId> [--name] [--description] [--parent-role-slug] [--propagate-to-other-org] [--can-create-own] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
14
+ blocks iam roles assign-permissions <slug> [--add-permissions a,b] [--remove-permissions a,b] [--organization-id] [--dry-run] [--yes] [--json]
15
+ blocks iam roles assignable [--json]
16
+
17
+ blocks iam permissions list [--page] [--page-size] [--search] [--type <0-3>] [--severity <0-4>] [--resource-group] [--tags a,b] [--resources a,b] [--is-built-in] [--is-archived] [--roles a,b] [--organization-id] [--filter '<json>'] [--json]
18
+ blocks iam permissions get <id> [--json]
19
+ blocks iam permissions create --name <n> [--type] [--description] [--resource] [--resource-group] [--tags a,b] [--severity] [--is-built-in] [--dependent-permissions a,b] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
20
+ blocks iam permissions update <id> [same flags as create, plus --is-archived] [--dry-run] [--yes] [--json]
21
+ blocks iam permissions by-severity [--json]
22
+ ```
23
+
24
+ Mutating commands (`create`, `update`, `assign-permissions`) follow the same discipline as every other mutating command in this CLI: pass `--dry-run` first to see the exact request body and endpoint with no network call, then re-run with `--yes` (or you'll be prompted to confirm) to actually send it.
25
+
26
+ ## Two equally real surfaces
27
+
28
+ The choice is about *where the human is*, not which one is "allowed":
29
+
30
+ - **`blocks iam roles/permissions *` from a terminal** — a human (or an agent acting on that human's explicit, in-the-moment instruction) runs the CLI directly. `--dry-run` is the CLI's own review step; treat a clean dry-run plus the human's go-ahead as the confirmation, then run with `--yes`.
31
+ - **`blocksClient.iam.roles`/`iam.permissions` from inside an app** — for building an admin settings screen. If you're building that user their own admin screen, and *they* click "Create role" through that screen, calling `iam.roles.create()` from the app's code is exactly what the SDK method is for.
32
+
33
+ The actual line is not "CLI vs SDK" — it's:
34
+
35
+ - **Fine:** a command run because a human just asked for this specific change (terminal or in-app), bounded by that human's own IAM permissions.
36
+ - **Not fine:** an agent deciding on its own to call `create`/`update`/`assignPermissions` (CLI or SDK) — whether to "help out," to fix something it noticed, or as part of a larger task the user didn't ask it to take this specific action for.
37
+
38
+ ## SDK methods (write side)
39
+
40
+ All from `blocksClient.iam`, per `iam-client.ts`:
41
+
42
+ - `permissions.create(request)` / `permissions.update(id, request)` — define or edit a permission.
43
+ - `permissions.list(request)` — paged/filtered search.
44
+ - `permissions.bySeverity()` — permissions grouped by severity, handy for a categorized picker.
45
+ - `permissions.get(id)` — one permission's detail.
46
+ - `roles.create(request)` / `roles.update(request)` — define or edit a role.
47
+ - `roles.list(request)` / `roles.get(id)` — search / fetch one role.
48
+ - `roles.assignPermissions(request)` — attach/detach permissions on a role.
49
+ - `roles.assignable()` — same read method as feature-gating; also useful here to limit which roles this admin's screen lets them touch at all.
50
+ - `resources.groups()` — metadata for grouping permissions by resource in the UI (e.g. a permissions picker organized by resource/module).
51
+
52
+ The SDK deliberately leaves these request bodies as open `Record<string, unknown>` rather than locking you to a fixed shape — confirm exact field names against the portal or a `list()`/`get()` response before hardcoding new ones. Two fields the SDK's own types do pin down: a role's `slug` (`BlocksRole.slug`) is its stable key — use it, not `itemId`, anywhere the API expects a role reference (e.g. `assignPermissions`); a permission's `resource` and `roles[]` (`BlocksPermission`) tell you what it's scoped to and which roles already hold it.
53
+
54
+ ## Confirm before mutating
55
+
56
+ **Before calling any of `permissions.create`, `permissions.update`, `roles.create`, `roles.update`, or `roles.assignPermissions` from your admin screen's code, the screen itself must get an explicit, in-the-moment confirmation from the admin operating it** — mirror the discipline `blocks iam roles/permissions create|update|assign-permissions` enforces natively with `--dry-run` before `--yes`. Concretely, that means the screen should:
57
+
58
+ 1. Let the admin build up the change in the UI (pick a role, check/uncheck permissions, edit a name) without calling anything yet.
59
+ 2. Show a clear summary of exactly what will change — "Grant `Editor` role: + `content::publish`, − `content::archive`" — before any network call.
60
+ 3. Only fire the `create`/`update`/`assignPermissions` call after the admin clicks an explicit confirm ("Save changes", "Create role") for *that specific* change.
61
+
62
+ Don't collapse steps 2–3 into an auto-save on every checkbox click, and don't have an agent call these methods proactively (e.g. as part of "let me clean up your roles" or "I'll just add the permission you mentioned needing") — only in direct response to the human's own confirmed action through the screen.
63
+
64
+ ## Examples
65
+
66
+ SDK, wired to a confirm step:
67
+
68
+ ```tsx
69
+ // src/features/admin/roles/useAssignPermissions.ts
70
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
71
+ import { blocksClient } from "../../../lib/blocks/client";
72
+
73
+ export function useAssignPermissions() {
74
+ const queryClient = useQueryClient();
75
+ return useMutation({
76
+ mutationFn: (delta: { slug: string; addPermissions: string[]; removePermissions: string[] }) =>
77
+ blocksClient.iam.roles.assignPermissions(delta),
78
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["iam", "roles"] })
79
+ });
80
+ }
81
+ ```
82
+
83
+ ```tsx
84
+ // in the role editor component, only after the admin reviews a diff and clicks "Save":
85
+ const assign = useAssignPermissions();
86
+ const onConfirmSave = () =>
87
+ assign.mutate({ slug: role.slug, addPermissions: pendingAdds, removePermissions: pendingRemoves });
88
+ ```
89
+
90
+ The equivalent from a terminal, doing the same review-then-confirm dance the CLI enforces natively:
91
+
92
+ ```
93
+ blocks use my-tenant-id # or pass --project my-tenant-id on every call below
94
+ blocks iam roles create --name Editor --description "Content editors" --dry-run
95
+ # review the printed request body, then:
96
+ blocks iam roles create --name Editor --description "Content editors" --yes
97
+
98
+ blocks iam roles assign-permissions editor --add-permissions content::publish --dry-run
99
+ blocks iam roles assign-permissions editor --add-permissions content::publish --yes
100
+ ```
101
+
102
+ ## Gotchas
103
+
104
+ - **CLI mutations are project-scoped, not account-scoped** — `blocks iam roles create/update/assign-permissions` and `blocks iam permissions create/update` all require a selected project (`blocks use <tenantId>` or `--project <tenantId>`) and run against the impersonated-project token; `blocks iam me` is the one IAM command that uses the account token instead, so don't expect `iam me`'s auth context to carry over to these.
105
+ - **Role hierarchy and permission assignment key off `slug`**, not `itemId` — grab it from `roles.list()`/`roles.get()` (or `blocks iam roles list/get`) before calling `assignPermissions`.
106
+ - **Permission assignment ultimately uses permission `itemId`s** — the CLI resolves `resource` strings like `content::publish` before mutation; SDK/backend callers should pass permission ids directly in `addPermissions` / `removePermissions`.
107
+ - **`roles.assignPermissions` is additive/subtractive** (`addPermissions[]` / `removePermissions[]` in one call), not a full-set replace — compute the delta from what's checked/unchecked, don't resend the entire permission list as "adds."
108
+ - **`roles.assignable()` scopes to the caller** — always populate role pickers from it rather than `roles.list()`, so an admin can't be shown (or attempt to grant) a role above their own authority.
109
+ - **Never fire a create/update/assign-permissions call — CLI or SDK — without a human confirming that specific change first** (a reviewed `--dry-run` plus explicit go-ahead on the CLI, an explicit in-UI confirm for the SDK) — no auto-provisioning "default roles," no agent-initiated cleanup of permissions, no batch edits without a per-change confirm.
110
+ - **OIDC/identity-provider client provisioning is always portal-only**, independent of everything above — if a request drifts into "create an OIDC client" or "add an identity provider," that's a different skill's territory (or no skill's — send the user to the portal), not something to bolt onto this one.