@seliseblocks/cli-os 0.1.4 → 0.2.0
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 +84 -47
- 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 +12 -22
- package/dist/commands/iam/email/available.d.ts +1 -0
- package/dist/commands/iam/email/available.js +17 -0
- package/dist/commands/iam/me.js +4 -1
- 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/login.js +32 -1
- 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/commands/use.js +18 -3
- package/dist/index.js +965 -160
- package/dist/lib/api.d.ts +2 -1
- package/dist/lib/api.js +64 -20
- package/dist/lib/args.d.ts +3 -0
- package/dist/lib/args.js +15 -0
- package/dist/lib/auth.d.ts +6 -2
- package/dist/lib/auth.js +74 -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 +43 -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 +2 -2
- 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
package/dist/lib/api.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ type RequestOptions = {
|
|
|
5
5
|
body?: unknown;
|
|
6
6
|
impersonatedProjectAuth?: boolean;
|
|
7
7
|
method?: string;
|
|
8
|
+
preferImpersonatedProjectAuth?: boolean;
|
|
8
9
|
projectTenantId?: string;
|
|
9
|
-
query?: Record<string, string | number | boolean | undefined>;
|
|
10
|
+
query?: Record<string, string | number | boolean | string[] | undefined>;
|
|
10
11
|
};
|
|
11
12
|
export declare function blocksRequest<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
12
13
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -6,31 +6,75 @@ export async function blocksRequest(path, options = {}) {
|
|
|
6
6
|
const baseUrl = options.apiUrl ?? profile.apiUrl;
|
|
7
7
|
const url = buildUrl(baseUrl, path);
|
|
8
8
|
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
9
|
-
if (value
|
|
10
|
-
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
for (const item of value)
|
|
13
|
+
url.searchParams.append(key, item);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
url.searchParams.set(key, String(value));
|
|
11
17
|
}
|
|
12
|
-
const
|
|
18
|
+
const isFormData = options.body instanceof FormData;
|
|
19
|
+
const baseHeaders = {
|
|
13
20
|
Accept: "application/json"
|
|
14
21
|
};
|
|
15
|
-
if (options.body !== undefined)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
if (options.body !== undefined && !isFormData)
|
|
23
|
+
baseHeaders["Content-Type"] = "application/json";
|
|
24
|
+
const send = async (forceRefresh) => {
|
|
25
|
+
const headers = { ...baseHeaders };
|
|
26
|
+
if (options.accountAuth) {
|
|
27
|
+
const account = await getAccountSession(options.accountName, { forceRefresh });
|
|
28
|
+
headers.Authorization = `Bearer ${account.accessToken}`;
|
|
29
|
+
headers["x-blocks-key"] = account.accountTenant;
|
|
30
|
+
}
|
|
31
|
+
if (options.impersonatedProjectAuth) {
|
|
32
|
+
const project = await getImpersonatedProjectSession(options.accountName, options.projectTenantId, { forceRefresh });
|
|
33
|
+
headers.Authorization = `Bearer ${project.accessToken}`;
|
|
34
|
+
// The impersonated token is minted and signed by the root tenant's IdP --
|
|
35
|
+
// its JWKS only exists under the root tenant, so signature validation
|
|
36
|
+
// needs x-blocks-key pointed at root, not the target project. The actual
|
|
37
|
+
// tenant-data scoping comes from a claim already inside the validated
|
|
38
|
+
// token, not from this header.
|
|
39
|
+
headers["x-blocks-key"] = project.accountTenant;
|
|
40
|
+
}
|
|
41
|
+
if (options.preferImpersonatedProjectAuth) {
|
|
42
|
+
const tenantId = options.projectTenantId ?? config.selectedProject?.tenantId;
|
|
43
|
+
if (tenantId) {
|
|
44
|
+
const project = await getImpersonatedProjectSession(options.accountName, tenantId, { forceRefresh });
|
|
45
|
+
headers.Authorization = `Bearer ${project.accessToken}`;
|
|
46
|
+
headers["x-blocks-key"] = project.accountTenant;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const account = await getAccountSession(options.accountName, { forceRefresh });
|
|
50
|
+
headers.Authorization = `Bearer ${account.accessToken}`;
|
|
51
|
+
headers["x-blocks-key"] = account.accountTenant;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return fetch(url, {
|
|
55
|
+
body: options.body === undefined ? undefined : isFormData ? options.body : JSON.stringify(options.body),
|
|
56
|
+
headers,
|
|
57
|
+
method: options.method ?? (options.body === undefined ? "GET" : "POST")
|
|
58
|
+
}).catch((error) => {
|
|
59
|
+
throw new Error(`Blocks API request failed for ${url.origin}${url.pathname}: ${error.message}`);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const method = options.method ?? (options.body === undefined ? "GET" : "POST");
|
|
63
|
+
let response = await send(false);
|
|
64
|
+
if (response.status === 401 && (options.accountAuth || options.impersonatedProjectAuth || options.preferImpersonatedProjectAuth)) {
|
|
65
|
+
// The locally cached expiry said the token was still good, but the server
|
|
66
|
+
// rejected it anyway (early revocation, clock skew, forced logout server-side).
|
|
67
|
+
// Force one refresh-and-retry before giving up -- this is what actually
|
|
68
|
+
// prevents a spurious 're-run blocks login' when the refresh token is still valid.
|
|
69
|
+
response = await send(true);
|
|
21
70
|
}
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
else if (response.status === 500 && method === "GET") {
|
|
72
|
+
// Some tenant-scoped read endpoints (mfa config, signup-settings) intermittently
|
|
73
|
+
// 500 with a JWKS/kid lookup failure right after impersonation, then succeed on
|
|
74
|
+
// an immediate identical retry once the signing-key cache catches up. Safe to
|
|
75
|
+
// retry blindly here because GET is idempotent.
|
|
76
|
+
response = await send(false);
|
|
26
77
|
}
|
|
27
|
-
const response = await fetch(url, {
|
|
28
|
-
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
29
|
-
headers,
|
|
30
|
-
method: options.method ?? (options.body === undefined ? "GET" : "POST")
|
|
31
|
-
}).catch((error) => {
|
|
32
|
-
throw new Error(`Blocks API request failed for ${url.origin}${url.pathname}: ${error.message}`);
|
|
33
|
-
});
|
|
34
78
|
const text = await response.text();
|
|
35
79
|
const data = parseJson(text);
|
|
36
80
|
if (!response.ok) {
|
package/dist/lib/args.d.ts
CHANGED
|
@@ -9,3 +9,6 @@ export declare function stringFlag(flags: Record<string, string | boolean>, name
|
|
|
9
9
|
}): string;
|
|
10
10
|
export declare function integerFlag(flags: Record<string, string | boolean>, name: string, defaultValue: number): number;
|
|
11
11
|
export declare function booleanFlag(flags: Record<string, string | boolean>, name: string): boolean;
|
|
12
|
+
export declare function optionalIntegerFlag(flags: Record<string, string | boolean>, name: string): number | undefined;
|
|
13
|
+
/** Unlike `booleanFlag`, distinguishes "not passed" (undefined) from an explicit `--flag=false`. */
|
|
14
|
+
export declare function optionalBooleanFlag(flags: Record<string, string | boolean>, name: string): boolean | undefined;
|
package/dist/lib/args.js
CHANGED
|
@@ -42,3 +42,18 @@ export function integerFlag(flags, name, defaultValue) {
|
|
|
42
42
|
export function booleanFlag(flags, name) {
|
|
43
43
|
return flags[name] === true || flags[name] === "true";
|
|
44
44
|
}
|
|
45
|
+
export function optionalIntegerFlag(flags, name) {
|
|
46
|
+
const value = flags[name];
|
|
47
|
+
if (value === undefined || value === true)
|
|
48
|
+
return undefined;
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
if (!Number.isInteger(parsed))
|
|
51
|
+
throw new Error(`--${name} must be an integer`);
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
/** Unlike `booleanFlag`, distinguishes "not passed" (undefined) from an explicit `--flag=false`. */
|
|
55
|
+
export function optionalBooleanFlag(flags, name) {
|
|
56
|
+
if (!(name in flags))
|
|
57
|
+
return undefined;
|
|
58
|
+
return booleanFlag(flags, name);
|
|
59
|
+
}
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export type ProjectSession = {
|
|
|
12
12
|
accountTenant: string;
|
|
13
13
|
tenantId: string;
|
|
14
14
|
};
|
|
15
|
+
export type SessionOptions = {
|
|
16
|
+
forceRefresh?: boolean;
|
|
17
|
+
};
|
|
15
18
|
export type DeviceAuthorizationResponse = {
|
|
16
19
|
device_code: string;
|
|
17
20
|
expires_in: number;
|
|
@@ -25,7 +28,8 @@ export type DevicePollingOptions = {
|
|
|
25
28
|
};
|
|
26
29
|
export declare function requestDeviceAuthorization(profile: AccountProfile): Promise<DeviceAuthorizationResponse>;
|
|
27
30
|
export declare function pollDeviceToken(profile: AccountProfile, device: DeviceAuthorizationResponse, options?: DevicePollingOptions): Promise<TokenResponse>;
|
|
28
|
-
export declare function getAccountSession(accountOverride?: string): Promise<AccountSession>;
|
|
31
|
+
export declare function getAccountSession(accountOverride?: string, options?: SessionOptions): Promise<AccountSession>;
|
|
29
32
|
export declare function selectProject(tenantId: string): Promise<void>;
|
|
30
|
-
export declare function getImpersonatedProjectSession(accountOverride?: string, tenantOverride?: string): Promise<ProjectSession>;
|
|
33
|
+
export declare function getImpersonatedProjectSession(accountOverride?: string, tenantOverride?: string, options?: SessionOptions): Promise<ProjectSession>;
|
|
34
|
+
export declare function stopProjectImpersonation(accountOverride?: string, tenantOverride?: string): Promise<void>;
|
|
31
35
|
export declare function revokeCurrentSession(accountOverride?: string): Promise<void>;
|
package/dist/lib/auth.js
CHANGED
|
@@ -4,7 +4,6 @@ import { readTokenStore, writeTokenStore } from "./token-store.js";
|
|
|
4
4
|
import { getClientSecret } from "./secret-store.js";
|
|
5
5
|
import { CliActionableError } from "./errors.js";
|
|
6
6
|
const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
7
|
-
const FALLBACK_IMPERSONATION_CLIENT_ID = "57214b67-aa9c-4307-92ab-a25e35180fac";
|
|
8
7
|
const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 3;
|
|
9
8
|
export async function requestDeviceAuthorization(profile) {
|
|
10
9
|
const rootTenantId = await resolveRootTenantForDevice(profile);
|
|
@@ -81,33 +80,33 @@ export async function pollDeviceToken(profile, device, options = {}) {
|
|
|
81
80
|
continue;
|
|
82
81
|
}
|
|
83
82
|
if (response.error === "access_denied") {
|
|
84
|
-
throw new CliActionableError("Device authorization was denied.", "device_login_denied", "blocks
|
|
83
|
+
throw new CliActionableError("Device authorization was denied.", "device_login_denied", "blocks login");
|
|
85
84
|
}
|
|
86
85
|
if (response.error === "expired_token") {
|
|
87
|
-
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks
|
|
86
|
+
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks login");
|
|
88
87
|
}
|
|
89
88
|
if (response.error) {
|
|
90
|
-
throw new CliActionableError(response.error_description ?? response.error, "device_login_failed", "blocks
|
|
89
|
+
throw new CliActionableError(response.error_description ?? response.error, "device_login_failed", "blocks login");
|
|
91
90
|
}
|
|
92
91
|
return response;
|
|
93
92
|
}
|
|
94
|
-
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks
|
|
93
|
+
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks login");
|
|
95
94
|
}
|
|
96
95
|
function throwIfTooManyTransientErrors(count, cause) {
|
|
97
96
|
if (count <= MAX_CONSECUTIVE_TRANSIENT_ERRORS)
|
|
98
97
|
return;
|
|
99
98
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
100
|
-
throw new CliActionableError(`Could not reach the identity provider while waiting for device approval (${detail}).`, "device_login_network_error", "Check your network connection and run 'blocks
|
|
99
|
+
throw new CliActionableError(`Could not reach the identity provider while waiting for device approval (${detail}).`, "device_login_network_error", "Check your network connection and run 'blocks login' again.");
|
|
101
100
|
}
|
|
102
|
-
export async function getAccountSession(accountOverride) {
|
|
101
|
+
export async function getAccountSession(accountOverride, options = {}) {
|
|
103
102
|
let config = await readConfig();
|
|
104
103
|
let store = await readTokenStore();
|
|
105
104
|
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
106
105
|
const token = store.accounts[name]?.account;
|
|
107
106
|
if (!token?.accessToken || !token.accountTenant) {
|
|
108
|
-
throw new Error(`Account '${name}' is not logged in. Run 'blocks
|
|
107
|
+
throw new Error(`Account '${name}' is not logged in. Run 'blocks login' first.`);
|
|
109
108
|
}
|
|
110
|
-
if (!isExpiring(token.expiresAt)) {
|
|
109
|
+
if (!options.forceRefresh && !isExpiring(token.expiresAt)) {
|
|
111
110
|
return {
|
|
112
111
|
accessToken: token.accessToken,
|
|
113
112
|
account: name,
|
|
@@ -116,7 +115,7 @@ export async function getAccountSession(accountOverride) {
|
|
|
116
115
|
};
|
|
117
116
|
}
|
|
118
117
|
if (!token.refreshToken) {
|
|
119
|
-
throw new Error(`Account '${name}' token expired and no refresh token is available. Run 'blocks
|
|
118
|
+
throw new Error(`Account '${name}' token expired and no refresh token is available. Run 'blocks login' again.`);
|
|
120
119
|
}
|
|
121
120
|
const refreshed = await refreshToken(profile.oidcUrl, profile.clientId, token.refreshToken, await getClientSecret(name), profile.rootTenantId ?? token.accountTenant);
|
|
122
121
|
const next = applyAccountToken(config, store, name, profile.clientId, refreshed);
|
|
@@ -142,16 +141,16 @@ export async function selectProject(tenantId) {
|
|
|
142
141
|
}
|
|
143
142
|
});
|
|
144
143
|
}
|
|
145
|
-
export async function getImpersonatedProjectSession(accountOverride, tenantOverride) {
|
|
144
|
+
export async function getImpersonatedProjectSession(accountOverride, tenantOverride, options = {}) {
|
|
146
145
|
let config = await readConfig();
|
|
147
146
|
let store = await readTokenStore();
|
|
148
147
|
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
149
148
|
const tenantId = tenantOverride ?? config.selectedProject?.tenantId;
|
|
150
149
|
if (!tenantId) {
|
|
151
|
-
throw new Error("No project selected. Run 'blocks
|
|
150
|
+
throw new Error("No project selected. Run 'blocks use <tenantId>' first.");
|
|
152
151
|
}
|
|
153
152
|
const projectToken = store.accounts[name]?.projects?.[tenantId];
|
|
154
|
-
if (projectToken?.accessToken && !isExpiring(projectToken.expiresAt)) {
|
|
153
|
+
if (!options.forceRefresh && projectToken?.accessToken && !isExpiring(projectToken.expiresAt)) {
|
|
155
154
|
return {
|
|
156
155
|
accessToken: projectToken.accessToken,
|
|
157
156
|
account: name,
|
|
@@ -168,12 +167,12 @@ export async function getImpersonatedProjectSession(accountOverride, tenantOverr
|
|
|
168
167
|
await writeTokenStore(store);
|
|
169
168
|
return projectSessionFromToken(name, tenantId, store.accounts[name].projects[tenantId], store.accounts[name]?.account?.accountTenant ?? profile.rootTenantId);
|
|
170
169
|
}
|
|
171
|
-
const account = await getAccountSession(name);
|
|
170
|
+
const account = await getAccountSession(name, options);
|
|
172
171
|
config = await readConfig();
|
|
173
172
|
store = await readTokenStore();
|
|
174
173
|
const rootRefresh = store.accounts[name]?.account?.refreshToken;
|
|
175
174
|
if (!rootRefresh) {
|
|
176
|
-
throw new Error(`Project impersonation needs a fresh account refresh token. Run 'blocks
|
|
175
|
+
throw new Error(`Project impersonation needs a fresh account refresh token. Run 'blocks login' again.`);
|
|
177
176
|
}
|
|
178
177
|
const data = await impersonateProject({
|
|
179
178
|
accessToken: account.accessToken,
|
|
@@ -195,6 +194,57 @@ export async function getImpersonatedProjectSession(accountOverride, tenantOverr
|
|
|
195
194
|
await writeTokenStore(next.store);
|
|
196
195
|
return projectSessionFromToken(name, tenantId, next.store.accounts[name].projects[tenantId], account.accountTenant);
|
|
197
196
|
}
|
|
197
|
+
// Ends the active project impersonation and restores a fresh, refreshable
|
|
198
|
+
// account-level session. The IAM server revokes the account refresh token the
|
|
199
|
+
// moment it's used to start an impersonation (see impersonateProject) and
|
|
200
|
+
// only ever hands back a project-scoped one in exchange -- calling
|
|
201
|
+
// '/impersonation/stop' is the only way to get a new account-level refresh
|
|
202
|
+
// token back. No-ops if no project is selected or nothing was ever
|
|
203
|
+
// impersonated for it.
|
|
204
|
+
export async function stopProjectImpersonation(accountOverride, tenantOverride) {
|
|
205
|
+
const config = await readConfig();
|
|
206
|
+
const { name, profile } = getAccountProfile(config, accountOverride);
|
|
207
|
+
const tenantId = tenantOverride ?? config.selectedProject?.tenantId;
|
|
208
|
+
if (!tenantId)
|
|
209
|
+
return;
|
|
210
|
+
const store = await readTokenStore();
|
|
211
|
+
if (!store.accounts[name]?.projects?.[tenantId]?.refreshToken)
|
|
212
|
+
return;
|
|
213
|
+
// The stop endpoint requires a currently-valid bearer token to authenticate
|
|
214
|
+
// the call, so refresh the project session first if it's expiring.
|
|
215
|
+
const project = await getImpersonatedProjectSession(name, tenantId);
|
|
216
|
+
const beforeStop = await readTokenStore();
|
|
217
|
+
const projectToken = beforeStop.accounts[name]?.projects?.[tenantId];
|
|
218
|
+
if (!projectToken?.refreshToken)
|
|
219
|
+
return;
|
|
220
|
+
const refreshed = await postStopImpersonation(profile.apiUrl, project.accessToken, project.accountTenant, projectToken.refreshToken);
|
|
221
|
+
const latestConfig = await readConfig();
|
|
222
|
+
const latestStore = await readTokenStore();
|
|
223
|
+
const next = applyAccountToken(latestConfig, latestStore, name, profile.clientId, refreshed);
|
|
224
|
+
const { [tenantId]: _removed, ...remainingProjects } = next.store.accounts[name]?.projects ?? {};
|
|
225
|
+
next.store.accounts[name] = {
|
|
226
|
+
...next.store.accounts[name],
|
|
227
|
+
projects: remainingProjects
|
|
228
|
+
};
|
|
229
|
+
await writeConfig(next.config);
|
|
230
|
+
await writeTokenStore(next.store);
|
|
231
|
+
}
|
|
232
|
+
async function postStopImpersonation(apiUrl, accessToken, accountTenant, refreshToken) {
|
|
233
|
+
const response = await fetch(new URL("/iam/v4/auth/impersonation/stop", apiUrl), {
|
|
234
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
235
|
+
headers: {
|
|
236
|
+
Accept: "application/json",
|
|
237
|
+
Authorization: `Bearer ${accessToken}`,
|
|
238
|
+
"Content-Type": "application/json",
|
|
239
|
+
"x-blocks-key": accountTenant
|
|
240
|
+
},
|
|
241
|
+
method: "POST"
|
|
242
|
+
});
|
|
243
|
+
const data = parseJson(await response.text());
|
|
244
|
+
if (response.ok && !data.error)
|
|
245
|
+
return data;
|
|
246
|
+
throw new Error(data.error_description ?? data.error ?? `Stop impersonation failed with HTTP ${response.status}`);
|
|
247
|
+
}
|
|
198
248
|
export async function revokeCurrentSession(accountOverride) {
|
|
199
249
|
const config = await readConfig();
|
|
200
250
|
const store = await readTokenStore();
|
|
@@ -236,7 +286,7 @@ async function refreshToken(oidcUrl, clientId, refreshToken, clientSecret, rootT
|
|
|
236
286
|
// The OIDC server's exact wording for an expired/revoked refresh token
|
|
237
287
|
// varies (invalid_grant, blank description, etc.) -- don't depend on it.
|
|
238
288
|
// Always surface a clear, consistent next step instead.
|
|
239
|
-
throw new CliActionableError(`Refresh token was rejected by the identity provider (${error.message}).`, "refresh_token_rejected", "blocks
|
|
289
|
+
throw new CliActionableError(`Refresh token was rejected by the identity provider (${error.message}).`, "refresh_token_rejected", "blocks login");
|
|
240
290
|
}
|
|
241
291
|
// fetch() itself failed (DNS, connection refused, TLS, etc.) -- the
|
|
242
292
|
// refresh token may still be valid, so don't tell the user to log in.
|
|
@@ -245,22 +295,9 @@ async function refreshToken(oidcUrl, clientId, refreshToken, clientSecret, rootT
|
|
|
245
295
|
}
|
|
246
296
|
}
|
|
247
297
|
async function impersonateProject(args) {
|
|
248
|
-
const first = await postImpersonate(args, args.clientId);
|
|
249
|
-
if (first.ok)
|
|
250
|
-
return first.data;
|
|
251
|
-
const message = first.data.error_description ?? first.data.error ?? "";
|
|
252
|
-
if (args.clientId !== FALLBACK_IMPERSONATION_CLIENT_ID && /invalid_client|client configuration/i.test(message)) {
|
|
253
|
-
const fallback = await postImpersonate(args, FALLBACK_IMPERSONATION_CLIENT_ID);
|
|
254
|
-
if (fallback.ok)
|
|
255
|
-
return fallback.data;
|
|
256
|
-
throw new Error(fallback.data.error_description ?? fallback.data.error ?? `Impersonation failed with HTTP ${fallback.status}`);
|
|
257
|
-
}
|
|
258
|
-
throw new Error(first.data.error_description ?? first.data.error ?? `Impersonation failed with HTTP ${first.status}`);
|
|
259
|
-
}
|
|
260
|
-
async function postImpersonate(args, clientId) {
|
|
261
298
|
const response = await fetch(new URL("/iam/v4/auth/impersonate", args.apiUrl), {
|
|
262
299
|
body: JSON.stringify({
|
|
263
|
-
client_id: clientId,
|
|
300
|
+
client_id: args.clientId,
|
|
264
301
|
refresh_token: args.refreshToken,
|
|
265
302
|
targeted_tenant_id: args.tenantId
|
|
266
303
|
}),
|
|
@@ -273,11 +310,13 @@ async function postImpersonate(args, clientId) {
|
|
|
273
310
|
method: "POST"
|
|
274
311
|
});
|
|
275
312
|
const data = parseJson(await response.text());
|
|
276
|
-
|
|
277
|
-
data
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
if (response.ok && !data.error)
|
|
314
|
+
return data;
|
|
315
|
+
const message = data.error_description ?? data.error ?? "";
|
|
316
|
+
if (/invalid_client|client configuration/i.test(message)) {
|
|
317
|
+
throw new CliActionableError(`Impersonation failed: the account's OIDC client ('${args.clientId}') is not registered for impersonation (${message}).`, "impersonation_invalid_client", "Check the client_id in 'blocks auth config get', then contact an admin to register it for impersonation.");
|
|
318
|
+
}
|
|
319
|
+
throw new Error(message || `Impersonation failed with HTTP ${response.status}`);
|
|
281
320
|
}
|
|
282
321
|
// Thrown only once we have an actual response from the identity provider
|
|
283
322
|
// that rejects the request (bad grant, bad client, etc.) -- as opposed to
|
package/dist/lib/config.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir, platform } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
const BAD_GATEWAY_OS_URL = "https://
|
|
5
|
-
const DEFAULT_API_URL = "https://
|
|
4
|
+
const BAD_GATEWAY_OS_URL = "https://os.seliseblocks.com";
|
|
5
|
+
const DEFAULT_API_URL = "https://api.seliseblocks.com";
|
|
6
6
|
const DEFAULT_OS_CLIENT_ID = "4a633b13-1108-4fbf-84fd-b196c9dcdee2";
|
|
7
7
|
const DEFAULT_OIDC_URL = "https://iam.seliseblocks.com";
|
|
8
|
-
const DEFAULT_OS_URL = "https://
|
|
8
|
+
const DEFAULT_OS_URL = "https://api.seliseblocks.com";
|
|
9
9
|
const DEFAULT_PROFILE_TIMESTAMP = "2026-01-01T00:00:00.000Z";
|
|
10
10
|
const DEFAULT_ROOT_TENANT_ID = "d7e5554c758541db8a18694b64ef423d";
|
|
11
11
|
const DEFAULT_SCOPE = "openid profile offline_access";
|
|
@@ -20,16 +20,16 @@ export function defaults() {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
export function configDir() {
|
|
23
|
-
const override = nonEmptyEnv("
|
|
23
|
+
const override = nonEmptyEnv("BLOCKS_CONFIG_DIR");
|
|
24
24
|
if (override)
|
|
25
25
|
return override;
|
|
26
26
|
if (platform() === "win32") {
|
|
27
|
-
return join(nonEmptyEnv("APPDATA") ?? join(homedir(), "AppData", "Roaming"), "seliseblocks", "cli
|
|
27
|
+
return join(nonEmptyEnv("APPDATA") ?? join(homedir(), "AppData", "Roaming"), "seliseblocks", "cli");
|
|
28
28
|
}
|
|
29
29
|
if (platform() === "darwin") {
|
|
30
|
-
return join(homedir(), "Library", "Application Support", "seliseblocks", "cli
|
|
30
|
+
return join(homedir(), "Library", "Application Support", "seliseblocks", "cli");
|
|
31
31
|
}
|
|
32
|
-
return join(nonEmptyEnv("XDG_CONFIG_HOME") ?? join(homedir(), ".config"), "seliseblocks", "cli
|
|
32
|
+
return join(nonEmptyEnv("XDG_CONFIG_HOME") ?? join(homedir(), ".config"), "seliseblocks", "cli");
|
|
33
33
|
}
|
|
34
34
|
export function configPath() {
|
|
35
35
|
return join(configDir(), "config.json");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type Flags = Record<string, string | boolean>;
|
|
2
|
+
/**
|
|
3
|
+
* Reads a JSON object payload from `--body '<json>'` or `--file <path.json>`.
|
|
4
|
+
* Rich IAM/MFA/Auth-admin payloads have far more optional fields than are
|
|
5
|
+
* worth turning into individual flags; this is the escape hatch every
|
|
6
|
+
* create/update command layers convenience flags on top of.
|
|
7
|
+
*/
|
|
8
|
+
export declare function jsonBodyFlag(flags: Flags, options?: {
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}): Promise<Record<string, unknown>>;
|
|
11
|
+
/** Splits a comma-separated flag value (e.g. `--roles admin,editor`) into a trimmed string array. */
|
|
12
|
+
export declare function listFlag(flags: Flags, name: string): string[] | undefined;
|
|
13
|
+
/** Parses a single named flag as an arbitrary JSON value (object or array), e.g. `--attachments '["a.pdf"]'`. */
|
|
14
|
+
export declare function jsonFlag(flags: Flags, name: string): unknown;
|
|
15
|
+
/** Drops `undefined` values so unset convenience flags never overwrite a `--body`/`--file` payload's fields. */
|
|
16
|
+
export declare function compact<T extends object>(obj: T): Partial<T>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { stringFlag } from "./args.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reads a JSON object payload from `--body '<json>'` or `--file <path.json>`.
|
|
5
|
+
* Rich IAM/MFA/Auth-admin payloads have far more optional fields than are
|
|
6
|
+
* worth turning into individual flags; this is the escape hatch every
|
|
7
|
+
* create/update command layers convenience flags on top of.
|
|
8
|
+
*/
|
|
9
|
+
export async function jsonBodyFlag(flags, options = {}) {
|
|
10
|
+
const inline = stringFlag(flags, "body");
|
|
11
|
+
if (inline)
|
|
12
|
+
return parseJsonObject(inline, "--body");
|
|
13
|
+
const file = stringFlag(flags, "file");
|
|
14
|
+
if (file)
|
|
15
|
+
return parseJsonObject(await readFile(file, "utf8"), `--file ${file}`);
|
|
16
|
+
if (options.required)
|
|
17
|
+
throw new Error("Provide --body '<json>' or --file <path.json> with the request payload.");
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
/** Splits a comma-separated flag value (e.g. `--roles admin,editor`) into a trimmed string array. */
|
|
21
|
+
export function listFlag(flags, name) {
|
|
22
|
+
const value = flags[name];
|
|
23
|
+
if (typeof value !== "string" || !value)
|
|
24
|
+
return undefined;
|
|
25
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
/** Parses a single named flag as an arbitrary JSON value (object or array), e.g. `--attachments '["a.pdf"]'`. */
|
|
28
|
+
export function jsonFlag(flags, name) {
|
|
29
|
+
const raw = stringFlag(flags, name);
|
|
30
|
+
if (!raw)
|
|
31
|
+
return undefined;
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`Could not parse JSON from --${name}: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Drops `undefined` values so unset convenience flags never overwrite a `--body`/`--file` payload's fields. */
|
|
40
|
+
export function compact(obj) {
|
|
41
|
+
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));
|
|
42
|
+
}
|
|
43
|
+
function parseJsonObject(text, source) {
|
|
44
|
+
let value;
|
|
45
|
+
try {
|
|
46
|
+
value = JSON.parse(text);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Could not parse JSON from ${source}: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
52
|
+
throw new Error(`${source} must be a JSON object.`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ProjectRecord = {
|
|
2
|
+
applications?: Array<{
|
|
3
|
+
domain?: string;
|
|
4
|
+
}>;
|
|
5
|
+
environment?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
tenantId?: string;
|
|
8
|
+
} & Record<string, unknown>;
|
|
9
|
+
export type ProjectGroupRecord = {
|
|
10
|
+
name?: string;
|
|
11
|
+
projects?: ProjectRecord[];
|
|
12
|
+
tenantGroupId?: string;
|
|
13
|
+
} & Record<string, unknown>;
|
|
14
|
+
export type TenantAssetResponse = {
|
|
15
|
+
assets?: {
|
|
16
|
+
resources?: Array<{
|
|
17
|
+
link?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
resourceId?: string;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare function listProjectGroups(flags: Record<string, string | boolean>): Promise<ProjectGroupRecord[]>;
|
|
24
|
+
export declare function findProjectByTenantId(tenantId: string, flags: Record<string, string | boolean>): Promise<{
|
|
25
|
+
group: ProjectGroupRecord;
|
|
26
|
+
project: ProjectRecord;
|
|
27
|
+
}>;
|
|
28
|
+
export declare function resolveSelectedProject(flags: Record<string, string | boolean>): Promise<{
|
|
29
|
+
group: ProjectGroupRecord;
|
|
30
|
+
project: ProjectRecord;
|
|
31
|
+
tenantId: string;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function getProjectAssets(tenantGroupId: string, flags: Record<string, string | boolean>): Promise<TenantAssetResponse>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { blocksRequest } from "./api.js";
|
|
2
|
+
import { requestContext } from "./request-context.js";
|
|
3
|
+
import { selectedProject } from "./workspace.js";
|
|
4
|
+
// Project metadata (Project/Gets, Project/GetAsset) is authorized at the
|
|
5
|
+
// account/tenant-group level, above any single project's own API surface --
|
|
6
|
+
// but the platform's permission check rebuilds to the root tenant while
|
|
7
|
+
// impersonating, and the underlying query filters by user id rather than
|
|
8
|
+
// tenant, so the impersonated project session works here too. Prefer it when
|
|
9
|
+
// a project is selected (avoids an extra account-session refresh mid-project
|
|
10
|
+
// work); fall back to the account token when nothing is selected yet.
|
|
11
|
+
export async function listProjectGroups(flags) {
|
|
12
|
+
return blocksRequest("/os/v4/Project/Gets", {
|
|
13
|
+
preferImpersonatedProjectAuth: true,
|
|
14
|
+
query: { page: 0, pageSize: 100, tenantGroupId: "" },
|
|
15
|
+
...requestContext(flags)
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export async function findProjectByTenantId(tenantId, flags) {
|
|
19
|
+
const groups = await listProjectGroups(flags);
|
|
20
|
+
for (const group of groups) {
|
|
21
|
+
const project = (group.projects ?? []).find((item) => item.tenantId === tenantId);
|
|
22
|
+
if (project)
|
|
23
|
+
return { group, project };
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Project '${tenantId}' was not found in Project/Gets.`);
|
|
26
|
+
}
|
|
27
|
+
// Resolves the CLI's currently selected project (blocks.json, --project flag,
|
|
28
|
+
// or 'blocks use') and looks it up via Project/Gets. Throws the same
|
|
29
|
+
// actionable "No project selected" error as selectedProject() when nothing
|
|
30
|
+
// is selected -- callers should not catch that error, letting it surface to
|
|
31
|
+
// the user with the "run blocks use <tenantId>" next step intact.
|
|
32
|
+
export async function resolveSelectedProject(flags) {
|
|
33
|
+
const tenantId = await selectedProject(flags);
|
|
34
|
+
const { group, project } = await findProjectByTenantId(tenantId, flags);
|
|
35
|
+
return { group, project, tenantId };
|
|
36
|
+
}
|
|
37
|
+
export async function getProjectAssets(tenantGroupId, flags) {
|
|
38
|
+
return blocksRequest("/os/v4/Project/GetAsset", {
|
|
39
|
+
preferImpersonatedProjectAuth: true,
|
|
40
|
+
query: { page: 0, pageSize: 100, tenantGroupId },
|
|
41
|
+
...requestContext(flags)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
export async function promptText(message) {
|
|
4
|
+
const rl = createInterface({ input, output });
|
|
5
|
+
try {
|
|
6
|
+
return (await rl.question(message)).trim();
|
|
7
|
+
}
|
|
8
|
+
finally {
|
|
9
|
+
rl.close();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function selectFromList(message, options) {
|
|
13
|
+
const rl = createInterface({ input, output });
|
|
14
|
+
try {
|
|
15
|
+
console.log(message);
|
|
16
|
+
options.forEach((option, index) => console.log(` ${index + 1}) ${option}`));
|
|
17
|
+
while (true) {
|
|
18
|
+
const answer = (await rl.question(`Choose 1-${options.length}: `)).trim();
|
|
19
|
+
const choice = Number(answer);
|
|
20
|
+
if (Number.isInteger(choice) && choice >= 1 && choice <= options.length)
|
|
21
|
+
return choice - 1;
|
|
22
|
+
console.log(`Enter a number between 1 and ${options.length}.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
rl.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -55,7 +55,7 @@ export async function writeDashboard(root) {
|
|
|
55
55
|
" </li>",
|
|
56
56
|
" <li>",
|
|
57
57
|
" <Languages size={18} />",
|
|
58
|
-
" <span>Edit <code>blocks/localization/*.en.json</code>, then run <code>blocks
|
|
58
|
+
" <span>Edit <code>blocks/localization/*.en.json</code>, then run <code>blocks localization push</code> to sync copy to Blocks.</span>",
|
|
59
59
|
" </li>",
|
|
60
60
|
" </ul>",
|
|
61
61
|
" </div>",
|
|
@@ -18,7 +18,7 @@ export async function writeRootFiles(root, options) {
|
|
|
18
18
|
},
|
|
19
19
|
dependencies: {
|
|
20
20
|
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
|
21
|
-
"@seliseblocks/client": "^0.1.
|
|
21
|
+
"@seliseblocks/client": "^0.1.3",
|
|
22
22
|
"@tanstack/react-query": "^5.101.4",
|
|
23
23
|
clsx: "^2.1.1",
|
|
24
24
|
"lucide-react": "^1.28.0",
|
|
@@ -122,7 +122,7 @@ export async function writeRootFiles(root, options) {
|
|
|
122
122
|
"- `/login/callback` — completes the hosted IAM callback via `blocksClient.auth.idp.callback()`, then returns to the page you started from.",
|
|
123
123
|
"- `/` and `/profile` — protected; redirect to `/login` when signed out.",
|
|
124
124
|
"- Sidebar + topbar shell matching the `@seliseblocks/blocks-kit` look (icon-only rail on narrow screens, avatar dropdown, notifications menu, active-item accent bar).",
|
|
125
|
-
"- `blocks/localization/*.en.json` local i18n seed files for AI or human edits. Sync them through `blocks
|
|
125
|
+
"- `blocks/localization/*.en.json` local i18n seed files for AI or human edits. Sync them through `blocks localization validate` and `blocks localization push`; the runtime app reads Localization service data through `blocksClient.localization`.",
|
|
126
126
|
"",
|
|
127
127
|
"IAM's hosted login sets the session as a **Secure, httpOnly** cookie by default -- this app never reads, stores, or refreshes a token itself. \"Signed in\" is determined by calling `blocksClient.auth.userInfo()` (`GET /iam/v4/auth/me`), which the browser's cookie authenticates automatically; this is different from `blocksClient.iam.me()`, the full IAM profile call used on the Profile page. Logging out calls `blocksClient.auth.logout()` so IAM ends the session server-side. A cached bearer token (and `blocksClient.auth.oidc.refreshToken()` to refresh it) is only used if a tenant's OIDC config explicitly returns tokens in the response body instead of a cookie.",
|
|
128
128
|
""
|