@seliseblocks/cli-os 0.2.7 → 0.2.9

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.
Files changed (42) hide show
  1. package/AI_USAGE_GUIDE.md +560 -560
  2. package/LICENSE +21 -21
  3. package/README.md +173 -173
  4. package/bin/run.js +2 -2
  5. package/dist/commands/data/rules/deploy.js +84 -9
  6. package/dist/commands/data/rules/pull.js +13 -7
  7. package/dist/commands/data/schema/get.js +31 -0
  8. package/dist/commands/data/schema/list.js +8 -1
  9. package/dist/commands/data/schema/pull.js +22 -9
  10. package/dist/commands/data/schema/push.js +31 -9
  11. package/dist/index.js +702 -692
  12. package/dist/lib/data-files.d.ts +14 -0
  13. package/dist/lib/data-files.js +91 -0
  14. package/dist/lib/data-response.d.ts +13 -0
  15. package/dist/lib/data-response.js +26 -0
  16. package/dist/skills/blocks-data-gateway-configuration/SKILL.md +204 -204
  17. package/dist/skills/blocks-data-gateway-crud/SKILL.md +223 -223
  18. package/dist/skills/blocks-data-storage/SKILL.md +253 -253
  19. package/dist/skills/blocks-data-storage/flows/object-management.md +124 -124
  20. package/dist/skills/blocks-frontend-local-https/SKILL.md +100 -100
  21. package/dist/skills/blocks-iam-access-control/SKILL.md +49 -49
  22. package/dist/skills/blocks-iam-access-control/flows/feature-gating.md +38 -38
  23. package/dist/skills/blocks-iam-access-control/flows/manage-roles-permissions.md +110 -110
  24. package/dist/skills/blocks-iam-account/SKILL.md +169 -169
  25. package/dist/skills/blocks-iam-mfa/SKILL.md +124 -124
  26. package/dist/skills/blocks-iam-organizations/SKILL.md +43 -43
  27. package/dist/skills/blocks-iam-organizations/flows/admin-mutations.md +89 -89
  28. package/dist/skills/blocks-iam-organizations/flows/read-and-switch.md +57 -57
  29. package/dist/skills/blocks-iam-sso-oidc-configuration/SKILL.md +105 -105
  30. package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +80 -80
  31. package/dist/skills/blocks-iam-users/SKILL.md +131 -131
  32. package/dist/skills/blocks-localization-configuration/SKILL.md +149 -149
  33. package/dist/skills/blocks-localization-implementation/SKILL.md +63 -63
  34. package/dist/skills/blocks-mail/SKILL.md +95 -95
  35. package/dist/skills/blocks-notification/SKILL.md +69 -69
  36. package/dist/skills/blocks-notifier/SKILL.md +107 -107
  37. package/dist/skills/blocks-onboarding/SKILL.md +77 -78
  38. package/dist/skills/blocks-release-deployment/SKILL.md +81 -81
  39. package/dist/skills/blocks-secrets/SKILL.md +81 -81
  40. package/dist/skills/blocks-storage-configuration/SKILL.md +93 -93
  41. package/dist/skills/lint.mjs +168 -168
  42. package/package.json +47 -47
