bt-cli 0.4.46__tar.gz → 0.4.48__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.46 → bt_cli-0.4.48}/CLAUDE.md +1 -1
- {bt_cli-0.4.46 → bt_cli-0.4.48}/PKG-INFO +3 -3
- {bt_cli-0.4.46 → bt_cli-0.4.48}/README.md +2 -2
- {bt_cli-0.4.46 → bt_cli-0.4.48}/pyproject.toml +1 -1
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/secrets/SKILL.md +34 -11
- bt_cli-0.4.48/src/bt_cli/secrets/commands/_hints.py +30 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/dynamic.py +48 -12
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/folders.py +8 -2
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/static.py +8 -2
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/test_commands.py +89 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.env.example +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/.gitignore +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/assets/cli-help.png +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/assets/cli-output.png +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/bt-cli.spec +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/bt_entry.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/epml-clients-server-side-filters-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/pf-implementation-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/pf_onboard.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/requests.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/clients.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/group_policies.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/group_policy.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/client/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/client/base.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/integrations.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/leases.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/models/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/conftest.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws-smoke-test.sh +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/__init__.py +0 -0
- {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/test_client.py +0 -0
|
@@ -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.4.
|
|
3
|
+
BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.48**
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bt-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.48
|
|
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
|
|
@@ -121,8 +121,8 @@ export BT_SECRETS_API_VERSION=2026-02-16 # default (doc page is
|
|
|
121
121
|
Common commands:
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
|
-
bt secrets folders list -p lab # filter server-side
|
|
125
|
-
bt secrets dynamic list -p lab
|
|
124
|
+
bt secrets folders list -p lab # filter server-side (recursive default)
|
|
125
|
+
bt secrets dynamic list -p lab # all dynamic-secret configs under lab/
|
|
126
126
|
bt secrets dynamic generate lab/aws/ec2-readonly # mint shell `export AWS_*` lines
|
|
127
127
|
eval $(bt secrets dynamic generate lab/aws/ec2-readonly) # load into current shell
|
|
128
128
|
bt secrets leases list lab/aws/ec2-readonly # who has active leases
|
|
@@ -74,8 +74,8 @@ export BT_SECRETS_API_VERSION=2026-02-16 # default (doc page is
|
|
|
74
74
|
Common commands:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
bt secrets folders list -p lab # filter server-side
|
|
78
|
-
bt secrets dynamic list -p lab
|
|
77
|
+
bt secrets folders list -p lab # filter server-side (recursive default)
|
|
78
|
+
bt secrets dynamic list -p lab # all dynamic-secret configs under lab/
|
|
79
79
|
bt secrets dynamic generate lab/aws/ec2-readonly # mint shell `export AWS_*` lines
|
|
80
80
|
eval $(bt secrets dynamic generate lab/aws/ec2-readonly) # load into current shell
|
|
81
81
|
bt secrets leases list lab/aws/ec2-readonly # who has active leases
|
|
@@ -56,10 +56,13 @@ bt secrets folders get lab/aws # folder=lab, name=aws
|
|
|
56
56
|
|
|
57
57
|
## Folders
|
|
58
58
|
|
|
59
|
+
`list` is **recursive by default** (mirrors how users browse the UI tree).
|
|
60
|
+
Pass `--shallow` / `-S` for direct children only.
|
|
61
|
+
|
|
59
62
|
```bash
|
|
60
|
-
bt secrets folders list # all
|
|
61
|
-
bt secrets folders list -p lab #
|
|
62
|
-
bt secrets folders list -p lab
|
|
63
|
+
bt secrets folders list # all folders, anywhere in the site
|
|
64
|
+
bt secrets folders list -p lab # everything under lab/ recursively
|
|
65
|
+
bt secrets folders list -p lab --shallow # only direct children of lab/
|
|
63
66
|
bt secrets folders get lab/aws # metadata
|
|
64
67
|
bt secrets folders create lab/aws/staging
|
|
65
68
|
bt secrets folders delete lab/aws/staging
|
|
@@ -72,7 +75,9 @@ Versioned key/value secrets. The body is a JSON object — pass it inline via
|
|
|
72
75
|
`--data` or from disk via `--data-file`.
|
|
73
76
|
|
|
74
77
|
```bash
|
|
75
|
-
bt secrets static list
|
|
78
|
+
bt secrets static list # everything in the site (recursive default)
|
|
79
|
+
bt secrets static list -p lab # everything under lab/
|
|
80
|
+
bt secrets static list -p lab --shallow # direct children of lab/ only
|
|
76
81
|
bt secrets static get lab/db-pass # default JSON output (values are structured)
|
|
77
82
|
bt secrets static get lab/db-pass --version 2 # specific historical version
|
|
78
83
|
bt secrets static create lab/db-pass -d '{"password":"hunter2","username":"admin"}'
|
|
@@ -85,17 +90,31 @@ bt secrets static delete lab/db-pass --force
|
|
|
85
90
|
Configurations that mint short-lived credentials on demand (AWS STS today).
|
|
86
91
|
|
|
87
92
|
```bash
|
|
88
|
-
bt secrets dynamic list -
|
|
93
|
+
bt secrets dynamic list # all dynamic-secret configs in the site
|
|
94
|
+
bt secrets dynamic list -p lab # everything under lab/
|
|
95
|
+
bt secrets dynamic list -p lab --shallow # only items directly at lab/ (usually none — they nest)
|
|
89
96
|
bt secrets dynamic get lab/aws/ec2-readonly
|
|
90
97
|
|
|
91
98
|
# Mint fresh credentials — default prints shell `export AWS_*` lines.
|
|
92
99
|
bt secrets dynamic generate lab/aws/ec2-readonly
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
|
|
101
|
+
# Apply to the current session (a child process can't mutate its parent's env,
|
|
102
|
+
# so the CLI emits shell-native syntax that you eval into your shell):
|
|
103
|
+
|
|
104
|
+
# bash / zsh:
|
|
105
|
+
eval $(bt secrets dynamic generate lab/aws/ec2-readonly)
|
|
106
|
+
|
|
107
|
+
# PowerShell:
|
|
108
|
+
bt secrets dynamic generate lab/aws/ec2-readonly --format powershell | iex
|
|
109
|
+
|
|
110
|
+
# cmd.exe:
|
|
111
|
+
for /f "delims=" %i in ('bt secrets dynamic generate lab/aws/ec2-readonly --format cmd') do %i
|
|
112
|
+
|
|
113
|
+
aws sts get-caller-identity # verify the creds are active
|
|
95
114
|
|
|
96
115
|
# Other formats:
|
|
97
116
|
bt secrets dynamic generate lab/aws/ec2-readonly --format json # full payload w/ leaseId
|
|
98
|
-
bt secrets dynamic generate lab/aws/ec2-readonly --format env # KEY=value
|
|
117
|
+
bt secrets dynamic generate lab/aws/ec2-readonly --format env # bare KEY=value
|
|
99
118
|
bt secrets dynamic generate lab/aws/ec2-readonly --format dotenv # KEY="value" for .env files
|
|
100
119
|
```
|
|
101
120
|
|
|
@@ -188,11 +207,15 @@ bt secrets leases list lab/aws/full-admin -o json \
|
|
|
188
207
|
### Browse the tree
|
|
189
208
|
|
|
190
209
|
```bash
|
|
191
|
-
bt secrets folders list -p lab
|
|
192
|
-
bt secrets static list -p lab
|
|
193
|
-
bt secrets dynamic list -p lab
|
|
210
|
+
bt secrets folders list -p lab
|
|
211
|
+
bt secrets static list -p lab
|
|
212
|
+
bt secrets dynamic list -p lab
|
|
194
213
|
```
|
|
195
214
|
|
|
215
|
+
`list` is recursive by default; the API otherwise only returns items
|
|
216
|
+
directly at the requested path (this trips up newcomers — passing
|
|
217
|
+
`-p lab` returns 0 in shallow mode because the items live in `lab/aws`).
|
|
218
|
+
|
|
196
219
|
## API Notes
|
|
197
220
|
|
|
198
221
|
- Base URL: `https://api.beyondtrust.io/site/<site-id>/secrets`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Shared empty-result hint for `bt secrets <X> list` commands.
|
|
2
|
+
|
|
3
|
+
When server-side filtering returns 0 rows it's almost always one of:
|
|
4
|
+
* --path points one level too high (items live in subfolders)
|
|
5
|
+
* --shallow was passed but the user thought it was recursive
|
|
6
|
+
* the path simply doesn't exist
|
|
7
|
+
|
|
8
|
+
The default "No results found" output gives no clue which. This helper
|
|
9
|
+
prints a one-liner that nudges the user toward the next thing to try.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from bt_cli.core.output import console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def empty_hint(path: Optional[str], recursive: bool, kind: str) -> None:
|
|
18
|
+
"""Print a tip when a list call returns no rows."""
|
|
19
|
+
if recursive:
|
|
20
|
+
where = f" under {path!r}" if path else ""
|
|
21
|
+
console.print(f"[dim]No {kind} found{where}.[/dim]")
|
|
22
|
+
console.print(
|
|
23
|
+
"[dim]Tip: pass --shallow for direct children only, or check the path.[/dim]"
|
|
24
|
+
)
|
|
25
|
+
else:
|
|
26
|
+
at = f" directly at {path!r}" if path else " at the root"
|
|
27
|
+
console.print(f"[dim]No {kind}{at} (--shallow mode).[/dim]")
|
|
28
|
+
console.print(
|
|
29
|
+
"[dim]Tip: drop --shallow (default is recursive) to search subfolders.[/dim]"
|
|
30
|
+
)
|
|
@@ -20,6 +20,7 @@ from bt_cli.core.output import (
|
|
|
20
20
|
print_table,
|
|
21
21
|
)
|
|
22
22
|
from bt_cli.secrets.client import split_path
|
|
23
|
+
from bt_cli.secrets.commands._hints import empty_hint
|
|
23
24
|
|
|
24
25
|
app = typer.Typer(no_args_is_help=True, help="Dynamic secrets (e.g. AWS STS, mint on demand)")
|
|
25
26
|
|
|
@@ -27,10 +28,12 @@ app = typer.Typer(no_args_is_help=True, help="Dynamic secrets (e.g. AWS STS, min
|
|
|
27
28
|
class GenerateFormat(str, Enum):
|
|
28
29
|
"""Output formats for `dynamic generate`."""
|
|
29
30
|
|
|
30
|
-
EXPORT = "export"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
EXPORT = "export" # default — bash/zsh `export AWS_*` lines
|
|
32
|
+
POWERSHELL = "powershell" # PowerShell `$env:AWS_* = "..."` lines
|
|
33
|
+
CMD = "cmd" # Windows cmd.exe `set AWS_*=...` lines
|
|
34
|
+
ENV = "env" # bare KEY=value lines (no `export`)
|
|
35
|
+
JSON = "json" # raw API payload
|
|
36
|
+
DOTENV = "dotenv" # KEY="value" lines suitable for .env files
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
def _dynamic_row(item: dict) -> dict:
|
|
@@ -50,6 +53,13 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
|
|
|
50
53
|
(``accessKeyId``, ``secretAccessKey``, ``sessionToken``). For shell
|
|
51
54
|
consumption we map those to the standard ``AWS_*`` env-var names that
|
|
52
55
|
boto3, aws-cli, and the EC2 SDK all read.
|
|
56
|
+
|
|
57
|
+
A subprocess can't mutate its parent shell's environment, so each
|
|
58
|
+
format is designed to be piped into the shell's eval mechanism:
|
|
59
|
+
|
|
60
|
+
* EXPORT → `eval $(bt secrets dynamic generate <path>)`
|
|
61
|
+
* POWERSHELL → `bt secrets dynamic generate <path> --format powershell | iex`
|
|
62
|
+
* CMD → cmd.exe needs a temp .bat or a `for /f` loop — see SKILL.md
|
|
53
63
|
"""
|
|
54
64
|
aws_map = [
|
|
55
65
|
("AWS_ACCESS_KEY_ID", "accessKeyId"),
|
|
@@ -62,6 +72,16 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
|
|
|
62
72
|
value = secret[payload_key]
|
|
63
73
|
if fmt is GenerateFormat.EXPORT:
|
|
64
74
|
lines.append(f"export {env_name}={value}")
|
|
75
|
+
elif fmt is GenerateFormat.POWERSHELL:
|
|
76
|
+
# `Invoke-Expression` parses these as statements that set the
|
|
77
|
+
# process env. Quote the value so PowerShell doesn't try to
|
|
78
|
+
# interpret embedded characters.
|
|
79
|
+
lines.append(f'$env:{env_name} = "{value}"')
|
|
80
|
+
elif fmt is GenerateFormat.CMD:
|
|
81
|
+
# `set` in cmd.exe doesn't quote and doesn't tolerate spaces
|
|
82
|
+
# around `=`. Values from STS are alphanumeric-plus-/+=_-, so
|
|
83
|
+
# bare assignment is safe.
|
|
84
|
+
lines.append(f"set {env_name}={value}")
|
|
65
85
|
elif fmt is GenerateFormat.ENV:
|
|
66
86
|
lines.append(f"{env_name}={value}")
|
|
67
87
|
elif fmt is GenerateFormat.DOTENV:
|
|
@@ -69,19 +89,27 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
|
|
|
69
89
|
if not lines:
|
|
70
90
|
return ""
|
|
71
91
|
if "expiration" in secret:
|
|
72
|
-
# Trailing comment is
|
|
73
|
-
|
|
92
|
+
# Trailing comment is informational — all four script-y formats
|
|
93
|
+
# tolerate `#` (bash/PowerShell) or treat the line as a label-style
|
|
94
|
+
# noop for cmd's set, which we replace with REM below.
|
|
95
|
+
if fmt is GenerateFormat.CMD:
|
|
96
|
+
lines.append(f"REM expires: {secret['expiration']}")
|
|
97
|
+
else:
|
|
98
|
+
lines.append(f"# expires: {secret['expiration']}")
|
|
74
99
|
return "\n".join(lines)
|
|
75
100
|
|
|
76
101
|
|
|
77
102
|
@app.command("list")
|
|
78
103
|
def list_dynamic(
|
|
79
104
|
path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter"),
|
|
80
|
-
recursive: bool = typer.Option(
|
|
105
|
+
recursive: bool = typer.Option(
|
|
106
|
+
True, "--recursive/--shallow", "-r/-S",
|
|
107
|
+
help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
|
|
108
|
+
),
|
|
81
109
|
deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted configs"),
|
|
82
110
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
|
|
83
111
|
) -> None:
|
|
84
|
-
"""List dynamic-secret configs
|
|
112
|
+
"""List dynamic-secret configs. Recursive by default — pass --shallow for direct children only."""
|
|
85
113
|
from bt_cli.secrets.client import get_client
|
|
86
114
|
|
|
87
115
|
try:
|
|
@@ -90,6 +118,8 @@ def list_dynamic(
|
|
|
90
118
|
|
|
91
119
|
if output == OutputFormat.JSON:
|
|
92
120
|
print_json(data)
|
|
121
|
+
elif not data:
|
|
122
|
+
empty_hint(path, recursive, "dynamic secrets")
|
|
93
123
|
else:
|
|
94
124
|
print_table(
|
|
95
125
|
[_dynamic_row(r) for r in data],
|
|
@@ -144,15 +174,21 @@ def generate(
|
|
|
144
174
|
full_path: str = typer.Argument(..., help="Full dynamic-secret path"),
|
|
145
175
|
format_: GenerateFormat = typer.Option(
|
|
146
176
|
GenerateFormat.EXPORT, "--format", "-F",
|
|
147
|
-
help="Output format: export | env | json | dotenv",
|
|
177
|
+
help="Output format: export | powershell | cmd | env | json | dotenv",
|
|
148
178
|
case_sensitive=False,
|
|
149
179
|
),
|
|
150
180
|
) -> None:
|
|
151
181
|
"""Mint fresh credentials from a dynamic-secret config.
|
|
152
182
|
|
|
153
|
-
Default ``--format export`` prints shell ``export AWS_*`` lines
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
Default ``--format export`` prints shell ``export AWS_*`` lines.
|
|
184
|
+
|
|
185
|
+
\b
|
|
186
|
+
Apply in your current session:
|
|
187
|
+
bash / zsh: eval $(bt secrets dynamic generate <path>)
|
|
188
|
+
PowerShell: bt secrets dynamic generate <path> --format powershell | iex
|
|
189
|
+
cmd.exe: for /f "delims=" %i in ('bt secrets dynamic generate <path> --format cmd') do %i
|
|
190
|
+
|
|
191
|
+
Use ``--format json`` to see the full payload (lease id, expiration).
|
|
156
192
|
|
|
157
193
|
Heads up: every call mints real STS credentials and shows up in the
|
|
158
194
|
audit trail. Don't loop this in development.
|
|
@@ -14,6 +14,7 @@ from bt_cli.core.output import (
|
|
|
14
14
|
print_table,
|
|
15
15
|
)
|
|
16
16
|
from bt_cli.secrets.client import split_path
|
|
17
|
+
from bt_cli.secrets.commands._hints import empty_hint
|
|
17
18
|
|
|
18
19
|
app = typer.Typer(no_args_is_help=True, help="Folders for organizing secrets")
|
|
19
20
|
|
|
@@ -30,11 +31,14 @@ def _folder_row(item: dict) -> dict:
|
|
|
30
31
|
@app.command("list")
|
|
31
32
|
def list_folders(
|
|
32
33
|
path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter folders"),
|
|
33
|
-
recursive: bool = typer.Option(
|
|
34
|
+
recursive: bool = typer.Option(
|
|
35
|
+
True, "--recursive/--shallow", "-r/-S",
|
|
36
|
+
help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
|
|
37
|
+
),
|
|
34
38
|
deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted folders"),
|
|
35
39
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
|
|
36
40
|
) -> None:
|
|
37
|
-
"""List folders
|
|
41
|
+
"""List folders. Recursive by default — pass --shallow for direct children only."""
|
|
38
42
|
from bt_cli.secrets.client import get_client
|
|
39
43
|
|
|
40
44
|
try:
|
|
@@ -43,6 +47,8 @@ def list_folders(
|
|
|
43
47
|
|
|
44
48
|
if output == OutputFormat.JSON:
|
|
45
49
|
print_json(data)
|
|
50
|
+
elif not data:
|
|
51
|
+
empty_hint(path, recursive, "folders")
|
|
46
52
|
else:
|
|
47
53
|
print_table(
|
|
48
54
|
[_folder_row(r) for r in data],
|
|
@@ -16,6 +16,7 @@ from bt_cli.core.output import (
|
|
|
16
16
|
print_table,
|
|
17
17
|
)
|
|
18
18
|
from bt_cli.secrets.client import split_path
|
|
19
|
+
from bt_cli.secrets.commands._hints import empty_hint
|
|
19
20
|
|
|
20
21
|
app = typer.Typer(no_args_is_help=True, help="Static (key/value, versioned) secrets")
|
|
21
22
|
|
|
@@ -34,12 +35,15 @@ def _static_row(item: dict) -> dict:
|
|
|
34
35
|
@app.command("list")
|
|
35
36
|
def list_static(
|
|
36
37
|
path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter (server-side)"),
|
|
37
|
-
recursive: bool = typer.Option(
|
|
38
|
+
recursive: bool = typer.Option(
|
|
39
|
+
True, "--recursive/--shallow", "-r/-S",
|
|
40
|
+
help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
|
|
41
|
+
),
|
|
38
42
|
versions: bool = typer.Option(False, "--versions", help="Include version history"),
|
|
39
43
|
deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted secrets"),
|
|
40
44
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
|
|
41
45
|
) -> None:
|
|
42
|
-
"""List static secrets
|
|
46
|
+
"""List static secrets. Recursive by default — pass --shallow for direct children only."""
|
|
43
47
|
from bt_cli.secrets.client import get_client
|
|
44
48
|
|
|
45
49
|
try:
|
|
@@ -50,6 +54,8 @@ def list_static(
|
|
|
50
54
|
|
|
51
55
|
if output == OutputFormat.JSON:
|
|
52
56
|
print_json(data)
|
|
57
|
+
elif not data:
|
|
58
|
+
empty_hint(path, recursive, "static secrets")
|
|
53
59
|
else:
|
|
54
60
|
print_table(
|
|
55
61
|
[_static_row(r) for r in data],
|
|
@@ -133,6 +133,49 @@ class TestDynamic:
|
|
|
133
133
|
assert "ec2-readonly" in result.stdout
|
|
134
134
|
assert route.called
|
|
135
135
|
|
|
136
|
+
@respx.mock
|
|
137
|
+
def test_dynamic_list_recursive_is_default(self, env_overrides):
|
|
138
|
+
"""No -r / --recursive flag → still sends recursive=true (new default)."""
|
|
139
|
+
route = respx.get(_url("/dynamic")).mock(
|
|
140
|
+
return_value=httpx.Response(200, json={"data": []})
|
|
141
|
+
)
|
|
142
|
+
result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab"])
|
|
143
|
+
assert result.exit_code == 0
|
|
144
|
+
assert "recursive=true" in str(route.calls[0].request.url)
|
|
145
|
+
|
|
146
|
+
@respx.mock
|
|
147
|
+
def test_dynamic_list_shallow_opts_out(self, env_overrides):
|
|
148
|
+
"""--shallow turns off recursion."""
|
|
149
|
+
route = respx.get(_url("/dynamic")).mock(
|
|
150
|
+
return_value=httpx.Response(200, json={"data": []})
|
|
151
|
+
)
|
|
152
|
+
result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab", "--shallow"])
|
|
153
|
+
assert result.exit_code == 0
|
|
154
|
+
assert "recursive=true" not in str(route.calls[0].request.url)
|
|
155
|
+
|
|
156
|
+
@respx.mock
|
|
157
|
+
def test_dynamic_list_empty_recursive_hint(self, env_overrides):
|
|
158
|
+
"""Empty + recursive → hint nudges toward path check."""
|
|
159
|
+
respx.get(_url("/dynamic")).mock(
|
|
160
|
+
return_value=httpx.Response(200, json={"data": []})
|
|
161
|
+
)
|
|
162
|
+
result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "nowhere"])
|
|
163
|
+
assert result.exit_code == 0
|
|
164
|
+
assert "No dynamic secrets" in result.stdout
|
|
165
|
+
assert "'nowhere'" in result.stdout
|
|
166
|
+
assert "--shallow" in result.stdout # hint mentions the alternative
|
|
167
|
+
|
|
168
|
+
@respx.mock
|
|
169
|
+
def test_dynamic_list_empty_shallow_hint(self, env_overrides):
|
|
170
|
+
"""Empty + --shallow → hint nudges toward dropping --shallow."""
|
|
171
|
+
respx.get(_url("/dynamic")).mock(
|
|
172
|
+
return_value=httpx.Response(200, json={"data": []})
|
|
173
|
+
)
|
|
174
|
+
result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab", "--shallow"])
|
|
175
|
+
assert result.exit_code == 0
|
|
176
|
+
assert "--shallow mode" in result.stdout
|
|
177
|
+
assert "drop --shallow" in result.stdout
|
|
178
|
+
|
|
136
179
|
@respx.mock
|
|
137
180
|
def test_dynamic_generate_export_shape(self, env_overrides):
|
|
138
181
|
respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
|
|
@@ -183,6 +226,52 @@ class TestDynamic:
|
|
|
183
226
|
assert "AWS_ACCESS_KEY_ID=AKIA-FAKE" in result.stdout
|
|
184
227
|
assert "export" not in result.stdout.split("\n")[0]
|
|
185
228
|
|
|
229
|
+
@respx.mock
|
|
230
|
+
def test_dynamic_generate_powershell_shape(self, env_overrides):
|
|
231
|
+
"""PowerShell format: $env:VAR = "val" — pipe to Invoke-Expression."""
|
|
232
|
+
respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
|
|
233
|
+
return_value=httpx.Response(201, json={"secret": {
|
|
234
|
+
"accessKeyId": "ASIA-FAKE",
|
|
235
|
+
"secretAccessKey": "SAK-FAKE",
|
|
236
|
+
"sessionToken": "TOK-FAKE",
|
|
237
|
+
"expiration": "2026-01-01T00:00:00Z",
|
|
238
|
+
}})
|
|
239
|
+
)
|
|
240
|
+
result = CliRunner().invoke(
|
|
241
|
+
secrets_app,
|
|
242
|
+
["dynamic", "generate", "lab/aws/ec2-readonly", "--format", "powershell"],
|
|
243
|
+
)
|
|
244
|
+
assert result.exit_code == 0
|
|
245
|
+
assert '$env:AWS_ACCESS_KEY_ID = "ASIA-FAKE"' in result.stdout
|
|
246
|
+
assert '$env:AWS_SECRET_ACCESS_KEY = "SAK-FAKE"' in result.stdout
|
|
247
|
+
assert '$env:AWS_SESSION_TOKEN = "TOK-FAKE"' in result.stdout
|
|
248
|
+
# Comment must use `#` (valid PowerShell line comment)
|
|
249
|
+
assert "# expires:" in result.stdout
|
|
250
|
+
# Must NOT contain bash `export` syntax
|
|
251
|
+
assert "export AWS_" not in result.stdout
|
|
252
|
+
|
|
253
|
+
@respx.mock
|
|
254
|
+
def test_dynamic_generate_cmd_shape(self, env_overrides):
|
|
255
|
+
"""cmd.exe format: set VAR=val — for `for /f` extraction."""
|
|
256
|
+
respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
|
|
257
|
+
return_value=httpx.Response(201, json={"secret": {
|
|
258
|
+
"accessKeyId": "ASIA-FAKE",
|
|
259
|
+
"secretAccessKey": "SAK-FAKE",
|
|
260
|
+
"sessionToken": "TOK-FAKE",
|
|
261
|
+
"expiration": "2026-01-01T00:00:00Z",
|
|
262
|
+
}})
|
|
263
|
+
)
|
|
264
|
+
result = CliRunner().invoke(
|
|
265
|
+
secrets_app,
|
|
266
|
+
["dynamic", "generate", "lab/aws/ec2-readonly", "--format", "cmd"],
|
|
267
|
+
)
|
|
268
|
+
assert result.exit_code == 0
|
|
269
|
+
assert "set AWS_ACCESS_KEY_ID=ASIA-FAKE" in result.stdout
|
|
270
|
+
assert "set AWS_SECRET_ACCESS_KEY=SAK-FAKE" in result.stdout
|
|
271
|
+
# cmd.exe uses REM, not #, for comments
|
|
272
|
+
assert "REM expires:" in result.stdout
|
|
273
|
+
assert "# expires" not in result.stdout
|
|
274
|
+
|
|
186
275
|
|
|
187
276
|
class TestLeases:
|
|
188
277
|
@respx.mock
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|