bt-cli 0.4.41__tar.gz → 0.4.44__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.41 → bt_cli-0.4.44}/.claude/skills/entitle/SKILL.md +27 -0
- {bt_cli-0.4.41/src/bt_cli/data → bt_cli-0.4.44/.claude}/skills/pra/SKILL.md +3 -0
- {bt_cli-0.4.41/src/bt_cli/data → bt_cli-0.4.44}/CLAUDE.md +1 -1
- {bt_cli-0.4.41 → bt_cli-0.4.44}/PKG-INFO +2 -1
- bt_cli-0.4.44/epml-clients-server-side-filters-plan.md +206 -0
- bt_cli-0.4.44/pf-implementation-plan.md +187 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/pyproject.toml +2 -1
- bt_cli-0.4.44/scripts/pf_onboard.py +356 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/cli.py +22 -1
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/config.py +14 -4
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/skills/epml/SKILL.md +12 -3
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/client/base.py +51 -6
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/requests.py +112 -35
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/client/base.py +32 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/__init__.py +2 -0
- bt_cli-0.4.44/src/bt_cli/epml/commands/clients.py +190 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/client/base.py +217 -6
- bt_cli-0.4.44/src/bt_cli/pra/commands/group_policies.py +531 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/policies.py +4 -72
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/quick.py +213 -0
- bt_cli-0.4.44/src/bt_cli/pra/commands/users.py +235 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/__init__.py +16 -0
- bt_cli-0.4.44/src/bt_cli/pra/models/group_policy.py +45 -0
- bt_cli-0.4.41/src/bt_cli/pra/commands/users.py +0 -87
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.claude/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.env.example +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/.gitignore +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/README.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/assets/cli-help.png +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/assets/cli-output.png +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/bt-cli.spec +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/bt_entry.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44/src/bt_cli/data}/CLAUDE.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.41/.claude → bt_cli-0.4.44/src/bt_cli/data}/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/conftest.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.41 → bt_cli-0.4.44}/tests/pws-smoke-test.sh +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:**
|
|
@@ -13,6 +13,9 @@ description: Privileged Remote Access commands for jump items, vault accounts, a
|
|
|
13
13
|
- `bt pra jump-items tunnel delete` - Deletes protocol tunnel
|
|
14
14
|
- `bt pra jump-groups delete` - Deletes jump group
|
|
15
15
|
- `bt pra vault accounts delete` - Deletes vault account
|
|
16
|
+
- `bt pra users delete` - Deletes a user (non-admins only)
|
|
17
|
+
- `bt pra policies group delete` - Deletes a Group Policy
|
|
18
|
+
- `bt pra policies group {jumpoints|members|jump-groups|vault-accounts|vault-account-groups|teams} remove` - Detaches a sub-resource from a Group Policy
|
|
16
19
|
|
|
17
20
|
List affected resources first, then ask for explicit confirmation.
|
|
18
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bt-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.44
|
|
4
4
|
Summary: BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM
|
|
5
5
|
Author-email: Dave Grendysz <dgrendysz@beyondtrust.com>
|
|
6
6
|
License: MIT
|
|
@@ -23,6 +23,7 @@ Requires-Dist: httpx>=0.27.0
|
|
|
23
23
|
Requires-Dist: pydantic>=2.0.0
|
|
24
24
|
Requires-Dist: python-dotenv>=1.0.0
|
|
25
25
|
Requires-Dist: pyyaml>=6.0.0
|
|
26
|
+
Requires-Dist: questionary>=2.0.0
|
|
26
27
|
Requires-Dist: rich<14.0.0,>=13.7.0
|
|
27
28
|
Requires-Dist: shellingham>=1.5.0
|
|
28
29
|
Requires-Dist: truststore>=0.8.0; python_version >= '3.10'
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Plan: Server-side filters + pagination on `bt epml clients list`
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Today `bt epml clients list` fetches the **entire** license-client inventory in one shot and filters client-side. The EPMLC API at `GET /api/epml/license/clients` supports server-side pagination, sorting, date-windowing, and pattern matching (per the [reference](https://docs.beyondtrust.com/epm-l/reference/epmlgetlicenseclients)). We don't expose any of that. On a tenant with thousands of clients it's slow and unnecessarily heavy.
|
|
6
|
+
|
|
7
|
+
This plan adds the missing flags, keeps current behavior working, and tightens the listing for large tenants.
|
|
8
|
+
|
|
9
|
+
## Current vs server
|
|
10
|
+
|
|
11
|
+
| Server query param | Type | Today's CLI | Notes |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| `limit` | int | ❌ missing | server-side page size |
|
|
14
|
+
| `offset` | int | ❌ missing | server-side page offset |
|
|
15
|
+
| `older` | int (epoch) | ❌ missing | last-updated **before** |
|
|
16
|
+
| `newer` | int (epoch) | ❌ missing | last-updated **since** |
|
|
17
|
+
| `order` | enum (`uuid`/`fqdn`/`addr`/`lastupdated`/`retired`) | ❌ missing | sort field |
|
|
18
|
+
| `direction` | enum (`asc`/`desc`) | ❌ missing | sort direction |
|
|
19
|
+
| `fqdn` | string (wildcard) | ⚠️ client-side substring | server-side wildcard match |
|
|
20
|
+
| `addr` | string (wildcard) | ❌ missing | server-side wildcard match |
|
|
21
|
+
| `exclude_retired` | bool | ⚠️ client-side filter (`--include-retired` flips) | server-side equivalent |
|
|
22
|
+
| `--component` | n/a | ✓ client-side only | NOT in server API; keep local |
|
|
23
|
+
|
|
24
|
+
Source method: `EPMLClient.list_license_clients(host_id)` at `src/bt_cli/epml/client/base.py:237`.
|
|
25
|
+
CLI command: `list_clients` at `src/bt_cli/epml/commands/clients.py:56`.
|
|
26
|
+
|
|
27
|
+
## Proposed CLI surface
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
bt epml clients list [OPTIONS]
|
|
31
|
+
|
|
32
|
+
--host, -H PMUL host id (uses host-scoped path)
|
|
33
|
+
--component, -c License bucket filter (CLIENT-SIDE; server doesn't have this)
|
|
34
|
+
{RBPClnts, PBULPolClnts, FIMClnts, SudoPolClnts}
|
|
35
|
+
--fqdn TEXT FQDN wildcard (server-side; supports * and ?)
|
|
36
|
+
--addr TEXT Address wildcard (server-side; supports * and ?)
|
|
37
|
+
--include-retired Include retired clients (default: hidden via server's
|
|
38
|
+
exclude_retired=true; flag flips it to false)
|
|
39
|
+
--since TEXT Only clients updated since this time
|
|
40
|
+
(accepts ISO-8601, "1h", "7d", or epoch)
|
|
41
|
+
--before TEXT Only clients updated before this time (same formats)
|
|
42
|
+
--order [uuid|fqdn|addr|lastupdated|retired]
|
|
43
|
+
Sort field (default: fqdn)
|
|
44
|
+
--direction [asc|desc] Sort direction (default: asc)
|
|
45
|
+
--limit, -n INTEGER Page size (default: 200; max: server-defined)
|
|
46
|
+
--all Iterate all pages until exhausted (overrides --limit
|
|
47
|
+
for the per-call value but pages internally)
|
|
48
|
+
--output, -o table | json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Flag-naming notes
|
|
52
|
+
|
|
53
|
+
- `--since` / `--before` are friendlier than `--newer` / `--older`. Accept ISO-8601, relative durations (`1h`, `7d`, `30m`), and raw epoch ints. Convert to epoch before the API call.
|
|
54
|
+
- `--addr` mirrors the API param name verbatim (server already speaks wildcards there).
|
|
55
|
+
- `--component` stays as a **client-side** filter with a warning in `--help` since the server doesn't support it.
|
|
56
|
+
- Drop the current `--fqdn` substring behavior in favor of the server's wildcard matcher. If the user passes a plain string like `web-01`, transparently wrap in `*web-01*` so the substring UX is preserved. Document the auto-wrap behavior.
|
|
57
|
+
- Default to `--exclude-retired=true` (matches today's UX where retired clients are hidden unless `--include-retired` is passed). Done via flipping the server param.
|
|
58
|
+
|
|
59
|
+
## Implementation steps
|
|
60
|
+
|
|
61
|
+
### 1. Extend `EPMLClient.list_license_clients`
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
def list_license_clients(
|
|
65
|
+
self,
|
|
66
|
+
host_id: Optional[int] = None,
|
|
67
|
+
*,
|
|
68
|
+
limit: Optional[int] = None,
|
|
69
|
+
offset: Optional[int] = None,
|
|
70
|
+
older: Optional[int] = None, # epoch
|
|
71
|
+
newer: Optional[int] = None, # epoch
|
|
72
|
+
order: Optional[str] = None, # uuid|fqdn|addr|lastupdated|retired
|
|
73
|
+
direction: Optional[str] = None, # asc|desc
|
|
74
|
+
fqdn: Optional[str] = None,
|
|
75
|
+
addr: Optional[str] = None,
|
|
76
|
+
exclude_retired: Optional[bool] = None,
|
|
77
|
+
) -> Dict[str, Any]:
|
|
78
|
+
"""GET license clients with full server-side filter set."""
|
|
79
|
+
params: Dict[str, Any] = {}
|
|
80
|
+
for k, v in (
|
|
81
|
+
("limit", limit), ("offset", offset),
|
|
82
|
+
("older", older), ("newer", newer),
|
|
83
|
+
("order", order), ("direction", direction),
|
|
84
|
+
("fqdn", fqdn), ("addr", addr),
|
|
85
|
+
("exclude_retired", exclude_retired),
|
|
86
|
+
):
|
|
87
|
+
if v is not None:
|
|
88
|
+
params[k] = v
|
|
89
|
+
if host_id is not None:
|
|
90
|
+
return self.get(f"/api/pbul/{host_id}/licenseinfo/clients", params=params or None)
|
|
91
|
+
return self.get("/api/epml/license/clients", params=params or None)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Backwards-compatible: existing call sites pass no kwargs.
|
|
95
|
+
|
|
96
|
+
### 2. Add a paginating helper for `--all`
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
def iter_license_clients(
|
|
100
|
+
self,
|
|
101
|
+
page_size: int = 200,
|
|
102
|
+
**filters,
|
|
103
|
+
) -> Iterator[Dict[str, Any]]:
|
|
104
|
+
"""Yield every matching client across as many pages as needed."""
|
|
105
|
+
offset = 0
|
|
106
|
+
while True:
|
|
107
|
+
page = self.list_license_clients(limit=page_size, offset=offset, **filters)
|
|
108
|
+
rows = (page.get("Data") or page.get("data") or []) if isinstance(page, dict) else []
|
|
109
|
+
if not rows:
|
|
110
|
+
return
|
|
111
|
+
yield from rows
|
|
112
|
+
if len(rows) < page_size:
|
|
113
|
+
return
|
|
114
|
+
offset += page_size
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Update the CLI command
|
|
118
|
+
|
|
119
|
+
In `src/bt_cli/epml/commands/clients.py`:
|
|
120
|
+
- Add the new typer options.
|
|
121
|
+
- Parse `--since`/`--before` to epoch using a small helper (accept `1h`/`7d`/`30m` patterns + ISO-8601 + raw integer).
|
|
122
|
+
- Auto-wrap plain `--fqdn` values in `*` if no glob char present (preserves substring UX).
|
|
123
|
+
- Branch on `--all`: paginate via `iter_license_clients`, otherwise single page.
|
|
124
|
+
- Keep `--component` as a post-filter (server doesn't support it). Document this in the option help text.
|
|
125
|
+
|
|
126
|
+
### 4. Time-spec parser
|
|
127
|
+
|
|
128
|
+
Small new helper in `core/timeparse.py` (or co-located if there's no shared util module):
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
def parse_time_to_epoch(s: str) -> int:
|
|
132
|
+
"""Accept '1h', '7d', '30m', ISO-8601, or raw epoch int. Return UTC epoch."""
|
|
133
|
+
...
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Used by both `--since` and `--before`. Tests cover each input form.
|
|
137
|
+
|
|
138
|
+
### 5. Output formatting
|
|
139
|
+
|
|
140
|
+
No change needed — `_flatten_user`-style helpers already handle the row shape. Only thing to verify: when paginating with `--all`, table rendering streams or buffers? For now buffer (dataset typically <10k rows; rich.Table handles fine). Document the trade-off.
|
|
141
|
+
|
|
142
|
+
### 6. Tests
|
|
143
|
+
|
|
144
|
+
In `tests/epml/test_clients.py` (or wherever the existing client tests live):
|
|
145
|
+
|
|
146
|
+
- `test_list_license_clients_passes_server_params` — respx-mock, assert query string carries all the params we set
|
|
147
|
+
- `test_list_license_clients_no_params_unchanged` — backwards compat: no kwargs → no `params` arg → same URL as today
|
|
148
|
+
- `test_iter_license_clients_paginates` — three pages of 200 + a partial; verify offset progression and termination
|
|
149
|
+
- `test_iter_license_clients_terminates_on_short_page`
|
|
150
|
+
- `test_clients_list_command_auto_wraps_fqdn_substring` — `--fqdn web-01` becomes `*web-01*`; `--fqdn '*web*'` is passed through
|
|
151
|
+
- `test_clients_list_command_since_relative` — `--since 1h` parses correctly
|
|
152
|
+
- `test_clients_list_command_since_iso` — ISO-8601 form
|
|
153
|
+
- `test_clients_list_component_is_post_filter` — server returns 100 rows; `--component RBPClnts` filters client-side
|
|
154
|
+
- `test_parse_time_to_epoch_accepts_*` — unit tests for the helper
|
|
155
|
+
|
|
156
|
+
### 7. Skill + docs
|
|
157
|
+
|
|
158
|
+
Update `src/bt_cli/data/skills/epml/SKILL.md`:
|
|
159
|
+
- The "License clients" section already exists. Replace with the new flag set.
|
|
160
|
+
- Add a one-liner about `--all` and the auto-wrap fqdn behavior.
|
|
161
|
+
|
|
162
|
+
Update `src/bt_cli/data/CLAUDE.md` if it has a clients line.
|
|
163
|
+
|
|
164
|
+
Update `btcli/CLAUDE.md`'s API quirks table — add a note that EPML clients endpoint supports server-side pagination and sorting.
|
|
165
|
+
|
|
166
|
+
## Backwards compatibility
|
|
167
|
+
|
|
168
|
+
- `--component`, `--fqdn`, `--include-retired`, `--host`, `--output` all keep their current meaning.
|
|
169
|
+
- `--fqdn`'s substring semantics preserved via the auto-wrap.
|
|
170
|
+
- Default sort changes from "whatever the server returned" to `fqdn asc` — small cosmetic change; document it.
|
|
171
|
+
|
|
172
|
+
## Edge cases
|
|
173
|
+
|
|
174
|
+
- **Empty Data array on a non-zero `Total`** — server bug or a filter that removed everything; treat as "end of pages" in `iter_license_clients`.
|
|
175
|
+
- **Server caps `limit`** — if the server returns fewer rows than asked, that's normal pagination, not necessarily end-of-data. The "fewer than page_size → done" heuristic is good enough today but could need revisiting.
|
|
176
|
+
- **`--all` with `--limit`** — `--limit` becomes the per-call page size; `--all` toggles iteration. Document.
|
|
177
|
+
- **Wildcard escape** — server interprets `*` and `?`. If a user has a literal `*` in an fqdn (unlikely but possible), no escape syntax exists. Note in help text.
|
|
178
|
+
|
|
179
|
+
## Verification
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Single page, default
|
|
183
|
+
bt epml clients list
|
|
184
|
+
|
|
185
|
+
# Server-side fqdn wildcard
|
|
186
|
+
bt epml clients list --fqdn 'web-*'
|
|
187
|
+
|
|
188
|
+
# Server-side address wildcard
|
|
189
|
+
bt epml clients list --addr '10.0.0.*'
|
|
190
|
+
|
|
191
|
+
# Recent activity
|
|
192
|
+
bt epml clients list --since 1h
|
|
193
|
+
bt epml clients list --since 2026-05-01T00:00:00Z
|
|
194
|
+
|
|
195
|
+
# Sorted
|
|
196
|
+
bt epml clients list --order lastupdated --direction desc --limit 20
|
|
197
|
+
|
|
198
|
+
# Full sweep
|
|
199
|
+
bt epml clients list --all -o json | jq '.Data | length'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Out of scope
|
|
203
|
+
|
|
204
|
+
- `bt epml clients retire` flag additions (separate endpoint; can mirror later if needed).
|
|
205
|
+
- Changing the `host-scoped` URL behavior (`/api/pbul/{hostid}/licenseinfo/clients`) — current logic keeps it.
|
|
206
|
+
- Streaming output for `--all` (buffer for now).
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Plan: Add Pathfinder (PF) Product to BeyondTrust CLI
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
The BeyondTrust CLI (`bt-cli`) currently supports 4 products: PWS, PRA, Entitle, and EPMW. The Pathfinder platform (internally "Nomine Authentication API") handles identity management — users, machine-to-machine auth, SAML providers, site/product access control, tokens, and auditing. Its OpenAPI spec lives at `/projects/reference/apidoc/pf/swagger.yaml` (67+ endpoints, 80+ schemas). This plan adds full PF support following the established patterns.
|
|
6
|
+
|
|
7
|
+
## Command Tree: `bt pf`
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
bt pf
|
|
11
|
+
auth test|status
|
|
12
|
+
users list|get|me|access|my-access|update|delete|bulk-delete|invite|cancel-invite
|
|
13
|
+
machines list|get|create|delete|activate
|
|
14
|
+
auditing list
|
|
15
|
+
saml list|create|import|update|delete
|
|
16
|
+
product-access list|org|org-defaults|grant|revoke|grant-org|revoke-org
|
|
17
|
+
site-access list|org|grant|revoke|grant-org|revoke-org
|
|
18
|
+
tokens list|create|delete
|
|
19
|
+
mcp-tokens list|create|delete
|
|
20
|
+
mfa enable|disable|disable-user|count
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Implementation Phases
|
|
24
|
+
|
|
25
|
+
### Phase 1: Foundation (core infra + auth test)
|
|
26
|
+
|
|
27
|
+
**1a. Add `MachineAuth` strategy** — `src/bt_cli/core/auth.py`
|
|
28
|
+
- New class: `MachineAuth(AuthStrategy)` with `machine_id`, `machine_secret`
|
|
29
|
+
- `authenticate()`: POST `/bt.identity/Machine/AccessToken` with JSON `{machineId, machineSecret}`
|
|
30
|
+
- Returns `{access_token, token_type, expires_in}` → sets Bearer token
|
|
31
|
+
- No sign_out needed (JWT is stateless)
|
|
32
|
+
|
|
33
|
+
**1b. Add `PFConfig`** — `src/bt_cli/core/config.py`
|
|
34
|
+
- Dataclass: `PFConfig(ProductConfig)` with `machine_id: str`, `machine_secret: str`
|
|
35
|
+
- `load_pf_config()` function — env vars: `BT_PF_API_URL`, `BT_PF_MACHINE_ID`, `BT_PF_MACHINE_SECRET`, `BT_PF_VERIFY_SSL`, `BT_PF_TIMEOUT`
|
|
36
|
+
- Add `"pf": load_pf_config` to `load_config()` dispatcher
|
|
37
|
+
|
|
38
|
+
**1c. Update config_file.py** — `src/bt_cli/core/config_file.py`
|
|
39
|
+
- Add `"pf"` entry to `PRODUCTS` dict (fields: api_url, machine_id, machine_secret, verify_ssl, timeout)
|
|
40
|
+
|
|
41
|
+
**1d. Create module structure:**
|
|
42
|
+
- `src/bt_cli/pf/__init__.py`
|
|
43
|
+
- `src/bt_cli/pf/client/__init__.py` — exports `PFClient, get_client`
|
|
44
|
+
- `src/bt_cli/pf/client/base.py` — client with context manager, token caching, HTTP methods
|
|
45
|
+
- `src/bt_cli/pf/commands/__init__.py` — registers all sub-typer apps
|
|
46
|
+
- `src/bt_cli/pf/commands/auth.py` — `test` (call list_users limit=1) and `status`
|
|
47
|
+
- `src/bt_cli/pf/models/__init__.py`
|
|
48
|
+
- `src/bt_cli/pf/models/common.py` — `CommandResponse` base model
|
|
49
|
+
|
|
50
|
+
**1e. Register in CLI** — `src/bt_cli/cli.py`
|
|
51
|
+
- Add `_get_pf_app()` lazy loader (after line 83)
|
|
52
|
+
- Add `app.add_typer()` registration (after line 124)
|
|
53
|
+
- Update help text in `main_callback` docstring to include Pathfinder
|
|
54
|
+
|
|
55
|
+
**Verify:** `bt pf auth test` connects and authenticates.
|
|
56
|
+
|
|
57
|
+
### Phase 2: Core Resources (users, machines, auditing)
|
|
58
|
+
|
|
59
|
+
**2a. Models:**
|
|
60
|
+
- `models/user.py` — `NomineUser` (id, email, firstName, lastName, active, mfaEnabled)
|
|
61
|
+
- `models/machine.py` — `Machine` (id, activationToken, active, tenantId, organizationId, timestamps)
|
|
62
|
+
- `models/audit.py` — `AuditRecord` (id, userId, organizationId, auditTimeUtc, description, metadata)
|
|
63
|
+
|
|
64
|
+
**2b. Client methods** in `pf/client/base.py`:
|
|
65
|
+
- Users: `list_users()`, `get_user_info()`, `get_user_access()`, `get_my_access()`, `update_user()`, `delete_user()`, `bulk_delete_users()`, `invite_user()`, `cancel_invitation()`
|
|
66
|
+
- Machines: `list_machines()`, `get_machine()`, `create_machine()`, `delete_machine()`, `activate_machine()`
|
|
67
|
+
- Auditing: `list_audit_records()`
|
|
68
|
+
|
|
69
|
+
**2c. Commands:**
|
|
70
|
+
- `commands/users.py` — 10 subcommands
|
|
71
|
+
- `commands/machines.py` — 5 subcommands
|
|
72
|
+
- `commands/auditing.py` — 1 subcommand (list with date/email/description filters)
|
|
73
|
+
|
|
74
|
+
**API quirks:**
|
|
75
|
+
- `GET /api/Users` returns plain array (no pagination wrapper)
|
|
76
|
+
- `GET /api/Machine` returns only UUIDs in `machines[]`, need `GET /api/Machine/{id}` for details
|
|
77
|
+
- Machine create returns `activationToken` (30min TTL) → `activate` exchanges code for `machineSecret`
|
|
78
|
+
|
|
79
|
+
### Phase 3: Access Management (product-access, site-access, saml)
|
|
80
|
+
|
|
81
|
+
**3a. Models:**
|
|
82
|
+
- `models/access.py` — `ProductAccessRule`, `SiteAccessRule`
|
|
83
|
+
- `models/saml_provider.py` — `SamlProvider`
|
|
84
|
+
|
|
85
|
+
**3b. Client methods:**
|
|
86
|
+
- Product access: `list_product_access()`, `list_org_product_access()`, `get_org_defaults()`, `grant_product_access()`, `revoke_product_access()`, `grant_org_product_access()`, `revoke_org_product_access()`
|
|
87
|
+
- Site access: `list_site_access()`, `list_org_site_access()`, `grant_site_access()`, `revoke_site_access()`, `grant_org_site_access()`, `revoke_org_site_access()`
|
|
88
|
+
- SAML: `list_saml_providers()`, `create_saml_provider()`, `import_saml_provider()`, `update_saml_provider()`, `delete_saml_provider()`
|
|
89
|
+
|
|
90
|
+
**3c. Commands:**
|
|
91
|
+
- `commands/product_access.py` — 7 subcommands
|
|
92
|
+
- `commands/site_access.py` — 6 subcommands
|
|
93
|
+
- `commands/saml.py` — 5 subcommands (import reads XML file from disk)
|
|
94
|
+
|
|
95
|
+
### Phase 4: Tokens & MFA
|
|
96
|
+
|
|
97
|
+
**4a. Models:**
|
|
98
|
+
- `models/token.py` — `UserPersonalAccessToken`, `UserMcpAccessToken`
|
|
99
|
+
|
|
100
|
+
**4b. Commands:**
|
|
101
|
+
- `commands/tokens.py` — list, create (--expiration-days), delete
|
|
102
|
+
- `commands/mcp_tokens.py` — list, create (--expiration-days), delete
|
|
103
|
+
- `commands/mfa.py` — enable (--token-code), disable, disable-user, count
|
|
104
|
+
|
|
105
|
+
### Phase 5: CLI Integration
|
|
106
|
+
|
|
107
|
+
Update `src/bt_cli/cli.py`:
|
|
108
|
+
- Add `_test_pf_connection()` → call `get_user_info()` for whoami
|
|
109
|
+
- Add PF to `whoami` command results
|
|
110
|
+
- Add PF section to `tree_command` + add `"pf"` to valid product list
|
|
111
|
+
- Add all PF commands to `_get_all_commands()`
|
|
112
|
+
- Update `skills` command help text
|
|
113
|
+
|
|
114
|
+
### Phase 6: Tests
|
|
115
|
+
|
|
116
|
+
**6a. Fixtures** — `tests/conftest.py`: add `mock_pf_config`, `pf_env_vars`
|
|
117
|
+
**6b. Mock responses** — `tests/fixtures/responses.py`: add `PF_*` constants
|
|
118
|
+
**6c. Test files:**
|
|
119
|
+
- `tests/pf/__init__.py`
|
|
120
|
+
- `tests/pf/test_client.py` — client unit tests with `@respx.mock`
|
|
121
|
+
- `tests/pf/test_commands.py` — CLI command tests with patched config/client
|
|
122
|
+
|
|
123
|
+
### Phase 7: Skill File
|
|
124
|
+
|
|
125
|
+
- `src/bt_cli/data/skills/pf/SKILL.md` — command reference, destructive ops warnings, workflow examples, API quirks
|
|
126
|
+
- Update `btcli/CLAUDE.md` and `src/bt_cli/data/CLAUDE.md` to include PF
|
|
127
|
+
|
|
128
|
+
## Files to Create (27 files)
|
|
129
|
+
|
|
130
|
+
| File | Purpose |
|
|
131
|
+
|------|---------|
|
|
132
|
+
| `src/bt_cli/pf/__init__.py` | Package marker |
|
|
133
|
+
| `src/bt_cli/pf/client/__init__.py` | Export PFClient, get_client |
|
|
134
|
+
| `src/bt_cli/pf/client/base.py` | HTTP client with Machine auth token caching |
|
|
135
|
+
| `src/bt_cli/pf/commands/__init__.py` | Register all command sub-apps |
|
|
136
|
+
| `src/bt_cli/pf/commands/auth.py` | test, status |
|
|
137
|
+
| `src/bt_cli/pf/commands/users.py` | User management (10 commands) |
|
|
138
|
+
| `src/bt_cli/pf/commands/machines.py` | Machine registration (5 commands) |
|
|
139
|
+
| `src/bt_cli/pf/commands/auditing.py` | Audit log listing |
|
|
140
|
+
| `src/bt_cli/pf/commands/saml.py` | SAML provider CRUD + import |
|
|
141
|
+
| `src/bt_cli/pf/commands/product_access.py` | Product access rules (7 commands) |
|
|
142
|
+
| `src/bt_cli/pf/commands/site_access.py` | Site access rules (6 commands) |
|
|
143
|
+
| `src/bt_cli/pf/commands/tokens.py` | Personal access tokens |
|
|
144
|
+
| `src/bt_cli/pf/commands/mcp_tokens.py` | MCP access tokens |
|
|
145
|
+
| `src/bt_cli/pf/commands/mfa.py` | MFA management |
|
|
146
|
+
| `src/bt_cli/pf/models/__init__.py` | Package marker |
|
|
147
|
+
| `src/bt_cli/pf/models/common.py` | CommandResponse base |
|
|
148
|
+
| `src/bt_cli/pf/models/user.py` | NomineUser |
|
|
149
|
+
| `src/bt_cli/pf/models/machine.py` | Machine |
|
|
150
|
+
| `src/bt_cli/pf/models/audit.py` | AuditRecord |
|
|
151
|
+
| `src/bt_cli/pf/models/saml_provider.py` | SamlProvider |
|
|
152
|
+
| `src/bt_cli/pf/models/access.py` | ProductAccessRule, SiteAccessRule |
|
|
153
|
+
| `src/bt_cli/pf/models/token.py` | PAT, MCP token models |
|
|
154
|
+
| `tests/pf/__init__.py` | Test package |
|
|
155
|
+
| `tests/pf/test_client.py` | Client unit tests |
|
|
156
|
+
| `tests/pf/test_commands.py` | CLI command tests |
|
|
157
|
+
| `src/bt_cli/data/skills/pf/SKILL.md` | Claude Code skill |
|
|
158
|
+
|
|
159
|
+
## Files to Modify (7 files)
|
|
160
|
+
|
|
161
|
+
| File | Change |
|
|
162
|
+
|------|--------|
|
|
163
|
+
| `src/bt_cli/core/auth.py` | Add `MachineAuth` class (~30 lines) |
|
|
164
|
+
| `src/bt_cli/core/config.py` | Add `PFConfig` dataclass + `load_pf_config()` (~40 lines) |
|
|
165
|
+
| `src/bt_cli/core/config_file.py` | Add `"pf"` to PRODUCTS dict (~15 lines) |
|
|
166
|
+
| `src/bt_cli/cli.py` | Lazy loader, registration, whoami, tree, find, help text |
|
|
167
|
+
| `tests/conftest.py` | Add `mock_pf_config`, `pf_env_vars` fixtures |
|
|
168
|
+
| `tests/fixtures/responses.py` | Add `PF_*` mock response constants |
|
|
169
|
+
| `btcli/CLAUDE.md` + `src/bt_cli/data/CLAUDE.md` | Add PF to docs |
|
|
170
|
+
|
|
171
|
+
## Key Design Decisions
|
|
172
|
+
|
|
173
|
+
1. **Auth: New `MachineAuth` strategy** — PF uses `machineId`+`machineSecret` → JWT, not standard OAuth form-encoded flow. Clean separation.
|
|
174
|
+
2. **No path prefix** — PF endpoints are at `/api/*` and `/bt.identity/*` directly off base URL (unlike PWS `/api/public/v3` or Entitle `/public/v1`).
|
|
175
|
+
3. **Pagination** — PF uses `page`/`pageSize` but some endpoints (GET /api/Users) return plain arrays. Client handles both.
|
|
176
|
+
4. **Skip browser/internal endpoints** — `/bt.identity/Authentication/*`, `/api/noauth/*`, `/api/Support/*`, `/Test/*` are not CLI-appropriate.
|
|
177
|
+
5. **Env var prefix: `BT_PF_`** — Consistent with `BT_PWS_`, `BT_PRA_`, etc.
|
|
178
|
+
|
|
179
|
+
## Verification
|
|
180
|
+
|
|
181
|
+
1. `bt pf auth test` — connects with machine credentials
|
|
182
|
+
2. `bt pf users list` / `bt pf users me` — reads user data
|
|
183
|
+
3. `bt pf machines list` — lists registered machines
|
|
184
|
+
4. `bt tree pf` — shows full command tree
|
|
185
|
+
5. `bt whoami` — includes PF status
|
|
186
|
+
6. `pytest tests/pf/ -v` — all unit tests pass
|
|
187
|
+
7. `bt find saml` — finds PF SAML commands
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bt-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.44"
|
|
8
8
|
description = "BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -44,6 +44,7 @@ dependencies = [
|
|
|
44
44
|
"pyyaml>=6.0.0",
|
|
45
45
|
"shellingham>=1.5.0",
|
|
46
46
|
"truststore>=0.8.0;python_version>='3.10'", # Use OS certificate store
|
|
47
|
+
"questionary>=2.0.0",
|
|
47
48
|
]
|
|
48
49
|
|
|
49
50
|
[project.optional-dependencies]
|