bt-cli 0.4.26__tar.gz → 0.4.28__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.26 → bt_cli-0.4.28}/CLAUDE.md +1 -1
- {bt_cli-0.4.26 → bt_cli-0.4.28}/PKG-INFO +1 -1
- {bt_cli-0.4.26 → bt_cli-0.4.28}/pyproject.toml +1 -1
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/client/base.py +12 -4
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/jump_items.py +128 -22
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/client/beyondinsight.py +43 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/attributes.py +90 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.env.example +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/.gitignore +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/README.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/assets/cli-help.png +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/assets/cli-output.png +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/bt-cli.spec +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/bt_entry.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/conftest.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.26 → bt_cli-0.4.28}/tests/pws-smoke-test.sh +0 -0
|
@@ -489,19 +489,27 @@ class PRAClient:
|
|
|
489
489
|
name: Optional[str] = None,
|
|
490
490
|
hostname: Optional[str] = None,
|
|
491
491
|
) -> List[Dict[str, Any]]:
|
|
492
|
-
"""List Protocol Tunnel Jump items (TCP, MSSQL, K8s) with optional filters.
|
|
492
|
+
"""List Protocol Tunnel Jump items (TCP, MSSQL, K8s) with optional filters.
|
|
493
|
+
|
|
494
|
+
Note: tunnel_type is filtered client-side as the API doesn't support it.
|
|
495
|
+
"""
|
|
493
496
|
params = {}
|
|
494
497
|
if jump_group_id:
|
|
495
498
|
params["jump_group_id"] = jump_group_id
|
|
496
|
-
if tunnel_type:
|
|
497
|
-
params["tunnel_type"] = tunnel_type
|
|
498
499
|
if tag:
|
|
499
500
|
params["tag"] = tag
|
|
500
501
|
if name:
|
|
501
502
|
params["name"] = name
|
|
502
503
|
if hostname:
|
|
503
504
|
params["hostname"] = hostname
|
|
504
|
-
|
|
505
|
+
|
|
506
|
+
items = self.get_paginated("/jump-item/protocol-tunnel-jump", params)
|
|
507
|
+
|
|
508
|
+
# Filter tunnel_type client-side (API doesn't support this filter)
|
|
509
|
+
if tunnel_type:
|
|
510
|
+
items = [i for i in items if i.get("tunnel_type") == tunnel_type]
|
|
511
|
+
|
|
512
|
+
return items
|
|
505
513
|
|
|
506
514
|
def get_protocol_tunnel(self, item_id: int) -> Dict[str, Any]:
|
|
507
515
|
"""Get a Protocol Tunnel Jump item."""
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
"""Jump Item commands (shell, RDP, VNC, web, tunnels)."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from fnmatch import fnmatch
|
|
4
|
+
from typing import List, Optional
|
|
4
5
|
|
|
5
6
|
import httpx
|
|
6
7
|
import typer
|
|
7
8
|
|
|
8
9
|
from bt_cli.core.output import OutputFormat, print_table, print_json, print_error, print_success, print_api_error
|
|
9
10
|
|
|
11
|
+
|
|
12
|
+
def _filter_by_pattern(items: List[dict], pattern: str, field: str = "name") -> List[dict]:
|
|
13
|
+
"""Filter items by fnmatch pattern on a field."""
|
|
14
|
+
return [item for item in items if fnmatch(item.get(field, "").lower(), pattern.lower())]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _find_by_name(items: List[dict], name: str) -> Optional[dict]:
|
|
18
|
+
"""Find a single item by exact name match (case-insensitive)."""
|
|
19
|
+
name_lower = name.lower()
|
|
20
|
+
matches = [item for item in items if item.get("name", "").lower() == name_lower]
|
|
21
|
+
if len(matches) == 1:
|
|
22
|
+
return matches[0]
|
|
23
|
+
elif len(matches) > 1:
|
|
24
|
+
raise ValueError(f"Multiple items found with name '{name}'. Use ID instead.")
|
|
25
|
+
return None
|
|
26
|
+
|
|
10
27
|
app = typer.Typer(no_args_is_help=True)
|
|
11
28
|
|
|
12
29
|
# Shell Jump subcommands
|
|
@@ -20,6 +37,7 @@ def list_shell_jumps(
|
|
|
20
37
|
jumpoint_id: Optional[int] = typer.Option(None, "--jumpoint", "-j", help="Filter by Jumpoint"),
|
|
21
38
|
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag (exact match)"),
|
|
22
39
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name (exact match)"),
|
|
40
|
+
name_pattern: Optional[str] = typer.Option(None, "--name-pattern", help="Filter by name pattern (e.g., 'prod-*')"),
|
|
23
41
|
hostname: Optional[str] = typer.Option(None, "--hostname", help="Filter by hostname (exact match)"),
|
|
24
42
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
25
43
|
):
|
|
@@ -36,6 +54,10 @@ def list_shell_jumps(
|
|
|
36
54
|
hostname=hostname,
|
|
37
55
|
)
|
|
38
56
|
|
|
57
|
+
# Client-side pattern filtering
|
|
58
|
+
if name_pattern:
|
|
59
|
+
items = _filter_by_pattern(items, name_pattern)
|
|
60
|
+
|
|
39
61
|
if output == OutputFormat.JSON:
|
|
40
62
|
print_json(items)
|
|
41
63
|
else:
|
|
@@ -206,27 +228,44 @@ def update_shell_jump(
|
|
|
206
228
|
|
|
207
229
|
@shell_app.command("delete")
|
|
208
230
|
def delete_shell_jump(
|
|
209
|
-
item_id: int = typer.Argument(
|
|
231
|
+
item_id: Optional[int] = typer.Argument(None, help="Shell Jump ID"),
|
|
232
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Delete by name instead of ID"),
|
|
210
233
|
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
211
234
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
|
|
212
235
|
):
|
|
213
|
-
"""Delete a Shell Jump item."""
|
|
236
|
+
"""Delete a Shell Jump item by ID or name."""
|
|
214
237
|
from bt_cli.pra.client import get_client
|
|
215
238
|
|
|
239
|
+
if not item_id and not name:
|
|
240
|
+
print_error("Either ITEM_ID or --name is required")
|
|
241
|
+
raise typer.Exit(1)
|
|
242
|
+
|
|
216
243
|
try:
|
|
217
244
|
client = get_client()
|
|
218
|
-
|
|
219
|
-
if
|
|
245
|
+
|
|
246
|
+
# Look up by name if provided
|
|
247
|
+
if name and not item_id:
|
|
248
|
+
items = client.list_shell_jumps(name=name)
|
|
249
|
+
if not items:
|
|
250
|
+
print_error(f"No shell jump found with name '{name}'")
|
|
251
|
+
raise typer.Exit(1)
|
|
252
|
+
if len(items) > 1:
|
|
253
|
+
print_error(f"Multiple shell jumps found with name '{name}'. Use ID instead.")
|
|
254
|
+
raise typer.Exit(1)
|
|
255
|
+
item = items[0]
|
|
256
|
+
item_id = item.get("id")
|
|
257
|
+
else:
|
|
220
258
|
item = client.get_shell_jump(item_id)
|
|
259
|
+
|
|
260
|
+
if dry_run:
|
|
221
261
|
print_success(f"[DRY-RUN] Would delete shell jump: {item.get('name')} (ID: {item_id})")
|
|
222
262
|
return
|
|
223
263
|
|
|
224
264
|
if not force:
|
|
225
|
-
item = client.get_shell_jump(item_id)
|
|
226
265
|
typer.confirm(f"Delete shell jump '{item.get('name')}' (ID: {item_id})?", abort=True)
|
|
227
266
|
|
|
228
267
|
client.delete_shell_jump(item_id)
|
|
229
|
-
print_success(f"Deleted shell jump {item_id}")
|
|
268
|
+
print_success(f"Deleted shell jump '{item.get('name')}' (ID: {item_id})")
|
|
230
269
|
except httpx.HTTPStatusError as e:
|
|
231
270
|
print_api_error(e, "delete shell jump")
|
|
232
271
|
raise typer.Exit(1)
|
|
@@ -249,6 +288,7 @@ def list_rdp_jumps(
|
|
|
249
288
|
jumpoint_id: Optional[int] = typer.Option(None, "--jumpoint", "-j", help="Filter by Jumpoint"),
|
|
250
289
|
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag (exact match)"),
|
|
251
290
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name (exact match)"),
|
|
291
|
+
name_pattern: Optional[str] = typer.Option(None, "--name-pattern", help="Filter by name pattern (e.g., 'prod-*')"),
|
|
252
292
|
hostname: Optional[str] = typer.Option(None, "--hostname", help="Filter by hostname (exact match)"),
|
|
253
293
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
254
294
|
):
|
|
@@ -265,6 +305,9 @@ def list_rdp_jumps(
|
|
|
265
305
|
hostname=hostname,
|
|
266
306
|
)
|
|
267
307
|
|
|
308
|
+
if name_pattern:
|
|
309
|
+
items = _filter_by_pattern(items, name_pattern)
|
|
310
|
+
|
|
268
311
|
if output == OutputFormat.JSON:
|
|
269
312
|
print_json(items)
|
|
270
313
|
else:
|
|
@@ -382,26 +425,43 @@ def create_rdp_jump(
|
|
|
382
425
|
|
|
383
426
|
@rdp_app.command("delete")
|
|
384
427
|
def delete_rdp_jump(
|
|
385
|
-
item_id: int = typer.Argument(
|
|
428
|
+
item_id: Optional[int] = typer.Argument(None, help="RDP Jump ID"),
|
|
429
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Delete by name instead of ID"),
|
|
386
430
|
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
387
431
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
|
|
388
432
|
):
|
|
389
|
-
"""Delete a Remote RDP Jump item."""
|
|
433
|
+
"""Delete a Remote RDP Jump item by ID or name."""
|
|
390
434
|
from bt_cli.pra.client import get_client
|
|
391
435
|
|
|
436
|
+
if not item_id and not name:
|
|
437
|
+
print_error("Either ITEM_ID or --name is required")
|
|
438
|
+
raise typer.Exit(1)
|
|
439
|
+
|
|
392
440
|
try:
|
|
393
441
|
client = get_client()
|
|
394
|
-
|
|
442
|
+
|
|
443
|
+
if name and not item_id:
|
|
444
|
+
items = client.list_rdp_jumps(name=name)
|
|
445
|
+
if not items:
|
|
446
|
+
print_error(f"No RDP jump found with name '{name}'")
|
|
447
|
+
raise typer.Exit(1)
|
|
448
|
+
if len(items) > 1:
|
|
449
|
+
print_error(f"Multiple RDP jumps found with name '{name}'. Use ID instead.")
|
|
450
|
+
raise typer.Exit(1)
|
|
451
|
+
item = items[0]
|
|
452
|
+
item_id = item.get("id")
|
|
453
|
+
else:
|
|
395
454
|
item = client.get_rdp_jump(item_id)
|
|
455
|
+
|
|
456
|
+
if dry_run:
|
|
396
457
|
print_success(f"[DRY-RUN] Would delete RDP jump: {item.get('name')} (ID: {item_id})")
|
|
397
458
|
return
|
|
398
459
|
|
|
399
460
|
if not force:
|
|
400
|
-
item = client.get_rdp_jump(item_id)
|
|
401
461
|
typer.confirm(f"Delete RDP jump '{item.get('name')}' (ID: {item_id})?", abort=True)
|
|
402
462
|
|
|
403
463
|
client.delete_rdp_jump(item_id)
|
|
404
|
-
print_success(f"Deleted RDP jump {item_id}")
|
|
464
|
+
print_success(f"Deleted RDP jump '{item.get('name')}' (ID: {item_id})")
|
|
405
465
|
except httpx.HTTPStatusError as e:
|
|
406
466
|
print_api_error(e, "delete RDP jump")
|
|
407
467
|
raise typer.Exit(1)
|
|
@@ -423,6 +483,7 @@ def list_vnc_jumps(
|
|
|
423
483
|
jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
|
|
424
484
|
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag (exact match)"),
|
|
425
485
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name (exact match)"),
|
|
486
|
+
name_pattern: Optional[str] = typer.Option(None, "--name-pattern", help="Filter by name pattern (e.g., 'prod-*')"),
|
|
426
487
|
hostname: Optional[str] = typer.Option(None, "--hostname", help="Filter by hostname (exact match)"),
|
|
427
488
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
428
489
|
):
|
|
@@ -438,6 +499,9 @@ def list_vnc_jumps(
|
|
|
438
499
|
hostname=hostname,
|
|
439
500
|
)
|
|
440
501
|
|
|
502
|
+
if name_pattern:
|
|
503
|
+
items = _filter_by_pattern(items, name_pattern)
|
|
504
|
+
|
|
441
505
|
if output == OutputFormat.JSON:
|
|
442
506
|
print_json(items)
|
|
443
507
|
else:
|
|
@@ -470,6 +534,7 @@ def list_web_jumps(
|
|
|
470
534
|
jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
|
|
471
535
|
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag (exact match)"),
|
|
472
536
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name (exact match)"),
|
|
537
|
+
name_pattern: Optional[str] = typer.Option(None, "--name-pattern", help="Filter by name pattern (e.g., 'prod-*')"),
|
|
473
538
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
474
539
|
):
|
|
475
540
|
"""List Web Jump items."""
|
|
@@ -483,6 +548,9 @@ def list_web_jumps(
|
|
|
483
548
|
name=name,
|
|
484
549
|
)
|
|
485
550
|
|
|
551
|
+
if name_pattern:
|
|
552
|
+
items = _filter_by_pattern(items, name_pattern)
|
|
553
|
+
|
|
486
554
|
if output == OutputFormat.JSON:
|
|
487
555
|
print_json(items)
|
|
488
556
|
else:
|
|
@@ -545,26 +613,43 @@ def get_web_jump(
|
|
|
545
613
|
|
|
546
614
|
@web_app.command("delete")
|
|
547
615
|
def delete_web_jump(
|
|
548
|
-
item_id: int = typer.Argument(
|
|
616
|
+
item_id: Optional[int] = typer.Argument(None, help="Web Jump ID"),
|
|
617
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Delete by name instead of ID"),
|
|
549
618
|
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
550
619
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
|
|
551
620
|
):
|
|
552
|
-
"""Delete a Web Jump item."""
|
|
621
|
+
"""Delete a Web Jump item by ID or name."""
|
|
553
622
|
from bt_cli.pra.client import get_client
|
|
554
623
|
|
|
624
|
+
if not item_id and not name:
|
|
625
|
+
print_error("Either ITEM_ID or --name is required")
|
|
626
|
+
raise typer.Exit(1)
|
|
627
|
+
|
|
555
628
|
try:
|
|
556
629
|
client = get_client()
|
|
557
|
-
|
|
630
|
+
|
|
631
|
+
if name and not item_id:
|
|
632
|
+
items = client.list_web_jumps(name=name)
|
|
633
|
+
if not items:
|
|
634
|
+
print_error(f"No web jump found with name '{name}'")
|
|
635
|
+
raise typer.Exit(1)
|
|
636
|
+
if len(items) > 1:
|
|
637
|
+
print_error(f"Multiple web jumps found with name '{name}'. Use ID instead.")
|
|
638
|
+
raise typer.Exit(1)
|
|
639
|
+
item = items[0]
|
|
640
|
+
item_id = item.get("id")
|
|
641
|
+
else:
|
|
558
642
|
item = client.get_web_jump(item_id)
|
|
643
|
+
|
|
644
|
+
if dry_run:
|
|
559
645
|
print_success(f"[DRY-RUN] Would delete web jump: {item.get('name')} (ID: {item_id})")
|
|
560
646
|
return
|
|
561
647
|
|
|
562
648
|
if not force:
|
|
563
|
-
item = client.get_web_jump(item_id)
|
|
564
649
|
typer.confirm(f"Delete web jump '{item.get('name')}' (ID: {item_id})?", abort=True)
|
|
565
650
|
|
|
566
651
|
client.delete_web_jump(item_id)
|
|
567
|
-
print_success(f"Deleted web jump {item_id}")
|
|
652
|
+
print_success(f"Deleted web jump '{item.get('name')}' (ID: {item_id})")
|
|
568
653
|
except httpx.HTTPStatusError as e:
|
|
569
654
|
print_api_error(e, "delete web jump")
|
|
570
655
|
raise typer.Exit(1)
|
|
@@ -587,6 +672,7 @@ def list_protocol_tunnels(
|
|
|
587
672
|
tunnel_type: Optional[str] = typer.Option(None, "--type", help="Filter by type: tcp, mssql, psql, mysql, k8s"),
|
|
588
673
|
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag (exact match)"),
|
|
589
674
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name (exact match)"),
|
|
675
|
+
name_pattern: Optional[str] = typer.Option(None, "--name-pattern", help="Filter by name pattern (e.g., 'prod-*')"),
|
|
590
676
|
hostname: Optional[str] = typer.Option(None, "--hostname", help="Filter by hostname (exact match)"),
|
|
591
677
|
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
592
678
|
):
|
|
@@ -603,6 +689,9 @@ def list_protocol_tunnels(
|
|
|
603
689
|
hostname=hostname,
|
|
604
690
|
)
|
|
605
691
|
|
|
692
|
+
if name_pattern:
|
|
693
|
+
items = _filter_by_pattern(items, name_pattern)
|
|
694
|
+
|
|
606
695
|
if output == OutputFormat.JSON:
|
|
607
696
|
print_json(items)
|
|
608
697
|
else:
|
|
@@ -741,26 +830,43 @@ def create_protocol_tunnel(
|
|
|
741
830
|
|
|
742
831
|
@tunnel_app.command("delete")
|
|
743
832
|
def delete_protocol_tunnel(
|
|
744
|
-
item_id: int = typer.Argument(
|
|
833
|
+
item_id: Optional[int] = typer.Argument(None, help="Protocol Tunnel ID"),
|
|
834
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Delete by name instead of ID"),
|
|
745
835
|
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
746
836
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be deleted without deleting"),
|
|
747
837
|
):
|
|
748
|
-
"""Delete a Protocol Tunnel Jump item."""
|
|
838
|
+
"""Delete a Protocol Tunnel Jump item by ID or name."""
|
|
749
839
|
from bt_cli.pra.client import get_client
|
|
750
840
|
|
|
841
|
+
if not item_id and not name:
|
|
842
|
+
print_error("Either ITEM_ID or --name is required")
|
|
843
|
+
raise typer.Exit(1)
|
|
844
|
+
|
|
751
845
|
try:
|
|
752
846
|
client = get_client()
|
|
753
|
-
|
|
847
|
+
|
|
848
|
+
if name and not item_id:
|
|
849
|
+
items = client.list_protocol_tunnels(name=name)
|
|
850
|
+
if not items:
|
|
851
|
+
print_error(f"No protocol tunnel found with name '{name}'")
|
|
852
|
+
raise typer.Exit(1)
|
|
853
|
+
if len(items) > 1:
|
|
854
|
+
print_error(f"Multiple protocol tunnels found with name '{name}'. Use ID instead.")
|
|
855
|
+
raise typer.Exit(1)
|
|
856
|
+
item = items[0]
|
|
857
|
+
item_id = item.get("id")
|
|
858
|
+
else:
|
|
754
859
|
item = client.get_protocol_tunnel(item_id)
|
|
860
|
+
|
|
861
|
+
if dry_run:
|
|
755
862
|
print_success(f"[DRY-RUN] Would delete protocol tunnel: {item.get('name')} (ID: {item_id})")
|
|
756
863
|
return
|
|
757
864
|
|
|
758
865
|
if not force:
|
|
759
|
-
item = client.get_protocol_tunnel(item_id)
|
|
760
866
|
typer.confirm(f"Delete protocol tunnel '{item.get('name')}' (ID: {item_id})?", abort=True)
|
|
761
867
|
|
|
762
868
|
client.delete_protocol_tunnel(item_id)
|
|
763
|
-
print_success(f"Deleted protocol tunnel {item_id}")
|
|
869
|
+
print_success(f"Deleted protocol tunnel '{item.get('name')}' (ID: {item_id})")
|
|
764
870
|
except httpx.HTTPStatusError as e:
|
|
765
871
|
print_api_error(e, "delete protocol tunnel")
|
|
766
872
|
raise typer.Exit(1)
|
|
@@ -910,3 +910,46 @@ class BeyondInsightMixin:
|
|
|
910
910
|
attribute_id: Attribute ID to remove
|
|
911
911
|
"""
|
|
912
912
|
self.delete(f"/ManagedSystems/{managed_system_id}/Attributes/{attribute_id}")
|
|
913
|
+
|
|
914
|
+
def get_managed_account_attributes(
|
|
915
|
+
self: "PasswordSafeClient",
|
|
916
|
+
account_id: int,
|
|
917
|
+
) -> list[dict[str, Any]]:
|
|
918
|
+
"""Get attributes for a managed account.
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
account_id: Managed account ID
|
|
922
|
+
|
|
923
|
+
Returns:
|
|
924
|
+
List of attribute objects assigned to this account
|
|
925
|
+
"""
|
|
926
|
+
return self.get(f"/ManagedAccounts/{account_id}/Attributes")
|
|
927
|
+
|
|
928
|
+
def assign_managed_account_attribute(
|
|
929
|
+
self: "PasswordSafeClient",
|
|
930
|
+
account_id: int,
|
|
931
|
+
attribute_id: int,
|
|
932
|
+
) -> dict[str, Any]:
|
|
933
|
+
"""Assign an attribute to a managed account.
|
|
934
|
+
|
|
935
|
+
Args:
|
|
936
|
+
account_id: Managed account ID
|
|
937
|
+
attribute_id: Attribute ID to assign
|
|
938
|
+
|
|
939
|
+
Returns:
|
|
940
|
+
The created assignment
|
|
941
|
+
"""
|
|
942
|
+
return self.post(f"/ManagedAccounts/{account_id}/Attributes/{attribute_id}")
|
|
943
|
+
|
|
944
|
+
def remove_managed_account_attribute(
|
|
945
|
+
self: "PasswordSafeClient",
|
|
946
|
+
account_id: int,
|
|
947
|
+
attribute_id: int,
|
|
948
|
+
) -> None:
|
|
949
|
+
"""Remove an attribute from a managed account.
|
|
950
|
+
|
|
951
|
+
Args:
|
|
952
|
+
account_id: Managed account ID
|
|
953
|
+
attribute_id: Attribute ID to remove
|
|
954
|
+
"""
|
|
955
|
+
self.delete(f"/ManagedAccounts/{account_id}/Attributes/{attribute_id}")
|
|
@@ -174,3 +174,93 @@ def remove_attribute(
|
|
|
174
174
|
except Exception as e:
|
|
175
175
|
print_api_error(e, "remove attribute")
|
|
176
176
|
raise typer.Exit(1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Account attribute commands
|
|
180
|
+
@app.command("show-for-account")
|
|
181
|
+
def show_account_attributes(
|
|
182
|
+
account_id: int = typer.Argument(..., help="Managed Account ID"),
|
|
183
|
+
output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
|
|
184
|
+
):
|
|
185
|
+
"""Show attributes assigned to a managed account."""
|
|
186
|
+
from bt_cli.pws.client import get_client
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
with get_client() as client:
|
|
190
|
+
attributes = client.get_managed_account_attributes(account_id)
|
|
191
|
+
|
|
192
|
+
if output == OutputFormat.JSON:
|
|
193
|
+
print_json(attributes)
|
|
194
|
+
else:
|
|
195
|
+
if not attributes:
|
|
196
|
+
print_error(f"No attributes assigned to managed account {account_id}")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
columns = [
|
|
200
|
+
("ID", "AttributeID"),
|
|
201
|
+
("Name", "AttributeName"),
|
|
202
|
+
("Type", "AttributeType"),
|
|
203
|
+
]
|
|
204
|
+
print_table(attributes, columns, title=f"Attributes for Managed Account {account_id}")
|
|
205
|
+
except httpx.HTTPStatusError as e:
|
|
206
|
+
print_api_error(e, "show account attributes")
|
|
207
|
+
raise typer.Exit(1)
|
|
208
|
+
except httpx.RequestError as e:
|
|
209
|
+
print_api_error(e, "show account attributes")
|
|
210
|
+
raise typer.Exit(1)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
print_api_error(e, "show account attributes")
|
|
213
|
+
raise typer.Exit(1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.command("assign-to-account")
|
|
217
|
+
def assign_account_attribute(
|
|
218
|
+
account_id: int = typer.Argument(..., help="Managed Account ID"),
|
|
219
|
+
attribute_id: int = typer.Argument(..., help="Attribute ID to assign"),
|
|
220
|
+
):
|
|
221
|
+
"""Assign an attribute to a managed account."""
|
|
222
|
+
from bt_cli.pws.client import get_client
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
with get_client() as client:
|
|
226
|
+
client.assign_managed_account_attribute(account_id, attribute_id)
|
|
227
|
+
print_success(f"Assigned attribute {attribute_id} to managed account {account_id}")
|
|
228
|
+
except httpx.HTTPStatusError as e:
|
|
229
|
+
print_api_error(e, "assign attribute to account")
|
|
230
|
+
raise typer.Exit(1)
|
|
231
|
+
except httpx.RequestError as e:
|
|
232
|
+
print_api_error(e, "assign attribute to account")
|
|
233
|
+
raise typer.Exit(1)
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print_api_error(e, "assign attribute to account")
|
|
236
|
+
raise typer.Exit(1)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@app.command("remove-from-account")
|
|
240
|
+
def remove_account_attribute(
|
|
241
|
+
account_id: int = typer.Argument(..., help="Managed Account ID"),
|
|
242
|
+
attribute_id: int = typer.Argument(..., help="Attribute ID to remove"),
|
|
243
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
244
|
+
):
|
|
245
|
+
"""Remove an attribute from a managed account."""
|
|
246
|
+
from bt_cli.pws.client import get_client
|
|
247
|
+
|
|
248
|
+
if not force:
|
|
249
|
+
typer.confirm(
|
|
250
|
+
f"Remove attribute {attribute_id} from managed account {account_id}?",
|
|
251
|
+
abort=True
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
with get_client() as client:
|
|
256
|
+
client.remove_managed_account_attribute(account_id, attribute_id)
|
|
257
|
+
print_success(f"Removed attribute {attribute_id} from managed account {account_id}")
|
|
258
|
+
except httpx.HTTPStatusError as e:
|
|
259
|
+
print_api_error(e, "remove attribute from account")
|
|
260
|
+
raise typer.Exit(1)
|
|
261
|
+
except httpx.RequestError as e:
|
|
262
|
+
print_api_error(e, "remove attribute from account")
|
|
263
|
+
raise typer.Exit(1)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
print_api_error(e, "remove attribute from account")
|
|
266
|
+
raise typer.Exit(1)
|
|
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
|
|
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
|
|
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
|