@seliseblocks/cli-os 0.2.11 → 0.2.12

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 (47) hide show
  1. package/AI_USAGE_GUIDE.md +551 -560
  2. package/README.md +171 -173
  3. package/dist/commands/auth/refresh.js +21 -2
  4. package/dist/commands/mfa/generate.js +7 -4
  5. package/dist/commands/mfa/method-set.js +13 -4
  6. package/dist/commands/mfa/totp-enable.d.ts +3 -3
  7. package/dist/commands/mfa/totp-enable.js +4 -4
  8. package/dist/commands/mfa/verify.js +4 -2
  9. package/dist/commands/projects/create.js +110 -16
  10. package/dist/index.js +696 -703
  11. package/package.json +2 -2
  12. package/dist/commands/sdk/client.d.ts +0 -1
  13. package/dist/commands/sdk/client.js +0 -99
  14. package/dist/commands/skill/add.d.ts +0 -1
  15. package/dist/commands/skill/add.js +0 -19
  16. package/dist/commands/skill/list.d.ts +0 -1
  17. package/dist/commands/skill/list.js +0 -15
  18. package/dist/commands/skill/show.d.ts +0 -1
  19. package/dist/commands/skill/show.js +0 -15
  20. package/dist/lib/skills.d.ts +0 -17
  21. package/dist/lib/skills.js +0 -69
  22. package/dist/skills/blocks-data-gateway-configuration/SKILL.md +0 -204
  23. package/dist/skills/blocks-data-gateway-crud/SKILL.md +0 -223
  24. package/dist/skills/blocks-data-storage/SKILL.md +0 -253
  25. package/dist/skills/blocks-data-storage/flows/object-management.md +0 -124
  26. package/dist/skills/blocks-frontend-local-https/SKILL.md +0 -100
  27. package/dist/skills/blocks-iam-access-control/SKILL.md +0 -49
  28. package/dist/skills/blocks-iam-access-control/flows/feature-gating.md +0 -38
  29. package/dist/skills/blocks-iam-access-control/flows/manage-roles-permissions.md +0 -110
  30. package/dist/skills/blocks-iam-account/SKILL.md +0 -169
  31. package/dist/skills/blocks-iam-mfa/SKILL.md +0 -124
  32. package/dist/skills/blocks-iam-organizations/SKILL.md +0 -43
  33. package/dist/skills/blocks-iam-organizations/flows/admin-mutations.md +0 -89
  34. package/dist/skills/blocks-iam-organizations/flows/read-and-switch.md +0 -57
  35. package/dist/skills/blocks-iam-sso-oidc-configuration/SKILL.md +0 -105
  36. package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +0 -80
  37. package/dist/skills/blocks-iam-users/SKILL.md +0 -131
  38. package/dist/skills/blocks-localization-configuration/SKILL.md +0 -149
  39. package/dist/skills/blocks-localization-implementation/SKILL.md +0 -63
  40. package/dist/skills/blocks-mail/SKILL.md +0 -95
  41. package/dist/skills/blocks-notification/SKILL.md +0 -69
  42. package/dist/skills/blocks-notifier/SKILL.md +0 -107
  43. package/dist/skills/blocks-onboarding/SKILL.md +0 -77
  44. package/dist/skills/blocks-release-deployment/SKILL.md +0 -81
  45. package/dist/skills/blocks-secrets/SKILL.md +0 -81
  46. package/dist/skills/blocks-storage-configuration/SKILL.md +0 -93
  47. package/dist/skills/lint.mjs +0 -168
