@seliseblocks/cli-os 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_USAGE_GUIDE.md +328 -58
- package/README.md +83 -46
- package/dist/commands/auth/client-credentials/delete.d.ts +1 -0
- package/dist/commands/auth/client-credentials/delete.js +23 -0
- package/dist/commands/auth/client-credentials/list.d.ts +1 -0
- package/dist/commands/auth/client-credentials/list.js +14 -0
- package/dist/commands/auth/client-credentials/save.d.ts +1 -0
- package/dist/commands/auth/client-credentials/save.js +41 -0
- package/dist/commands/auth/config/get.d.ts +1 -0
- package/dist/commands/auth/config/get.js +14 -0
- package/dist/commands/auth/config/save.d.ts +1 -0
- package/dist/commands/auth/config/save.js +37 -0
- package/dist/commands/auth/idp/create.d.ts +1 -0
- package/dist/commands/auth/idp/create.js +46 -0
- package/dist/commands/auth/idp/delete.d.ts +1 -0
- package/dist/commands/auth/idp/delete.js +23 -0
- package/dist/commands/auth/idp/get.d.ts +1 -0
- package/dist/commands/auth/idp/get.js +16 -0
- package/dist/commands/auth/idp/list.d.ts +1 -0
- package/dist/commands/auth/idp/list.js +14 -0
- package/dist/commands/auth/idp/status.d.ts +1 -0
- package/dist/commands/auth/idp/status.js +27 -0
- package/dist/commands/auth/idp/update.d.ts +7 -0
- package/dist/commands/auth/idp/update.js +45 -0
- package/dist/commands/auth/oidc-clients/delete.d.ts +1 -0
- package/dist/commands/auth/oidc-clients/delete.js +23 -0
- package/dist/commands/auth/oidc-clients/get.d.ts +1 -0
- package/dist/commands/auth/oidc-clients/get.js +16 -0
- package/dist/commands/auth/oidc-clients/list.d.ts +1 -0
- package/dist/commands/auth/oidc-clients/list.js +14 -0
- package/dist/commands/auth/oidc-clients/rotate-secret.d.ts +1 -0
- package/dist/commands/auth/oidc-clients/rotate-secret.js +23 -0
- package/dist/commands/auth/oidc-clients/save.d.ts +2 -0
- package/dist/commands/auth/oidc-clients/save.js +57 -0
- package/dist/commands/auth/refresh.js +1 -1
- package/dist/commands/data/config/create.d.ts +1 -0
- package/dist/commands/data/config/create.js +38 -0
- package/dist/commands/data/config/get.d.ts +1 -0
- package/dist/commands/data/config/get.js +14 -0
- package/dist/commands/data/config/update.d.ts +1 -0
- package/dist/commands/data/config/update.js +41 -0
- package/dist/commands/data/files/create-folder.d.ts +1 -0
- package/dist/commands/data/files/create-folder.js +34 -0
- package/dist/commands/data/files/delete-folder.d.ts +1 -0
- package/dist/commands/data/files/delete-folder.js +25 -0
- package/dist/commands/data/files/delete.d.ts +1 -0
- package/dist/commands/data/files/delete.js +28 -0
- package/dist/commands/data/files/dms-list.d.ts +1 -0
- package/dist/commands/data/files/dms-list.js +27 -0
- package/dist/commands/data/files/dms-upload.d.ts +6 -0
- package/dist/commands/data/files/dms-upload.js +41 -0
- package/dist/commands/data/files/get-many.d.ts +1 -0
- package/dist/commands/data/files/get-many.js +24 -0
- package/dist/commands/data/files/get.d.ts +1 -0
- package/dist/commands/data/files/get.js +21 -0
- package/dist/commands/data/files/info.d.ts +1 -0
- package/dist/commands/data/files/info.js +30 -0
- package/dist/commands/data/files/presigned-upload-url.d.ts +6 -0
- package/dist/commands/data/files/presigned-upload-url.js +34 -0
- package/dist/commands/data/files/update-additional-info.d.ts +1 -0
- package/dist/commands/data/files/update-additional-info.js +26 -0
- package/dist/commands/data/files/upload-to-local-storage.d.ts +5 -0
- package/dist/commands/data/files/upload-to-local-storage.js +55 -0
- package/dist/commands/data/files/upload-to-url.d.ts +8 -0
- package/dist/commands/data/files/upload-to-url.js +36 -0
- package/dist/commands/data/files/upload.d.ts +7 -0
- package/dist/commands/data/files/upload.js +145 -0
- package/dist/commands/data/rules/policy/delete.d.ts +1 -0
- package/dist/commands/data/rules/policy/delete.js +24 -0
- package/dist/commands/data/rules/policy/get.d.ts +1 -0
- package/dist/commands/data/rules/policy/get.js +17 -0
- package/dist/commands/data/schema/aggregation.d.ts +1 -0
- package/dist/commands/data/schema/aggregation.js +26 -0
- package/dist/commands/data/schema/change-logs.d.ts +1 -0
- package/dist/commands/data/schema/change-logs.js +14 -0
- package/dist/commands/data/schema/delete.d.ts +1 -0
- package/dist/commands/data/schema/delete.js +24 -0
- package/dist/commands/data/schema/fields.d.ts +5 -0
- package/dist/commands/data/schema/fields.js +39 -0
- package/dist/commands/data/schema/get-by-name.d.ts +1 -0
- package/dist/commands/data/schema/get-by-name.js +17 -0
- package/dist/commands/data/schema/get.d.ts +1 -0
- package/dist/commands/data/schema/get.js +17 -0
- package/dist/commands/data/schema/info/list.d.ts +1 -0
- package/dist/commands/data/schema/info/list.js +15 -0
- package/dist/commands/data/schema/info/save.d.ts +1 -0
- package/dist/commands/data/schema/info/save.js +34 -0
- package/dist/commands/data/schema/info/update.d.ts +1 -0
- package/dist/commands/data/schema/info/update.js +35 -0
- package/dist/commands/data/sync.d.ts +7 -0
- package/dist/commands/data/sync.js +46 -0
- package/dist/commands/data/validation/by-schema-field.d.ts +1 -0
- package/dist/commands/data/validation/by-schema-field.js +18 -0
- package/dist/commands/data/validation/by-schema.d.ts +1 -0
- package/dist/commands/data/validation/by-schema.js +17 -0
- package/dist/commands/data/validation/delete.d.ts +1 -0
- package/dist/commands/data/validation/delete.js +24 -0
- package/dist/commands/data/validation/get.d.ts +1 -0
- package/dist/commands/data/validation/get.js +17 -0
- package/dist/commands/data/validation/list.d.ts +1 -0
- package/dist/commands/data/validation/list.js +25 -0
- package/dist/commands/data/validation/save.d.ts +6 -0
- package/dist/commands/data/validation/save.js +44 -0
- package/dist/commands/deselect.js +1 -1
- package/dist/commands/iam/email/available.d.ts +1 -0
- package/dist/commands/iam/email/available.js +17 -0
- package/dist/commands/iam/organizations/config-get.d.ts +1 -0
- package/dist/commands/iam/organizations/config-get.js +14 -0
- package/dist/commands/iam/organizations/config-save.d.ts +1 -0
- package/dist/commands/iam/organizations/config-save.js +34 -0
- package/dist/commands/iam/organizations/create.d.ts +1 -0
- package/dist/commands/iam/organizations/create.js +37 -0
- package/dist/commands/iam/organizations/get.d.ts +1 -0
- package/dist/commands/iam/organizations/get.js +16 -0
- package/dist/commands/iam/organizations/list.d.ts +1 -0
- package/dist/commands/iam/organizations/list.js +26 -0
- package/dist/commands/iam/organizations/my.d.ts +1 -0
- package/dist/commands/iam/organizations/my.js +14 -0
- package/dist/commands/iam/organizations/update.d.ts +1 -0
- package/dist/commands/iam/organizations/update.js +39 -0
- package/dist/commands/iam/permissions/by-severity.d.ts +1 -0
- package/dist/commands/iam/permissions/by-severity.js +14 -0
- package/dist/commands/iam/permissions/create.d.ts +1 -0
- package/dist/commands/iam/permissions/create.js +39 -0
- package/dist/commands/iam/permissions/get.d.ts +1 -0
- package/dist/commands/iam/permissions/get.js +16 -0
- package/dist/commands/iam/permissions/list.d.ts +1 -0
- package/dist/commands/iam/permissions/list.js +41 -0
- package/dist/commands/iam/permissions/update.d.ts +1 -0
- package/dist/commands/iam/permissions/update.js +40 -0
- package/dist/commands/iam/resources/features.d.ts +1 -0
- package/dist/commands/iam/resources/features.js +19 -0
- package/dist/commands/iam/resources/groups.d.ts +1 -0
- package/dist/commands/iam/resources/groups.js +14 -0
- package/dist/commands/iam/roles/assign-permissions.d.ts +1 -0
- package/dist/commands/iam/roles/assign-permissions.js +33 -0
- package/dist/commands/iam/roles/assignable.d.ts +1 -0
- package/dist/commands/iam/roles/assignable.js +14 -0
- package/dist/commands/iam/roles/create.d.ts +1 -0
- package/dist/commands/iam/roles/create.js +35 -0
- package/dist/commands/iam/roles/get.d.ts +1 -0
- package/dist/commands/iam/roles/get.js +16 -0
- package/dist/commands/iam/roles/list.d.ts +1 -0
- package/dist/commands/iam/roles/list.js +34 -0
- package/dist/commands/iam/roles/update.d.ts +1 -0
- package/dist/commands/iam/roles/update.js +35 -0
- package/dist/commands/iam/signup-settings/get.d.ts +1 -0
- package/dist/commands/iam/signup-settings/get.js +14 -0
- package/dist/commands/iam/signup-settings/save.d.ts +1 -0
- package/dist/commands/iam/signup-settings/save.js +32 -0
- package/dist/commands/iam/users/access-grant.d.ts +1 -0
- package/dist/commands/iam/users/access-grant.js +32 -0
- package/dist/commands/iam/users/access-revoke.d.ts +1 -0
- package/dist/commands/iam/users/access-revoke.js +24 -0
- package/dist/commands/iam/users/activate.d.ts +1 -0
- package/dist/commands/iam/users/activate.js +24 -0
- package/dist/commands/iam/users/create.d.ts +1 -0
- package/dist/commands/iam/users/create.js +39 -0
- package/dist/commands/iam/users/deactivate.d.ts +1 -0
- package/dist/commands/iam/users/deactivate.js +24 -0
- package/dist/commands/iam/users/exists.d.ts +1 -0
- package/dist/commands/iam/users/exists.js +17 -0
- package/dist/commands/iam/users/get.d.ts +1 -0
- package/dist/commands/iam/users/get.js +17 -0
- package/dist/commands/iam/users/list.d.ts +1 -0
- package/dist/commands/iam/users/list.js +34 -0
- package/dist/commands/iam/users/update.d.ts +1 -0
- package/dist/commands/iam/users/update.js +35 -0
- package/dist/commands/init.js +0 -12
- package/dist/commands/localization/assistant/translation-suggestion.d.ts +1 -0
- package/dist/commands/localization/assistant/translation-suggestion.js +35 -0
- package/dist/commands/localization/config/get-webhook.d.ts +1 -0
- package/dist/commands/localization/config/get-webhook.js +14 -0
- package/dist/commands/localization/config/save-webhook.d.ts +1 -0
- package/dist/commands/localization/config/save-webhook.js +47 -0
- package/dist/commands/localization/glossary/delete.d.ts +1 -0
- package/dist/commands/localization/glossary/delete.js +24 -0
- package/dist/commands/localization/glossary/get.d.ts +1 -0
- package/dist/commands/localization/glossary/get.js +17 -0
- package/dist/commands/localization/glossary/list.d.ts +1 -0
- package/dist/commands/localization/glossary/list.js +22 -0
- package/dist/commands/localization/glossary/save.d.ts +1 -0
- package/dist/commands/localization/glossary/save.js +38 -0
- package/dist/commands/localization/glossary/suggested.d.ts +1 -0
- package/dist/commands/localization/glossary/suggested.js +20 -0
- package/dist/commands/localization/key/delete-keys.d.ts +1 -0
- package/dist/commands/localization/key/delete-keys.js +28 -0
- package/dist/commands/localization/key/delete.d.ts +1 -0
- package/dist/commands/localization/key/delete.js +24 -0
- package/dist/commands/localization/key/generate-uilm-file.d.ts +1 -0
- package/dist/commands/localization/key/generate-uilm-file.js +29 -0
- package/dist/commands/localization/key/get-by-names.d.ts +1 -0
- package/dist/commands/localization/key/get-by-names.js +24 -0
- package/dist/commands/localization/key/get-language-file-generation-history.d.ts +1 -0
- package/dist/commands/localization/key/get-language-file-generation-history.js +19 -0
- package/dist/commands/localization/key/get-localization-timeline.d.ts +1 -0
- package/dist/commands/localization/key/get-localization-timeline.js +28 -0
- package/dist/commands/localization/key/get-timeline-by-operation-id.d.ts +1 -0
- package/dist/commands/localization/key/get-timeline-by-operation-id.js +21 -0
- package/dist/commands/localization/key/get-timeline.d.ts +1 -0
- package/dist/commands/localization/key/get-timeline.js +25 -0
- package/dist/commands/localization/key/get-uilm-exported-files.d.ts +1 -0
- package/dist/commands/localization/key/get-uilm-exported-files.js +22 -0
- package/dist/commands/localization/key/get-uilm-file.d.ts +1 -0
- package/dist/commands/localization/key/get-uilm-file.js +18 -0
- package/dist/commands/localization/key/get.d.ts +1 -0
- package/dist/commands/localization/key/get.js +17 -0
- package/dist/commands/localization/key/list.d.ts +1 -0
- package/dist/commands/localization/key/list.js +39 -0
- package/dist/commands/localization/key/rollback.d.ts +1 -0
- package/dist/commands/localization/key/rollback.js +24 -0
- package/dist/commands/localization/key/save.d.ts +1 -0
- package/dist/commands/localization/key/save.js +44 -0
- package/dist/commands/localization/key/translate-all.d.ts +1 -0
- package/dist/commands/localization/key/translate-all.js +30 -0
- package/dist/commands/localization/key/translate-and-export.d.ts +7 -0
- package/dist/commands/localization/key/translate-and-export.js +132 -0
- package/dist/commands/localization/key/translate-key.d.ts +1 -0
- package/dist/commands/localization/key/translate-key.js +27 -0
- package/dist/commands/localization/key/translate-keys.d.ts +1 -0
- package/dist/commands/localization/key/translate-keys.js +31 -0
- package/dist/commands/localization/key/uilm-export.d.ts +1 -0
- package/dist/commands/localization/key/uilm-export.js +33 -0
- package/dist/commands/localization/key/uilm-import.d.ts +1 -0
- package/dist/commands/localization/key/uilm-import.js +26 -0
- package/dist/commands/localization/language/delete.d.ts +1 -0
- package/dist/commands/localization/language/delete.js +24 -0
- package/dist/commands/localization/language/list-for-tenant.d.ts +1 -0
- package/dist/commands/localization/language/list-for-tenant.js +14 -0
- package/dist/commands/localization/language/list.d.ts +1 -0
- package/dist/commands/localization/language/list.js +14 -0
- package/dist/commands/localization/language/save.d.ts +1 -0
- package/dist/commands/localization/language/save.js +36 -0
- package/dist/commands/localization/language/set-default.d.ts +1 -0
- package/dist/commands/localization/language/set-default.js +24 -0
- package/dist/commands/localization/module/list-for-tenant.d.ts +1 -0
- package/dist/commands/localization/module/list-for-tenant.js +14 -0
- package/dist/commands/localization/module/list.d.ts +1 -0
- package/dist/commands/localization/module/list.js +14 -0
- package/dist/commands/localization/module/save.d.ts +1 -0
- package/dist/commands/localization/module/save.js +32 -0
- package/dist/commands/localization/module/tag-glossary.d.ts +1 -0
- package/dist/commands/localization/module/tag-glossary.js +28 -0
- package/dist/commands/mail/config/delete.d.ts +1 -0
- package/dist/commands/mail/config/delete.js +24 -0
- package/dist/commands/mail/config/duplicate.d.ts +1 -0
- package/dist/commands/mail/config/duplicate.js +24 -0
- package/dist/commands/mail/config/get.d.ts +1 -0
- package/dist/commands/mail/config/get.js +17 -0
- package/dist/commands/mail/config/list.d.ts +1 -0
- package/dist/commands/mail/config/list.js +14 -0
- package/dist/commands/mail/config/save.d.ts +1 -0
- package/dist/commands/mail/config/save.js +44 -0
- package/dist/commands/mail/mailbox/get.d.ts +1 -0
- package/dist/commands/mail/mailbox/get.js +17 -0
- package/dist/commands/mail/mailbox/list.d.ts +1 -0
- package/dist/commands/mail/mailbox/list.js +24 -0
- package/dist/commands/mail/send.d.ts +1 -0
- package/dist/commands/mail/send.js +40 -0
- package/dist/commands/mail/sendtoany.d.ts +1 -0
- package/dist/commands/mail/sendtoany.js +41 -0
- package/dist/commands/mail/template/clone.d.ts +1 -0
- package/dist/commands/mail/template/clone.js +33 -0
- package/dist/commands/mail/template/delete.d.ts +1 -0
- package/dist/commands/mail/template/delete.js +24 -0
- package/dist/commands/mail/template/get.d.ts +1 -0
- package/dist/commands/mail/template/get.js +17 -0
- package/dist/commands/mail/template/list.d.ts +1 -0
- package/dist/commands/mail/template/list.js +24 -0
- package/dist/commands/mail/template/save.d.ts +1 -0
- package/dist/commands/mail/template/save.js +37 -0
- package/dist/commands/mfa/backup-codes/generate.d.ts +1 -0
- package/dist/commands/mfa/backup-codes/generate.js +22 -0
- package/dist/commands/mfa/backup-codes/list.d.ts +1 -0
- package/dist/commands/mfa/backup-codes/list.js +14 -0
- package/dist/commands/mfa/backup-codes/use.d.ts +1 -0
- package/dist/commands/mfa/backup-codes/use.js +18 -0
- package/dist/commands/mfa/config-get.d.ts +1 -0
- package/dist/commands/mfa/config-get.js +14 -0
- package/dist/commands/mfa/config-save.d.ts +1 -0
- package/dist/commands/mfa/config-save.js +36 -0
- package/dist/commands/mfa/disable.d.ts +1 -0
- package/dist/commands/mfa/disable.js +22 -0
- package/dist/commands/mfa/generate.d.ts +1 -0
- package/dist/commands/mfa/generate.js +22 -0
- package/dist/commands/mfa/method-set.d.ts +1 -0
- package/dist/commands/mfa/method-set.js +20 -0
- package/dist/commands/mfa/resend.d.ts +1 -0
- package/dist/commands/mfa/resend.js +20 -0
- package/dist/commands/mfa/totp-enable.d.ts +10 -0
- package/dist/commands/mfa/totp-enable.js +52 -0
- package/dist/commands/mfa/totp-setup.d.ts +1 -0
- package/dist/commands/mfa/totp-setup.js +15 -0
- package/dist/commands/mfa/totp-verify-setup.d.ts +1 -0
- package/dist/commands/mfa/totp-verify-setup.js +17 -0
- package/dist/commands/mfa/verify.d.ts +1 -0
- package/dist/commands/mfa/verify.js +26 -0
- package/dist/commands/new/web.js +115 -15
- package/dist/commands/notification/delete.d.ts +1 -0
- package/dist/commands/notification/delete.js +24 -0
- package/dist/commands/notification/get.d.ts +1 -0
- package/dist/commands/notification/get.js +17 -0
- package/dist/commands/notification/list.d.ts +1 -0
- package/dist/commands/notification/list.js +22 -0
- package/dist/commands/notification/save.d.ts +1 -0
- package/dist/commands/notification/save.js +34 -0
- package/dist/commands/notifier/list.d.ts +1 -0
- package/dist/commands/notifier/list.js +23 -0
- package/dist/commands/notifier/mark-all-read.d.ts +1 -0
- package/dist/commands/notifier/mark-all-read.js +23 -0
- package/dist/commands/notifier/mark-read.d.ts +1 -0
- package/dist/commands/notifier/mark-read.js +24 -0
- package/dist/commands/notifier/notify.d.ts +1 -0
- package/dist/commands/notifier/notify.js +39 -0
- package/dist/commands/notifier/unread.d.ts +1 -0
- package/dist/commands/notifier/unread.js +25 -0
- package/dist/commands/projects/get.js +16 -14
- package/dist/commands/projects/list.js +2 -7
- package/dist/commands/release/builds/list.js +21 -3
- package/dist/commands/release/deploy.js +121 -30
- package/dist/commands/sdk/client.d.ts +1 -0
- package/dist/commands/sdk/client.js +99 -0
- package/dist/commands/secrets/get.d.ts +1 -0
- package/dist/commands/secrets/get.js +21 -0
- package/dist/commands/secrets/save.d.ts +1 -0
- package/dist/commands/secrets/save.js +51 -0
- package/dist/commands/skill/add.d.ts +1 -0
- package/dist/commands/skill/add.js +19 -0
- package/dist/commands/skill/list.d.ts +1 -0
- package/dist/commands/skill/list.js +15 -0
- package/dist/commands/skill/show.d.ts +1 -0
- package/dist/commands/skill/show.js +15 -0
- package/dist/commands/storage/config/delete.d.ts +1 -0
- package/dist/commands/storage/config/delete.js +24 -0
- package/dist/commands/storage/config/get.d.ts +1 -0
- package/dist/commands/storage/config/get.js +17 -0
- package/dist/commands/storage/config/list.d.ts +1 -0
- package/dist/commands/storage/config/list.js +14 -0
- package/dist/commands/storage/config/save.d.ts +1 -0
- package/dist/commands/storage/config/save.js +49 -0
- package/dist/index.js +950 -152
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/api.js +39 -21
- package/dist/lib/args.d.ts +3 -0
- package/dist/lib/args.js +15 -0
- package/dist/lib/auth.d.ts +5 -2
- package/dist/lib/auth.js +23 -35
- package/dist/lib/config.js +7 -7
- package/dist/lib/json-flag.d.ts +17 -0
- package/dist/lib/json-flag.js +55 -0
- package/dist/lib/project-info.d.ts +33 -0
- package/dist/lib/project-info.js +39 -0
- package/dist/lib/prompt.d.ts +2 -0
- package/dist/lib/prompt.js +28 -0
- package/dist/lib/scaffold-web/dashboard.js +1 -1
- package/dist/lib/scaffold-web/root-files.js +1 -1
- package/dist/lib/secret-store.js +7 -7
- package/dist/lib/skills.d.ts +17 -0
- package/dist/lib/skills.js +69 -0
- package/dist/lib/token.d.ts +2 -0
- package/dist/lib/token.js +12 -0
- package/dist/lib/workspace.d.ts +0 -4
- package/dist/lib/workspace.js +1 -2
- package/dist/skills/blocks-data-gateway-configuration/SKILL.md +204 -0
- package/dist/skills/blocks-data-gateway-crud/SKILL.md +223 -0
- package/dist/skills/blocks-data-storage/SKILL.md +161 -0
- package/dist/skills/blocks-frontend-local-https/SKILL.md +100 -0
- package/dist/skills/blocks-iam-access-control/SKILL.md +49 -0
- package/dist/skills/blocks-iam-access-control/flows/feature-gating.md +38 -0
- package/dist/skills/blocks-iam-access-control/flows/manage-roles-permissions.md +109 -0
- package/dist/skills/blocks-iam-account/SKILL.md +169 -0
- package/dist/skills/blocks-iam-mfa/SKILL.md +124 -0
- package/dist/skills/blocks-iam-organizations/SKILL.md +43 -0
- package/dist/skills/blocks-iam-organizations/flows/admin-mutations.md +89 -0
- package/dist/skills/blocks-iam-organizations/flows/read-and-switch.md +57 -0
- package/dist/skills/blocks-iam-sso-oidc-configuration/SKILL.md +89 -0
- package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +80 -0
- package/dist/skills/blocks-iam-users/SKILL.md +131 -0
- package/dist/skills/blocks-localization-configuration/SKILL.md +149 -0
- package/dist/skills/blocks-localization-implementation/SKILL.md +63 -0
- package/dist/skills/blocks-mail/SKILL.md +95 -0
- package/dist/skills/blocks-notification/SKILL.md +69 -0
- package/dist/skills/blocks-notifier/SKILL.md +107 -0
- package/dist/skills/blocks-onboarding/SKILL.md +78 -0
- package/dist/skills/blocks-release-deployment/SKILL.md +81 -0
- package/dist/skills/blocks-secrets/SKILL.md +81 -0
- package/dist/skills/blocks-storage-configuration/SKILL.md +93 -0
- package/dist/skills/lint.mjs +168 -0
- package/package.json +4 -4
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blocks-iam-sso-oidc-implementation
|
|
3
|
+
description: "Extend or debug the hosted SSO/OIDC login flow `blocks new web` scaffolds into every Blocks app: redirectToProvider → `/login/callback` → session, via the single `blocksClient`. Covers `AuthProvider` status/claims, `RequireAuth`/`RedirectIfAuthenticated` guards, and token refresh. Use for a login button, the OIDC callback, protected routes, a disabled login button, redirect loops, or a session that doesn't stick — on an app `blocks new web` already created. Requires a registered OIDC client (`blocks-iam-sso-oidc-configuration`) and HTTPS on the real domain for testing (`blocks-frontend-local-https`)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blocks IAM — SSO / OIDC Implementation (scaffolded frontend)
|
|
7
|
+
|
|
8
|
+
`blocks new web <name>` already generates a complete, working hosted-login flow. Don't reinvent it — read what's there, extend it, or fix it. Every Blocks call in this flow goes through the single `blocksClient` instance (`src/lib/blocks/client.ts`, `@seliseblocks/client`); there is no raw `fetch`/`curl` anywhere in this stack.
|
|
9
|
+
|
|
10
|
+
## The files, and what each one actually does
|
|
11
|
+
|
|
12
|
+
| File | Role |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `src/lib/blocks/config.ts` | Reads `VITE_BLOCKS_*` env vars; `isLoginConfigured()` = `apiUrl && oidcUrl && oidcClientId` all present |
|
|
15
|
+
| `src/lib/blocks/client.ts` | The one `blocksClient = createBlocksClient({...})` instance, with `oidc: { clientId, scope, url: oidcUrl }` |
|
|
16
|
+
| `src/lib/blocks/auth.ts` | `startLogin`, `completeLogin`, `fetchSessionClaims`, `logout`, `getValidAccessToken` — the session/token logic |
|
|
17
|
+
| `src/lib/blocks/jwt.ts` | `decodeJwtPayload`/`isJwtExpired` — only relevant if a tenant's OIDC config returns bearer tokens in the body |
|
|
18
|
+
| `src/app/providers/AuthProvider.tsx` | React context: `status`/`claims`/`login`/`logout`/`refresh`, polling + visibility-driven refresh |
|
|
19
|
+
| `src/app/router/guards.tsx` | `RequireAuth`, `RedirectIfAuthenticated` |
|
|
20
|
+
| `src/app/router/routes.tsx` | Wires `/login`, `/login/callback`, and the protected route table (`/`, `/assets`, `/profile`, `/error`) |
|
|
21
|
+
| `src/features/auth/LoginPage.tsx` | The login button |
|
|
22
|
+
| `src/features/auth/CallbackPage.tsx` | The `/login/callback` handler |
|
|
23
|
+
|
|
24
|
+
## The flow, traced through the generated code
|
|
25
|
+
|
|
26
|
+
1. **Login button.** `LoginPage`'s button calls `useAuth().login(returnTo)`, which is `AuthProvider`'s `login` calling `startLogin(returnTo)` in `lib/blocks/auth.ts`. `startLogin` throws a clear error if `oidcClientId` isn't set (`"Login is not configured. Set VITE_BLOCKS_OIDC_CLIENT_ID in .env."`), stashes `returnTo` (default `"/"`) in `sessionStorage`, then calls `blocksClient.auth.idp.redirectToProvider()` with no arguments — it relies entirely on the client's configured `oidc` defaults.
|
|
27
|
+
- The button itself is `disabled={!configured || pending}` — if `isLoginConfigured()` is false, `LoginPage` renders a warning `Alert` with the exact callback URL (`{origin}/login/callback`) to register, instead of letting the click fail. **"Login button does nothing" is almost always an empty `VITE_BLOCKS_OIDC_CLIENT_ID`.**
|
|
28
|
+
2. **`redirectToProvider()`** (SDK, `auth-client.ts`) calls `auth.idp.initiate()`, then `window.location.assign(response.redirect_uri)`. `initiate` itself is also directly callable (e.g. to get the URL without immediately navigating, such as opening it in a new tab) but the scaffold never calls it directly; only `redirectToProvider` is wired to the button.
|
|
29
|
+
3. The user authenticates on Blocks-hosted IAM.
|
|
30
|
+
4. IAM redirects back to `<origin>/login/callback?code=...&state=...`. That path is the SDK's *default* `redirectUri` — the scaffold's `client.ts` never passes an explicit `redirectUri`, so `createBlocksClient` derives `${window.location.origin}/login/callback` at runtime (see `browserRedirectUri()` in the SDK's `client.ts`). This is exactly the route `routes.tsx` handles, so it lines up with zero config — **but** it means the OIDC client's registered `redirect_uris` must include `/login/callback` under **every origin** this app runs on (dev HTTPS origin and prod origin both — see the scaffold's own README and `blocks-iam-sso-oidc-configuration`).
|
|
31
|
+
5. `routes.tsx` matches `path === "/login/callback"` and renders `CallbackPage` directly — **not** wrapped in `RequireAuth` or `RedirectIfAuthenticated`, since the user is by definition not yet authenticated when they land here.
|
|
32
|
+
6. `CallbackPage`'s one-shot effect (guarded with a `useRef` so React 18 Strict Mode's double-invoke doesn't run it twice) calls `completeLogin(window.location.href)`. `completeLogin` reads and clears the stashed `returnTo`, then calls `blocksClient.auth.idp.callback(callbackUrl)`, passing the full URL so the SDK parses `code`/`state`/`error` itself.
|
|
33
|
+
- On the default cookie flow, IAM sets the session as a **Secure, httpOnly cookie** via `Set-Cookie` on this response and returns no token in the body — `completeLogin` only caches a bearer token if the response body actually contains one (a non-default, explicit-token OIDC config). The SDK never stores tokens itself either way; every call sets `credentials: "include"` so the cookie rides along automatically once IAM has set it.
|
|
34
|
+
- If `data.error` is present, `completeLogin` returns `{ ok: false, message }` and `CallbackPage` shows an inline error `Alert` plus a button back to `/login` — it never silently strands the user on a blank screen.
|
|
35
|
+
7. On success, `CallbackPage` calls `refresh()` (from `AuthProvider`) and then `onNavigate(result.returnTo)`. `refresh()` calls `fetchSessionClaims()` → `blocksClient.auth.userInfo()` to confirm the cookie actually landed and to populate `claims`/`status` before the app navigates away from the callback screen.
|
|
36
|
+
|
|
37
|
+
## Session state and route guards
|
|
38
|
+
|
|
39
|
+
- **`AuthProvider`** is the single source of truth for `status` (`"loading" | "authenticated" | "unauthenticated"`) and `claims`. It calls `refresh()` on mount, every 5 minutes (`STATUS_POLL_MS`, a backup interval — not the primary signal), and immediately whenever the tab regains visibility (catches sign-out in another tab or session expiry while backgrounded). It never inspects local storage to decide auth state — asking IAM directly (`userInfo()`) is the only source of truth, because the default flow holds no locally readable token by design.
|
|
40
|
+
- **`RequireAuth`** wraps every protected route in `routes.tsx` (`/`, `/assets`, `/profile`, `/error`). While `status !== "authenticated"` it renders `LoadingScreen`; once `status` resolves to `"unauthenticated"` it navigates to `/login?returnTo=<currentPath>` from a `useEffect` (not render-time — reading `window.location` live at render would double-nest the `returnTo` param under Strict Mode's double-invoked effects).
|
|
41
|
+
- **`RedirectIfAuthenticated`** wraps `/login` itself so an already-signed-in user hitting `/login` bounces straight to `/` instead of seeing the login button again.
|
|
42
|
+
- Adding a new protected page: add it to the `protectedRoutes` map in `routes.tsx` — it's automatically wrapped in `RequireAuth` and `AppShell` by the existing router code, nothing else to wire.
|
|
43
|
+
|
|
44
|
+
## The `@seliseblocks/client` methods behind all of this
|
|
45
|
+
|
|
46
|
+
All under `blocksClient.auth`:
|
|
47
|
+
|
|
48
|
+
- **`idp.initiate(request?)`** — starts the flow, returns `{ redirect_uri }`. Uses the client's configured `oidc` defaults (`clientId`, `redirectUri`) unless you pass overrides per call.
|
|
49
|
+
- **`idp.redirectToProvider(request?)`** — calls `initiate` then `window.location.assign(...)`. This is what `startLogin` (and therefore the login button) actually calls; reach for this directly in any new login entry point rather than re-implementing initiate+navigate.
|
|
50
|
+
- **`idp.callback(callbackUrlOrObject)`** — completes the flow. Pass `window.location.href` directly (what `completeLogin` does), or `{ code, state, error?, error_description? }` if you've parsed the URL yourself. Returns IAM's auth response as-is; the SDK never stores tokens — your app decides what, if anything, to keep (the scaffold keeps nothing in the default cookie flow).
|
|
51
|
+
- **`idp.uiConfig()`** — public UI config (e.g. captcha settings). **Not currently called anywhere in the scaffold** — if you're extending `LoginPage` with captcha or tenant-specific login UI, call this before rendering that UI, not before.
|
|
52
|
+
- **`oidc.refreshToken(request?)`** — a separate call from the IdP-controller hosted flow, using a refresh-token grant. `getValidAccessToken()` in `lib/blocks/auth.ts` is already wired as the 401-retry/expiry path: it returns a cached, unexpired token if present, otherwise calls this (de-duplicating concurrent callers via `refreshInFlight`) if a refresh token happens to be cached. In the default cookie-only flow there's usually nothing cached to refresh, so this mostly matters for tenants whose OIDC config explicitly returns tokens in the response body.
|
|
53
|
+
- **`isAuthenticated()`** — returns a plain boolean. The scaffold's own `fetchSessionClaims()` calls the lower-level `userInfo()` instead (same underlying check) because `AuthProvider` needs the claims payload, not just a boolean — reach for `isAuthenticated()` yourself for a one-off check that doesn't need claims, rather than hand-rolling another call.
|
|
54
|
+
|
|
55
|
+
## Config
|
|
56
|
+
|
|
57
|
+
`createBlocksClient` needs an `oidc` block: `clientId` (required), `url` (required — kept for app metadata, not used to build the authorize URL), `redirectUri`/`scope` (optional, default to `${origin}/login/callback` / `openid profile`). The scaffold populates this from `VITE_BLOCKS_OIDC_CLIENT_ID` / `VITE_BLOCKS_OIDC_URL` / `VITE_BLOCKS_OIDC_SCOPE` in `.env`.
|
|
58
|
+
|
|
59
|
+
**This `clientId` is the public OIDC client registered for *this app*** — see the sibling **`blocks-iam-sso-oidc-configuration`** skill for how to resolve or create one (`blocks auth oidc-clients list` / `save`, no portal needed). Don't confuse it with `blocks login` itself, which authenticates the CLI with no setup and needs no registration at all (see **blocks-onboarding**) — the two are unrelated and neither can substitute for the other.
|
|
60
|
+
|
|
61
|
+
**`--client-id` and `--app-domain` are non-interactive-unsafe when omitted.** `blocks new web`'s client-id and domain resolution both fall back to an interactive selection prompt ("Choose an OIDC client... or create/skip" / "Multiple domains found... choose one") when the flag is missing and there's more than one candidate (or, for the client id, always — even zero candidates offers "Create"/"Skip"). There is no stdin in a non-interactive/agent-driven run, so this hangs waiting for a selection instead of quietly scaffolding with a blank/default value. A blank `oidcClientId` only happens if a human sitting at the terminal interactively picks "Skip". An agent running `blocks new web` should always resolve and pass both `--client-id` and `--app-domain` explicitly up front — see **`blocks-iam-sso-oidc-configuration`** for resolving/creating an OIDC client via `auth oidc-clients list`/`save`, and `project.applications[].domain` (from the project record) for the app domain — rather than omitting either and hoping for a graceful non-interactive default.
|
|
62
|
+
|
|
63
|
+
## Gotchas
|
|
64
|
+
|
|
65
|
+
- **Disabled login button, no error** → `isLoginConfigured()` is false, almost always because `VITE_BLOCKS_OIDC_CLIENT_ID` is empty in `.env`. Don't assume `blocks new web` was just run without `--client-id` and "left this blank on purpose" — omitting `--client-id` (or `--app-domain`, when a project has multiple domains) drops into an interactive `selectFromList()` prompt with no graceful non-interactive fallback; in an agent-driven run with no stdin, that hangs rather than scaffolding a blank value. A blank client id only results from a human interactively choosing "Skip." Always pass `--client-id` explicitly (see the Config section above).
|
|
66
|
+
- **Login redirects back but the app still shows logged out** → this is an HTTPS/cookie problem, not an app-logic bug — the session cookie is Secure and won't be stored/sent on `http://localhost`. Cross-reference **`blocks-frontend-local-https`** rather than debugging `AuthProvider`.
|
|
67
|
+
- **Redirect URI mismatch** → the SDK derives `redirectUri` from `window.location.origin` at runtime; if the app runs under more than one origin (dev HTTPS host, prod domain), the registered OIDC client's `redirect_uris` must list `/login/callback` under **each** of them, or IAM rejects the authorize request for the ones missing.
|
|
68
|
+
- **Activation is a separate concern.** Already-activated users go straight through this flow. Only users invited/created inactive via the portal or API need a one-time `/activate` step first — out of scope here, see **`blocks-iam-account`**.
|
|
69
|
+
- **Don't add a `RequireAuth`/`RedirectIfAuthenticated` guard around `/login/callback`** — it must stay reachable while the user is still unauthenticated, by design.
|
|
70
|
+
- **Don't hand-roll a "check if logged in" fetch** — call `blocksClient.auth.isAuthenticated()` or reuse `AuthProvider`'s `status`/`refresh()`, never infer auth state from `sessionStorage`/`localStorage` (the default flow keeps no readable token there at all).
|
|
71
|
+
- **Custom app domain, session never sticks (cookie calls silently fail)** → on a custom (non-`*.seliseblocks.com`) app domain, the hosted-login session cookie is only stored/sent if `VITE_BLOCKS_API_URL` shares the app's registrable domain. The default `https://api.seliseblocks.com` does not share a registrable domain with e.g. `abc.slsblx.com`, so the browser never stores the cross-site cookie and cookie-based calls (`userInfo()`/`/iam/me`, `logout`, the OIDC callback flow this skill documents) silently fail. For a custom domain, `VITE_BLOCKS_API_URL` must be `https://blocksapi.<registrable-domain>` (e.g. `abc.slsblx.com` → `https://blocksapi.slsblx.com`), not the default.
|
|
72
|
+
|
|
73
|
+
## Example trigger prompts
|
|
74
|
+
|
|
75
|
+
- "Add a login button and handle the OIDC callback"
|
|
76
|
+
- "Why is my login button disabled?"
|
|
77
|
+
- "Add a new protected page that requires the user to be signed in"
|
|
78
|
+
- "The user gets redirected back from IAM but the app still shows them as logged out"
|
|
79
|
+
- "Wire up token refresh for when the session expires"
|
|
80
|
+
- "How does this scaffolded app know if someone is logged in?"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blocks-iam-users
|
|
3
|
+
description: "Manage OTHER users' IAM records via `blocksClient.iam.users.*` (never raw fetch/curl), or the equivalent project-scoped `blocks iam users *` / `blocks iam email available` CLI. Covers reads (`get`, `list`, `emailAvailable`, `exists`) and admin mutations (`create`, `update`, `activate`, `deactivate`, `updateAccess`, `revokeAccess`) — CLI mutations require `--dry-run`/`--yes`. Use to invite, edit, deactivate/reactivate, list/search users, or grant/revoke roles/org access. Not for the current user's own profile (blocks-iam-account) or role/permission definitions (blocks-iam-access-control)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blocks IAM — Managing Other Users
|
|
7
|
+
|
|
8
|
+
This skill is about an **admin managing other people's IAM accounts** from inside a Blocks app — inviting them, editing their profile, changing their access, deactivating them. It is not about the signed-in user managing their own account (that's the **blocks-iam-account** skill) and not about defining the roles/permissions being assigned (that's **blocks-iam-access-control**).
|
|
9
|
+
|
|
10
|
+
Everything here goes through the SDK: `blocksClient.iam.users.*` on the app's single `@seliseblocks/client` instance (created once, typically at `src/lib/blocks/client.ts` by `blocks new web`). **Never raw `fetch`/`curl` against `api.seliseblocks.com`.**
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { blocksClient } from "../../lib/blocks/client";
|
|
14
|
+
|
|
15
|
+
const { data } = await blocksClient.iam.users.get(userId);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Two surfaces, same operations: SDK (in-app) and CLI (`blocks iam users *`)
|
|
19
|
+
|
|
20
|
+
There are two legitimate ways to drive full user administration (create, update, deactivate, activate, access grant/revoke) — both are covered by this skill:
|
|
21
|
+
|
|
22
|
+
- **SDK — `blocksClient.iam.users.*`** — build the capability **as a feature inside a signed-in admin's own app**: the admin is looking at a screen, clicking "Deactivate" on a specific user row, and their own IAM permissions gate whether the call succeeds.
|
|
23
|
+
- **CLI — `blocks iam users *` / `blocks iam email available`** — the same operations, invoked directly from a terminal or an agent's shell tool. These are fully wired, project-scoped commands (see "CLI surface" below), not a read-only stub — `iam me` is a separate, account-scoped command for the CLI operator's own identity and is not the only IAM command the CLI has.
|
|
24
|
+
|
|
25
|
+
What is **not** legitimate on either surface: an agent deciding on its own, without the human explicitly directing that specific action in the moment, to call `create`/`update`/`deactivate`/`activate`/`updateAccess`/`revokeAccess` (SDK) or `users create`/`update`/`activate`/`deactivate`/`access grant`/`access revoke` (CLI). State the exact change in plain language and get the user's explicit go-ahead first, every time, even if they asked for something adjacent a moment ago. The CLI enforces this mechanically — every mutating command requires `--dry-run` (preview only, no call) or `--yes`/an interactive "yes" before it executes — but that built-in gate doesn't replace stating the change and getting a real go-ahead when an agent is the one typing the command.
|
|
26
|
+
|
|
27
|
+
## Safe surface — reads and checks, no confirmation needed
|
|
28
|
+
|
|
29
|
+
These don't change anything, so there's no caveat to apply:
|
|
30
|
+
|
|
31
|
+
| Method | What it does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `iam.users.get(id, { organizationId? })` | One user record, optionally scoped to an org. |
|
|
34
|
+
| `iam.users.list(request)` | Paged/filtered user query. **This is a POST-read contract** — `list` sends `{ pageNo, pageSize, filter, search, ... }` as a POST body, it is not a GET. |
|
|
35
|
+
| `iam.users.emailAvailable(query)` | Public duplicate-email check for invite/signup forms. No auth needed. |
|
|
36
|
+
| `iam.users.exists(email)` | Existence check by email. |
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const page = await blocksClient.iam.users.list({ pageNo: 1, pageSize: 20, search: "jane" });
|
|
40
|
+
const check = await blocksClient.iam.users.emailAvailable({ email: "new.hire@example.com" });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Sensitive surface — confirm the exact change before calling
|
|
44
|
+
|
|
45
|
+
Every method below mutates a real account. Before calling any of them, restate to the user in plain language exactly what will change (which user, which field, which effect) and wait for an explicit yes — do not infer consent from an earlier, more general request.
|
|
46
|
+
|
|
47
|
+
| Method | What it does |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `iam.users.create(request)` | Invites/provisions a user in the active tenant/organization. |
|
|
50
|
+
| `iam.users.update(id, request)` | Edits an IAM profile's fields. |
|
|
51
|
+
| `iam.users.deactivate(request)` | Removes access without deleting the record. |
|
|
52
|
+
| `iam.users.activate(request)` | Restores access for a previously deactivated account. |
|
|
53
|
+
| `iam.users.updateAccess(request)` | Grants or changes roles/permissions/org access for a user. |
|
|
54
|
+
| `iam.users.revokeAccess(request)` | Removes roles/permissions/org access from a user. |
|
|
55
|
+
|
|
56
|
+
Example — deactivating a user:
|
|
57
|
+
|
|
58
|
+
> Agent: "This will deactivate **jane.doe@example.com** (user id `usr_8a2f`) — she'll immediately lose access but her record and history stay intact. Confirm?"
|
|
59
|
+
> User: "Yes, deactivate her."
|
|
60
|
+
> *(only then)* `await blocksClient.iam.users.deactivate({ userId: "usr_8a2f" });`
|
|
61
|
+
|
|
62
|
+
Never chain a mutation straight off a read (e.g. don't look a user up and deactivate them in the same breath just because the user asked to "find inactive-looking accounts") — surface what you found, then get a decision on each mutation separately.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// After the user explicitly confirms creating this exact invite:
|
|
66
|
+
await blocksClient.iam.users.create({
|
|
67
|
+
email: "new.hire@example.com",
|
|
68
|
+
firstName: "New",
|
|
69
|
+
lastName: "Hire",
|
|
70
|
+
roles: ["member"]
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// After the user explicitly confirms this exact access change:
|
|
74
|
+
await blocksClient.iam.users.updateAccess({ userId: "usr_8a2f", roles: ["editor"] });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## CLI surface — `blocks iam users *`, `blocks iam email available`
|
|
78
|
+
|
|
79
|
+
These are real, fully-wired commands — not a stub and not limited to `iam me`. `iam me` is a separate, account-scoped command (current CLI operator's own identity via the account token); every command below is **project-scoped**: it requires a project already selected (`blocks use <project-tenant-id>`) and calls IAM with an impersonated project token, same as the rest of the project-scoped CLI surface.
|
|
80
|
+
|
|
81
|
+
Reads — no confirmation needed:
|
|
82
|
+
|
|
83
|
+
| Command | What it does |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `blocks iam users list [--page 1] [--page-size 20] [--email <e>] [--name <n>] [--organization-id <id>] [--sort-by <field>] [--sort-desc] [--filter '<json>'] [--json]` | Paged/filtered user query. `--filter` merges a raw JSON object over the convenience flags. |
|
|
86
|
+
| `blocks iam users get <id> [--organization-id <id>] [--json]` | One user record, optionally scoped to an org. |
|
|
87
|
+
| `blocks iam users exists <email> [--json]` | Existence check by email. |
|
|
88
|
+
| `blocks iam email available <email> [--json]` | Duplicate-email check. |
|
|
89
|
+
|
|
90
|
+
Mutations — every one supports `--dry-run` (print the request body and exit, no call) and requires either `--yes` or a typed `yes` at an interactive prompt before it executes:
|
|
91
|
+
|
|
92
|
+
| Command | What it does |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `blocks iam users create --email <e>\|--user-name <n> [--first-name] [--last-name] [--password] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Invites/provisions a user. |
|
|
95
|
+
| `blocks iam users update <id> [--first-name] [--last-name] [--phone-number] [--organization-id] [--roles a,b] [--permissions a,b] [--body '<json>'\|--file <path>] [--dry-run] [--yes] [--json]` | Edits an IAM profile's fields. |
|
|
96
|
+
| `blocks iam users activate <userId> [--reason <text>] [--dry-run] [--yes] [--json]` | Restores access for a previously deactivated account. |
|
|
97
|
+
| `blocks iam users deactivate <userId> [--dry-run] [--yes] [--json]` | Removes access without deleting the record. |
|
|
98
|
+
| `blocks iam users access grant <userId> [--roles a,b] [--permissions a,b] [--organization-id] [--dry-run] [--yes] [--json]` | Grants roles/permissions/org access (requires at least one of `--roles`/`--permissions`). |
|
|
99
|
+
| `blocks iam users access revoke <userId> [--organization-id] [--dry-run] [--yes] [--json]` | Revokes org access for a user. |
|
|
100
|
+
|
|
101
|
+
Command segments joined by a space also accept a colon (`iam:users:access:grant` etc.) — both forms resolve to the same handler; `blocks iam users --help`-style docs in the CLI's own `--help` output use the space form shown above.
|
|
102
|
+
|
|
103
|
+
Example — deactivating a user from the CLI, dry-run first:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
blocks iam users deactivate usr_8a2f --dry-run # preview the request body, no call made
|
|
107
|
+
blocks iam users deactivate usr_8a2f --yes # after the user explicitly confirms
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Apply the same confirm-before-mutating discipline here as with the SDK: state which user and which effect, wait for an explicit yes, don't chain a mutating command straight off a `list`/`get` just because the user asked to "find" something.
|
|
111
|
+
|
|
112
|
+
## Gotchas
|
|
113
|
+
|
|
114
|
+
- **`list` is a POST**, not a GET — don't assume query-string filtering.
|
|
115
|
+
- **Roles are referenced by slug**, as defined in blocks-iam-access-control — not by their internal item ids.
|
|
116
|
+
- **`organizationId`** matters in multi-org projects — pass it to `get` when you need a user's record in a specific org context.
|
|
117
|
+
- **Every request/response type in the SDK is a loosely-typed `Record<string, unknown>`** (`BlocksUser`, `BlocksBaseResponse`, etc. only guarantee a few common fields) — treat fields defensively and confirm shape against a live response for the project rather than assuming a fixed schema.
|
|
118
|
+
- **The CLI is project-scoped, not account-scoped** — `blocks iam users *`/`blocks iam email available` need a selected project (`blocks use <project-tenant-id>`) and use an impersonated project token; `iam me` is the one exception that runs on the account token instead.
|
|
119
|
+
- **Don't duplicate blocks-iam-account** — if the ask is "let me update my own profile" or "let me reset my password," that's the current user acting on themselves, not this skill.
|
|
120
|
+
|
|
121
|
+
## Example triggers
|
|
122
|
+
|
|
123
|
+
- "Invite a user and set their roles"
|
|
124
|
+
- "Deactivate this user's account"
|
|
125
|
+
- "List all users in the org, filtered by status"
|
|
126
|
+
- "Check if this email is already registered before I show the invite form"
|
|
127
|
+
- "Grant this user the editor role"
|
|
128
|
+
- "Revoke this user's access to the finance org"
|
|
129
|
+
- "Update this user's phone number"
|
|
130
|
+
- "Reactivate this account"
|
|
131
|
+
- "From the terminal, deactivate user usr_8a2f in the current project" → use `blocks iam users deactivate usr_8a2f`, `--dry-run` first, then `--yes` after explicit confirmation
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blocks-localization-configuration
|
|
3
|
+
description: "Configure app translations (i18n) for a SELISE Blocks project through the `blocks` CLI — never raw fetch/curl. Covers authoring local i18n JSON dictionaries, validate/push/pull with the Localization service, managing languages and modules directly, glossary terms, AI translation suggestions, and the composed translate-and-export flow. Use for 'add translations for my login screen', 'push/pull localization changes', 'create a module', 'add a new language'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blocks Localization — Configuration
|
|
7
|
+
|
|
8
|
+
Translations (i18n) for a Blocks project's static UI text — labels, titles, button copy — are authored locally as JSON and synced to the Localization service entirely through the `blocks` CLI. There is no supported reason to hand-roll raw `fetch`/`curl` calls anymore, and there's no SDK-based authoring path either — `@seliseblocks/client`'s localization surface (`languages()`, `modules()`, `languagesForCurrentTenant()`, `translations()`, `cloudTranslations()`, `keysByNames()`) is entirely **read-only**, meant for apps to *consume* translations at runtime, not to author them. Authoring is CLI-only.
|
|
9
|
+
|
|
10
|
+
**Prerequisite:** `blocks init` has been run and a project is selected (`blocks use <tenantId>`). Note `blocks init` does **not** create a `blocks/localization/` folder — it only scaffolds `blocks/data/schemas/`, `blocks/data/rules.json`, and `.env.example`. The `blocks/localization/` directory and its dictionary files come into existence lazily, the first time `blocks localization pull` writes one out (or the first time you author one by hand). If either the project selection is missing, or auth state is unknown, run the blocks-onboarding skill first — it covers `auth status` probing, login, and project selection in detail; this skill assumes that's already done.
|
|
11
|
+
|
|
12
|
+
## The three commands
|
|
13
|
+
|
|
14
|
+
| Command | What it does |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `blocks localization validate --module <name> --language <culture> [--file <path>] [--json]` | Validates a local i18n JSON dictionary. **Local-only, no API call.** |
|
|
17
|
+
| `blocks localization push --module <name> --language <culture> [--file <path>] [--route <route>] [--context <text>] [--dry-run] [--yes] [--json]` | Creates/updates keys from the local dictionary. If the module doesn't exist yet, creates it first — **this is the only way this tooling creates a module.** Mutating. |
|
|
18
|
+
| `blocks localization pull --module <name> --language <culture> [--out <path>] [--json]` | Downloads the **published** cloud dictionary into a local JSON file. Read-only, overwrites the local file. |
|
|
19
|
+
|
|
20
|
+
`--module` is the feature-area bundle name (`common`, `login`, `dashboard`, …). `--language` is a culture code (`en`, `de-DE`, `bn-BD`, …) — see the culture-matching gotcha below before picking one.
|
|
21
|
+
|
|
22
|
+
## File convention
|
|
23
|
+
|
|
24
|
+
Local dictionaries default to:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
blocks/localization/<module>.<language>.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
for example `blocks/localization/login.de-DE.json`. Pass `--file`/`--out` to override the path. Content is a flat or nested JSON object of string values — nested objects are flattened with `.` before validation/push, so either of these is fine and produces the same keys:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "login.title": "Anmelden", "login.submit": "Absenden" }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{ "login": { "title": "Anmelden", "submit": "Absenden" } }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Key names must match `^[A-Za-z0-9][A-Za-z0-9._:-]*$` (letters, numbers, dot, dash, underscore, colon — no spaces) after flattening, and every value must be a non-empty string. `localization validate` enforces exactly this, locally, before anything touches the network.
|
|
41
|
+
|
|
42
|
+
## Workflow: add or update translations
|
|
43
|
+
|
|
44
|
+
1. **Generate or edit the local dictionary** at `blocks/localization/<module>.<language>.json` — write the JSON yourself (nested or flat), covering every key the screen/feature needs.
|
|
45
|
+
2. **Validate locally, no API call:**
|
|
46
|
+
```bash
|
|
47
|
+
blocks localization validate --module login --language de-DE --json
|
|
48
|
+
```
|
|
49
|
+
Fix every flagged key/value before moving on.
|
|
50
|
+
3. **Dry-run the push** to see exactly what would happen (module create-or-reuse, key count, target project):
|
|
51
|
+
```bash
|
|
52
|
+
blocks localization push --module login --language de-DE --dry-run --json
|
|
53
|
+
```
|
|
54
|
+
4. **Get user approval, then push for real:**
|
|
55
|
+
```bash
|
|
56
|
+
blocks localization push --module login --language de-DE --yes --json
|
|
57
|
+
```
|
|
58
|
+
This is mutating. Never skip straight to `--yes`. Every key in the file is saved as immediately published, so a successful push is live for reads right away — there is no separate "generate/publish" step in this CLI.
|
|
59
|
+
|
|
60
|
+
Optional flags on `push`: `--route <route>` tags every key in this push with one route (e.g. the screen path the strings belong to); `--context <text>` attaches one context/hint string to every key in the push — both apply to the whole file, not per-key.
|
|
61
|
+
|
|
62
|
+
## Workflow: multiple languages for the same screen
|
|
63
|
+
|
|
64
|
+
Each `push` call carries exactly one `--language` (one culture stamped onto every key in that file). Translating one module into several languages means **one dictionary file and one push per language**, all against the same `--module`:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
blocks localization validate --module login --language de-DE --json
|
|
68
|
+
blocks localization validate --module login --language bn-BD --json
|
|
69
|
+
blocks localization push --module login --language de-DE --dry-run --json
|
|
70
|
+
blocks localization push --module login --language bn-BD --dry-run --json
|
|
71
|
+
# after approval:
|
|
72
|
+
blocks localization push --module login --language de-DE --yes --json
|
|
73
|
+
blocks localization push --module login --language bn-BD --yes --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The module (`login`) is only created on the *first* push that needs it; the second push reuses the module the first one created.
|
|
77
|
+
|
|
78
|
+
## Refreshing local files from the cloud
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
blocks localization pull --module login --language de-DE --out blocks/localization/login.de-DE.json --json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use this to pull down what's actually published before editing further — same reasoning as pulling data schemas before editing them: don't blindly overwrite translations someone else edited in the portal or in a prior session.
|
|
85
|
+
|
|
86
|
+
## Managing languages directly
|
|
87
|
+
|
|
88
|
+
Pushing translations into an existing language and *configuring the tenant's set of languages* are different operations — the latter has its own standalone commands, independent of `push`/`pull`:
|
|
89
|
+
|
|
90
|
+
| Command | What it does |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `blocks localization language save --language-name <n> --language-code <c> [--is-default] [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a language. Omit `--item-id` to create a new one. Mutating, full dry-run/confirm gate. |
|
|
93
|
+
| `blocks localization language delete <languageName> [--dry-run] [--yes] [--json]` | Deletes a language. Mutating. |
|
|
94
|
+
| `blocks localization language set-default <languageName> [--dry-run] [--yes] [--json]` | Marks a language as the tenant default. Mutating. |
|
|
95
|
+
| `blocks localization language list [--json]` | Lists all languages. Read-only. |
|
|
96
|
+
| `blocks localization language list-for-tenant [--json]` | Lists languages configured for the current tenant. Read-only. |
|
|
97
|
+
|
|
98
|
+
So "add German as a supported language for the tenant" is a real, supported request: `blocks localization language save --language-name German --language-code de-DE --dry-run`, get approval, then re-run with `--yes`. This is distinct from `localization push --language de-DE`, which stamps translations onto keys and doesn't touch the tenant's language configuration at all.
|
|
99
|
+
|
|
100
|
+
## Managing modules directly
|
|
101
|
+
|
|
102
|
+
A module is still created implicitly by the first `localization push` into it, but it can also be created or updated on its own, with no keys, via a standalone command:
|
|
103
|
+
|
|
104
|
+
| Command | What it does |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `blocks localization module save --module-name <n> [--item-id <id>] [--dry-run] [--yes] [--json]` | Creates or updates a module. Omit `--item-id` to create a new one. Mutating. |
|
|
107
|
+
| `blocks localization module list [--json]` | Lists all modules. Read-only. |
|
|
108
|
+
| `blocks localization module list-for-tenant [--json]` | Lists modules configured for the current tenant. Read-only. |
|
|
109
|
+
|
|
110
|
+
So "create a `billing` module with no keys yet" is directly supported: `blocks localization module save --module-name billing --dry-run` → approve → `--yes`. `localization push`'s implicit module creation is just a convenience on top of the same underlying call, not the only path to it.
|
|
111
|
+
|
|
112
|
+
## Other localization commands
|
|
113
|
+
|
|
114
|
+
A few more commands round out the surface beyond push/pull/validate/language/module — useful, but secondary to the core authoring workflow above:
|
|
115
|
+
|
|
116
|
+
| Command | What it does |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `blocks localization key translate-and-export --module-id <id> [--wait] [--dry-run] [--yes] [--json]` | Composed flow: `translate-all` (machine-translates every untranslated key in the module) → if `--wait`, polls until the operation settles → `generate-uilm-file` → `uilm-export`. Without `--wait` the three steps just fire back-to-back. Mutating. |
|
|
119
|
+
| `blocks localization glossary save --name <n> [--item-id <id>] [--language <c>] [--type <t>] [--context <text>] [--additional-note <text>] [--is-global] [--module-ids a,b] [--dry-run] [--yes] [--json]` | Creates or updates a glossary term. Mutating. |
|
|
120
|
+
| `blocks localization glossary list [--search <text>] [--module-id <id>] [--is-global] [--page-number <n>] [--page-size <n>] [--json]` | Lists glossary terms. Read-only. |
|
|
121
|
+
| `blocks localization glossary get <itemId> [--json]` | Fetches one glossary term. Read-only. |
|
|
122
|
+
| `blocks localization glossary suggested <itemId> [--max-results <n>] [--json]` | Suggests glossary terms relevant to an item. Read-only. |
|
|
123
|
+
| `blocks localization glossary delete <itemId> [--dry-run] [--yes] [--json]` | Deletes a glossary term. Mutating. |
|
|
124
|
+
| `blocks localization assistant translation-suggestion --source-text <text> [--current-language <c>] [--destination-language <name>] [--destination-language-code <c>] [--module-id <id>] [--glossary-ids a,b] [--element-type <t>] [--element-application-context <text>] [--element-detail-context <text>] [--max-character-length <n>] [--temperature <n>] [--json]` | Gets an AI translation suggestion for a single string. Read-only (no confirm gate). |
|
|
125
|
+
| `blocks localization config get-webhook [--json]` | Gets the tenant's localization webhook config. Read-only. |
|
|
126
|
+
| `blocks localization config save-webhook --url <url> --content-type <type> --secret <s> --header-key <k> [--is-disabled] [--item-id <id>] [--dry-run] [--yes] [--json]` | Saves the tenant's localization webhook config. Mutating; the secret is redacted in `--dry-run` output. |
|
|
127
|
+
|
|
128
|
+
## Gotchas
|
|
129
|
+
|
|
130
|
+
- **`--dry-run` before `--yes`** on `localization push` — always. Same pattern as every other mutating `blocks` command.
|
|
131
|
+
- **`--language` on `push` is not validated against configured cultures.** `localization push` stamps whatever string you pass as `--language` directly into each key's `culture` field — it does not check that culture against the tenant's actual configured languages, and doesn't call `language list`/`list-for-tenant` to look. Get the culture code wrong (`de` instead of `de-DE`, or a culture the tenant never configured via `language save`) and the key saves without error but may never surface at runtime, because runtime lookups match by the tenant's real configured `languageCode`. Confirm the exact culture code with the user (or run `localization language list-for-tenant`) before pushing, especially for less common languages like Bengali (`bn-BD` vs `bn`).
|
|
132
|
+
- **Module auto-create is silent and permanent.** The first push against a new `--module` name creates it with no separate confirmation prompt beyond the push's own `--dry-run`/`--yes` gate — `--dry-run` output will tell you a module lookup happened, but won't distinguish "will create" from "already exists" as clearly as it could, so read the dry-run JSON's module info carefully, or ask the user to confirm the module name is intentional (typos become new, mostly-empty modules). Prefer `localization module save` first if the user wants the module created deliberately, without an accompanying key push.
|
|
133
|
+
- **`localization validate` is local-only** — it confirms the JSON is well-formed and keys/values pass the naming rules; it does not confirm the push will succeed against the server (module resolution, auth, project selection). Still run `--dry-run` on the actual push.
|
|
134
|
+
- **One culture per file/push.** Don't try to cram multiple languages into one dictionary file — the format is flat key → string value, not key → {culture: value}. Multiple languages means multiple files and multiple push invocations (see above).
|
|
135
|
+
- **No standalone "generate" or "publish" step for `push`.** `shouldPublish: true` is baked into every key `localization push` sends — once the push succeeds, the translations are live. That's separate from `key generate-uilm-file`/`key uilm-export` (and the composed `key translate-and-export`), which build downloadable runtime language files rather than affect `push`'s own publish behavior.
|
|
136
|
+
- **`language delete` and `set-default` are permanent, mutating calls** — always dry-run first, and confirm with the user before deleting a language or changing the tenant default, since either can affect what's visible at runtime for existing translations.
|
|
137
|
+
|
|
138
|
+
## Example trigger prompts
|
|
139
|
+
|
|
140
|
+
- "Add German translations for my login screen." → push `login.de-DE.json` after validate + dry-run + approval.
|
|
141
|
+
- "Add German and Bengali translations for my login screen." → two dictionary files, two validate/push pairs (`de-DE`, `bn-BD`), same module.
|
|
142
|
+
- "Set up a `common` module for shared strings like Save/Cancel/Delete." → write `common.<language>.json` with those keys, validate, push (this is what creates the `common` module) — or use `localization module save --module-name common` directly if no keys exist yet.
|
|
143
|
+
- "Pull the latest translations for the dashboard module before I edit them." → `localization pull --module dashboard --language en`.
|
|
144
|
+
- "Validate my localization file before pushing." → `localization validate` only, no network call.
|
|
145
|
+
- "Can we add Bengali as a new supported language for the tenant?" → `localization language save --language-name Bengali --language-code bn-BD --dry-run`, confirm, then `--yes`.
|
|
146
|
+
- "Create a new translation module called `billing` with no keys yet." → `localization module save --module-name billing --dry-run` → confirm → `--yes`.
|
|
147
|
+
- "Machine-translate the whole `login` module and give me the export." → `localization key translate-and-export --module-id <id> --wait --dry-run` → confirm → `--yes`.
|
|
148
|
+
- "Suggest a translation for this button label." → `localization assistant translation-suggestion --source-text "Save changes" --destination-language-code de-DE`.
|
|
149
|
+
- "What's our webhook config for localization events?" → `localization config get-webhook`.
|
|
@@ -0,0 +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."
|
|
@@ -0,0 +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.
|