@@ -1,63 +1,63 @@
1
- ---
2
- name: blocks-localization-implementation
3
- description: "Consume SELISE Blocks localization at runtime in a scaffolded frontend, entirely through the `@seliseblocks/client` SDK's `localization` namespace — never raw fetch/curl. Use for making a Blocks web app multilingual on the client: language/module discovery, loading dictionaries, the built-in `t()` lookup, and a language switcher that reloads and re-renders. Frontend consumption only — authoring/pushing translation content is the sibling skill blocks-localization-configuration."
4
- ---
5
-
6
- # Blocks Localization — Implementation (frontend)
7
-
8
- Make a scaffolded Blocks web app render its UI in the user's language, using only the `localization` namespace on the SDK client — `createBlocksClient(...).localization`. No fetch, no manual query strings, no hand-rolled caching: the SDK client already does all of that.
9
-
10
- The translations themselves (keys, modules, per-language values) are authored and pushed with the blocks-localization-configuration skill (uses `blocks localization *`). This skill only covers loading and rendering them in the app.
11
-
12
- ## The client and its cache
13
-
14
- `localization` is a `BlocksLocalizationClient` instance created once inside `createBlocksClient()`. It holds its dictionary cache as instance state — a `Map` keyed by `` `${language}:${moduleName}` `` plus an `activeLanguage`. That cache is **only as shared as the client instance is**: the canonical scaffold creates one `blocksClient` singleton (`src/lib/blocks/client.ts`, from `createBlocksClient()`) and imports it everywhere, so every `t()` call sees every dictionary loaded anywhere in the app. If you instantiate a second `createBlocksClient()` somewhere, it gets its own empty cache — don't do that; import the one singleton.
15
-
16
- ## Public vs. authenticated methods
17
-
18
- Every method has a matching pair — one public, one tenant/session-scoped:
19
-
20
- | Public (no token sent) | Authenticated (sends token when configured) |
21
- |---|---|
22
- | `languages()` | `languagesForCurrentTenant()` |
23
- | `modules()` | `modulesForCurrentTenant()` |
24
- | `translations(moduleName, language)` | `cloudTranslations(moduleName, language)` |
25
- | `load(language, modules[])` | `loadCloud(language, modules[])` |
26
-
27
- - `languages()`/`modules()`/`translations()`/`load()` are explicitly public — all tenant-supported cultures and translation bundles, usable pre-login for a language picker or startup locale selection.
28
- - `languagesForCurrentTenant()`/`modulesForCurrentTenant()`/`cloudTranslations()`/`loadCloud()` are scoped to whichever tenant the active `x-blocks-key` resolves to (and send the caller's access token if the client is configured with one) — use these for protected, tenant-specific dictionaries behind a signed-in session.
29
-
30
- `keysByNames({ keyNames, moduleId? })` fetches specific key records (metadata/translations) without downloading a whole module dictionary; useful for a one-off label or an admin screen that inspects individual keys.
31
-
32
- **Argument order matters**: `translations(moduleName, language)` and `cloudTranslations(moduleName, language)` take module first, language second — easy to transpose.
33
-
34
- ## Startup sequence
35
-
36
- 1. **List languages** on app boot (or in a query hook) — `blocksClient.localization.languagesForCurrentTenant()` (or `languages()` if you want it available pre-login). Use `isDefault`/`languageCode` from the result to build the picker and preselect a default, falling back to a persisted user choice (e.g. `localStorage`).
37
- 2. **Load dictionaries** for the active language — `blocksClient.localization.load(language, modules)` where `modules` is the list of bundles the app needs (e.g. `["common", "dashboard", "assets"]`). `load()` fetches each module's dictionary in parallel via `translations()` and merges them into one object, with later modules in the array overwriting earlier ones on key collision. Use `loadCloud()` instead for protected dictionaries once the user is signed in.
38
- 3. **Render labels** with `blocksClient.localization.t(key, fallback, { language, moduleName })`. It reads from dictionaries already loaded by `translations()`/`load()` (or their cloud equivalents) — it does not fetch anything itself. Missing key → `fallback` → the raw key, in that order.
39
- 4. **Switch language**: on switcher change, call `load()` (or `loadCloud()`) again with the new language and the same module list, then re-render. There's no separate "invalidate" step — loading a language populates its own cache entries; you don't need to clear the old language's entries (they just stop being read once `activeLanguage`/your app state moves on).
40
-
41
- The canonical scaffold (`blocks new web`) wires exactly this pattern in `src/lib/i18n/LocalizationProvider.tsx`: a React context holds `language` state (seeded from `localStorage`), a `useEffect` on `language` calls `blocksClient.localization.load(language, MODULES)` and stores the merged dictionary in state, and `t(key, fallback)` reads `cloudDictionary[key] ?? defaultDictionary[key] ?? fallback ?? key` — layering the network dictionary over a build-time `defaultDictionary` (from `src/lib/i18n/dictionary.ts`, generated from the same keys as the seed JSON in `blocks/localization/*.json`) as an offline/first-paint safety net, itself falling back to the caller-supplied fallback and finally the key. Mirror this shape rather than inventing your own provider — it's already generated into new projects. Note the scaffold's own `t()` is a plain function on context, not the SDK's `localization.t()` — either is fine; the SDK's built-in `t()` needs no separate context/provider if you're happy reading `blocksClient.localization.t(...)` directly in components.
42
-
43
- ## `t()` lookup details worth knowing
44
-
45
- - If you pass `moduleName`, `t()` does an exact `` `${language}:${moduleName}` `` cache lookup — deterministic.
46
- - If you omit `moduleName`, `t()` scans all cached dictionaries and returns the first match whose cache key starts with `` `${language}:` `` (or any language if you didn't pass one). Scan order follows Map insertion order, which is the order the underlying HTTP requests *resolved* in (not necessarily the order you listed modules in `load()`) — fine when keys are unique across modules, ambiguous if two modules define the same key. Pass `moduleName` explicitly whenever you know it and key collisions across modules are possible.
47
- - `t()` never throws and never fetches — call `load()`/`translations()` (or the cloud variants) first, or every lookup falls straight to `fallback`/the key.
48
-
49
- ## Gotchas
50
-
51
- - **No raw fetch/curl, ever** — every read here goes through `blocksClient.localization.*`. The SDK already sends `x-blocks-key` and, for the authenticated variants, the caller's access token when configured.
52
- - **One client, one cache** — don't call `createBlocksClient()` more than once in the app; import the scaffold's `blocksClient` singleton everywhere `t()`/`load()` is needed, or dictionaries loaded in one part of the app won't be visible in another.
53
- - **`translations`/`cloudTranslations` take `(moduleName, language)`** — module first.
54
- - **Dictionaries only contain string values** — the SDK strips any non-string fields from the raw response (and unwraps a `data` envelope if present) before caching, so don't expect nested objects in a loaded dictionary.
55
- - **This skill doesn't author content.** Adding a new key/module or changing a translated value goes through `blocks-localization-configuration`'s `blocks localization *` commands, not this skill.
56
-
57
- ## Example trigger prompts
58
-
59
- - "Add a language switcher and translate the UI."
60
- - "Load the `common` and `dashboard` translation modules on app startup and use them to render labels."
61
- - "The Assets page still has hard-coded English strings — replace them with `t()` lookups."
62
- - "Show only the languages this tenant actually has configured, with the default one preselected."
63
- - "I need one specific translated key without pulling down the whole module."
1
+ ---
2
+ name: blocks-localization-implementation
3
+ description: "Consume SELISE Blocks localization at runtime in a scaffolded frontend, entirely through the `@seliseblocks/client` SDK's `localization` namespace — never raw fetch/curl. Use for making a Blocks web app multilingual on the client: language/module discovery, loading dictionaries, the built-in `t()` lookup, and a language switcher that reloads and re-renders. Frontend consumption only — authoring/pushing translation content is the sibling skill blocks-localization-configuration."
4
+ ---
5
+
6
+ # Blocks Localization — Implementation (frontend)
7
+
8
+ Make a scaffolded Blocks web app render its UI in the user's language, using only the `localization` namespace on the SDK client — `createBlocksClient(...).localization`. No fetch, no manual query strings, no hand-rolled caching: the SDK client already does all of that.
9
+
10
+ The translations themselves (keys, modules, per-language values) are authored and pushed with the blocks-localization-configuration skill (uses `blocks localization *`). This skill only covers loading and rendering them in the app.
11
+
12
+ ## The client and its cache
13
+
14
+ `localization` is a `BlocksLocalizationClient` instance created once inside `createBlocksClient()`. It holds its dictionary cache as instance state — a `Map` keyed by `` `${language}:${moduleName}` `` plus an `activeLanguage`. That cache is **only as shared as the client instance is**: the canonical scaffold creates one `blocksClient` singleton (`src/lib/blocks/client.ts`, from `createBlocksClient()`) and imports it everywhere, so every `t()` call sees every dictionary loaded anywhere in the app. If you instantiate a second `createBlocksClient()` somewhere, it gets its own empty cache — don't do that; import the one singleton.
15
+
16
+ ## Public vs. authenticated methods
17
+
18
+ Every method has a matching pair — one public, one tenant/session-scoped:
19
+
20
+ | Public (no token sent) | Authenticated (sends token when configured) |
21
+ |---|---|
22
+ | `languages()` | `languagesForCurrentTenant()` |
23
+ | `modules()` | `modulesForCurrentTenant()` |
24
+ | `translations(moduleName, language)` | `cloudTranslations(moduleName, language)` |
25
+ | `load(language, modules[])` | `loadCloud(language, modules[])` |
26
+
27
+ - `languages()`/`modules()`/`translations()`/`load()` are explicitly public — all tenant-supported cultures and translation bundles, usable pre-login for a language picker or startup locale selection.
28
+ - `languagesForCurrentTenant()`/`modulesForCurrentTenant()`/`cloudTranslations()`/`loadCloud()` are scoped to whichever tenant the active `x-blocks-key` resolves to (and send the caller's access token if the client is configured with one) — use these for protected, tenant-specific dictionaries behind a signed-in session.
29
+
30
+ `keysByNames({ keyNames, moduleId? })` fetches specific key records (metadata/translations) without downloading a whole module dictionary; useful for a one-off label or an admin screen that inspects individual keys.
31
+
32
+ **Argument order matters**: `translations(moduleName, language)` and `cloudTranslations(moduleName, language)` take module first, language second — easy to transpose.
33
+
34
+ ## Startup sequence
35
+
36
+ 1. **List languages** on app boot (or in a query hook) — `blocksClient.localization.languagesForCurrentTenant()` (or `languages()` if you want it available pre-login). Use `isDefault`/`languageCode` from the result to build the picker and preselect a default, falling back to a persisted user choice (e.g. `localStorage`).
37
+ 2. **Load dictionaries** for the active language — `blocksClient.localization.load(language, modules)` where `modules` is the list of bundles the app needs (e.g. `["common", "dashboard", "assets"]`). `load()` fetches each module's dictionary in parallel via `translations()` and merges them into one object, with later modules in the array overwriting earlier ones on key collision. Use `loadCloud()` instead for protected dictionaries once the user is signed in.
38
+ 3. **Render labels** with `blocksClient.localization.t(key, fallback, { language, moduleName })`. It reads from dictionaries already loaded by `translations()`/`load()` (or their cloud equivalents) — it does not fetch anything itself. Missing key → `fallback` → the raw key, in that order.
39
+ 4. **Switch language**: on switcher change, call `load()` (or `loadCloud()`) again with the new language and the same module list, then re-render. There's no separate "invalidate" step — loading a language populates its own cache entries; you don't need to clear the old language's entries (they just stop being read once `activeLanguage`/your app state moves on).
40
+
41
+ The canonical scaffold (`blocks new web`) wires exactly this pattern in `src/lib/i18n/LocalizationProvider.tsx`: a React context holds `language` state (seeded from `localStorage`), a `useEffect` on `language` calls `blocksClient.localization.load(language, MODULES)` and stores the merged dictionary in state, and `t(key, fallback)` reads `cloudDictionary[key] ?? defaultDictionary[key] ?? fallback ?? key` — layering the network dictionary over a build-time `defaultDictionary` (from `src/lib/i18n/dictionary.ts`, generated from the same keys as the seed JSON in `blocks/localization/*.json`) as an offline/first-paint safety net, itself falling back to the caller-supplied fallback and finally the key. Mirror this shape rather than inventing your own provider — it's already generated into new projects. Note the scaffold's own `t()` is a plain function on context, not the SDK's `localization.t()` — either is fine; the SDK's built-in `t()` needs no separate context/provider if you're happy reading `blocksClient.localization.t(...)` directly in components.
42
+
43
+ ## `t()` lookup details worth knowing
44
+
45
+ - If you pass `moduleName`, `t()` does an exact `` `${language}:${moduleName}` `` cache lookup — deterministic.
46
+ - If you omit `moduleName`, `t()` scans all cached dictionaries and returns the first match whose cache key starts with `` `${language}:` `` (or any language if you didn't pass one). Scan order follows Map insertion order, which is the order the underlying HTTP requests *resolved* in (not necessarily the order you listed modules in `load()`) — fine when keys are unique across modules, ambiguous if two modules define the same key. Pass `moduleName` explicitly whenever you know it and key collisions across modules are possible.
47
+ - `t()` never throws and never fetches — call `load()`/`translations()` (or the cloud variants) first, or every lookup falls straight to `fallback`/the key.
48
+
49
+ ## Gotchas
50
+
51
+ - **No raw fetch/curl, ever** — every read here goes through `blocksClient.localization.*`. The SDK already sends `x-blocks-key` and, for the authenticated variants, the caller's access token when configured.
52
+ - **One client, one cache** — don't call `createBlocksClient()` more than once in the app; import the scaffold's `blocksClient` singleton everywhere `t()`/`load()` is needed, or dictionaries loaded in one part of the app won't be visible in another.
53
+ - **`translations`/`cloudTranslations` take `(moduleName, language)`** — module first.
54
+ - **Dictionaries only contain string values** — the SDK strips any non-string fields from the raw response (and unwraps a `data` envelope if present) before caching, so don't expect nested objects in a loaded dictionary.
55
+ - **This skill doesn't author content.** Adding a new key/module or changing a translated value goes through `blocks-localization-configuration`'s `blocks localization *` commands, not this skill.
56
+
57
+ ## Example trigger prompts
58
+
59
+ - "Add a language switcher and translate the UI."
60
+ - "Load the `common` and `dashboard` translation modules on app startup and use them to render labels."
61
+ - "The Assets page still has hard-coded English strings — replace them with `t()` lookups."
62
+ - "Show only the languages this tenant actually has configured, with the default one preselected."
63
+ - "I need one specific translated key without pulling down the whole module."
@@ -1,95 +1,95 @@
1
- ---
2
- name: blocks-mail
3
- description: "Send transactional email via the SDK's `blocksClient.mail.send()`/`sendToAny()`, or administer mail via the project-scoped `blocks mail config|template|mailbox *` CLI — server config, template CRUD/clone, mailbox reads, none of which have an SDK equivalent. CLI also exposes `mail send`/`sendtoany` as an admin/terminal mirror of the SDK calls. CLI mutations require `--dry-run` before `--yes`. Use for app email sending, or managing SMTP/inbound providers, templates, mailbox history."
4
- ---
5
-
6
- # Blocks Mail
7
-
8
- Blocks mail has **two distinct surfaces that don't fully overlap**:
9
-
10
- - **SDK — `blocksClient.mail.send()` / `sendToAny()`** — the only mail operations exposed to app code. Use this when the question is "how do I send an email from my app."
11
- - **CLI — `blocks mail config|template|mailbox *`** — server/provider configuration, email template CRUD/clone, and mailbox message reads. **No SDK equivalent at all.** The SDK's own documentation says so directly: mail server/template/mailbox management is a CLI/admin concern, not exposed to app code. If a user asks "how do I configure our SMTP provider from my app" or "how do I edit a template from code," the answer is: you don't — that's a `blocks mail config *` / `blocks mail template *` terminal command, not an SDK call.
12
- - **CLI — `blocks mail send` / `blocks mail sendtoany`** — also exist, hitting the *same* underlying send as the SDK's `send`/`sendToAny`. These are the terminal/admin-token way to fire the same send, not a different feature — useful for testing a template from a shell or scripting a one-off send, but app runtime code should use the SDK call instead of shelling out.
13
-
14
- ## SDK — sending mail (`blocksClient.mail.*`)
15
-
16
- ```ts
17
- import { blocksClient } from "../../lib/blocks/client";
18
-
19
- await blocksClient.mail.send({
20
- to: ["jane@example.com"],
21
- purpose: "welcome",
22
- language: "en",
23
- subjectDataContext: { firstName: "Jane" },
24
- bodyDataContext: { firstName: "Jane" }
25
- });
26
- ```
27
-
28
- - **`blocksClient.mail.send(request)`** — sends through the tenant's default mail configuration.
29
- - **`blocksClient.mail.sendToAny(request)`** — same request shape plus `isTestMail`.
30
-
31
- `BlocksSendMailRequest` fields: `to?: string[]`, `cc?: string[]`, `bcc?: string[]`, `replyTo?: string[]`, `attachments?: string[]`, `language?: string`, `purpose?: string`, `projectKey?: string` (defaults to the tenant's `x-blocks-key` server-side when omitted), `subjectDataContext?: Record<string, string>`, `bodyDataContext?: Record<string, string>`, `sendPhoneNumberAsEmail?: boolean`. `BlocksSendMailToAnyRequest` extends that with `isTestMail?: boolean`. The response type is an untyped `Record<string, unknown>` — the SDK doesn't shape the response further.
32
-
33
- `purpose`/`language` are how the send picks a template server-side; the CLI and SDK don't define what `purpose` values exist for a given tenant — that comes from whatever templates were saved via `mail template save` (see below), so don't guess a purpose string that hasn't been confirmed to exist.
34
-
35
- ## CLI — administering mail (`blocks mail config|template|mailbox *`)
36
-
37
- Everything under `mail config`, `mail template`, and `mail mailbox` is project-scoped: every command requires an impersonated project session, resolving the target project from whichever project is selected with `blocks use <tenantId>`, the workspace's `blocks.json`, or an explicit `--project <tenantId>`. There is no account-level mode for any mail command, including `mail send`/`mail sendtoany`.
38
-
39
- ### `mail config` — SMTP/inbound provider configuration
40
-
41
- - **`blocks mail config list [--json]`** — read-only.
42
- - **`blocks mail config get <name> [--json]`** — read-only (positional arg, or `--name`).
43
- - **`blocks mail config save [--configuration-id <id>] [--name <n>] [--host <h>] [--port <p>] [--enable-ssl] [--inbound] [--provider <n>] [--sender-name <n>] [--sender-address <addr>] [--sender-username <u>] [--account-password <p>] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — upsert: omit `--configuration-id` to create, pass it to update. `--provider` and `--port` are raw integers (the CLI doesn't document the provider enum's meaning — don't guess a value). `--account-password` is redacted (`***`) in `--dry-run` output only; the live response and stored value are still sensitive.
44
- - **`blocks mail config delete <configurationId> [--dry-run] [--yes] [--json]`**
45
- - **`blocks mail config duplicate <configurationId> [--dry-run] [--yes] [--json]`**
46
-
47
- ### `mail template` — email template CRUD/clone
48
-
49
- - **`blocks mail template list [--configuration-id <id>] [--language <l>] [--search <q>] [--sort-by <field>] [--sort-desc] [--page-number 1] [--page-size 20] [--json]`** — read-only.
50
- - **`blocks mail template get <itemId> [--json]`** — read-only.
51
- - **`blocks mail template save [--item-id <id>] [--name <n>] [--configuration-id <id>] [--language <l>] [--subject <s>] [--template-body <html>] [--json-content <json>] [--image-id <id>] [--image-url <url>] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — upsert: omit `--item-id` to create, pass it to update.
52
- - **`blocks mail template delete <itemId> [--dry-run] [--yes] [--json]`**
53
- - **`blocks mail template clone <itemId> [--name <n>] [--configuration-id <id>] [--language <l>] [--subject <s>] [--dry-run] [--yes] [--json]`**
54
-
55
- ### `mail mailbox` — mailbox message reads
56
-
57
- - **`blocks mail mailbox list [--inbound] [--page-number 1] [--page-size 20] [--search <q>] [--start-date <date>] [--end-date <date>] [--status <s>] [--json]`** — read-only. There is **no `--configuration-id` flag** on this command (see Gotchas — this corrects a stale example elsewhere in this repo's own docs).
58
- - **`blocks mail mailbox get <messageId> [--json]`** — read-only (positional arg, or `--id`).
59
-
60
- ### `mail send` / `mail sendtoany` — CLI mirror of the SDK send calls
61
-
62
- - **`blocks mail send [--to a,b] [--cc a,b] [--bcc a,b] [--reply-to a,b] [--purpose <p>] [--language <l>] [--project-key <k>] [--subject-data-context '<json>'] [--body-data-context '<json>'] [--attachments '<json>'] [--send-phone-number-as-email] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — `--project-key` defaults to the selected project's tenant id.
63
- - **`blocks mail sendtoany [same flags, plus --is-test-mail] [--dry-run] [--yes] [--json]`**
64
-
65
- `--to`/`--cc`/`--bcc`/`--reply-to` are comma-separated lists (`a@x.com,b@y.com`); `--attachments`/`--subject-data-context`/`--body-data-context` take raw JSON strings (parsed as JSON, so quote them for the shell).
66
-
67
- ## Mutation discipline
68
-
69
- Every write command (`config save/delete/duplicate`, `template save/delete/clone`, `send`, `sendtoany`) follows the same two-gate pattern used throughout this CLI:
70
-
71
- 1. **`--dry-run`** short-circuits before any network call and prints a full preview of exactly what would be sent, with secrets already redacted.
72
- 2. Without `--dry-run`, a confirmation step either accepts `--yes` outright or, interactively, prompts to type "yes" to continue, and cancels on anything else. There is no way to mutate without one of these two gates.
73
-
74
- `list`/`get` commands under `config`, `template`, and `mailbox` never mutate and need neither flag.
75
-
76
- ## Gotchas
77
-
78
- - **The premise that mail has no SDK path at all is wrong for sending.** `blocksClient.mail.send()`/`sendToAny()` exist and are the correct answer for "send email from my app." Only `config`/`template`/`mailbox` administration is CLI-only.
79
- - **`mail mailbox list` does not take `--configuration-id`.** This CLI's own usage guide has previously shown an example with that flag that isn't backed by the actual flag list — the real command only reads `--inbound`, `--page-number`, `--page-size`, `--search`, `--start-date`, `--end-date`, `--status`. The CLI's flag parser silently ignores unrecognized `--` flags rather than erroring, so a stale example like that "works" without doing what it implies. Don't repeat it; use the real flags above.
80
- - **`--account-password` (config save) is redacted only in `--dry-run` output.** The live `config save`/`config get` response is not redacted — treat it as a secret regardless.
81
- - **`--provider` and `--port` on `config save` are raw values with no documented enum/meaning in the CLI** — don't invent what a given integer means; ask the user or read it back from `config get` on an existing configuration.
82
- - **`purpose`/`language` on `send`/`sendtoany` select a template implicitly** — there's no lookup or validation for which `purpose` strings are valid for a tenant. Confirm against `mail template list`/`get` rather than guessing a purpose name.
83
- - **`mail send` and `mail sendtoany` are still project-scoped CLI commands**, not account-level — same project-selection/impersonated-token requirement as `config`/`template`/`mailbox`.
84
- - **`--dry-run` before `--yes`, always** — same discipline as every other mutating `blocks` command in this pack; never jump straight to `--yes` on a mail write.
85
-
86
- ## Example trigger prompts
87
-
88
- - "Send a welcome email to jane@example.com from the app." → SDK `blocksClient.mail.send(...)`.
89
- - "Send a test email to this address from the terminal." → `blocks mail sendtoany --to <addr> --is-test-mail --dry-run --json`, then `--yes` after approval.
90
- - "List the mail server configurations for this project." → `blocks mail config list --json`.
91
- - "Set up a new SMTP configuration for this project." → `blocks mail config save --name <n> --host <h> --port <p> --enable-ssl --sender-name <n> --sender-address <addr> --account-password <p> --dry-run --json`, then `--yes`.
92
- - "Show me the password-reset email template." → `blocks mail template list --search <query> --json`, then `blocks mail template get <itemId> --json`.
93
- - "Clone this template into a new language." → `blocks mail template clone <itemId> --language <code> --name <n> --dry-run --json`.
94
- - "What mail was sent out last week?" → `blocks mail mailbox list --start-date <date> --end-date <date> --json`.
95
- - "How do I edit an email template from my app's code?" → not supported; template CRUD is CLI-only (`blocks mail template save`), no SDK path.
1
+ ---
2
+ name: blocks-mail
3
+ description: "Send transactional email via the SDK's `blocksClient.mail.send()`/`sendToAny()`, or administer mail via the project-scoped `blocks mail config|template|mailbox *` CLI — server config, template CRUD/clone, mailbox reads, none of which have an SDK equivalent. CLI also exposes `mail send`/`sendtoany` as an admin/terminal mirror of the SDK calls. CLI mutations require `--dry-run` before `--yes`. Use for app email sending, or managing SMTP/inbound providers, templates, mailbox history."
4
+ ---
5
+
6
+ # Blocks Mail
7
+
8
+ Blocks mail has **two distinct surfaces that don't fully overlap**:
9
+
10
+ - **SDK — `blocksClient.mail.send()` / `sendToAny()`** — the only mail operations exposed to app code. Use this when the question is "how do I send an email from my app."
11
+ - **CLI — `blocks mail config|template|mailbox *`** — server/provider configuration, email template CRUD/clone, and mailbox message reads. **No SDK equivalent at all.** The SDK's own documentation says so directly: mail server/template/mailbox management is a CLI/admin concern, not exposed to app code. If a user asks "how do I configure our SMTP provider from my app" or "how do I edit a template from code," the answer is: you don't — that's a `blocks mail config *` / `blocks mail template *` terminal command, not an SDK call.
12
+ - **CLI — `blocks mail send` / `blocks mail sendtoany`** — also exist, hitting the *same* underlying send as the SDK's `send`/`sendToAny`. These are the terminal/admin-token way to fire the same send, not a different feature — useful for testing a template from a shell or scripting a one-off send, but app runtime code should use the SDK call instead of shelling out.
13
+
14
+ ## SDK — sending mail (`blocksClient.mail.*`)
15
+
16
+ ```ts
17
+ import { blocksClient } from "../../lib/blocks/client";
18
+
19
+ await blocksClient.mail.send({
20
+ to: ["jane@example.com"],
21
+ purpose: "welcome",
22
+ language: "en",
23
+ subjectDataContext: { firstName: "Jane" },
24
+ bodyDataContext: { firstName: "Jane" }
25
+ });
26
+ ```
27
+
28
+ - **`blocksClient.mail.send(request)`** — sends through the tenant's default mail configuration.
29
+ - **`blocksClient.mail.sendToAny(request)`** — same request shape plus `isTestMail`.
30
+
31
+ `BlocksSendMailRequest` fields: `to?: string[]`, `cc?: string[]`, `bcc?: string[]`, `replyTo?: string[]`, `attachments?: string[]`, `language?: string`, `purpose?: string`, `projectKey?: string` (defaults to the tenant's `x-blocks-key` server-side when omitted), `subjectDataContext?: Record<string, string>`, `bodyDataContext?: Record<string, string>`, `sendPhoneNumberAsEmail?: boolean`. `BlocksSendMailToAnyRequest` extends that with `isTestMail?: boolean`. The response type is an untyped `Record<string, unknown>` — the SDK doesn't shape the response further.
32
+
33
+ `purpose`/`language` are how the send picks a template server-side; the CLI and SDK don't define what `purpose` values exist for a given tenant — that comes from whatever templates were saved via `mail template save` (see below), so don't guess a purpose string that hasn't been confirmed to exist.
34
+
35
+ ## CLI — administering mail (`blocks mail config|template|mailbox *`)
36
+
37
+ Everything under `mail config`, `mail template`, and `mail mailbox` is project-scoped: every command requires an impersonated project session, resolving the target project from whichever project is selected with `blocks use <tenantId>`, the workspace's `blocks.json`, or an explicit `--project <tenantId>`. There is no account-level mode for any mail command, including `mail send`/`mail sendtoany`.
38
+
39
+ ### `mail config` — SMTP/inbound provider configuration
40
+
41
+ - **`blocks mail config list [--json]`** — read-only.
42
+ - **`blocks mail config get <name> [--json]`** — read-only (positional arg, or `--name`).
43
+ - **`blocks mail config save [--configuration-id <id>] [--name <n>] [--host <h>] [--port <p>] [--enable-ssl] [--inbound] [--provider <n>] [--sender-name <n>] [--sender-address <addr>] [--sender-username <u>] [--account-password <p>] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — upsert: omit `--configuration-id` to create, pass it to update. `--provider` and `--port` are raw integers (the CLI doesn't document the provider enum's meaning — don't guess a value). `--account-password` is redacted (`***`) in `--dry-run` output only; the live response and stored value are still sensitive.
44
+ - **`blocks mail config delete <configurationId> [--dry-run] [--yes] [--json]`**
45
+ - **`blocks mail config duplicate <configurationId> [--dry-run] [--yes] [--json]`**
46
+
47
+ ### `mail template` — email template CRUD/clone
48
+
49
+ - **`blocks mail template list [--configuration-id <id>] [--language <l>] [--search <q>] [--sort-by <field>] [--sort-desc] [--page-number 1] [--page-size 20] [--json]`** — read-only.
50
+ - **`blocks mail template get <itemId> [--json]`** — read-only.
51
+ - **`blocks mail template save [--item-id <id>] [--name <n>] [--configuration-id <id>] [--language <l>] [--subject <s>] [--template-body <html>] [--json-content <json>] [--image-id <id>] [--image-url <url>] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — upsert: omit `--item-id` to create, pass it to update.
52
+ - **`blocks mail template delete <itemId> [--dry-run] [--yes] [--json]`**
53
+ - **`blocks mail template clone <itemId> [--name <n>] [--configuration-id <id>] [--language <l>] [--subject <s>] [--dry-run] [--yes] [--json]`**
54
+
55
+ ### `mail mailbox` — mailbox message reads
56
+
57
+ - **`blocks mail mailbox list [--inbound] [--page-number 1] [--page-size 20] [--search <q>] [--start-date <date>] [--end-date <date>] [--status <s>] [--json]`** — read-only. There is **no `--configuration-id` flag** on this command (see Gotchas — this corrects a stale example elsewhere in this repo's own docs).
58
+ - **`blocks mail mailbox get <messageId> [--json]`** — read-only (positional arg, or `--id`).
59
+
60
+ ### `mail send` / `mail sendtoany` — CLI mirror of the SDK send calls
61
+
62
+ - **`blocks mail send [--to a,b] [--cc a,b] [--bcc a,b] [--reply-to a,b] [--purpose <p>] [--language <l>] [--project-key <k>] [--subject-data-context '<json>'] [--body-data-context '<json>'] [--attachments '<json>'] [--send-phone-number-as-email] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — `--project-key` defaults to the selected project's tenant id.
63
+ - **`blocks mail sendtoany [same flags, plus --is-test-mail] [--dry-run] [--yes] [--json]`**
64
+
65
+ `--to`/`--cc`/`--bcc`/`--reply-to` are comma-separated lists (`a@x.com,b@y.com`); `--attachments`/`--subject-data-context`/`--body-data-context` take raw JSON strings (parsed as JSON, so quote them for the shell).
66
+
67
+ ## Mutation discipline
68
+
69
+ Every write command (`config save/delete/duplicate`, `template save/delete/clone`, `send`, `sendtoany`) follows the same two-gate pattern used throughout this CLI:
70
+
71
+ 1. **`--dry-run`** short-circuits before any network call and prints a full preview of exactly what would be sent, with secrets already redacted.
72
+ 2. Without `--dry-run`, a confirmation step either accepts `--yes` outright or, interactively, prompts to type "yes" to continue, and cancels on anything else. There is no way to mutate without one of these two gates.
73
+
74
+ `list`/`get` commands under `config`, `template`, and `mailbox` never mutate and need neither flag.
75
+
76
+ ## Gotchas
77
+
78
+ - **The premise that mail has no SDK path at all is wrong for sending.** `blocksClient.mail.send()`/`sendToAny()` exist and are the correct answer for "send email from my app." Only `config`/`template`/`mailbox` administration is CLI-only.
79
+ - **`mail mailbox list` does not take `--configuration-id`.** This CLI's own usage guide has previously shown an example with that flag that isn't backed by the actual flag list — the real command only reads `--inbound`, `--page-number`, `--page-size`, `--search`, `--start-date`, `--end-date`, `--status`. The CLI's flag parser silently ignores unrecognized `--` flags rather than erroring, so a stale example like that "works" without doing what it implies. Don't repeat it; use the real flags above.
80
+ - **`--account-password` (config save) is redacted only in `--dry-run` output.** The live `config save`/`config get` response is not redacted — treat it as a secret regardless.
81
+ - **`--provider` and `--port` on `config save` are raw values with no documented enum/meaning in the CLI** — don't invent what a given integer means; ask the user or read it back from `config get` on an existing configuration.
82
+ - **`purpose`/`language` on `send`/`sendtoany` select a template implicitly** — there's no lookup or validation for which `purpose` strings are valid for a tenant. Confirm against `mail template list`/`get` rather than guessing a purpose name.
83
+ - **`mail send` and `mail sendtoany` are still project-scoped CLI commands**, not account-level — same project-selection/impersonated-token requirement as `config`/`template`/`mailbox`.
84
+ - **`--dry-run` before `--yes`, always** — same discipline as every other mutating `blocks` command in this pack; never jump straight to `--yes` on a mail write.
85
+
86
+ ## Example trigger prompts
87
+
88
+ - "Send a welcome email to jane@example.com from the app." → SDK `blocksClient.mail.send(...)`.
89
+ - "Send a test email to this address from the terminal." → `blocks mail sendtoany --to <addr> --is-test-mail --dry-run --json`, then `--yes` after approval.
90
+ - "List the mail server configurations for this project." → `blocks mail config list --json`.
91
+ - "Set up a new SMTP configuration for this project." → `blocks mail config save --name <n> --host <h> --port <p> --enable-ssl --sender-name <n> --sender-address <addr> --account-password <p> --dry-run --json`, then `--yes`.
92
+ - "Show me the password-reset email template." → `blocks mail template list --search <query> --json`, then `blocks mail template get <itemId> --json`.
93
+ - "Clone this template into a new language." → `blocks mail template clone <itemId> --language <code> --name <n> --dry-run --json`.
94
+ - "What mail was sent out last week?" → `blocks mail mailbox list --start-date <date> --end-date <date> --json`.
95
+ - "How do I edit an email template from my app's code?" → not supported; template CRUD is CLI-only (`blocks mail template save`), no SDK path.
@@ -1,69 +1,69 @@
1
- ---
2
- name: blocks-notification
3
- description: "Manage SELISE Blocks notification-channel configuration via `blocks notification *` — no SDK path exists (`@seliseblocks/client` has no `notification` namespace, only the unrelated real-time `notifier` surface). Covers `notification list`/`get` (read configs) and `notification save`/`delete` (project-scoped mutations, impersonated-project-token only). Use for 'list/get notification configs', 'save/update a channel', 'delete a config'. Always `--dry-run` before `--yes` on save/delete."
4
- ---
5
-
6
- # Blocks Notification — Channel Configuration
7
-
8
- Manage notification-channel configuration through `blocks notification *`. This is **100% CLI, no SDK equivalent** — `@seliseblocks/client` has no `notification` namespace at all. It does have a `notifier` namespace (backing `blocks notifier *`), but that's a **different, unrelated surface**: `notifier` pushes real-time/offline notifications and reads a user's own inbox; `notification` (this skill) manages the tenant's notification *channel configuration* — which channel/method a notification type uses, not sending one. Never write a frontend/app-code path for channel configuration — it's always this CLI.
9
-
10
- **Prerequisite:** a project is selected (`blocks use <tenantId>`, or pass `--project <tenantId>`) — see the blocks-onboarding skill. Every one of the four commands requires an impersonated project session; there is no account-level mode for any of them.
11
-
12
- ## Safe read commands
13
-
14
- - **`blocks notification list [--page <n>] [--page-size <n>] [--sort-by <property>] [--sort-desc] [--filter <text>] [--json]`** — read-only.
15
- - **`blocks notification get <itemId> [--json]`** — `itemId` may be given positionally or as `--id <itemId>`; one of the two is required (the command throws `Missing --id` if neither is given). Read-only.
16
-
17
- Neither read command mutates anything and neither accepts `--dry-run` (there's nothing to preview — no request body is built, no confirmation gate exists for either).
18
-
19
- ## Mutating: save (create or update a channel config)
20
-
21
- ```bash
22
- blocks notification save --name <n> --channel <0|1> --type <0-3> --dry-run --json # preview first
23
- blocks notification save --name <n> --channel <0|1> --type <0-3> --yes --json # only after approval
24
- blocks notification save --update --body '<json>' --yes --json # full custom payload
25
- ```
26
-
27
- The request body is built by merging (in this order, later keys win) whatever `--body '<json>'` or `--file <path.json>` supplies, then these convenience flags layered on top (so an unset convenience flag never overwrites a value from `--body`/`--file`):
28
-
29
- | Flag | Body field | Notes |
30
- |---|---|---|
31
- | `--channel <int>` | `channelToNotify` | Raw integer — the CLI does **not** validate or enum-check the value itself. |
32
- | `--type <int>` | `notificationType` | Same — raw integer, no validation in the command. |
33
- | `--enable-persistence` | `enablePersistence` | Boolean flag; only ever sent as `true` when passed — passing it absent never sends an explicit `false`. |
34
- | `--update` | `isUpdateRequest` | Same true-only pattern as `--enable-persistence`. Set this when saving over an existing config rather than creating a new one. |
35
- | `--name <text>` | `name` | |
36
- | `--notify-method <text>` | `notifyMethod` | |
37
-
38
- `--dry-run` prints the resolved request and returns — no network call, no confirmation prompt. Without `--dry-run`, it prompts interactively for a typed `yes` before sending (unless `--yes` is also passed); only then does it resolve the selected project and issue the request. Always show `--dry-run` output and get explicit approval before re-running with `--yes`.
39
-
40
- `--channel`/`--type` don't have machine-readable names published by the API; the CLI's own usage docs note they correspond to the OS API's channel/receiver-type enums (`--channel` roughly `0|1`, `--type` roughly `0-3`) — treat those ranges as documentation convention, not something this command enforces, and ask the user for the exact intended value rather than guessing one.
41
-
42
- ## Mutating: delete
43
-
44
- ```bash
45
- blocks notification delete <itemId> --dry-run --json
46
- blocks notification delete <itemId> --yes --json
47
- ```
48
-
49
- Same `itemId` resolution as `get` — positional arg or `--id`, one required. `--dry-run` prints the resolved query and returns; otherwise it goes through the same confirmation gate as `save` (interactive `yes` prompt unless `--yes` is passed) before resolving the project and sending the request.
50
-
51
- ## Gotchas
52
-
53
- - **No SDK path, ever.** If asked "how do I manage notification channels from my app," the answer is: you don't — this is CLI-only, human/CI-operated configuration, not something to wire into frontend or backend app code.
54
- - **`notification` and `notifier` are not the same thing.** `notification save/list/get/delete` (this skill) configures *which channel/method* a notification type uses. `notifier notify/list/unread/mark-read/mark-all-read` *sends* notifications and reads a user's inbox — a separate command family with its own commands, not covered here. Don't answer a "send a notification" request with `notification save`, and don't answer a "configure the notification channel" request with `notifier`.
55
- - **`list` and `get` have no `--dry-run`.** Only `save` and `delete` build a request that's worth previewing; the two read commands hit the API directly. Don't tell a user to `--dry-run` a `list` or `get` call.
56
- - **`--enable-persistence` and `--update` are true-only flags.** Passing them sets the field to `true`; not passing them omits the field entirely (never sends an explicit `false`). If a user wants to explicitly *unset* persistence or force a plain create, that has to go through `--body`/`--file` directly, not the convenience flag.
57
- - **`--channel`/`--type` are unvalidated raw integers.** The command will happily send any integer you give it — there's no local check against the underlying enums. Confirm the intended value with the user (or check the Blocks portal/API docs) rather than inventing one.
58
- - **`itemId` for `get`/`delete` is always required**, positional or `--id` — never guessed. Ask the user rather than assuming a value.
59
- - **Every command is project-scoped.** All four require a resolved project and an impersonated project token; behavior follows whichever project is currently selected via `blocks use` (or an explicit `--project` override).
60
- - **`--dry-run` before `--yes`, always**, on `save` and `delete` — same discipline as every other mutating `blocks` command in this pack.
61
-
62
- ## Example trigger prompts
63
-
64
- - "List the notification channel configs for this project."
65
- - "Get notification config `<itemId>`."
66
- - "Save a new notification config named `<name>` on channel 0, type 1." → preview with `--dry-run` first.
67
- - "Update the existing `<name>` notification config." → add `--update`, still `--dry-run` before `--yes`.
68
- - "Delete notification config `<itemId>`."
69
- - "Send a notification to these users" / "show me a user's unread notifications" → not this skill; that's `blocks notifier *`, a different command family for sending/reading, not channel configuration.
1
+ ---
2
+ name: blocks-notification
3
+ description: "Manage SELISE Blocks notification-channel configuration via `blocks notification *` — no SDK path exists (`@seliseblocks/client` has no `notification` namespace, only the unrelated real-time `notifier` surface). Covers `notification list`/`get` (read configs) and `notification save`/`delete` (project-scoped mutations, impersonated-project-token only). Use for 'list/get notification configs', 'save/update a channel', 'delete a config'. Always `--dry-run` before `--yes` on save/delete."
4
+ ---
5
+
6
+ # Blocks Notification — Channel Configuration
7
+
8
+ Manage notification-channel configuration through `blocks notification *`. This is **100% CLI, no SDK equivalent** — `@seliseblocks/client` has no `notification` namespace at all. It does have a `notifier` namespace (backing `blocks notifier *`), but that's a **different, unrelated surface**: `notifier` pushes real-time/offline notifications and reads a user's own inbox; `notification` (this skill) manages the tenant's notification *channel configuration* — which channel/method a notification type uses, not sending one. Never write a frontend/app-code path for channel configuration — it's always this CLI.
9
+
10
+ **Prerequisite:** a project is selected (`blocks use <tenantId>`, or pass `--project <tenantId>`) — see the blocks-onboarding skill. Every one of the four commands requires an impersonated project session; there is no account-level mode for any of them.
11
+
12
+ ## Safe read commands
13
+
14
+ - **`blocks notification list [--page <n>] [--page-size <n>] [--sort-by <property>] [--sort-desc] [--filter <text>] [--json]`** — read-only.
15
+ - **`blocks notification get <itemId> [--json]`** — `itemId` may be given positionally or as `--id <itemId>`; one of the two is required (the command throws `Missing --id` if neither is given). Read-only.
16
+
17
+ Neither read command mutates anything and neither accepts `--dry-run` (there's nothing to preview — no request body is built, no confirmation gate exists for either).
18
+
19
+ ## Mutating: save (create or update a channel config)
20
+
21
+ ```bash
22
+ blocks notification save --name <n> --channel <0|1> --type <0-3> --dry-run --json # preview first
23
+ blocks notification save --name <n> --channel <0|1> --type <0-3> --yes --json # only after approval
24
+ blocks notification save --update --body '<json>' --yes --json # full custom payload
25
+ ```
26
+
27
+ The request body is built by merging (in this order, later keys win) whatever `--body '<json>'` or `--file <path.json>` supplies, then these convenience flags layered on top (so an unset convenience flag never overwrites a value from `--body`/`--file`):
28
+
29
+ | Flag | Body field | Notes |
30
+ |---|---|---|
31
+ | `--channel <int>` | `channelToNotify` | Raw integer — the CLI does **not** validate or enum-check the value itself. |
32
+ | `--type <int>` | `notificationType` | Same — raw integer, no validation in the command. |
33
+ | `--enable-persistence` | `enablePersistence` | Boolean flag; only ever sent as `true` when passed — passing it absent never sends an explicit `false`. |
34
+ | `--update` | `isUpdateRequest` | Same true-only pattern as `--enable-persistence`. Set this when saving over an existing config rather than creating a new one. |
35
+ | `--name <text>` | `name` | |
36
+ | `--notify-method <text>` | `notifyMethod` | |
37
+
38
+ `--dry-run` prints the resolved request and returns — no network call, no confirmation prompt. Without `--dry-run`, it prompts interactively for a typed `yes` before sending (unless `--yes` is also passed); only then does it resolve the selected project and issue the request. Always show `--dry-run` output and get explicit approval before re-running with `--yes`.
39
+
40
+ `--channel`/`--type` don't have machine-readable names published by the API; the CLI's own usage docs note they correspond to the OS API's channel/receiver-type enums (`--channel` roughly `0|1`, `--type` roughly `0-3`) — treat those ranges as documentation convention, not something this command enforces, and ask the user for the exact intended value rather than guessing one.
41
+
42
+ ## Mutating: delete
43
+
44
+ ```bash
45
+ blocks notification delete <itemId> --dry-run --json
46
+ blocks notification delete <itemId> --yes --json
47
+ ```
48
+
49
+ Same `itemId` resolution as `get` — positional arg or `--id`, one required. `--dry-run` prints the resolved query and returns; otherwise it goes through the same confirmation gate as `save` (interactive `yes` prompt unless `--yes` is passed) before resolving the project and sending the request.
50
+
51
+ ## Gotchas
52
+
53
+ - **No SDK path, ever.** If asked "how do I manage notification channels from my app," the answer is: you don't — this is CLI-only, human/CI-operated configuration, not something to wire into frontend or backend app code.
54
+ - **`notification` and `notifier` are not the same thing.** `notification save/list/get/delete` (this skill) configures *which channel/method* a notification type uses. `notifier notify/list/unread/mark-read/mark-all-read` *sends* notifications and reads a user's inbox — a separate command family with its own commands, not covered here. Don't answer a "send a notification" request with `notification save`, and don't answer a "configure the notification channel" request with `notifier`.
55
+ - **`list` and `get` have no `--dry-run`.** Only `save` and `delete` build a request that's worth previewing; the two read commands hit the API directly. Don't tell a user to `--dry-run` a `list` or `get` call.
56
+ - **`--enable-persistence` and `--update` are true-only flags.** Passing them sets the field to `true`; not passing them omits the field entirely (never sends an explicit `false`). If a user wants to explicitly *unset* persistence or force a plain create, that has to go through `--body`/`--file` directly, not the convenience flag.
57
+ - **`--channel`/`--type` are unvalidated raw integers.** The command will happily send any integer you give it — there's no local check against the underlying enums. Confirm the intended value with the user (or check the Blocks portal/API docs) rather than inventing one.
58
+ - **`itemId` for `get`/`delete` is always required**, positional or `--id` — never guessed. Ask the user rather than assuming a value.
59
+ - **Every command is project-scoped.** All four require a resolved project and an impersonated project token; behavior follows whichever project is currently selected via `blocks use` (or an explicit `--project` override).
60
+ - **`--dry-run` before `--yes`, always**, on `save` and `delete` — same discipline as every other mutating `blocks` command in this pack.
61
+
62
+ ## Example trigger prompts
63
+
64
+ - "List the notification channel configs for this project."
65
+ - "Get notification config `<itemId>`."
66
+ - "Save a new notification config named `<name>` on channel 0, type 1." → preview with `--dry-run` first.
67
+ - "Update the existing `<name>` notification config." → add `--update`, still `--dry-run` before `--yes`.
68
+ - "Delete notification config `<itemId>`."
69
+ - "Send a notification to these users" / "show me a user's unread notifications" → not this skill; that's `blocks notifier *`, a different command family for sending/reading, not channel configuration.