@@ -1,149 +0,0 @@
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,63 +0,0 @@
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 +0,0 @@
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 +0,0 @@
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,107 +0,0 @@
1
- ---
2
- name: blocks-notifier
3
- description: "Push real-time/offline notifications and manage a signed-in user's own notification inbox, via both the SDK (`blocksClient.notifier.*`) and the CLI (`blocks notifier notify|list|unread|mark-read|mark-all-read`). Distinct from the sibling blocks-notification skill, which configures tenant notification-*channel* settings on a different backing service, not sending. `notifier unread` flattens its subscription filter into GET query params since Fetch forbids a GET body. `--dry-run` before `--yes` on CLI `notify`/`mark-read`/`mark-all-read`."
4
- ---
5
-
6
- # Blocks Notifier — Send & Inbox
7
-
8
- `notifier` pushes real-time/offline notifications to users, roles, or subscription-filter matches, and reads/manages the signed-in user's own notification inbox. This is a **separate, deliberate concern from the blocks-notification skill**, which manages a tenant's notification-*channel configuration* — which channel/method a notification type uses — on an unrelated backing service. Both this skill and the sibling skill confirm the same distinction from their own side. Don't merge them, and don't reconcile them as if one were a typo for the other — they hit different backing services. If the ask is "configure which channel a notification type uses," route to the blocks-notification skill instead.
9
-
10
- Unlike `blocks-notification` (100% CLI, no SDK path), **`notifier` has both a CLI and an SDK surface**, and every one of the five operations exists on both:
11
-
12
- | Operation | CLI | SDK (`blocksClient.notifier.*`) |
13
- |---|---|---|
14
- | Send a notification | `blocks notifier notify` | `notify(request)` |
15
- | List the inbox | `blocks notifier list` | `getNotifications(options)` |
16
- | Unread by subscription filter | `blocks notifier unread` | `getUnreadNotificationsBySubscriptionFilter(request)` |
17
- | Mark one read | `blocks notifier mark-read <id>` | `markNotificationAsRead(request)` |
18
- | Mark all read | `blocks notifier mark-all-read` | `markAllNotificationAsRead()` |
19
-
20
- ## CLI — `blocks notifier *`
21
-
22
- Every CLI command is project-scoped: each requires an impersonated project session — there is no account-level mode for any of the five. Project resolution follows the same order as everywhere else in this CLI: `--project <tenantId>` flag, then the workspace's `blocks.json`, then the globally selected project from `blocks use <tenantId>`. See the blocks-onboarding skill if no project is selected yet.
23
-
24
- - **`blocks notifier notify [--user-ids a,b] [--roles a,b] [--connection-id <id>] [--configuration-name <n>] [--subscription-filters '<json>'] [--denormalized-payload <text>] [--save-denormalized-payload-as-object] [--content-available] [--response-key <k>] [--response-value <v>] [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]`** — target with at least one of `--user-ids`/`--roles`/`--subscription-filters`. `--user-ids` and `--roles` are comma-separated lists; `--subscription-filters` is a raw JSON array string (e.g. `[{"context":"orders","actionName":"created","value":"*"}]`, matching `BlocksNotifierSubscriptionFilter[]`). The body is built by merging `--body`/`--file` first, then layering the convenience flags on top — so an explicit convenience flag always wins over the same field in `--body`/`--file`, and an unset one never overwrites what `--body`/`--file` supplied. `--content-available` and `--save-denormalized-payload-as-object` are true-only booleans (absent when not passed, never an explicit `false`).
25
- - **`blocks notifier list [--unread-only] [--page <n>] [--page-size <n>] [--sort-by <property>] [--sort-desc] [--filter <text>] [--json]`** — read-only.
26
- - **`blocks notifier unread [--user-id <id>] [--context <c>] [--action-name <a>] [--value <v>] [--order-by <1|2>] [--json]`** — read-only. See "The GET-with-a-body quirk" below.
27
- - **`blocks notifier mark-read <id> [--dry-run] [--yes] [--json]`** — `id` may be positional or `--id`; one of the two is required (`Missing --id` if neither given).
28
- - **`blocks notifier mark-all-read [--dry-run] [--yes] [--json]`** — no body/arguments needed.
29
-
30
- ### The GET-with-a-body quirk (`unread`)
31
-
32
- This operation is documented upstream as a read with a JSON request body, which the Fetch spec forbids sending on a plain GET-style read. Both the CLI and SDK work around it the same way: flatten the filter fields into the query string instead. Confirmed field names, read directly from source:
33
-
34
- ```
35
- UserId
36
- SubscriptionFilterData.Context
37
- SubscriptionFilterData.ActionName
38
- SubscriptionFilterData.Value
39
- OrderBy
40
- ```
41
-
42
- CLI flags map to them as `--user-id` -> `UserId`, `--context` -> `SubscriptionFilterData.Context`, `--action-name` -> `SubscriptionFilterData.ActionName`, `--value` -> `SubscriptionFilterData.Value`, `--order-by` -> `OrderBy` (an integer, `1|2` per the SDK type — its enum meaning isn't published anywhere, so treat it as opaque and confirm with the user rather than guessing which value means what).
43
-
44
- This flattening is a **client-side inference, not something verified against a live call** — both the CLI and SDK made the same choice independently, which is corroborating but not proof the real backend accepts it. If a live `notifier unread` call ever errors, re-check this against the actual API response rather than assuming the flattening above is still correct.
45
-
46
- ## SDK — `blocksClient.notifier.*`
47
-
48
- ```ts
49
- import { blocksClient } from "../../lib/blocks/client";
50
-
51
- await blocksClient.notifier.notify({
52
- userIds: ["user-1"],
53
- roles: ["admin"],
54
- denormalizedPayload: '{"orderId":"123"}',
55
- saveDenormalizedPayloadAsAnObject: true
56
- });
57
-
58
- const inbox = await blocksClient.notifier.getNotifications({ isUnreadOnly: true, page: 1, pageSize: 20 });
59
-
60
- const unread = await blocksClient.notifier.getUnreadNotificationsBySubscriptionFilter({
61
- userId: "user-1",
62
- subscriptionFilterData: { context: "orders", actionName: "created" }
63
- });
64
-
65
- await blocksClient.notifier.markNotificationAsRead({ id: notificationId });
66
- await blocksClient.notifier.markAllNotificationAsRead();
67
- ```
68
-
69
- - **`notify(request: BlocksNotifyRequest)`** — fields: `configurationName?`, `connectionId?`, `contentAvailable?: boolean`, `denormalizedPayload?: string`, `responseKey?`, `responseValue?`, `roles?: string[]`, `saveDenormalizedPayloadAsAnObject?: boolean`, `subscriptionFilters?: BlocksNotifierSubscriptionFilter[]`, `userIds?: string[]`.
70
- - **`getUnreadNotificationsBySubscriptionFilter(request)`** — fields: `orderBy?: 1 | 2` (opaque enum), `subscriptionFilterData?: { actionName?, context?, value? }`, `userId?`. Internally builds the same flattened query as the CLI (see above) — this is the SDK-side half of the same documented workaround.
71
- - **`getNotifications(options: BlocksGetNotificationsOptions = {})`** — options: `filter?`, `isUnreadOnly?`, `page?`, `pageSize?`, `sortBy?`, `sortDescending?`. Response shape: `{ notifications: Record<string, unknown>[], totalNotificationsCount: number, unReadNotificationsCount: number }`.
72
- - **`markAllNotificationAsRead()`** — no arguments.
73
- - **`markNotificationAsRead(request: BlocksMarkNotificationAsReadRequest)`** — request: `{ id: string }`.
74
-
75
- `BlocksOfflineNotification` (the shape of items `getUnreadNotificationsBySubscriptionFilter` resolves to) is a loose `Record<string, unknown>` plus known fields `correlationId?`, `createdTime?`, `denormalizedPayload?`, `id?`, `isRead?`, `payload?`, `readByRoles?: string[]`, `readByUserIds?: string[]`. `BlocksNotifierPassThroughResponse` (the `notify`/mark-read/mark-all-read return type) is an untyped `Record<string, unknown>` — the SDK doesn't shape it further; don't assume fields beyond what a live response actually contains.
76
-
77
- The SDK methods don't take a project/tenant argument per call — project context comes from however the app's shared `blocksClient` instance was constructed (its `xBlocksKey`/`appDomain`), same as every other `blocksClient.*` namespace. Don't create a second client just for notifier calls.
78
-
79
- ## Mutation discipline (CLI only)
80
-
81
- `notify`, `mark-read`, and `mark-all-read` are the three CLI mutations, and all three follow the same two-gate pattern used throughout this CLI:
82
-
83
- 1. **`--dry-run`** short-circuits before any network call or confirmation prompt, printing a preview of what would be sent (`mark-all-read`'s preview has no request body since it sends none).
84
- 2. Without `--dry-run`, a confirmation step accepts `--yes` outright or, interactively, prompts to type "yes" to continue, and cancels on anything else.
85
-
86
- Always show the `--dry-run` output and get explicit approval before re-running with `--yes`. `list` and `unread` are read-only and have neither flag — don't tell a user to `--dry-run` a `list` or `unread` call. The SDK methods have no equivalent gate at all; that discipline is a CLI-only convention for terminal/CI operators, not something app code needs to replicate.
87
-
88
- ## Gotchas
89
-
90
- - **`notifier` and `notification` are not the same thing, and this is not an oversight to fix.** `notifier` (this skill) sends notifications and reads a user's inbox, on both CLI and SDK. `notification` (the sibling skill) configures a tenant's notification channel settings, CLI-only. Don't answer a "send a notification" ask with `notification save`, and don't answer a "configure the channel" ask with `notifier`.
91
- - **`notifier unread`'s query-param flattening is an inferred client-side workaround for a Fetch-spec conflict, not verified against a live call.** See "The GET-with-a-body quirk" above. If it ever errors in practice, re-check whether the real endpoint tolerates a body server-side (some non-browser/non-Node HTTP stacks do) before assuming the flattening itself is broken.
92
- - **`--content-available` and `--save-denormalized-payload-as-object` on `notify` are true-only flags.** Passing them sends `true`; omitting them omits the field entirely — there's no way to send an explicit `false` through the convenience flags (use `--body`/`--file` for that).
93
- - **Convenience flags on `notify` win over `--body`/`--file`.** The merge order is `--body`/`--file` first, then the individual flags spread on top — so a flag like `--connection-id` always overrides the same key in `--body` if both are given.
94
- - **`order-by` (both CLI `--order-by` and SDK `orderBy`) is an opaque `1|2` enum** — its meaning isn't published anywhere. Ask the user or confirm from the live API rather than guessing which value sorts which way.
95
- - **Every CLI command is project-scoped**; there's no account-level mode. The SDK has no per-call project argument — it inherits whatever project the shared `blocksClient` was configured for.
96
- - **`mark-read`'s `id` (positional or `--id`) is always required** — never guessed or defaulted.
97
- - **`--dry-run` before `--yes`, always**, on the three CLI mutations — same discipline as every other mutating `blocks` command in this pack.
98
-
99
- ## Example trigger prompts
100
-
101
- - "Send a notification to these user IDs from my app." -> SDK `blocksClient.notifier.notify({ userIds: [...] })`.
102
- - "Push a notification to everyone matching this subscription filter." -> SDK `notify({ subscriptionFilters: [...] })`, or `blocks notifier notify --subscription-filters '<json>' --dry-run --json` from the terminal.
103
- - "Show me a user's unread notifications for the 'orders' context." -> `blocks notifier unread --user-id <id> --context orders --json`, or SDK `getUnreadNotificationsBySubscriptionFilter({ userId, subscriptionFilterData: { context: "orders" } })`.
104
- - "List my notification inbox, unread only." -> `blocks notifier list --unread-only --json`, or SDK `getNotifications({ isUnreadOnly: true })`.
105
- - "Mark this notification as read." -> `blocks notifier mark-read <id> --dry-run --json`, then `--yes`.
106
- - "Mark everything in the inbox as read." -> `blocks notifier mark-all-read --dry-run --json`, then `--yes`.
107
- - "Configure which channel the order-shipped notification uses." -> not this skill; that's the blocks-notification skill.
@@ -1,77 +0,0 @@
1
- ---
2
- name: blocks-onboarding
3
- description: "Onboard a user into SELISE Blocks before any other Blocks skill can run, using the `blocks` CLI — never raw API calls. Detects CLI/login/project state via `blocks auth status --json`/`doctor --json`, closes install/login/project gaps, resolves the app OIDC client, scaffolds with `blocks new web`, then runs `blocks init` only inside the generated/existing app directory when local Blocks files are needed. Use for new users or `not_logged_in`/`project_not_selected`."
4
- ---
5
-
6
- # Blocks — Onboarding
7
-
8
- Every other Blocks skill assumes: the `blocks` CLI is installed, the user is logged in (`login`), and a project is selected (`use`). This skill detects which of those is missing and closes the gap. **Everything here goes through `blocks` — never a raw `fetch`/`curl` against `api.seliseblocks.com`.**
9
-
10
- The CLI's own usage guide (bundled with the `blocks-cli` package) is the command-level ground truth (exact flags, defaults, failure codes); this skill is the conversational flow around it — what to ask, what's portal-only, and in what order.
11
-
12
- ## Probe first, ask second
13
-
14
- Run `blocks auth status --json` and branch on the result — don't interrogate the user about state that's discoverable:
15
-
16
- | Signal | State | Do this |
17
- |---|---|---|
18
- | command not found | CLI not installed | `npm install -g @seliseblocks/cli-os`, then re-probe |
19
- | `accountAccessToken`/`accountRefreshToken` both `"missing"` | Never logged in | Step 1 — `login` |
20
- | logged in, no project selected (check `blocks doctor --json`'s "Project selected" check) | No project selected | Step 2 — list/`use` |
21
- | logged in, project selected | Ready | Confirm the project with the user — always show the full accessible-project list and which one is currently selected, never silently continue on a prior session's selection — then hand off to the skill/task that brought you here |
22
-
23
- If anything looks broken rather than simply "not yet done" (unreadable/stale local token storage after a machine migration, Windows profile change, Keychain reset), run `blocks doctor --json` for the fuller diagnostic — it checks Node version, config/token/secret file locations, and token freshness in one pass. If storage itself is unreadable or corrupted, `blocks auth remove <account>` clears cached tokens and stored local credentials (restoring the packaged default account), then re-run `login`.
24
-
25
- ## Step 1 — Log in
26
-
27
- The CLI authenticates itself with no setup. There is no OIDC client to register in the portal for this, no client id/secret to collect from the user, and nothing about how the CLI does it to look up, print, or report — just log in:
28
-
29
- ```bash
30
- blocks login
31
- ```
32
-
33
- Device-code flow: it prints a verification URL and user code, opens the browser to the verification page when possible so the user only needs to click approve, then polls until the device is authorized; stores account access and refresh tokens and auto-refreshes later. Run it yourself rather than only telling the user to run it, so you can read the printed code/URL and confirm the result right after.
34
-
35
- Verify with `blocks auth status --json` — re-run after login rather than assuming it worked.
36
-
37
- ## Step 2 — Project
38
-
39
- Ask **what the user wants to build** and whether they already have a project, rather than assuming:
40
-
41
- ```bash
42
- blocks projects list --json
43
- ```
44
-
45
- Always show the full list of accessible projects, and if one already appears selected, say which one — never silently continue on a prior session's selection. If projects exist, confirm which one (and which environment) the user wants; never guess.
46
-
47
- **`projects create` is currently disabled in this CLI build** (commented out pending a product decision — there is no CLI path to create a new project). If none of the listed projects fit, tell the user a new project must be created from the Blocks portal first; once they confirm it exists, re-run `blocks projects list --json` and continue from here.
48
-
49
- Then select it:
50
-
51
- ```bash
52
- blocks use <x-blocks-key>
53
- ```
54
-
55
- Project (impersonation) tokens are created lazily from the account session the first time a project-scoped command needs one — never ask the user for a project token directly. If an impersonated project token later gets stuck, rejected, or expired and `blocks auth refresh --project --json` doesn't fix it, recover with:
56
-
57
- ```bash
58
- blocks deselect # drops the selection and its cached impersonation token
59
- blocks use <x-blocks-key> # reselect the same x-blocks-key to force a fresh impersonation
60
- ```
61
-
62
- ## Step 3 — Local workspace + hand off
63
-
64
- Route to what the user actually wants. Do **not** run `blocks init` from a parent workspace before scaffolding a new app; it creates `blocks.json` and `blocks/` in the current directory. For a new frontend, scaffold first, `cd <appName>`, then run `blocks init` there only when the work needs project-local Blocks files such as data schemas or rules. For an existing app, run `blocks init` from that app's root. Safe to re-run: it never overwrites files that already exist. (`init` does not create a localization folder or any release-related file — `blocks/localization/` only appears later, lazily, the first time `blocks localization pull` writes to it, and there is no `blocks/release/*` file at all.)
65
-
66
- - Building a frontend from scratch → resolve the app's public OIDC client first, then scaffold:
67
- - `blocks auth oidc-clients list --json` — check whether a client already registered for this project fits. If none fits, create one directly (no portal visit needed): `blocks auth oidc-clients save --client-display-name <appName> --client-type public --redirect-uris https://<domain>:5173/login/callback --scope "openid profile" --require-pkce --register-as-identity-provider --auto-redirect --dry-run --json`, then re-run with `--yes` after showing the dry-run output and getting approval. `--client-type public` is required — IAM derives `tokenEndpointAuthMethod` from it, so omitting it stores a browser client as confidential. `--register-as-identity-provider` creates the linked identity provider in the same call; nothing further to run. `--auto-redirect` matters too — the scaffolded login page already navigates straight to the provider itself, so without it IAM's hosted login page shows a redundant manual "continue" click. When updating an *existing* client instead of creating one, always pass `--item-id` — the save endpoint replaces the whole client document, and the CLI fetches the current one first to merge your change into it rather than resetting the rest. See the blocks-iam-sso-oidc-configuration skill for the full decision tree and field-level gotchas.
68
- - `blocks new web <name> --x-blocks-key <tenantId> --app-domain <domain> --client-id <the-resolved-client-id>`. **Always pass `--client-id` and `--app-domain` explicitly** — omitting either drops `new web` into an interactive pick-list prompt with no non-interactive escape (not even to "skip"), which hangs a scripted/agent run with no stdin to answer it. Omit `--blocks-api-url` unless the project uses a non-default gateway; the scaffold derives it from the app domain, e.g. `https://dqrsf.slsblx.com` -> `https://blocksapi.slsblx.com`. After scaffolding, `cd <name>` before installing packages, running `blocks init`, or adding local skill files so `blocks.json` and `blocks/` stay inside the app. Once it resolves the client id, `new web` also checks the tenant's AuthController config and turns on `isOidcEnabled` if it's off — nothing further to do for login to actually work; if you're wiring an existing app instead (`blocks sdk client`, no `new web` call), check that yourself first: `blocks auth config get --json`, and if `isOidcEnabled` is `false`, `blocks auth config save --oidc-enabled --dry-run --json` then `--yes`.
69
- - Defining data / CRUD / localization / release on an existing project → hand off to the matching skill; the project is already selected via `blocks use`, so its commands can proceed directly.
70
-
71
- ## Gotchas
72
-
73
- - **Only one OIDC client matters here, and it's not the CLI's.** The CLI authenticates itself with no setup — nothing to register, nothing portal-only about `blocks login` itself, and nothing about how it does so to look up or mention. The only OIDC client involved is the scaffolded app's *public* browser client for its own end-user login (Step 3) — and that no longer requires the portal either: `blocks auth oidc-clients list`/`save` resolve or create it entirely through the CLI on the project's impersonated token. The portal remains available if the user prefers it, but it's an alternative, not a requirement. Don't tell a user they need to register anything before `blocks login` will work, and don't send them to the portal for the app's OIDC client by default.
74
- - **`blocks new web` hangs a non-interactive run if `--client-id` or `--app-domain` is omitted** — it drops into an interactive pick-list (even to offer "skip") with no stdin to answer it in an agent-driven session. Always resolve both explicitly first (Step 3) rather than omitting either and hoping for a graceful default.
75
- - **Never open, read, print, or expose the CLI's local storage files** (its config/token/secret files on disk) or anything inside them — client ids, root tenant id, account names, tokens. Only ever interact with them through `blocks` commands, never by inspecting the files directly. `auth status`/`doctor` only ever report token state (`missing`/`valid`/`expired`), never the value.
76
- - **Known CLI error codes and fixes** (from the CLI's own error handling): `not_logged_in` → `blocks login`; `refresh_token_rejected` → `blocks login`; unreadable/stale local auth storage → `blocks auth remove <account>` then `blocks login`; `project_not_selected` → `blocks use <x-blocks-key>` (or pass `--project <tenantId>` for a single one-off command); `api_auth_failed` → `blocks auth status --json` then log in again; `impersonation_invalid_client` → not a stale-token problem, the account's OIDC client isn't registered for impersonation — check `blocks auth config get` and have an admin register it, `login`/`deselect`+`use` won't fix this one.
77
- - **`--dry-run` before `--yes`** on every mutating command (`auth oidc-clients save`, `data schema push`/`data rules deploy`, `localization push`, `release deploy`) — this recurs in every skill that mutates project state.