@seliseblocks/cli-os 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,131 +1,131 @@
1
- ---
2
- name: blocks-iam-users
3
- description: "Manage OTHER users' IAM records via `blocksClient.iam.users.*` (never raw fetch/curl), or the equivalent project-scoped `blocks iam users *` / `blocks iam email available` CLI. Covers reads (`get`, `list`, `emailAvailable`, `exists`) and admin mutations (`create`, `update`, `activate`, `deactivate`, `updateAccess`, `revokeAccess`) — CLI mutations require `--dry-run`/`--yes`. Use to invite, edit, deactivate/reactivate, list/search users, or grant/revoke roles/org access. Not for the current user's own profile (blocks-iam-account) or role/permission definitions (blocks-iam-access-control)."
4
- ---
5
-
6
- # Blocks IAM — Managing Other Users
7
-
8
- This skill is about an **admin managing other people's IAM accounts** from inside a Blocks app — inviting them, editing their profile, changing their access, deactivating them. It is not about the signed-in user managing their own account (that's the **blocks-iam-account** skill) and not about defining the roles/permissions being assigned (that's **blocks-iam-access-control**).
9
-
10
- Everything here goes through the SDK: `blocksClient.iam.users.*` on the app's single `@seliseblocks/client` instance (created once, typically at `src/lib/blocks/client.ts` by `blocks new web`). **Never raw `fetch`/`curl` against `api.seliseblocks.com`.**
11
-
12
- ```ts
13
- import { blocksClient } from "../../lib/blocks/client";
14
-
15
- const { data } = await blocksClient.iam.users.get(userId);
16
- ```
17
-
18
- ## Two surfaces, same operations: SDK (in-app) and CLI (`blocks iam users *`)
19
-
20
- There are two legitimate ways to drive full user administration (create, update, deactivate, activate, access grant/revoke) — both are covered by this skill:
21
-
22
- - **SDK — `blocksClient.iam.users.*`** — build the capability **as a feature inside a signed-in admin's own app**: the admin is looking at a screen, clicking "Deactivate" on a specific user row, and their own IAM permissions gate whether the call succeeds.
23
- - **CLI — `blocks iam users *` / `blocks iam email available`** — the same operations, invoked directly from a terminal or an agent's shell tool. These are fully wired, project-scoped commands (see "CLI surface" below), not a read-only stub — `iam me` is a separate, account-scoped command for the CLI operator's own identity and is not the only IAM command the CLI has.
24
-
25
- What is **not** legitimate on either surface: an agent deciding on its own, without the human explicitly directing that specific action in the moment, to call `create`/`update`/`deactivate`/`activate`/`updateAccess`/`revokeAccess` (SDK) or `users create`/`update`/`activate`/`deactivate`/`access grant`/`access revoke` (CLI). State the exact change in plain language and get the user's explicit go-ahead first, every time, even if they asked for something adjacent a moment ago. The CLI enforces this mechanically — every mutating command requires `--dry-run` (preview only, no call) or `--yes`/an interactive "yes" before it executes — but that built-in gate doesn't replace stating the change and getting a real go-ahead when an agent is the one typing the command.
26
-
27
- ## Safe surface — reads and checks, no confirmation needed
28
-
29
- These don't change anything, so there's no caveat to apply:
30
-
31
- | Method | What it does |
32
- |---|---|
33
- | `iam.users.get(id, { organizationId? })` | One user record, optionally scoped to an org. |
34
- | `iam.users.list(request)` | Paged/filtered user query. **This is a POST-read contract** — `list` sends `{ pageNo, pageSize, filter, search, ... }` as a POST body, it is not a GET. |
35
- | `iam.users.emailAvailable(query)` | Public duplicate-email check for invite/signup forms. No auth needed. |
36
- | `iam.users.exists(email)` | Existence check by email. |
37
-
38
- ```ts
39
- const page = await blocksClient.iam.users.list({ pageNo: 1, pageSize: 20, search: "jane" });
40
- const check = await blocksClient.iam.users.emailAvailable({ email: "new.hire@example.com" });
41
- ```
42
-
43
- ## Sensitive surface — confirm the exact change before calling
44
-
45
- Every method below mutates a real account. Before calling any of them, restate to the user in plain language exactly what will change (which user, which field, which effect) and wait for an explicit yes — do not infer consent from an earlier, more general request.
46
-
47
- | Method | What it does |
48
- |---|---|
49
- | `iam.users.create(request)` | Invites/provisions a user in the active tenant/organization. |
50
- | `iam.users.update(id, request)` | Edits an IAM profile's fields. |
51
- | `iam.users.deactivate(request)` | Removes access without deleting the record. |
52
- | `iam.users.activate(request)` | Restores access for a previously deactivated account. |
53
- | `iam.users.updateAccess(request)` | Grants or changes roles/permissions/org access for a user. |
54
- | `iam.users.revokeAccess(request)` | Removes roles/permissions/org access from a user. |
55
-
56
- Example — deactivating a user:
57
-
58
- > Agent: "This will deactivate **jane.doe@example.com** (user id `usr_8a2f`) — she'll immediately lose access but her record and history stay intact. Confirm?"
59
- > User: "Yes, deactivate her."
60
- > *(only then)* `await blocksClient.iam.users.deactivate({ userId: "usr_8a2f" });`
61
-
62
- Never chain a mutation straight off a read (e.g. don't look a user up and deactivate them in the same breath just because the user asked to "find inactive-looking accounts") — surface what you found, then get a decision on each mutation separately.
63
-
64
- ```ts
65
- // After the user explicitly confirms creating this exact invite:
66
- await blocksClient.iam.users.create({
67
- email: "new.hire@example.com",
68
- firstName: "New",
69
- lastName: "Hire",
70
- roles: ["member"]
71
- });
72
-
73
- // After the user explicitly confirms this exact access change:
74
- await blocksClient.iam.users.updateAccess({ userId: "usr_8a2f", roles: ["editor"] });
75
- ```
76
-
77
- ## CLI surface — `blocks iam users *`, `blocks iam email available`
78
-
79
- These are real, fully-wired commands — not a stub and not limited to `iam me`. `iam me` is a separate, account-scoped command (current CLI operator's own identity via the account token); every command below is **project-scoped**: it requires a project already selected (`blocks use <project-tenant-id>`) and calls IAM with an impersonated project token, same as the rest of the project-scoped CLI surface.
80
-
81
- Reads — no confirmation needed:
82
-
83
- | Command | What it does |
84
- |---|---|
85
- | `blocks iam users list [--page 1] [--page-size 20] [--email <e>] [--name <n>] [--organization-id <id>] [--sort-by <field>] [--sort-desc] [--filter '<json>'] [--json]` | Paged/filtered user query. `--filter` merges a raw JSON object over the convenience flags. |
86
- | `blocks iam users get <id> [--organization-id <id>] [--json]` | One user record, optionally scoped to an org. |
87
- | `blocks iam users exists <email> [--json]` | Existence check by email. |
88
- | `blocks iam email available <email> [--json]` | Duplicate-email check. |
89
-
90
- Mutations — every one supports `--dry-run` (print the request body and exit, no call) and requires either `--yes` or a typed `yes` at an interactive prompt before it executes:
91
-
92
- | Command | What it does |
93
- |---|---|
94
- | `blocks iam users create --email <e>\|--user-name <n> [--first-name] [--last-name] [--password] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Invites/provisions a user. |
95
- | `blocks iam users update <id> [--first-name] [--last-name] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Edits an IAM profile's fields. |
96
- | `blocks iam users activate <userId> [--reason <text>] [--dry-run] [--yes] [--json]` | Restores access for a previously deactivated account. |
97
- | `blocks iam users deactivate <userId> [--dry-run] [--yes] [--json]` | Removes access without deleting the record. |
98
- | `blocks iam users access grant <userId> [--roles a,b] [--permissions a,b] [--organization-id] [--dry-run] [--yes] [--json]` | Grants roles/permissions/org access (requires at least one of `--roles`/`--permissions`). |
99
- | `blocks iam users access revoke <userId> [--organization-id] [--dry-run] [--yes] [--json]` | Revokes org access for a user. |
100
-
101
- Command segments joined by a space also accept a colon (`iam:users:access:grant` etc.) — both forms resolve to the same handler; `blocks iam users --help`-style docs in the CLI's own `--help` output use the space form shown above.
102
-
103
- Example — deactivating a user from the CLI, dry-run first:
104
-
105
- ```sh
106
- blocks iam users deactivate usr_8a2f --dry-run # preview the request body, no call made
107
- blocks iam users deactivate usr_8a2f --yes # after the user explicitly confirms
108
- ```
109
-
110
- Apply the same confirm-before-mutating discipline here as with the SDK: state which user and which effect, wait for an explicit yes, don't chain a mutating command straight off a `list`/`get` just because the user asked to "find" something.
111
-
112
- ## Gotchas
113
-
114
- - **`list` is a POST**, not a GET — don't assume query-string filtering.
115
- - **Roles are referenced by slug**, as defined in blocks-iam-access-control — not by their internal item ids.
116
- - **`organizationId`** matters in multi-org projects — pass it to `get` when you need a user's record in a specific org context.
117
- - **Every request/response type in the SDK is a loosely-typed `Record<string, unknown>`** (`BlocksUser`, `BlocksBaseResponse`, etc. only guarantee a few common fields) — treat fields defensively and confirm shape against a live response for the project rather than assuming a fixed schema.
118
- - **The CLI is project-scoped, not account-scoped** — `blocks iam users *`/`blocks iam email available` need a selected project (`blocks use <project-tenant-id>`) and use an impersonated project token; `iam me` is the one exception that runs on the account token instead.
119
- - **Don't duplicate blocks-iam-account** — if the ask is "let me update my own profile" or "let me reset my password," that's the current user acting on themselves, not this skill.
120
-
121
- ## Example triggers
122
-
123
- - "Invite a user and set their roles"
124
- - "Deactivate this user's account"
125
- - "List all users in the org, filtered by status"
126
- - "Check if this email is already registered before I show the invite form"
127
- - "Grant this user the editor role"
128
- - "Revoke this user's access to the finance org"
129
- - "Update this user's phone number"
130
- - "Reactivate this account"
131
- - "From the terminal, deactivate user usr_8a2f in the current project" → use `blocks iam users deactivate usr_8a2f`, `--dry-run` first, then `--yes` after explicit confirmation
1
+ ---
2
+ name: blocks-iam-users
3
+ description: "Manage OTHER users' IAM records via `blocksClient.iam.users.*` (never raw fetch/curl), or the equivalent project-scoped `blocks iam users *` / `blocks iam email available` CLI. Covers reads (`get`, `list`, `emailAvailable`, `exists`) and admin mutations (`create`, `update`, `activate`, `deactivate`, `updateAccess`, `revokeAccess`) — CLI mutations require `--dry-run`/`--yes`. Use to invite, edit, deactivate/reactivate, list/search users, or grant/revoke roles/org access. Not for the current user's own profile (blocks-iam-account) or role/permission definitions (blocks-iam-access-control)."
4
+ ---
5
+
6
+ # Blocks IAM — Managing Other Users
7
+
8
+ This skill is about an **admin managing other people's IAM accounts** from inside a Blocks app — inviting them, editing their profile, changing their access, deactivating them. It is not about the signed-in user managing their own account (that's the **blocks-iam-account** skill) and not about defining the roles/permissions being assigned (that's **blocks-iam-access-control**).
9
+
10
+ Everything here goes through the SDK: `blocksClient.iam.users.*` on the app's single `@seliseblocks/client` instance (created once, typically at `src/lib/blocks/client.ts` by `blocks new web`). **Never raw `fetch`/`curl` against `api.seliseblocks.com`.**
11
+
12
+ ```ts
13
+ import { blocksClient } from "../../lib/blocks/client";
14
+
15
+ const { data } = await blocksClient.iam.users.get(userId);
16
+ ```
17
+
18
+ ## Two surfaces, same operations: SDK (in-app) and CLI (`blocks iam users *`)
19
+
20
+ There are two legitimate ways to drive full user administration (create, update, deactivate, activate, access grant/revoke) — both are covered by this skill:
21
+
22
+ - **SDK — `blocksClient.iam.users.*`** — build the capability **as a feature inside a signed-in admin's own app**: the admin is looking at a screen, clicking "Deactivate" on a specific user row, and their own IAM permissions gate whether the call succeeds.
23
+ - **CLI — `blocks iam users *` / `blocks iam email available`** — the same operations, invoked directly from a terminal or an agent's shell tool. These are fully wired, project-scoped commands (see "CLI surface" below), not a read-only stub — `iam me` is a separate, account-scoped command for the CLI operator's own identity and is not the only IAM command the CLI has.
24
+
25
+ What is **not** legitimate on either surface: an agent deciding on its own, without the human explicitly directing that specific action in the moment, to call `create`/`update`/`deactivate`/`activate`/`updateAccess`/`revokeAccess` (SDK) or `users create`/`update`/`activate`/`deactivate`/`access grant`/`access revoke` (CLI). State the exact change in plain language and get the user's explicit go-ahead first, every time, even if they asked for something adjacent a moment ago. The CLI enforces this mechanically — every mutating command requires `--dry-run` (preview only, no call) or `--yes`/an interactive "yes" before it executes — but that built-in gate doesn't replace stating the change and getting a real go-ahead when an agent is the one typing the command.
26
+
27
+ ## Safe surface — reads and checks, no confirmation needed
28
+
29
+ These don't change anything, so there's no caveat to apply:
30
+
31
+ | Method | What it does |
32
+ |---|---|
33
+ | `iam.users.get(id, { organizationId? })` | One user record, optionally scoped to an org. |
34
+ | `iam.users.list(request)` | Paged/filtered user query. **This is a POST-read contract** — `list` sends `{ pageNo, pageSize, filter, search, ... }` as a POST body, it is not a GET. |
35
+ | `iam.users.emailAvailable(query)` | Public duplicate-email check for invite/signup forms. No auth needed. |
36
+ | `iam.users.exists(email)` | Existence check by email. |
37
+
38
+ ```ts
39
+ const page = await blocksClient.iam.users.list({ pageNo: 1, pageSize: 20, search: "jane" });
40
+ const check = await blocksClient.iam.users.emailAvailable({ email: "new.hire@example.com" });
41
+ ```
42
+
43
+ ## Sensitive surface — confirm the exact change before calling
44
+
45
+ Every method below mutates a real account. Before calling any of them, restate to the user in plain language exactly what will change (which user, which field, which effect) and wait for an explicit yes — do not infer consent from an earlier, more general request.
46
+
47
+ | Method | What it does |
48
+ |---|---|
49
+ | `iam.users.create(request)` | Invites/provisions a user in the active tenant/organization. |
50
+ | `iam.users.update(id, request)` | Edits an IAM profile's fields. |
51
+ | `iam.users.deactivate(request)` | Removes access without deleting the record. |
52
+ | `iam.users.activate(request)` | Restores access for a previously deactivated account. |
53
+ | `iam.users.updateAccess(request)` | Grants or changes roles/permissions/org access for a user. |
54
+ | `iam.users.revokeAccess(request)` | Removes roles/permissions/org access from a user. |
55
+
56
+ Example — deactivating a user:
57
+
58
+ > Agent: "This will deactivate **jane.doe@example.com** (user id `usr_8a2f`) — she'll immediately lose access but her record and history stay intact. Confirm?"
59
+ > User: "Yes, deactivate her."
60
+ > *(only then)* `await blocksClient.iam.users.deactivate({ userId: "usr_8a2f" });`
61
+
62
+ Never chain a mutation straight off a read (e.g. don't look a user up and deactivate them in the same breath just because the user asked to "find inactive-looking accounts") — surface what you found, then get a decision on each mutation separately.
63
+
64
+ ```ts
65
+ // After the user explicitly confirms creating this exact invite:
66
+ await blocksClient.iam.users.create({
67
+ email: "new.hire@example.com",
68
+ firstName: "New",
69
+ lastName: "Hire",
70
+ roles: ["member"]
71
+ });
72
+
73
+ // After the user explicitly confirms this exact access change:
74
+ await blocksClient.iam.users.updateAccess({ userId: "usr_8a2f", roles: ["editor"] });
75
+ ```
76
+
77
+ ## CLI surface — `blocks iam users *`, `blocks iam email available`
78
+
79
+ These are real, fully-wired commands — not a stub and not limited to `iam me`. `iam me` is a separate, account-scoped command (current CLI operator's own identity via the account token); every command below is **project-scoped**: it requires a project already selected (`blocks use <project-tenant-id>`) and calls IAM with an impersonated project token, same as the rest of the project-scoped CLI surface.
80
+
81
+ Reads — no confirmation needed:
82
+
83
+ | Command | What it does |
84
+ |---|---|
85
+ | `blocks iam users list [--page 1] [--page-size 20] [--email <e>] [--name <n>] [--organization-id <id>] [--sort-by <field>] [--sort-desc] [--filter '<json>'] [--json]` | Paged/filtered user query. `--filter` merges a raw JSON object over the convenience flags. |
86
+ | `blocks iam users get <id> [--organization-id <id>] [--json]` | One user record, optionally scoped to an org. |
87
+ | `blocks iam users exists <email> [--json]` | Existence check by email. |
88
+ | `blocks iam email available <email> [--json]` | Duplicate-email check. |
89
+
90
+ Mutations — every one supports `--dry-run` (print the request body and exit, no call) and requires either `--yes` or a typed `yes` at an interactive prompt before it executes:
91
+
92
+ | Command | What it does |
93
+ |---|---|
94
+ | `blocks iam users create --email <e>\|--user-name <n> [--first-name] [--last-name] [--password] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Invites/provisions a user. |
95
+ | `blocks iam users update <id> [--first-name] [--last-name] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Edits an IAM profile's fields. |
96
+ | `blocks iam users activate <userId> [--reason <text>] [--dry-run] [--yes] [--json]` | Restores access for a previously deactivated account. |
97
+ | `blocks iam users deactivate <userId> [--dry-run] [--yes] [--json]` | Removes access without deleting the record. |
98
+ | `blocks iam users access grant <userId> [--roles a,b] [--permissions a,b] [--organization-id] [--dry-run] [--yes] [--json]` | Grants roles/permissions/org access (requires at least one of `--roles`/`--permissions`). |
99
+ | `blocks iam users access revoke <userId> [--organization-id] [--dry-run] [--yes] [--json]` | Revokes org access for a user. |
100
+
101
+ Command segments joined by a space also accept a colon (`iam:users:access:grant` etc.) — both forms resolve to the same handler; `blocks iam users --help`-style docs in the CLI's own `--help` output use the space form shown above.
102
+
103
+ Example — deactivating a user from the CLI, dry-run first:
104
+
105
+ ```sh
106
+ blocks iam users deactivate usr_8a2f --dry-run # preview the request body, no call made
107
+ blocks iam users deactivate usr_8a2f --yes # after the user explicitly confirms
108
+ ```
109
+
110
+ Apply the same confirm-before-mutating discipline here as with the SDK: state which user and which effect, wait for an explicit yes, don't chain a mutating command straight off a `list`/`get` just because the user asked to "find" something.
111
+
112
+ ## Gotchas
113
+
114
+ - **`list` is a POST**, not a GET — don't assume query-string filtering.
115
+ - **Roles are referenced by slug**, as defined in blocks-iam-access-control — not by their internal item ids.
116
+ - **`organizationId`** matters in multi-org projects — pass it to `get` when you need a user's record in a specific org context.
117
+ - **Every request/response type in the SDK is a loosely-typed `Record<string, unknown>`** (`BlocksUser`, `BlocksBaseResponse`, etc. only guarantee a few common fields) — treat fields defensively and confirm shape against a live response for the project rather than assuming a fixed schema.
118
+ - **The CLI is project-scoped, not account-scoped** — `blocks iam users *`/`blocks iam email available` need a selected project (`blocks use <project-tenant-id>`) and use an impersonated project token; `iam me` is the one exception that runs on the account token instead.
119
+ - **Don't duplicate blocks-iam-account** — if the ask is "let me update my own profile" or "let me reset my password," that's the current user acting on themselves, not this skill.
120
+
121
+ ## Example triggers
122
+
123
+ - "Invite a user and set their roles"
124
+ - "Deactivate this user's account"
125
+ - "List all users in the org, filtered by status"
126
+ - "Check if this email is already registered before I show the invite form"
127
+ - "Grant this user the editor role"
128
+ - "Revoke this user's access to the finance org"
129
+ - "Update this user's phone number"
130
+ - "Reactivate this account"
131
+ - "From the terminal, deactivate user usr_8a2f in the current project" → use `blocks iam users deactivate usr_8a2f`, `--dry-run` first, then `--yes` after explicit confirmation
@@ -1,149 +1,149 @@
1
- ---
2
- name: blocks-localization-configuration
3
- description: "Configure app translations (i18n) for a SELISE Blocks project through the `blocks` CLI — never raw fetch/curl. Covers authoring local i18n JSON dictionaries, validate/push/pull with the Localization service, managing languages and modules directly, glossary terms, AI translation suggestions, and the composed translate-and-export flow. Use for 'add translations for my login screen', 'push/pull localization changes', 'create a module', 'add a new language'."
4
- ---
5
-
6
- # Blocks Localization — Configuration
7
-
8
- Translations (i18n) for a Blocks project's static UI text — labels, titles, button copy — are authored locally as JSON and synced to the Localization service entirely through the `blocks` CLI. There is no supported reason to hand-roll raw `fetch`/`curl` calls anymore, and there's no SDK-based authoring path either — `@seliseblocks/client`'s localization surface (`languages()`, `modules()`, `languagesForCurrentTenant()`, `translations()`, `cloudTranslations()`, `keysByNames()`) is entirely **read-only**, meant for apps to *consume* translations at runtime, not to author them. Authoring is CLI-only.
9
-
10
- **Prerequisite:** `blocks init` has been run and a project is selected (`blocks use <tenantId>`). Note `blocks init` does **not** create a `blocks/localization/` folder — it only scaffolds `blocks/data/schemas/`, `blocks/data/rules.json`, and `.env.example`. The `blocks/localization/` directory and its dictionary files come into existence lazily, the first time `blocks localization pull` writes one out (or the first time you author one by hand). If either the project selection is missing, or auth state is unknown, run the blocks-onboarding skill first — it covers `auth status` probing, login, and project selection in detail; this skill assumes that's already done.
11
-
12
- ## The three commands
13
-
14
- | Command | What it does |
15
- |---|---|
16
- | `blocks localization validate --module <name> --language <culture> [--file <path>] [--json]` | Validates a local i18n JSON dictionary. **Local-only, no API call.** |
17
- | `blocks localization push --module <name> --language <culture> [--file <path>] [--route <route>] [--context <text>] [--dry-run] [--yes] [--json]` | Creates/updates keys from the local dictionary. If the module doesn't exist yet, creates it first — **this is the only way this tooling creates a module.** Mutating. |
18
- | `blocks localization pull --module <name> --language <culture> [--out <path>] [--json]` | Downloads the **published** cloud dictionary into a local JSON file. Read-only, overwrites the local file. |
19
-
20
- `--module` is the feature-area bundle name (`common`, `login`, `dashboard`, …). `--language` is a culture code (`en`, `de-DE`, `bn-BD`, …) — see the culture-matching gotcha below before picking one.
21
-
22
- ## File convention
23
-
24
- Local dictionaries default to:
25
-
26
- ```text
27
- blocks/localization/<module>.<language>.json
28
- ```
29
-
30
- for example `blocks/localization/login.de-DE.json`. Pass `--file`/`--out` to override the path. Content is a flat or nested JSON object of string values — nested objects are flattened with `.` before validation/push, so either of these is fine and produces the same keys:
31
-
32
- ```json
33
- { "login.title": "Anmelden", "login.submit": "Absenden" }
34
- ```
35
-
36
- ```json
37
- { "login": { "title": "Anmelden", "submit": "Absenden" } }
38
- ```
39
-
40
- Key names must match `^[A-Za-z0-9][A-Za-z0-9._:-]*$` (letters, numbers, dot, dash, underscore, colon — no spaces) after flattening, and every value must be a non-empty string. `localization validate` enforces exactly this, locally, before anything touches the network.
41
-
42
- ## Workflow: add or update translations
43
-
44
- 1. **Generate or edit the local dictionary** at `blocks/localization/<module>.<language>.json` — write the JSON yourself (nested or flat), covering every key the screen/feature needs.
45
- 2. **Validate locally, no API call:**
46
- ```bash
47
- blocks localization validate --module login --language de-DE --json
48
- ```
49
- Fix every flagged key/value before moving on.
50
- 3. **Dry-run the push** to see exactly what would happen (module create-or-reuse, key count, target project):
51
- ```bash
52
- blocks localization push --module login --language de-DE --dry-run --json
53
- ```
54
- 4. **Get user approval, then push for real:**
55
- ```bash
56
- blocks localization push --module login --language de-DE --yes --json
57
- ```
58
- This is mutating. Never skip straight to `--yes`. Every key in the file is saved as immediately published, so a successful push is live for reads right away — there is no separate "generate/publish" step in this CLI.
59
-
60
- Optional flags on `push`: `--route <route>` tags every key in this push with one route (e.g. the screen path the strings belong to); `--context <text>` attaches one context/hint string to every key in the push — both apply to the whole file, not per-key.
61
-
62
- ## Workflow: multiple languages for the same screen
63
-
64
- Each `push` call carries exactly one `--language` (one culture stamped onto every key in that file). Translating one module into several languages means **one dictionary file and one push per language**, all against the same `--module`:
65
-
66
- ```bash
67
- blocks localization validate --module login --language de-DE --json
68
- blocks localization validate --module login --language bn-BD --json
69
- blocks localization push --module login --language de-DE --dry-run --json
70
- blocks localization push --module login --language bn-BD --dry-run --json
71
- # after approval:
72
- blocks localization push --module login --language de-DE --yes --json
73
- blocks localization push --module login --language bn-BD --yes --json
74
- ```
75
-
76
- The module (`login`) is only created on the *first* push that needs it; the second push reuses the module the first one created.
77
-
78
- ## Refreshing local files from the cloud
79
-
80
- ```bash
81
- blocks localization pull --module login --language de-DE --out blocks/localization/login.de-DE.json --json
82
- ```
83
-
84
- Use this to pull down what's actually published before editing further — same reasoning as pulling data schemas before editing them: don't blindly overwrite translations someone else edited in the portal or in a prior session.
85
-
86
- ## Managing languages directly
87
-
88
- Pushing translations into an existing language and *configuring the tenant's set of languages* are different operations — the latter has its own standalone commands, independent of `push`/`pull`:
89
-
90
- | Command | What it does |
91
- |---|---|
92
- | `blocks localization language save --language-name <n> --language-code <c> [--is-default] [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a language. Omit `--item-id` to create a new one. Mutating, full dry-run/confirm gate. |
93
- | `blocks localization language delete <languageName> [--dry-run] [--yes] [--json]` | Deletes a language. Mutating. |
94
- | `blocks localization language set-default <languageName> [--dry-run] [--yes] [--json]` | Marks a language as the tenant default. Mutating. |
95
- | `blocks localization language list [--json]` | Lists all languages. Read-only. |
96
- | `blocks localization language list-for-tenant [--json]` | Lists languages configured for the current tenant. Read-only. |
97
-
98
- So "add German as a supported language for the tenant" is a real, supported request: `blocks localization language save --language-name German --language-code de-DE --dry-run`, get approval, then re-run with `--yes`. This is distinct from `localization push --language de-DE`, which stamps translations onto keys and doesn't touch the tenant's language configuration at all.
99
-
100
- ## Managing modules directly
101
-
102
- A module is still created implicitly by the first `localization push` into it, but it can also be created or updated on its own, with no keys, via a standalone command:
103
-
104
- | Command | What it does |
105
- |---|---|
106
- | `blocks localization module save --module-name <n> [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a module. Omit `--item-id` to create a new one. Mutating. |
107
- | `blocks localization module list [--json]` | Lists all modules. Read-only. |
108
- | `blocks localization module list-for-tenant [--json]` | Lists modules configured for the current tenant. Read-only. |
109
-
110
- So "create a `billing` module with no keys yet" is directly supported: `blocks localization module save --module-name billing --dry-run` → approve → `--yes`. `localization push`'s implicit module creation is just a convenience on top of the same underlying call, not the only path to it.
111
-
112
- ## Other localization commands
113
-
114
- A few more commands round out the surface beyond push/pull/validate/language/module — useful, but secondary to the core authoring workflow above:
115
-
116
- | Command | What it does |
117
- |---|---|
118
- | `blocks localization key translate-and-export --module-id <id> [--wait] [--dry-run] [--yes] [--json]` | Composed flow: `translate-all` (machine-translates every untranslated key in the module) → if `--wait`, polls until the operation settles → `generate-uilm-file` → `uilm-export`. Without `--wait` the three steps just fire back-to-back. Mutating. |
119
- | `blocks localization glossary save --name <n> [--item-id <id>] [--language <c>] [--type <t>] [--context <text>] [--additional-note <text>] [--is-global] [--module-ids a,b] [--dry-run] [--yes] [--json]` | Creates or updates a glossary term. Mutating. |
120
- | `blocks localization glossary list [--search <text>] [--module-id <id>] [--is-global] [--page-number <n>] [--page-size <n>] [--json]` | Lists glossary terms. Read-only. |
121
- | `blocks localization glossary get <itemId> [--json]` | Fetches one glossary term. Read-only. |
122
- | `blocks localization glossary suggested <itemId> [--max-results <n>] [--json]` | Suggests glossary terms relevant to an item. Read-only. |
123
- | `blocks localization glossary delete <itemId> [--dry-run] [--yes] [--json]` | Deletes a glossary term. Mutating. |
124
- | `blocks localization assistant translation-suggestion --source-text <text> [--current-language <c>] [--destination-language <name>] [--destination-language-code <c>] [--module-id <id>] [--glossary-ids a,b] [--element-type <t>] [--element-application-context <text>] [--element-detail-context <text>] [--max-character-length <n>] [--temperature <n>] [--json]` | Gets an AI translation suggestion for a single string. Read-only (no confirm gate). |
125
- | `blocks localization config get-webhook [--json]` | Gets the tenant's localization webhook config. Read-only. |
126
- | `blocks localization config save-webhook --url <url> --content-type <type> --secret <s> --header-key <k> [--is-disabled] [--item-id <id>] [--dry-run] [--yes] [--json]` | Saves the tenant's localization webhook config. Mutating; the secret is redacted in `--dry-run` output. |
127
-
128
- ## Gotchas
129
-
130
- - **`--dry-run` before `--yes`** on `localization push` — always. Same pattern as every other mutating `blocks` command.
131
- - **`--language` on `push` is not validated against configured cultures.** `localization push` stamps whatever string you pass as `--language` directly into each key's `culture` field — it does not check that culture against the tenant's actual configured languages, and doesn't call `language list`/`list-for-tenant` to look. Get the culture code wrong (`de` instead of `de-DE`, or a culture the tenant never configured via `language save`) and the key saves without error but may never surface at runtime, because runtime lookups match by the tenant's real configured `languageCode`. Confirm the exact culture code with the user (or run `localization language list-for-tenant`) before pushing, especially for less common languages like Bengali (`bn-BD` vs `bn`).
132
- - **Module auto-create is silent and permanent.** The first push against a new `--module` name creates it with no separate confirmation prompt beyond the push's own `--dry-run`/`--yes` gate — `--dry-run` output will tell you a module lookup happened, but won't distinguish "will create" from "already exists" as clearly as it could, so read the dry-run JSON's module info carefully, or ask the user to confirm the module name is intentional (typos become new, mostly-empty modules). Prefer `localization module save` first if the user wants the module created deliberately, without an accompanying key push.
133
- - **`localization validate` is local-only** — it confirms the JSON is well-formed and keys/values pass the naming rules; it does not confirm the push will succeed against the server (module resolution, auth, project selection). Still run `--dry-run` on the actual push.
134
- - **One culture per file/push.** Don't try to cram multiple languages into one dictionary file — the format is flat key → string value, not key → {culture: value}. Multiple languages means multiple files and multiple push invocations (see above).
135
- - **No standalone "generate" or "publish" step for `push`.** `shouldPublish: true` is baked into every key `localization push` sends — once the push succeeds, the translations are live. That's separate from `key generate-uilm-file`/`key uilm-export` (and the composed `key translate-and-export`), which build downloadable runtime language files rather than affect `push`'s own publish behavior.
136
- - **`language delete` and `set-default` are permanent, mutating calls** — always dry-run first, and confirm with the user before deleting a language or changing the tenant default, since either can affect what's visible at runtime for existing translations.
137
-
138
- ## Example trigger prompts
139
-
140
- - "Add German translations for my login screen." → push `login.de-DE.json` after validate + dry-run + approval.
141
- - "Add German and Bengali translations for my login screen." → two dictionary files, two validate/push pairs (`de-DE`, `bn-BD`), same module.
142
- - "Set up a `common` module for shared strings like Save/Cancel/Delete." → write `common.<language>.json` with those keys, validate, push (this is what creates the `common` module) — or use `localization module save --module-name common` directly if no keys exist yet.
143
- - "Pull the latest translations for the dashboard module before I edit them." → `localization pull --module dashboard --language en`.
144
- - "Validate my localization file before pushing." → `localization validate` only, no network call.
145
- - "Can we add Bengali as a new supported language for the tenant?" → `localization language save --language-name Bengali --language-code bn-BD --dry-run`, confirm, then `--yes`.
146
- - "Create a new translation module called `billing` with no keys yet." → `localization module save --module-name billing --dry-run` → confirm → `--yes`.
147
- - "Machine-translate the whole `login` module and give me the export." → `localization key translate-and-export --module-id <id> --wait --dry-run` → confirm → `--yes`.
148
- - "Suggest a translation for this button label." → `localization assistant translation-suggestion --source-text "Save changes" --destination-language-code de-DE`.
149
- - "What's our webhook config for localization events?" → `localization config get-webhook`.
1
+ ---
2
+ name: blocks-localization-configuration
3
+ description: "Configure app translations (i18n) for a SELISE Blocks project through the `blocks` CLI — never raw fetch/curl. Covers authoring local i18n JSON dictionaries, validate/push/pull with the Localization service, managing languages and modules directly, glossary terms, AI translation suggestions, and the composed translate-and-export flow. Use for 'add translations for my login screen', 'push/pull localization changes', 'create a module', 'add a new language'."
4
+ ---
5
+
6
+ # Blocks Localization — Configuration
7
+
8
+ Translations (i18n) for a Blocks project's static UI text — labels, titles, button copy — are authored locally as JSON and synced to the Localization service entirely through the `blocks` CLI. There is no supported reason to hand-roll raw `fetch`/`curl` calls anymore, and there's no SDK-based authoring path either — `@seliseblocks/client`'s localization surface (`languages()`, `modules()`, `languagesForCurrentTenant()`, `translations()`, `cloudTranslations()`, `keysByNames()`) is entirely **read-only**, meant for apps to *consume* translations at runtime, not to author them. Authoring is CLI-only.
9
+
10
+ **Prerequisite:** `blocks init` has been run and a project is selected (`blocks use <tenantId>`). Note `blocks init` does **not** create a `blocks/localization/` folder — it only scaffolds `blocks/data/schemas/`, `blocks/data/rules.json`, and `.env.example`. The `blocks/localization/` directory and its dictionary files come into existence lazily, the first time `blocks localization pull` writes one out (or the first time you author one by hand). If either the project selection is missing, or auth state is unknown, run the blocks-onboarding skill first — it covers `auth status` probing, login, and project selection in detail; this skill assumes that's already done.
11
+
12
+ ## The three commands
13
+
14
+ | Command | What it does |
15
+ |---|---|
16
+ | `blocks localization validate --module <name> --language <culture> [--file <path>] [--json]` | Validates a local i18n JSON dictionary. **Local-only, no API call.** |
17
+ | `blocks localization push --module <name> --language <culture> [--file <path>] [--route <route>] [--context <text>] [--dry-run] [--yes] [--json]` | Creates/updates keys from the local dictionary. If the module doesn't exist yet, creates it first — **this is the only way this tooling creates a module.** Mutating. |
18
+ | `blocks localization pull --module <name> --language <culture> [--out <path>] [--json]` | Downloads the **published** cloud dictionary into a local JSON file. Read-only, overwrites the local file. |
19
+
20
+ `--module` is the feature-area bundle name (`common`, `login`, `dashboard`, …). `--language` is a culture code (`en`, `de-DE`, `bn-BD`, …) — see the culture-matching gotcha below before picking one.
21
+
22
+ ## File convention
23
+
24
+ Local dictionaries default to:
25
+
26
+ ```text
27
+ blocks/localization/<module>.<language>.json
28
+ ```
29
+
30
+ for example `blocks/localization/login.de-DE.json`. Pass `--file`/`--out` to override the path. Content is a flat or nested JSON object of string values — nested objects are flattened with `.` before validation/push, so either of these is fine and produces the same keys:
31
+
32
+ ```json
33
+ { "login.title": "Anmelden", "login.submit": "Absenden" }
34
+ ```
35
+
36
+ ```json
37
+ { "login": { "title": "Anmelden", "submit": "Absenden" } }
38
+ ```
39
+
40
+ Key names must match `^[A-Za-z0-9][A-Za-z0-9._:-]*$` (letters, numbers, dot, dash, underscore, colon — no spaces) after flattening, and every value must be a non-empty string. `localization validate` enforces exactly this, locally, before anything touches the network.
41
+
42
+ ## Workflow: add or update translations
43
+
44
+ 1. **Generate or edit the local dictionary** at `blocks/localization/<module>.<language>.json` — write the JSON yourself (nested or flat), covering every key the screen/feature needs.
45
+ 2. **Validate locally, no API call:**
46
+ ```bash
47
+ blocks localization validate --module login --language de-DE --json
48
+ ```
49
+ Fix every flagged key/value before moving on.
50
+ 3. **Dry-run the push** to see exactly what would happen (module create-or-reuse, key count, target project):
51
+ ```bash
52
+ blocks localization push --module login --language de-DE --dry-run --json
53
+ ```
54
+ 4. **Get user approval, then push for real:**
55
+ ```bash
56
+ blocks localization push --module login --language de-DE --yes --json
57
+ ```
58
+ This is mutating. Never skip straight to `--yes`. Every key in the file is saved as immediately published, so a successful push is live for reads right away — there is no separate "generate/publish" step in this CLI.
59
+
60
+ Optional flags on `push`: `--route <route>` tags every key in this push with one route (e.g. the screen path the strings belong to); `--context <text>` attaches one context/hint string to every key in the push — both apply to the whole file, not per-key.
61
+
62
+ ## Workflow: multiple languages for the same screen
63
+
64
+ Each `push` call carries exactly one `--language` (one culture stamped onto every key in that file). Translating one module into several languages means **one dictionary file and one push per language**, all against the same `--module`:
65
+
66
+ ```bash
67
+ blocks localization validate --module login --language de-DE --json
68
+ blocks localization validate --module login --language bn-BD --json
69
+ blocks localization push --module login --language de-DE --dry-run --json
70
+ blocks localization push --module login --language bn-BD --dry-run --json
71
+ # after approval:
72
+ blocks localization push --module login --language de-DE --yes --json
73
+ blocks localization push --module login --language bn-BD --yes --json
74
+ ```
75
+
76
+ The module (`login`) is only created on the *first* push that needs it; the second push reuses the module the first one created.
77
+
78
+ ## Refreshing local files from the cloud
79
+
80
+ ```bash
81
+ blocks localization pull --module login --language de-DE --out blocks/localization/login.de-DE.json --json
82
+ ```
83
+
84
+ Use this to pull down what's actually published before editing further — same reasoning as pulling data schemas before editing them: don't blindly overwrite translations someone else edited in the portal or in a prior session.
85
+
86
+ ## Managing languages directly
87
+
88
+ Pushing translations into an existing language and *configuring the tenant's set of languages* are different operations — the latter has its own standalone commands, independent of `push`/`pull`:
89
+
90
+ | Command | What it does |
91
+ |---|---|
92
+ | `blocks localization language save --language-name <n> --language-code <c> [--is-default] [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a language. Omit `--item-id` to create a new one. Mutating, full dry-run/confirm gate. |
93
+ | `blocks localization language delete <languageName> [--dry-run] [--yes] [--json]` | Deletes a language. Mutating. |
94
+ | `blocks localization language set-default <languageName> [--dry-run] [--yes] [--json]` | Marks a language as the tenant default. Mutating. |
95
+ | `blocks localization language list [--json]` | Lists all languages. Read-only. |
96
+ | `blocks localization language list-for-tenant [--json]` | Lists languages configured for the current tenant. Read-only. |
97
+
98
+ So "add German as a supported language for the tenant" is a real, supported request: `blocks localization language save --language-name German --language-code de-DE --dry-run`, get approval, then re-run with `--yes`. This is distinct from `localization push --language de-DE`, which stamps translations onto keys and doesn't touch the tenant's language configuration at all.
99
+
100
+ ## Managing modules directly
101
+
102
+ A module is still created implicitly by the first `localization push` into it, but it can also be created or updated on its own, with no keys, via a standalone command:
103
+
104
+ | Command | What it does |
105
+ |---|---|
106
+ | `blocks localization module save --module-name <n> [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a module. Omit `--item-id` to create a new one. Mutating. |
107
+ | `blocks localization module list [--json]` | Lists all modules. Read-only. |
108
+ | `blocks localization module list-for-tenant [--json]` | Lists modules configured for the current tenant. Read-only. |
109
+
110
+ So "create a `billing` module with no keys yet" is directly supported: `blocks localization module save --module-name billing --dry-run` → approve → `--yes`. `localization push`'s implicit module creation is just a convenience on top of the same underlying call, not the only path to it.
111
+
112
+ ## Other localization commands
113
+
114
+ A few more commands round out the surface beyond push/pull/validate/language/module — useful, but secondary to the core authoring workflow above:
115
+
116
+ | Command | What it does |
117
+ |---|---|
118
+ | `blocks localization key translate-and-export --module-id <id> [--wait] [--dry-run] [--yes] [--json]` | Composed flow: `translate-all` (machine-translates every untranslated key in the module) → if `--wait`, polls until the operation settles → `generate-uilm-file` → `uilm-export`. Without `--wait` the three steps just fire back-to-back. Mutating. |
119
+ | `blocks localization glossary save --name <n> [--item-id <id>] [--language <c>] [--type <t>] [--context <text>] [--additional-note <text>] [--is-global] [--module-ids a,b] [--dry-run] [--yes] [--json]` | Creates or updates a glossary term. Mutating. |
120
+ | `blocks localization glossary list [--search <text>] [--module-id <id>] [--is-global] [--page-number <n>] [--page-size <n>] [--json]` | Lists glossary terms. Read-only. |
121
+ | `blocks localization glossary get <itemId> [--json]` | Fetches one glossary term. Read-only. |
122
+ | `blocks localization glossary suggested <itemId> [--max-results <n>] [--json]` | Suggests glossary terms relevant to an item. Read-only. |
123
+ | `blocks localization glossary delete <itemId> [--dry-run] [--yes] [--json]` | Deletes a glossary term. Mutating. |
124
+ | `blocks localization assistant translation-suggestion --source-text <text> [--current-language <c>] [--destination-language <name>] [--destination-language-code <c>] [--module-id <id>] [--glossary-ids a,b] [--element-type <t>] [--element-application-context <text>] [--element-detail-context <text>] [--max-character-length <n>] [--temperature <n>] [--json]` | Gets an AI translation suggestion for a single string. Read-only (no confirm gate). |
125
+ | `blocks localization config get-webhook [--json]` | Gets the tenant's localization webhook config. Read-only. |
126
+ | `blocks localization config save-webhook --url <url> --content-type <type> --secret <s> --header-key <k> [--is-disabled] [--item-id <id>] [--dry-run] [--yes] [--json]` | Saves the tenant's localization webhook config. Mutating; the secret is redacted in `--dry-run` output. |
127
+
128
+ ## Gotchas
129
+
130
+ - **`--dry-run` before `--yes`** on `localization push` — always. Same pattern as every other mutating `blocks` command.
131
+ - **`--language` on `push` is not validated against configured cultures.** `localization push` stamps whatever string you pass as `--language` directly into each key's `culture` field — it does not check that culture against the tenant's actual configured languages, and doesn't call `language list`/`list-for-tenant` to look. Get the culture code wrong (`de` instead of `de-DE`, or a culture the tenant never configured via `language save`) and the key saves without error but may never surface at runtime, because runtime lookups match by the tenant's real configured `languageCode`. Confirm the exact culture code with the user (or run `localization language list-for-tenant`) before pushing, especially for less common languages like Bengali (`bn-BD` vs `bn`).
132
+ - **Module auto-create is silent and permanent.** The first push against a new `--module` name creates it with no separate confirmation prompt beyond the push's own `--dry-run`/`--yes` gate — `--dry-run` output will tell you a module lookup happened, but won't distinguish "will create" from "already exists" as clearly as it could, so read the dry-run JSON's module info carefully, or ask the user to confirm the module name is intentional (typos become new, mostly-empty modules). Prefer `localization module save` first if the user wants the module created deliberately, without an accompanying key push.
133
+ - **`localization validate` is local-only** — it confirms the JSON is well-formed and keys/values pass the naming rules; it does not confirm the push will succeed against the server (module resolution, auth, project selection). Still run `--dry-run` on the actual push.
134
+ - **One culture per file/push.** Don't try to cram multiple languages into one dictionary file — the format is flat key → string value, not key → {culture: value}. Multiple languages means multiple files and multiple push invocations (see above).
135
+ - **No standalone "generate" or "publish" step for `push`.** `shouldPublish: true` is baked into every key `localization push` sends — once the push succeeds, the translations are live. That's separate from `key generate-uilm-file`/`key uilm-export` (and the composed `key translate-and-export`), which build downloadable runtime language files rather than affect `push`'s own publish behavior.
136
+ - **`language delete` and `set-default` are permanent, mutating calls** — always dry-run first, and confirm with the user before deleting a language or changing the tenant default, since either can affect what's visible at runtime for existing translations.
137
+
138
+ ## Example trigger prompts
139
+
140
+ - "Add German translations for my login screen." → push `login.de-DE.json` after validate + dry-run + approval.
141
+ - "Add German and Bengali translations for my login screen." → two dictionary files, two validate/push pairs (`de-DE`, `bn-BD`), same module.
142
+ - "Set up a `common` module for shared strings like Save/Cancel/Delete." → write `common.<language>.json` with those keys, validate, push (this is what creates the `common` module) — or use `localization module save --module-name common` directly if no keys exist yet.
143
+ - "Pull the latest translations for the dashboard module before I edit them." → `localization pull --module dashboard --language en`.
144
+ - "Validate my localization file before pushing." → `localization validate` only, no network call.
145
+ - "Can we add Bengali as a new supported language for the tenant?" → `localization language save --language-name Bengali --language-code bn-BD --dry-run`, confirm, then `--yes`.
146
+ - "Create a new translation module called `billing` with no keys yet." → `localization module save --module-name billing --dry-run` → confirm → `--yes`.
147
+ - "Machine-translate the whole `login` module and give me the export." → `localization key translate-and-export --module-id <id> --wait --dry-run` → confirm → `--yes`.
148
+ - "Suggest a translation for this button label." → `localization assistant translation-suggestion --source-text "Save changes" --destination-language-code de-DE`.
149
+ - "What's our webhook config for localization events?" → `localization config get-webhook`.