bt-cli 0.4.72__tar.gz → 0.5.0__tar.gz
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.
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/entitle/SKILL.md +166 -3
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pra/SKILL.md +3 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/CLAUDE.md +6 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/PKG-INFO +1 -1
- bt_cli-0.5.0/entitle-gaps-implementation-plan.md +378 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/pyproject.toml +1 -1
- bt_cli-0.5.0/scripts/sync-package-data.sh +77 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/cli.py +119 -356
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/commands/configure.py +9 -9
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/commands/learn.py +2 -2
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/commands/quick.py +5 -6
- bt_cli-0.5.0/src/bt_cli/core/command_tree.py +82 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/output.py +57 -4
- bt_cli-0.5.0/src/bt_cli/data/CLAUDE.md +141 -0
- bt_cli-0.5.0/src/bt_cli/data/skills/entitle/SKILL.md +468 -0
- {bt_cli-0.4.72/.claude → bt_cli-0.5.0/src/bt_cli/data}/skills/epml/SKILL.md +24 -3
- bt_cli-0.5.0/src/bt_cli/data/skills/pf/SKILL.md +116 -0
- bt_cli-0.5.0/src/bt_cli/data/skills/pra/SKILL.md +219 -0
- bt_cli-0.5.0/src/bt_cli/data/skills/pws/SKILL.md +353 -0
- bt_cli-0.5.0/src/bt_cli/data/skills/secrets/SKILL.md +229 -0
- bt_cli-0.5.0/src/bt_cli/data/skills/session-analysis/SKILL.md +72 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/client/base.py +358 -19
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/__init__.py +17 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/_helpers.py +68 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/agent_tokens.py +402 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/auth.py +4 -2
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/bundles.py +65 -4
- bt_cli-0.5.0/src/bt_cli/entitle/commands/chats.py +162 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/directory_groups.py +84 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/forwards.py +392 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/integrations.py +421 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/permissions.py +139 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/policies.py +336 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/requests.py +22 -10
- bt_cli-0.5.0/src/bt_cli/entitle/commands/resources.py +279 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/roles.py +368 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/tasks.py +281 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/user_accounts.py +209 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/users.py +81 -5
- bt_cli-0.5.0/src/bt_cli/entitle/commands/webhooks.py +516 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/workflow_webhook_writes.py +304 -0
- bt_cli-0.5.0/src/bt_cli/entitle/commands/workflows.py +712 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/client_pkg.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_roles.py +4 -8
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tmdategrps.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_usergrps.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/settings.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/groups.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/policies.py +16 -9
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/quick.py +16 -11
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/users.py +5 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/group_policies.py +17 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/import_export.py +3 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_items.py +20 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/quick.py +9 -9
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/vault.py +10 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/accounts.py +14 -5
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/assets.py +13 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/clouds.py +8 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/config.py +5 -6
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/credentials.py +3 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/databases.py +18 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/directories.py +8 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/functional.py +17 -3
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/import_export.py +3 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/platforms.py +3 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/quick.py +9 -10
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/search.py +7 -8
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/secrets.py +51 -7
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/sessions.py +7 -6
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/systems.py +8 -4
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/users.py +9 -9
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/workgroups.py +7 -3
- bt_cli-0.5.0/tests/_runner.py +21 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/conftest.py +44 -0
- bt_cli-0.5.0/tests/core/test_command_tree.py +132 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_json_stdout_purity.py +4 -10
- bt_cli-0.5.0/tests/core/test_package_data_sync.py +353 -0
- bt_cli-0.5.0/tests/data/entitle-api-reference.openapi.json +8930 -0
- bt_cli-0.5.0/tests/entitle/test_agent_tokens.py +322 -0
- bt_cli-0.5.0/tests/entitle/test_agent_tokens_writes.py +433 -0
- bt_cli-0.5.0/tests/entitle/test_chats.py +189 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/entitle/test_client.py +59 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/entitle/test_commands.py +326 -3
- bt_cli-0.5.0/tests/entitle/test_coverage.py +123 -0
- bt_cli-0.5.0/tests/entitle/test_directory_groups.py +222 -0
- bt_cli-0.5.0/tests/entitle/test_forwards.py +193 -0
- bt_cli-0.5.0/tests/entitle/test_forwards_writes.py +521 -0
- bt_cli-0.5.0/tests/entitle/test_integrations_writes.py +466 -0
- bt_cli-0.5.0/tests/entitle/test_policies_writes.py +413 -0
- bt_cli-0.5.0/tests/entitle/test_resources_writes.py +375 -0
- bt_cli-0.5.0/tests/entitle/test_roles_writes.py +390 -0
- bt_cli-0.5.0/tests/entitle/test_tasks.py +219 -0
- bt_cli-0.5.0/tests/entitle/test_tasks_writes.py +337 -0
- bt_cli-0.5.0/tests/entitle/test_user_accounts.py +260 -0
- bt_cli-0.5.0/tests/entitle/test_user_accounts_writes.py +419 -0
- bt_cli-0.5.0/tests/entitle/test_webhooks.py +279 -0
- bt_cli-0.5.0/tests/entitle/test_webhooks_writes.py +590 -0
- bt_cli-0.5.0/tests/entitle/test_workflow_webhook_writes.py +522 -0
- bt_cli-0.5.0/tests/entitle/test_workflows_writes.py +508 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra/test_jump_item_fixes.py +4 -10
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws/test_commands.py +4 -10
- bt_cli-0.4.72/.claude/skills/entitle/SKILL.md +0 -231
- bt_cli-0.4.72/.claude/skills/pra/SKILL.md +0 -153
- bt_cli-0.4.72/.claude/skills/pws/SKILL.md +0 -198
- bt_cli-0.4.72/scripts/sync-package-data.sh +0 -21
- bt_cli-0.4.72/src/bt_cli/data/CLAUDE.md +0 -118
- bt_cli-0.4.72/src/bt_cli/entitle/commands/integrations.py +0 -60
- bt_cli-0.4.72/src/bt_cli/entitle/commands/permissions.py +0 -70
- bt_cli-0.4.72/src/bt_cli/entitle/commands/policies.py +0 -97
- bt_cli-0.4.72/src/bt_cli/entitle/commands/resources.py +0 -131
- bt_cli-0.4.72/src/bt_cli/entitle/commands/roles.py +0 -74
- bt_cli-0.4.72/src/bt_cli/entitle/commands/workflows.py +0 -187
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pf/SKILL.md +0 -0
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/secrets/SKILL.md +0 -0
- {bt_cli-0.4.72/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/session-analysis/SKILL.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.env.example +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/.gitignore +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/README.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/assets/cli-help.png +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/assets/cli-output.png +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/bt-cli-cheatsheet.html +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/bt-cli.spec +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/bt_entry.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/epml-clients-server-side-filters-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/pf-implementation-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/scripts/pf_onboard.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/audit.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/jit.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/clients.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/machines.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/tokens.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/user.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pf/commands/users.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/group_policy.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/audit.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/client/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/client/base.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/_hints.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/dynamic.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/folders.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/integrations.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/leases.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/static.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/src/bt_cli/secrets/models/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_config_file.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_output.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pf/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pf/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pf/test_commands.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws/test_quick_sessions.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/pws-smoke-test.sh +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/secrets/__init__.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/secrets/test_client.py +0 -0
- {bt_cli-0.4.72 → bt_cli-0.5.0}/tests/secrets/test_commands.py +0 -0
|
@@ -5,6 +5,33 @@ description: Entitle commands for JIT access, bundles, workflows, and permission
|
|
|
5
5
|
|
|
6
6
|
# Entitle Commands (`bt entitle`)
|
|
7
7
|
|
|
8
|
+
## Two API tokens — read vs. write-on-behalf-of-user
|
|
9
|
+
|
|
10
|
+
Entitle issues two distinct kinds of bearer token. The CLI uses both:
|
|
11
|
+
|
|
12
|
+
| Token | Config field | Env var | Used for |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| **Org / admin** | `entitle.api_key` | `BT_ENTITLE_API_KEY` | All read-only commands (`list`, `get`) and admin writes (revoke, delete) |
|
|
15
|
+
| **User-context** | `entitle.user_api_key` | `BT_ENTITLE_USER_API_KEY` | On-behalf-of-a-user writes — currently `bt entitle requests create` |
|
|
16
|
+
|
|
17
|
+
The org/admin token **cannot** create access requests because requests are
|
|
18
|
+
made *as* a person, not on behalf of the org. If only the admin token is
|
|
19
|
+
configured, `bt entitle requests create` exits early with a panel explaining
|
|
20
|
+
how to add a user token.
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
# ~/.bt-cli/config.yaml
|
|
24
|
+
profiles:
|
|
25
|
+
default:
|
|
26
|
+
entitle:
|
|
27
|
+
api_url: https://api.us.entitle.io
|
|
28
|
+
api_key: <ORG_TOKEN> # broad read access
|
|
29
|
+
user_api_key: <USER_TOKEN> # personal token, for `requests create`
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The user token is generated from `app.entitle.io` → user profile → API tokens
|
|
33
|
+
→ **Create user token** (different page from the org token).
|
|
34
|
+
|
|
8
35
|
## IMPORTANT: Destructive Operations
|
|
9
36
|
|
|
10
37
|
**ALWAYS confirm with the user before:**
|
|
@@ -296,10 +323,146 @@ bt entitle roles list --resource <resource_id>
|
|
|
296
323
|
**Kubernetes:** EKS, AKS, GKE, Rancher
|
|
297
324
|
**BeyondTrust:** Password Safe, PRA, Remote Support
|
|
298
325
|
|
|
326
|
+
## Newer command families
|
|
327
|
+
|
|
328
|
+
Added in 0.5.0; the CLI now covers 75 of the API's 77 operations.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Grant/revoke work items behind an access request (read + status only)
|
|
332
|
+
bt entitle tasks list
|
|
333
|
+
bt entitle tasks get <task-id>
|
|
334
|
+
bt entitle tasks set-status <task-id> --status todo|inProgress|done
|
|
335
|
+
bt entitle tasks archive <task-id>
|
|
336
|
+
|
|
337
|
+
# Agent tokens — these are real credentials
|
|
338
|
+
bt entitle agent-tokens list
|
|
339
|
+
bt entitle agent-tokens create --name "collector" -o json # token shown ONCE
|
|
340
|
+
bt entitle agent-tokens update <id> --name "new name"
|
|
341
|
+
bt entitle agent-tokens delete <id>
|
|
342
|
+
|
|
343
|
+
# Outbound webhooks — two separate families
|
|
344
|
+
bt entitle audit-webhooks list | get | create | update | enable | delete
|
|
345
|
+
bt entitle workflow-webhooks list | get | create | update | delete
|
|
346
|
+
|
|
347
|
+
# Delegation: send someone's queue to someone else
|
|
348
|
+
bt entitle request-forwards list | get | for-user <user-id> | create | delete
|
|
349
|
+
bt entitle review-forwards list | get | create | delete
|
|
350
|
+
|
|
351
|
+
# Identity plumbing
|
|
352
|
+
bt entitle directory-groups list --search "DYN-ROLE"
|
|
353
|
+
bt entitle user-accounts list | create | delete
|
|
354
|
+
bt entitle users pats <user-id>
|
|
355
|
+
bt entitle permissions account <account-id>
|
|
356
|
+
bt entitle chats channels --integration-type slack
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Writes were added to the families that previously had reads only: `roles`,
|
|
360
|
+
`policies`, `workflows`, `integrations` all gained create/update/delete, and
|
|
361
|
+
`resources` and `bundles` gained `update`.
|
|
362
|
+
|
|
363
|
+
## Update semantics: PUT merges, but lists inside it do not
|
|
364
|
+
|
|
365
|
+
Live-verified per family. **Every family merges** — a field you leave out of the
|
|
366
|
+
body keeps its current value, so no command does read-modify-write and a `curl`
|
|
367
|
+
sending one field is safe.
|
|
368
|
+
|
|
369
|
+
Two qualifications that matter more than the rule:
|
|
370
|
+
|
|
371
|
+
- **A list you *do* send replaces that list wholesale.** `--role`, `--tag`,
|
|
372
|
+
`--group`, `--duration` set the whole collection; they never append. Passing
|
|
373
|
+
`-g` alone drops every other target.
|
|
374
|
+
- **`workflows` merges at the top level but REPLACES its `rules` array.** A
|
|
375
|
+
two-rule workflow plus a PUT carrying one rule leaves **one rule**, and each
|
|
376
|
+
rule object must be complete (`400 "rules.0.inGroups must be an array"`).
|
|
377
|
+
|
|
378
|
+
## Writes that return 200 and do nothing
|
|
379
|
+
|
|
380
|
+
The most dangerous behaviour in this API. All live-verified — **verify a write
|
|
381
|
+
with a follow-up GET**, because the response will not tell you:
|
|
382
|
+
|
|
383
|
+
| Operation | Silently ignored |
|
|
384
|
+
|---|---|
|
|
385
|
+
| `POST /roles` | drops `prerequisitePermissions` (CLI issues a follow-up PUT) |
|
|
386
|
+
| `PUT /roles/{id}` | ignores `name`, `resource`, `virtualizedRole` |
|
|
387
|
+
| `PUT /integrations/{id}` | ignores `allowChangingAccountPermissions`, `allowCreatingAccounts`, `readonly` |
|
|
388
|
+
| `PUT /policies/{id}` | a `sortOrder` past the last policy keeps the old value |
|
|
389
|
+
| workflow webhooks | `approvalAlgorithmIds` is read-only |
|
|
390
|
+
| both webhook families | `{"enabled": false}` on create *and* update |
|
|
391
|
+
| integrations update | an unknown `--owner` (but create 404s on it) |
|
|
392
|
+
|
|
393
|
+
**`POST` on both forwards families is an UPSERT keyed on the forwarder** — a
|
|
394
|
+
second create for the same person returns the *same id* with the target
|
|
395
|
+
replaced, 200, no 409. "Create a forward for Alice" therefore silently
|
|
396
|
+
redirects her queue away from whoever it pointed at. Check with
|
|
397
|
+
`request-forwards for-user <id>` first.
|
|
398
|
+
|
|
399
|
+
**`POST /usersAccounts` is idempotent** on `(user, account)`: it returns the
|
|
400
|
+
pre-existing row with its original `createdAt` rather than creating a duplicate.
|
|
401
|
+
A script that deletes "the row it just created" will be deleting a real one.
|
|
402
|
+
|
|
403
|
+
## Pagination: the envelope tells you
|
|
404
|
+
|
|
405
|
+
`pagination` present in the response means the endpoint really pages. Absent
|
|
406
|
+
means `page`/`perPage` are **silently ignored** and one GET returns everything —
|
|
407
|
+
requesting page 2 hands back the same first row.
|
|
408
|
+
|
|
409
|
+
| Ignores paging | Really pages |
|
|
410
|
+
|---|---|
|
|
411
|
+
| `/tasks`, `/applications`, `/auditLogsWebhooks`, `/workflowsWebhooks` | `/agentTokens`, `/directoryGroups`, `/usersAccounts`, both `*Forwards` |
|
|
412
|
+
|
|
413
|
+
`perPage` caps at **100** and 400s above it. `--limit` means *maximum results*
|
|
414
|
+
and bounds the fetch, so a small limit is cheap; it does not mean page size.
|
|
415
|
+
|
|
416
|
+
## Secrets and redaction
|
|
417
|
+
|
|
418
|
+
- **`agent-tokens create` returns a ~776-character token exactly once.** An
|
|
419
|
+
immediate GET returns only `id` and `name`. It is surfaced solely under
|
|
420
|
+
`-o json`, redacted in tables, with a stderr warning that it cannot be
|
|
421
|
+
retrieved again. Capture it at creation or delete and recreate.
|
|
422
|
+
- **Webhook header values are redacted by header NAME, not per hook.**
|
|
423
|
+
`Authorization`, `X-Api-Key`, `api-key`, `X-Secret`, `Token` read back as
|
|
424
|
+
`***`; `Cookie` and arbitrary names come back verbatim. **A GET-edit-PUT
|
|
425
|
+
round trip would store the literal `***` over a real secret**, which is why
|
|
426
|
+
neither webhook family offers `--file` and update sends only your flags.
|
|
427
|
+
|
|
428
|
+
## Tenant-shaped gotchas (not CLI bugs)
|
|
429
|
+
|
|
430
|
+
- **A policy has no name or description** — the body is only
|
|
431
|
+
`inGroups`/`bundles`/`roles`/`sortOrder`, so policies are trackable by UUID
|
|
432
|
+
alone. `POST` also rejects an empty grant (`"Policy must grant at least 1
|
|
433
|
+
permission"`), so a policy can never be created inert: any create is a real
|
|
434
|
+
grant for as long as it lives.
|
|
435
|
+
- **Webhooks cannot be created disabled** in either family, and `/disable`
|
|
436
|
+
404s. `DELETE` is the only off switch — which is why `audit-webhooks` has
|
|
437
|
+
`enable` and deliberately no `--disable` flag.
|
|
438
|
+
- **Deleted webhook names stay reserved**: recreating a just-deleted name 400s
|
|
439
|
+
with "Resource already exists".
|
|
440
|
+
- **`chats channels`** needs the *user* token (the org token 401s) and accepts
|
|
441
|
+
only `--integration-type slack`. It returns
|
|
442
|
+
`500 chatsService.missingScopes` until the tenant's Slack app is
|
|
443
|
+
re-authorized with `channels:read,groups:read,mpim:read,im:read`; the CLI
|
|
444
|
+
exits 3 with that message rather than a generic failure.
|
|
445
|
+
- **`orgRoles` is unreachable** — `GET /orgRoles` and
|
|
446
|
+
`PUT /orgRoles/identities` are 401 on *both* tokens, the same org-level
|
|
447
|
+
gating Pathfinder has. These are the only two API operations the CLI does not
|
|
448
|
+
implement, and they stay unimplemented until the gateway opens up.
|
|
449
|
+
- **Bundle create needs at least one role** (`"roles must contain at least 1
|
|
450
|
+
elements"`), and **role create** needs only `name` + `resource` +
|
|
451
|
+
`requestable` — the spec's required `prerequisitePermissions` is the field
|
|
452
|
+
the server discards. Virtual Application resources also need
|
|
453
|
+
`virtualizedRole`.
|
|
454
|
+
- **Omitting either side of a `usersAccounts` body is a 500, not a 400.**
|
|
455
|
+
|
|
299
456
|
## API Notes
|
|
300
457
|
|
|
301
458
|
- Base path: `/public/v1`
|
|
302
|
-
- Pagination: `page`/`perPage
|
|
303
|
-
|
|
459
|
+
- Pagination: `page`/`perPage`, but only where the response carries a
|
|
460
|
+
`pagination` key — see the table above. `perPage` caps at 100.
|
|
461
|
+
- Response: `{"result": [...]}`, with `pagination` alongside it on the
|
|
462
|
+
endpoints that really page. Single GETs wrap the object in `result` too;
|
|
463
|
+
`eligibleJitAccess` is the exception, returning `{roles, bundles, pagination}`.
|
|
304
464
|
- All IDs are UUIDs (strings)
|
|
305
|
-
- Bearer token auth
|
|
465
|
+
- Bearer token auth; some routes need the *user* token, not the org one
|
|
466
|
+
- Coverage: 75 of 77 operations. The two missing are the `orgRoles` pair,
|
|
467
|
+
blocked by org-level gating. `tests/entitle/test_coverage.py` fails if that
|
|
468
|
+
drifts.
|
|
@@ -14,6 +14,9 @@ description: Privileged Remote Access commands for jump items, vault accounts, a
|
|
|
14
14
|
- `bt pra jump-items tunnel delete` - Deletes protocol tunnel
|
|
15
15
|
- `bt pra jump-groups delete` - Deletes jump group
|
|
16
16
|
- `bt pra vault accounts delete` - Deletes vault account
|
|
17
|
+
- `bt pra users delete` - Deletes a user (non-admins only)
|
|
18
|
+
- `bt pra policies group delete` - Deletes a Group Policy
|
|
19
|
+
- `bt pra policies group {jumpoints|members|jump-groups|vault-accounts|vault-account-groups|teams} remove` - Detaches a sub-resource from a Group Policy
|
|
17
20
|
|
|
18
21
|
List affected resources first, then ask for explicit confirmation.
|
|
19
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BT-CLI
|
|
2
2
|
|
|
3
|
-
BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.
|
|
3
|
+
BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.5.0**
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ Use these slash commands for detailed product guidance:
|
|
|
42
42
|
| Cross-product | `bt quick` | `pasm-onboard`, `pasm-offboard`, `pasm-search` |
|
|
43
43
|
| Password Safe | `bt pws` | `systems`, `accounts`, `credentials`, `sessions`, `secrets`, `audit`, `quick` |
|
|
44
44
|
| PRA | `bt pra` | `jump-items`, `vault`, `jump-groups`, `quick` |
|
|
45
|
-
| Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions`, `jit`, `audit` |
|
|
45
|
+
| Entitle | `bt entitle` | `integrations`, `resources`, `roles`, `bundles`, `workflows`, `policies`, `permissions`, `users`, `accounts`, `requests`, `tasks`, `jit`, `audit`, `agent-tokens`, `audit-webhooks`, `workflow-webhooks`, `request-forwards`, `review-forwards`, `directory-groups`, `user-accounts`, `chats` |
|
|
46
46
|
| EPM Windows | `bt epmw` | `computers`, `groups`, `policies`, `requests`, `quick` |
|
|
47
47
|
| EPM Linux | `bt epml` | `rbp` (cmdgrps/hostgrps/usergrps/tmdategrps/roles/policy/tests/tx), `settings`, `users`, `audit`, `siems`, `quick` |
|
|
48
48
|
| Secrets API | `bt secrets` | `folders`, `static`, `dynamic` (incl. `generate`), `leases`, `integrations` |
|
|
@@ -71,7 +71,7 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
|
|
|
71
71
|
| Product | Pagination | Response Format |
|
|
72
72
|
|---------|------------|-----------------|
|
|
73
73
|
| PWS | `limit`/`offset` | `{"TotalCount": N, "Data": [...]}` |
|
|
74
|
-
| Entitle | `page`/`perPage` | `{"result": [...]
|
|
74
|
+
| Entitle | `page`/`perPage` (**only where `pagination` is in the response**; max 100) | `{"result": [...]}`, `pagination` alongside it when the endpoint really pages |
|
|
75
75
|
| PRA | `per_page`/`current_page` (1-indexed) | Array (pagination in headers) |
|
|
76
76
|
| EPMW | `pageNumber`/`pageSize` | `{"data": [...], "totalCount": N}` |
|
|
77
77
|
| EPML | varies — `take`/`skip` (audit, siems), `start`/`len` (iologs), plain arrays | mostly arrays; some `{total, data}` |
|
|
@@ -83,6 +83,7 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
|
|
|
83
83
|
- PWS assets: Must create via workgroup endpoint
|
|
84
84
|
- ECM integration: PWS system name must match PRA jump item name
|
|
85
85
|
- **`-o json` is pure JSON on stdout — in every product.** All human status/progress banners *and interactive prompts* go to **stderr** (`core/output.py` `err_console`; `typer.confirm`/`typer.prompt` are called with `err=True`), so `... -o json | jq` and `json.load(stdin)` are safe even for `quick` workflow commands that narrate their steps. Enforced by static guards in `tests/core/test_json_stdout_purity.py` — when adding a command, use `print_success`/`print_warning`/`print_info` or `err_console.print` for anything that isn't the command's data. Consequence: piping *table* output to a file drops the narration (it's on stderr) — that's intended. PRA API 4xx `errors` objects are surfaced field-by-field, not collapsed to "Validation Failed".
|
|
86
|
+
- **`bt tree` / `bt find` are derived from the live Typer app** (`core/command_tree.py` walks the Click tree) — **never hardcode a command list**. They used to be hand-maintained literals and silently drifted to 278-of-460 commands missing (all of `bt pws sessions`/`audit`, the whole `bt pf` product) before 0.4.73. `tests/core/test_command_tree.py` fails if any registered command is undiscoverable. New commands need no tree/find edit; help text comes from the docstring's first line.
|
|
86
87
|
- **PRA tunnel create gates fields by `tunnel_type`**: tcp→`--ports`, mssql→`--username`/`--database`, k8s→`--url`/`--ca-cert`. **psql/mysql are created BARE** — the public Config API has no write path for their username/database (POST rejects, PATCH no-ops, PUT 405s) though GET shows them; CLI warns + ignores those flags. Set in `/login` UI.
|
|
87
88
|
- **PRA `export jump-items` defaults to a REAL export** (all types: shell/rdp/vnc/web/tunnel, full type-specific columns) that round-trips through `import jump-items`; `--sample` gives the blank template. `bt pra jump-items web create` exists.
|
|
88
89
|
- **PRA vault `associate -t protocol_tunnel` is MSSQL-only** (live-verified): server rejects psql/mysql/tcp/k8s with "Only SQL Server Tunnels are allowed" — use tag/criteria association for those. **TCP `--ports` needs listen;destination *pairs*** (even count; odd → 400 "number of parameters is invalid"). Jumpoint listing is `bt pra jumpoints list` (plural).
|
|
@@ -91,8 +92,9 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
|
|
|
91
92
|
- **PWS session keystroke transcript = `GET /Sessions/{id}/Keystrokes`** → events `{KeystrokeID, TimeMarker (epoch ms), Type, Data}`. **Type 1 = user input** (typed keys; special keys as glyphs `⌫`/`→`/`↑`), **Type 2 = terminal output** (SSH stdout). SSH captures a full input+output transcript; **RDP is sparse** (typed keys / window events only, off by default → often empty). Surfaced as `bt pws sessions transcript <id>` (default = time-ordered readable transcript; `--input-only`; text/json/csv). Text trail, not the visual recording. (There is **no** `bt pws sessions export` — session *metadata* evidence is `bt pws audit export`.)
|
|
92
93
|
- **PWS session recording download = headless replay under `/pbsm/replay`** (there is **no** video-export API — `?video/?mp4/?webm` return JSON, not media). Flow: `GET /Sessions/{id}` for `Token`+`RecordKey`+`Protocol` → `POST /pbsm/replay` `{id:Token, record_key, protocol:"RDP"|"SSH", headless:true}` → `PUT /pbsm/replay/{rid}` `{offset:<ms>}` (seek) → `GET /pbsm/replay/{rid}?png=<scale>` (one PNG per offset) → `DELETE /pbsm/replay/{rid}`. **Warm-up:** the first `PUT` 404s (`"no such file or directory"`) until the renderer loads — retry ~0.5s. The seek response is state `{offset, next, eof, duration}`; **`next` = next recorded frame's offset**, enabling event-driven stepping. **`bt pws sessions analyze <id>` (or `--dir`)** preps a replay dump for LLM analysis without calling an LLM: reads `index.json`, selects **keyframes** (RDP frames whose PNG byte-size changed ≥`--threshold`%, dependency-free — no Pillow, UNION the frame that renders each **typed keystroke burst**, anchored via the recording `StartTime` → each keyframe tagged `screen-change`/`after-typing` with the `typed` text, so the model reads targeted spots not every frame), and prints a token/USD **cost estimate** (`--model opus|sonnet|haiku`) for focused (keyframes) vs all-frames; writes `analysis.json`. SSH → recommends the transcript over frames. Consumed by the `/session-analysis` skill (interactive; the model reads only the keyframes). Surfaced as `bt pws sessions replay <id>`. **Default = even coverage + dedup**: ~60 evenly-spaced frames across the whole session (`--frames N`) with consecutive-identical frames skipped (`--dedup`, sha1-based) — because event-driven over-samples RDP (it records many deltas as the screen paints, so `next`-stepping clusters at the login and burns the frame budget before covering the timeline). Modes are mutually exclusive: `--frames` (default) / `--fps` / `--interval` / `--all-frames` (event-driven forensic). Also `--from/--to`, `--scale`, `--max-frames`, `--no-dedup`; `--mp4` stitches via ffmpeg, else prints the command + install hint. Always `DELETE`s the replay in `finally`.
|
|
93
94
|
- Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
|
|
94
|
-
- Entitle
|
|
95
|
+
- **Entitle `--limit` means *maximum results*, not page size** (fixed 0.5.0): it bounds the fetch, so a small limit is cheap. It used to be forwarded as `perPage` while `paginate()` walked every page anyway, which made a small `--limit` *more* work — `--limit 2` over `/usersAccounts` was ~978 requests. `users list --all` is uncapped; without it you get one page.
|
|
95
96
|
- **Entitle tokens are region-bound**: a token minted in one region 401s `"The token received doesn't exist"` on the others. Servers: `api.entitle.io`, `api.us.entitle.io`, `api.ca.entitle.io` — set `BT_ENTITLE_API_URL`/`entitle.api_url` to the tenant's region (that misleading 401 also = wrong-region config, not just a dead token). **No `GET /users/{id}`** — `users get` resolves via `list_users(search=...)`.
|
|
97
|
+
- **Entitle write semantics** (all live-verified, 0.5.0; full detail in the entitle SKILL.md): **PUT merges** in every family — omitted fields survive, so no command does read-modify-write — but **a list you do send replaces that list**, and **`workflows` replaces its whole `rules` array**. Beware the **silent 200-OK no-ops**: `POST /roles` discards `prerequisitePermissions`, `PUT /roles` ignores `name`/`resource`/`virtualizedRole`, `PUT /integrations` ignores its three create-only booleans, and neither webhook family accepts `{"enabled": false}` (**DELETE is the only off switch**). **`POST` on both forwards families is an upsert keyed on the forwarder** — creating one silently retargets any existing forward for that person; **`POST /usersAccounts` is idempotent** and returns the pre-existing row, so never delete "the row you just created" without checking its id against a baseline. **Webhook header values are redacted by header NAME** (`Authorization`/`X-Api-Key`/`Token` → `***`; `Cookie` verbatim), so a GET-edit-PUT round trip would write `***` over a real secret — hence no `--file` on webhooks. **`agent-tokens create` returns a one-shot ~776-char token** (`-o json` only). **`orgRoles` (2 ops) is 401 on both tokens** and is the only gap left; `tests/entitle/test_coverage.py` guards 75/77.
|
|
96
98
|
- **Entitle `jit` / `audit`** (spec-derived, live-verified): `bt entitle jit list` wraps `GET /eligibleJitAccess` — with an org token `--user` is **required** (else 400), and the response is `{roles,bundles,pagination}` not the `result` envelope. `bt entitle audit search` wraps `POST /auditLogs/search` (Experimental) — **date window must be < 24h** (server 400s on ≥24h; CLI enforces client-side, default `--hours 24`), no pagination (returns all rows in range), each row's context is one of `integration`/`idp`/`ticket`/`user`. Full spec at `/projects/reference/entitle-api-reference.openapi.json`.
|
|
97
99
|
- Windows builds: Rich must be pinned to `<14.0.0` (Rich 14 has PyInstaller unicode issues)
|
|
98
100
|
- **EPML URL transform**: spec paths start with `/api/...` but real URL is `https://api.beyondtrust.io/site/<site-id>/epm/linux/<spec-path-minus-/api/>`. Encapsulated in client `_build_url` — never bypass it.
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Plan: Close the Entitle API coverage gap in `bt entitle`
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
`bt entitle` implements **26 of the 77 operations** in the Entitle public API
|
|
6
|
+
spec (`/projects/reference/entitle-api-reference.openapi.json`, 43 paths). This
|
|
7
|
+
plan covers the **51 missing operations**.
|
|
8
|
+
|
|
9
|
+
Measured 2026-09-03 by walking the live Typer tree (`core/command_tree.py`) and
|
|
10
|
+
diffing call sites in `entitle/client/base.py` against the spec:
|
|
11
|
+
|
|
12
|
+
| | count |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Spec paths | 43 |
|
|
15
|
+
| Spec operations | 77 |
|
|
16
|
+
| Covered by the client | 26 |
|
|
17
|
+
| **Missing** | **51** |
|
|
18
|
+
| Existing `bt entitle` leaf commands | 27 |
|
|
19
|
+
|
|
20
|
+
> **Correction (Phase 6):** the stale "43 paths vs CLI's 19" line is in the
|
|
21
|
+
> project *memory* file, not CLAUDE.md — this plan named the wrong file in
|
|
22
|
+
> Phase 0. Both are now updated. Count *operations*, not paths: a path with 5
|
|
23
|
+
> methods is 5 units of work.
|
|
24
|
+
|
|
25
|
+
Two families are missing entirely (no read, no write): `tasks`, `agentTokens`,
|
|
26
|
+
`auditLogsWebhooks`, `workflowsWebhooks`, `accessRequestForwards`,
|
|
27
|
+
`accessReviewForwards`, `usersAccounts`, `orgRoles`, `directoryGroups`,
|
|
28
|
+
`chats/channels`. Four families have reads but no writes: `roles`, `policies`,
|
|
29
|
+
`workflows`, `integrations`.
|
|
30
|
+
|
|
31
|
+
### Prerequisites before any live phase
|
|
32
|
+
|
|
33
|
+
1. **API keys.** Both tokens live in the Ninth Floor Vault:
|
|
34
|
+
`nfv get pws-prod-cli-workload` → `ENT-URL`, `ENT-KEY` (org/admin),
|
|
35
|
+
`ENT-USER-KEY` (user-context). Load them at runtime rather than writing them
|
|
36
|
+
to a `.env` — see `scratchpad/entitle-env.sh`.
|
|
37
|
+
2. **Region: US.** `ENT-URL` is `https://api.us.entitle.io`. Entitle tokens are
|
|
38
|
+
region-bound — a token minted in one region returns
|
|
39
|
+
`401 "The token received doesn't exist"` on the others. **That 401 is also
|
|
40
|
+
the symptom of a wrong-region config, not just a dead token** — do not chase
|
|
41
|
+
it as an auth bug.
|
|
42
|
+
3. **Two-token model.** `EntitleClient(config, use_user_token=...)` selects
|
|
43
|
+
between the org/admin token (`api_key`) and the user-context token
|
|
44
|
+
(`user_api_key`). Every new command must make a deliberate choice; see
|
|
45
|
+
Design decision 4.
|
|
46
|
+
|
|
47
|
+
### Live reconnaissance (run 2026-09-03, both tokens, US tenant)
|
|
48
|
+
|
|
49
|
+
Auth confirmed on both tokens (76 applications). Every missing read endpoint was
|
|
50
|
+
probed. Three findings change this plan:
|
|
51
|
+
|
|
52
|
+
**A. `pagination` in the envelope is the reliable signal for whether an endpoint
|
|
53
|
+
actually paginates.** Where it is absent, `page`/`perPage` are silently ignored
|
|
54
|
+
and a single GET returns the whole collection — verified by requesting
|
|
55
|
+
`page=2&perPage=1` and getting back the same first row.
|
|
56
|
+
|
|
57
|
+
| endpoint | envelope | pages? |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `/tasks` | `{result}` | **no — ignores paging** |
|
|
60
|
+
| `/auditLogsWebhooks` | `{result}` | **no — ignores paging** |
|
|
61
|
+
| `/workflowsWebhooks` | `{result}` | **no — ignores paging** |
|
|
62
|
+
| `/agentTokens` | `{pagination, result}` | yes |
|
|
63
|
+
| `/directoryGroups` | `{pagination, result}` | yes |
|
|
64
|
+
| `/usersAccounts` | `{pagination, result}` | yes |
|
|
65
|
+
| `/accessRequestForwards` | `{pagination, result}` | yes |
|
|
66
|
+
| `/accessReviewForwards` | `{pagination, result}` | yes |
|
|
67
|
+
|
|
68
|
+
The three non-paginating endpoints are the `/Sessions` pattern from Password
|
|
69
|
+
Safe. `paginate()` happens to terminate on them (`totalPages` defaults to 1), so
|
|
70
|
+
this is not a hang — but calling it implies a pagination that does not exist.
|
|
71
|
+
Use a single `get()` and cap display client-side, as `list_sessions` does.
|
|
72
|
+
|
|
73
|
+
**B. `/orgRoles` is unreachable — 401 on *both* tokens** (`request.unauthorized`).
|
|
74
|
+
`GET /orgRoles/identities` 404s, consistent with it being PUT-only. This is the
|
|
75
|
+
same org-level gating seen in Pathfinder. **Both orgRoles operations are struck
|
|
76
|
+
from the plan** and move to a documented-blocked list; `PUT
|
|
77
|
+
/orgRoles/identities` cannot be verified, so it must not ship blind. That
|
|
78
|
+
removes the riskiest operation in the original draft.
|
|
79
|
+
|
|
80
|
+
**C. `/chats/channels` is user-token-only and needs `integrationType`.** The
|
|
81
|
+
admin token always returns `401 "User must be logged in to use this action"`.
|
|
82
|
+
With the user token, the parameter is mandatory and the enum has exactly one
|
|
83
|
+
member — the server says so itself:
|
|
84
|
+
`"integrationType must be one of the following values: slack"`. On this tenant
|
|
85
|
+
the call then returns `500 chatsService.missingScopes`, i.e. the Slack
|
|
86
|
+
integration lacks scopes. Ship the command with `--integration-type slack` as
|
|
87
|
+
the only accepted value, and surface `missingScopes` as an actionable message
|
|
88
|
+
rather than a generic 500.
|
|
89
|
+
|
|
90
|
+
**D. `PUT` is a MERGE, not a full replace** (probed on a throwaway bundle,
|
|
91
|
+
created and deleted in the same run). Sending only `{name, description}` left
|
|
92
|
+
`category`, `tags`, `allowedDurations` and `roles` untouched. So **no
|
|
93
|
+
read-modify-write is needed** — the EPM-L upsert-on-id trap does not apply here.
|
|
94
|
+
A field that *is* sent replaces that field wholesale, so list-valued flags set
|
|
95
|
+
the list rather than appending. Re-probe per family before relying on this in
|
|
96
|
+
Phase 3; it is verified for `bundles` only.
|
|
97
|
+
|
|
98
|
+
**E. Every `DELETE` in the client was broken.** `_get_headers()` set
|
|
99
|
+
`Content-Type: application/json` on all requests including bodyless ones, and
|
|
100
|
+
the server answers `400 "Body cannot be empty when content-type is set to
|
|
101
|
+
'application/json'"`. That silently broke all three shipped delete paths —
|
|
102
|
+
`bundles delete`, `resources delete`, `permissions revoke`. Fixed in Phase 0;
|
|
103
|
+
the header is now sent only when there is a body.
|
|
104
|
+
|
|
105
|
+
**F. Two body/query constraints the spec does not state.** `perPage` caps at
|
|
106
|
+
**100** (larger 400s), now clamped in `paginate()`. And bundle create requires
|
|
107
|
+
**at least one role** — `"roles must contain at least 1 elements"` — so
|
|
108
|
+
`bundles create` without `--role` could never have succeeded; it now fails
|
|
109
|
+
client-side with a message naming the fix.
|
|
110
|
+
|
|
111
|
+
**G. Every family MERGES on PUT — with one important exception.** Probed
|
|
112
|
+
per family on throwaway objects (Phase 3): bundles, roles, policies, resources
|
|
113
|
+
and integrations all keep fields omitted from the body, so no `update` needs
|
|
114
|
+
read-modify-write. **Workflows merges at the top level but replaces the `rules`
|
|
115
|
+
array wholesale** — a 2-rule workflow plus a PUT carrying one rule leaves one
|
|
116
|
+
rule, and a rule object must be complete (`400 "rules.0.inGroups must be an
|
|
117
|
+
array"`). In every family a list you *do* pass is replaced, not appended.
|
|
118
|
+
|
|
119
|
+
**H. Silent 200-OK no-ops are the real hazard here.** Several writes report
|
|
120
|
+
success and do nothing: `POST /roles` drops `prerequisitePermissions`
|
|
121
|
+
(so `roles create` follows up with a PUT); `PUT /roles` ignores
|
|
122
|
+
`name`/`resource`/`virtualizedRole`; `PUT /integrations` accepts and ignores
|
|
123
|
+
the three create-only fields (`allowChangingAccountPermissions`,
|
|
124
|
+
`allowCreatingAccounts`, `readonly`); an unknown `--owner` is a no-op on
|
|
125
|
+
integration update but 404s on create; and `PUT` with a raised `sortOrder`
|
|
126
|
+
past the last policy silently keeps the old value. A `curl` user would believe
|
|
127
|
+
all of these landed. Verify writes with a follow-up GET.
|
|
128
|
+
|
|
129
|
+
**I. Spec inaccuracies found live.** `POST /roles` needs only
|
|
130
|
+
`name` + `resource` + `requestable` (the spec's `prerequisitePermissions` is
|
|
131
|
+
the field the server discards). Workflow approver types include **`Webhook`**
|
|
132
|
+
and **`TeamsChannel`**, both absent from the published spec; the notified-type
|
|
133
|
+
enum is that list minus `Automatic`. Integration create does not enforce the
|
|
134
|
+
spec's `maintainers`/`prerequisitePermissions`/`agentToken`.
|
|
135
|
+
|
|
136
|
+
**J. Policies cannot be created inert.** A policy has no `name` or
|
|
137
|
+
`description` at all — the body is only `inGroups`/`bundles`/`roles`/
|
|
138
|
+
`sortOrder`, so it is trackable by UUID alone — and `POST` rejects an empty
|
|
139
|
+
grant with `400 "Policy must grant at least 1 permission"`. Any policy create
|
|
140
|
+
is therefore a real grant for as long as it lives; do it only against a
|
|
141
|
+
Virtual Application, which provisions nothing downstream.
|
|
142
|
+
|
|
143
|
+
**K. Two writes silently overwrite or ignore, in ways the response hides.**
|
|
144
|
+
`POST` on both forwards families is an **upsert keyed on the forwarder** — a
|
|
145
|
+
second create for the same person returns the same id with the target replaced,
|
|
146
|
+
200, no 409. A naive "create a forward for Alice" therefore silently
|
|
147
|
+
redirects her queue away from whoever it pointed at. `POST /usersAccounts`, by
|
|
148
|
+
contrast, is **idempotent** on `(user, account)`: it returns the pre-existing
|
|
149
|
+
row with its original `createdAt` rather than creating a duplicate — so a
|
|
150
|
+
caller that deletes "the row it just made" would be deleting a real one.
|
|
151
|
+
|
|
152
|
+
**L. Webhooks cannot be created disabled, in either family.**
|
|
153
|
+
`{"enabled": false}` is accepted with 200 and silently discarded on create and
|
|
154
|
+
on `PUT`; `/disable` 404s. **DELETE is the only off switch.** The audit family
|
|
155
|
+
therefore exposes `enable` with no `--disable`, because a `--disable` flag
|
|
156
|
+
could only lie.
|
|
157
|
+
|
|
158
|
+
**M. Header redaction is by header NAME, not per hook, and it breaks
|
|
159
|
+
round-tripping.** `Authorization`, `X-Api-Key`, `api-key`, `X-Secret` and
|
|
160
|
+
`Token` read back as `***`; `Cookie` and arbitrary names come back verbatim.
|
|
161
|
+
Re-PUTting a body obtained from GET would therefore **store the literal `***`
|
|
162
|
+
over a real secret**, so neither webhook family offers `--file`, and update
|
|
163
|
+
sends only the flags given.
|
|
164
|
+
|
|
165
|
+
**N. `POST /agentTokens` returns a one-shot ~776-char credential** in
|
|
166
|
+
`{result:{id,name,token}}`; an immediate GET returns only `id`+`name`. It is
|
|
167
|
+
surfaced solely under `-o json`, redacted in tables, with an stderr warning
|
|
168
|
+
that it is unrecoverable. The spec documents this response bare, but it is
|
|
169
|
+
enveloped live.
|
|
170
|
+
|
|
171
|
+
**Revised scope: 49 implementable, 2 blocked.**
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Command tree (additions)
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
bt entitle
|
|
179
|
+
├── tasks list | get | set-status | archive
|
|
180
|
+
├── agent-tokens list | get | create | update | delete
|
|
181
|
+
├── audit-webhooks list | get | create | update | delete | enable
|
|
182
|
+
├── workflow-webhooks list | get | create | update | delete
|
|
183
|
+
├── request-forwards list | get | get-for-user | create | delete
|
|
184
|
+
├── review-forwards list | get | create | delete
|
|
185
|
+
├── user-accounts list | create | delete
|
|
186
|
+
├── directory-groups list
|
|
187
|
+
├── chats channels (user token; --integration-type slack)
|
|
188
|
+
├── users + pats
|
|
189
|
+
├── permissions + account
|
|
190
|
+
├── roles + create | update | delete
|
|
191
|
+
├── policies + create | update | delete
|
|
192
|
+
├── workflows + create | update | delete
|
|
193
|
+
├── integrations + create | update | delete
|
|
194
|
+
├── resources + update
|
|
195
|
+
└── bundles + update ← client method already exists
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`bundles update` is not one of the 51: `EntitleClient.update_bundle()` already
|
|
199
|
+
exists at `client/base.py:359` with no command exposing it. Free win, Phase 0.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Implementation phases
|
|
204
|
+
|
|
205
|
+
Ordered read-before-write so the models and table columns are already proven by
|
|
206
|
+
the time a phase starts mutating a live tenant.
|
|
207
|
+
|
|
208
|
+
### Phase 0 — Orphan + scaffolding (DONE)
|
|
209
|
+
|
|
210
|
+
- Expose `bt entitle bundles update` over the existing `update_bundle()`.
|
|
211
|
+
- Fix the `Content-Type` bug breaking every DELETE (finding E).
|
|
212
|
+
- Clamp `perPage` to 100 and validate bundle-create roles (finding F).
|
|
213
|
+
- Add `tests/entitle/test_coverage.py`: a **static guard** that parses the spec
|
|
214
|
+
and asserts every operation is either implemented or listed in an explicit
|
|
215
|
+
`KNOWN_UNIMPLEMENTED` set. This is the same class of fix as the derived
|
|
216
|
+
command tree in 0.4.73 — it makes the gap impossible to re-open silently, and
|
|
217
|
+
each later phase shrinks the allowlist.
|
|
218
|
+
- Replace the placeholder block at `tests/entitle/test_commands.py:526`.
|
|
219
|
+
|
|
220
|
+
### Phase 1 — Read-only, new families (11 ops) — DONE
|
|
221
|
+
|
|
222
|
+
| Ops | Commands |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `GET /tasks`, `GET /tasks/{id}` | `tasks list`, `tasks get` |
|
|
225
|
+
| `GET /agentTokens`, `GET /agentTokens/{id}` | `agent-tokens list/get` |
|
|
226
|
+
| `GET /directoryGroups` | `directory-groups list` |
|
|
227
|
+
| `GET /chats/channels` | `chats channels` |
|
|
228
|
+
| `GET /usersAccounts` | `user-accounts list` |
|
|
229
|
+
| `GET /users/{id}/personalAccessTokens` | `users pats` |
|
|
230
|
+
| `GET /permissions/{accountId}` | `permissions account` |
|
|
231
|
+
| `GET /auditLogsWebhooks`, `GET /workflowsWebhooks` | `*-webhooks list` |
|
|
232
|
+
|
|
233
|
+
Envelopes are already captured in finding A above — use that table rather than
|
|
234
|
+
re-deriving it. `tasks`, `audit-webhooks`, and `workflow-webhooks` take a single
|
|
235
|
+
`get()`, not `paginate()`. `chats channels` takes the **user** token.
|
|
236
|
+
|
|
237
|
+
### Phase 2 — Read-only, single-item gets + forwards (7 ops) — DONE
|
|
238
|
+
|
|
239
|
+
`GET` on `{id}` for `auditLogsWebhooks`, `workflowsWebhooks`,
|
|
240
|
+
`accessRequestForwards`, `accessReviewForwards`, plus
|
|
241
|
+
`GET /accessRequestForwards/user/{forwardUserId}`.
|
|
242
|
+
|
|
243
|
+
Split from Phase 1 only so Phase 1 can ship while list-shape questions on the
|
|
244
|
+
forwards families are still open.
|
|
245
|
+
|
|
246
|
+
### Phase 3 — Complete the half-done families (15 ops) — DONE
|
|
247
|
+
|
|
248
|
+
Writes on families that already have reads. Highest user-visible value: these
|
|
249
|
+
groups currently look broken (`bt entitle roles list` works, `roles create`
|
|
250
|
+
doesn't exist).
|
|
251
|
+
|
|
252
|
+
- `roles` create / update / delete
|
|
253
|
+
- `policies` create / update / delete
|
|
254
|
+
- `workflows` create / update / delete
|
|
255
|
+
- `integrations` create / update / delete
|
|
256
|
+
- `resources` update
|
|
257
|
+
|
|
258
|
+
**Live verify (create → update → delete on objects the agent itself made).**
|
|
259
|
+
Record for each: required vs optional body fields, whether `PUT` is a full
|
|
260
|
+
replace or a merge, and the 4xx body shape on validation failure.
|
|
261
|
+
|
|
262
|
+
> **`PUT` merges — verified for `bundles` (finding D), not yet for the others.**
|
|
263
|
+
> Probe each family the same way before writing its `update`: create a throwaway
|
|
264
|
+
> object, `PUT` a partial body, and check whether the omitted fields survive. If
|
|
265
|
+
> any family turns out to replace instead, that one needs read-modify-write and
|
|
266
|
+
> a warning in the skill file for direct-`curl` users.
|
|
267
|
+
|
|
268
|
+
### Phase 4 — Webhooks + agent tokens, write paths (10 ops) — DONE
|
|
269
|
+
|
|
270
|
+
- `auditLogsWebhooks` create / update / delete / **enable** (`PUT {id}/enable`)
|
|
271
|
+
- `workflowsWebhooks` create / update / delete
|
|
272
|
+
- `agentTokens` create / update / delete
|
|
273
|
+
|
|
274
|
+
**Handle the secret.** `POST /agentTokens` almost certainly returns a token value
|
|
275
|
+
exactly once. It must go to stdout as data under `-o json`, never to a log or a
|
|
276
|
+
status banner, and the table view should make the show-once behavior explicit.
|
|
277
|
+
|
|
278
|
+
**Webhook targets are outbound, and an enabled one receives real data.**
|
|
279
|
+
Creating an audit-log webhook on a live tenant means real audit events start
|
|
280
|
+
being POSTed to whatever URL it names. **Do not use a request-bin or
|
|
281
|
+
webhook.site style capture service** — that exfiltrates the tenant's audit
|
|
282
|
+
trail to a third party, which is worse than the bug being tested for. Point it
|
|
283
|
+
at `https://example.com/...` (IANA-reserved, nothing listening, nothing
|
|
284
|
+
recorded), leave it disabled wherever the test allows, and delete every webhook
|
|
285
|
+
created during verification.
|
|
286
|
+
|
|
287
|
+
### Phase 5 — Forwards + user accounts (6 ops) — DONE
|
|
288
|
+
|
|
289
|
+
- `accessRequestForwards` create / delete
|
|
290
|
+
- `accessReviewForwards` create / delete
|
|
291
|
+
- `usersAccounts` create / delete
|
|
292
|
+
|
|
293
|
+
`orgRoles` is **excluded** — see finding B. Do not implement `org-roles list` or
|
|
294
|
+
`set-identities` until the tenant's org-level gating is lifted; an unverifiable
|
|
295
|
+
org-wide identity write is not worth shipping blind.
|
|
296
|
+
|
|
297
|
+
### Phase 6 — Tests, skill, docs — DONE
|
|
298
|
+
|
|
299
|
+
- Drain `KNOWN_UNIMPLEMENTED` in the Phase 0 guard to empty.
|
|
300
|
+
- Update `src/bt_cli/data/skills/entitle/SKILL.md` with every new command plus
|
|
301
|
+
the envelope/body-shape findings from the live runs.
|
|
302
|
+
- Fix the stale "43 paths vs CLI's 19" line — it lives in the project memory
|
|
303
|
+
file, not `CLAUDE.md` — and add any gotcha worth a permanent warning
|
|
304
|
+
(region-binding is already there).
|
|
305
|
+
- **Do not touch `bt tree` / `bt find`** — they derive from the live Typer app.
|
|
306
|
+
New commands appear automatically; help text comes from the docstring's first
|
|
307
|
+
line, so **every new command needs a docstring** (see the 29 blank-help
|
|
308
|
+
commands found in the same audit that produced this plan).
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Files
|
|
313
|
+
|
|
314
|
+
**Create** (`src/bt_cli/entitle/`):
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
commands/tasks.py commands/agent_tokens.py
|
|
318
|
+
commands/webhooks.py commands/forwards.py
|
|
319
|
+
commands/user_accounts.py commands/directory_groups.py
|
|
320
|
+
commands/chats.py
|
|
321
|
+
models/task.py models/agent_token.py
|
|
322
|
+
models/webhook.py models/forward.py
|
|
323
|
+
tests/entitle/test_coverage.py
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Modify:**
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
client/base.py +49 methods
|
|
330
|
+
commands/__init__.py register new sub-apps
|
|
331
|
+
commands/{roles,policies,workflows,integrations,resources,bundles}.py
|
|
332
|
+
commands/{users,permissions}.py add pats / account
|
|
333
|
+
src/bt_cli/data/skills/entitle/SKILL.md
|
|
334
|
+
CLAUDE.md
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Key design decisions
|
|
340
|
+
|
|
341
|
+
1. **Group naming: kebab-case, mirroring the resource.** `agent-tokens`, not
|
|
342
|
+
`agenttokens` or `tokens`. Two webhook families stay separate
|
|
343
|
+
(`audit-webhooks`, `workflow-webhooks`) rather than a merged `webhooks
|
|
344
|
+
--type` — they are different endpoints with different bodies, and only the
|
|
345
|
+
audit one has `enable`.
|
|
346
|
+
2. **`tasks` mutations get verbs, not `update`.** `PUT /tasks/{id}` only sets
|
|
347
|
+
status and `DELETE` archives rather than deletes. `tasks set-status` and
|
|
348
|
+
`tasks archive` say what actually happens; `tasks delete` would lie.
|
|
349
|
+
3. **Pagination is chosen per endpoint from the finding-A table, not assumed.**
|
|
350
|
+
Endpoints whose envelope carries `pagination` go through `paginate()`; the
|
|
351
|
+
three that lack it take a single `get()` with client-side capping. Add a
|
|
352
|
+
comment at each single-`get()` call site saying why, so nobody "fixes" it
|
|
353
|
+
into a paginate later. If a new endpoint returns an envelope the four
|
|
354
|
+
existing branches don't handle, extend `paginate()` — never special-case at
|
|
355
|
+
the call site.
|
|
356
|
+
4. **Token choice is explicit per command.** Default to the org token; use the
|
|
357
|
+
user token only where the API demands on-behalf-of semantics (as
|
|
358
|
+
`requests create` does). Where an org token 400s, surface the same
|
|
359
|
+
actionable error `jit list` gives, not a raw 400.
|
|
360
|
+
5. **`-o json` purity is non-negotiable.** All banners, warnings, and
|
|
361
|
+
confirmations to stderr via `print_success`/`print_warning`/`err_console`,
|
|
362
|
+
and `typer.confirm(..., err=True)`. Enforced by
|
|
363
|
+
`tests/core/test_json_stdout_purity.py`.
|
|
364
|
+
6. **Destructive commands require confirmation.** Every `delete`, `archive`,
|
|
365
|
+
`revoke`, and `set-identities` prompts unless `--yes`. Prompt goes to stderr.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Verification
|
|
370
|
+
|
|
371
|
+
Per phase: unit tests green, `tests/core/test_json_stdout_purity.py` green,
|
|
372
|
+
`tests/core/test_command_tree.py` green, the Phase 0 coverage guard shrunk by
|
|
373
|
+
exactly the phase's operation count, and a live run of every new command against
|
|
374
|
+
the tenant with the response shape recorded in the skill file.
|
|
375
|
+
|
|
376
|
+
Full-live-verify hygiene: every object an agent creates is deleted in the same
|
|
377
|
+
phase. Nothing is created against a name that could collide with real tenant
|
|
378
|
+
data — prefix all test objects `btcli-verify-<phase>-<uuid>`.
|