bt-cli 0.4.31__tar.gz → 0.4.32__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.31 → bt_cli-0.4.32}/CLAUDE.md +1 -1
- {bt_cli-0.4.31 → bt_cli-0.4.32}/PKG-INFO +1 -1
- {bt_cli-0.4.31 → bt_cli-0.4.32}/pyproject.toml +1 -1
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/client/beyondinsight.py +36 -29
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/client/passwordsafe.py +24 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/search.py +40 -35
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/users.py +20 -6
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.env.example +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/.gitignore +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/README.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/assets/cli-help.png +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/assets/cli-output.png +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/bt-cli.spec +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/bt_entry.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/conftest.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.31 → bt_cli-0.4.32}/tests/pws-smoke-test.sh +0 -0
|
@@ -160,23 +160,24 @@ class BeyondInsightMixin:
|
|
|
160
160
|
search_term: str,
|
|
161
161
|
limit: Optional[int] = None,
|
|
162
162
|
) -> list[dict[str, Any]]:
|
|
163
|
-
"""Search for assets.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
"""Search for assets by substring across name, DNS, IP, domain.
|
|
164
|
+
|
|
165
|
+
POST /Assets/Search only supports exact-match on specific fields
|
|
166
|
+
(AssetName, DnsName, IPAddress, etc.), so we fetch and filter
|
|
167
|
+
client-side for a fuzzy search experience.
|
|
168
|
+
"""
|
|
169
|
+
assets = self.list_assets()
|
|
170
|
+
term = search_term.lower()
|
|
171
|
+
matched = [
|
|
172
|
+
a for a in assets
|
|
173
|
+
if term in (a.get("AssetName", "") or "").lower()
|
|
174
|
+
or term in (a.get("DnsName", "") or "").lower()
|
|
175
|
+
or term in (a.get("IPAddress", "") or "").lower()
|
|
176
|
+
or term in (a.get("DomainName", "") or "").lower()
|
|
177
|
+
]
|
|
178
|
+
if limit is not None:
|
|
179
|
+
matched = matched[:limit]
|
|
180
|
+
return matched
|
|
180
181
|
|
|
181
182
|
# =========================================================================
|
|
182
183
|
# Managed Systems
|
|
@@ -190,25 +191,31 @@ class BeyondInsightMixin:
|
|
|
190
191
|
) -> list[dict[str, Any]]:
|
|
191
192
|
"""List managed systems.
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
limit: Maximum number of results
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
List of managed system objects
|
|
194
|
+
/ManagedSystems only supports an exact-match ``name`` query param,
|
|
195
|
+
so ``search`` is applied client-side as a substring match across
|
|
196
|
+
SystemName / IPAddress / Description for parity with other commands.
|
|
200
197
|
"""
|
|
201
|
-
params = {}
|
|
202
|
-
if search:
|
|
203
|
-
params["search"] = search
|
|
198
|
+
params: dict[str, Any] = {}
|
|
204
199
|
if limit:
|
|
205
200
|
params["limit"] = limit
|
|
206
201
|
|
|
207
202
|
if workgroup_id:
|
|
208
|
-
|
|
203
|
+
systems = self.paginate(
|
|
209
204
|
f"/Workgroups/{workgroup_id}/ManagedSystems", params=params
|
|
210
205
|
)
|
|
211
|
-
|
|
206
|
+
else:
|
|
207
|
+
systems = self.paginate("/ManagedSystems", params=params)
|
|
208
|
+
|
|
209
|
+
if search:
|
|
210
|
+
term = search.lower()
|
|
211
|
+
systems = [
|
|
212
|
+
s for s in systems
|
|
213
|
+
if term in (s.get("SystemName", "") or "").lower()
|
|
214
|
+
or term in (s.get("IPAddress", "") or "").lower()
|
|
215
|
+
or term in (s.get("Description", "") or "").lower()
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
return systems
|
|
212
219
|
|
|
213
220
|
def get_managed_system(
|
|
214
221
|
self: "PasswordSafeClient", system_id: int
|
|
@@ -929,6 +929,30 @@ class PasswordSafeMixin:
|
|
|
929
929
|
"""
|
|
930
930
|
return self.get(f"/Users/{user_id}")
|
|
931
931
|
|
|
932
|
+
def get_user_by_username(
|
|
933
|
+
self: "PasswordSafeClient", username: str
|
|
934
|
+
) -> Optional[dict[str, Any]]:
|
|
935
|
+
"""Look up a single user by exact username (case-insensitive).
|
|
936
|
+
|
|
937
|
+
Uses the /Users?username= server-side lookup (single round trip,
|
|
938
|
+
no full-list fetch). Returns None on 404. Username match is
|
|
939
|
+
case-insensitive but exact — no substring matching.
|
|
940
|
+
"""
|
|
941
|
+
import httpx as _httpx
|
|
942
|
+
|
|
943
|
+
try:
|
|
944
|
+
result = self.get("/Users", params={"username": username})
|
|
945
|
+
except _httpx.HTTPStatusError as e:
|
|
946
|
+
if e.response.status_code == 404:
|
|
947
|
+
return None
|
|
948
|
+
raise
|
|
949
|
+
|
|
950
|
+
if isinstance(result, dict) and "UserID" in result:
|
|
951
|
+
return result
|
|
952
|
+
if isinstance(result, list):
|
|
953
|
+
return result[0] if result else None
|
|
954
|
+
return None
|
|
955
|
+
|
|
932
956
|
# =========================================================================
|
|
933
957
|
# Roles
|
|
934
958
|
# =========================================================================
|
|
@@ -42,6 +42,7 @@ def search(
|
|
|
42
42
|
"managed_accounts": [],
|
|
43
43
|
"managed_systems": [],
|
|
44
44
|
"assets": [],
|
|
45
|
+
"users": [],
|
|
45
46
|
"secrets": [],
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -51,7 +52,6 @@ def search(
|
|
|
51
52
|
|
|
52
53
|
console.print(f"[dim]Searching PWS for '{query}'...[/dim]\n")
|
|
53
54
|
|
|
54
|
-
# Search functional accounts
|
|
55
55
|
try:
|
|
56
56
|
functional = client.list_functional_accounts()
|
|
57
57
|
results["functional_accounts"] = [
|
|
@@ -63,52 +63,35 @@ def search(
|
|
|
63
63
|
except Exception as e:
|
|
64
64
|
console.print(f"[dim]Functional accounts: {e}[/dim]")
|
|
65
65
|
|
|
66
|
-
# Search managed accounts
|
|
67
66
|
try:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
a for a in all_accounts
|
|
75
|
-
if query_lower in str(a.get("AccountName", "")).lower()
|
|
76
|
-
or query_lower in str(a.get("SystemName", "")).lower()
|
|
77
|
-
]
|
|
78
|
-
results["managed_accounts"] = accounts[:10]
|
|
67
|
+
all_accounts = client.list_managed_accounts(limit=500)
|
|
68
|
+
results["managed_accounts"] = [
|
|
69
|
+
a for a in all_accounts
|
|
70
|
+
if query_lower in str(a.get("AccountName", "")).lower()
|
|
71
|
+
or query_lower in str(a.get("SystemName", "")).lower()
|
|
72
|
+
][:10]
|
|
79
73
|
except Exception as e:
|
|
80
74
|
console.print(f"[dim]Managed accounts: {e}[/dim]")
|
|
81
75
|
|
|
82
|
-
# Search managed systems
|
|
83
76
|
try:
|
|
84
|
-
|
|
85
|
-
if not systems:
|
|
86
|
-
all_systems = client.list_managed_systems(limit=200)
|
|
87
|
-
systems = [
|
|
88
|
-
s for s in all_systems
|
|
89
|
-
if query_lower in str(s.get("SystemName", "")).lower()
|
|
90
|
-
or query_lower in str(s.get("IPAddress", "")).lower()
|
|
91
|
-
or query_lower in str(s.get("Description", "")).lower()
|
|
92
|
-
]
|
|
93
|
-
results["managed_systems"] = systems[:10]
|
|
77
|
+
results["managed_systems"] = client.list_managed_systems(search=query)[:10]
|
|
94
78
|
except Exception as e:
|
|
95
79
|
console.print(f"[dim]Managed systems: {e}[/dim]")
|
|
96
80
|
|
|
97
|
-
# Search assets
|
|
98
81
|
try:
|
|
99
|
-
assets = client.search_assets(query, limit=
|
|
100
|
-
if not assets:
|
|
101
|
-
all_assets = client.list_assets(limit=200)
|
|
102
|
-
assets = [
|
|
103
|
-
a for a in all_assets
|
|
104
|
-
if query_lower in str(a.get("AssetName", "")).lower()
|
|
105
|
-
or query_lower in str(a.get("IPAddress", "")).lower()
|
|
106
|
-
or query_lower in str(a.get("DnsName", "")).lower()
|
|
107
|
-
]
|
|
108
|
-
results["assets"] = assets[:10]
|
|
82
|
+
results["assets"] = client.search_assets(query, limit=10)
|
|
109
83
|
except Exception as e:
|
|
110
84
|
console.print(f"[dim]Assets: {e}[/dim]")
|
|
111
85
|
|
|
86
|
+
try:
|
|
87
|
+
exact_user = client.get_user_by_username(query)
|
|
88
|
+
if exact_user:
|
|
89
|
+
results["users"] = [exact_user]
|
|
90
|
+
else:
|
|
91
|
+
results["users"] = client.list_users(search=query, limit=10)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
console.print(f"[dim]Users: {e}[/dim]")
|
|
94
|
+
|
|
112
95
|
# Search secrets (if accessible)
|
|
113
96
|
try:
|
|
114
97
|
# Get all safes and search folders/secrets
|
|
@@ -222,6 +205,28 @@ def search(
|
|
|
222
205
|
console.print(table)
|
|
223
206
|
console.print()
|
|
224
207
|
|
|
208
|
+
# Users
|
|
209
|
+
if results["users"]:
|
|
210
|
+
total_found += len(results["users"])
|
|
211
|
+
table = Table(title="Users")
|
|
212
|
+
table.add_column("ID", style="cyan")
|
|
213
|
+
table.add_column("Username", style="green")
|
|
214
|
+
table.add_column("Name", style="yellow")
|
|
215
|
+
table.add_column("Email", style="blue")
|
|
216
|
+
|
|
217
|
+
for u in results["users"]:
|
|
218
|
+
first = u.get("FirstName") or ""
|
|
219
|
+
last = u.get("LastName") or ""
|
|
220
|
+
display_name = f"{first} {last}".strip() or "-"
|
|
221
|
+
table.add_row(
|
|
222
|
+
str(u.get("UserID", "")),
|
|
223
|
+
u.get("UserName", ""),
|
|
224
|
+
display_name,
|
|
225
|
+
u.get("EmailAddress") or "-",
|
|
226
|
+
)
|
|
227
|
+
console.print(table)
|
|
228
|
+
console.print()
|
|
229
|
+
|
|
225
230
|
# Secrets
|
|
226
231
|
if results["secrets"]:
|
|
227
232
|
total_found += len(results["secrets"])
|
|
@@ -228,25 +228,39 @@ def list_users(
|
|
|
228
228
|
|
|
229
229
|
@app.command("get")
|
|
230
230
|
def get_user(
|
|
231
|
-
|
|
231
|
+
user: str = typer.Argument(..., help="User ID (int) or exact username"),
|
|
232
232
|
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
233
233
|
) -> None:
|
|
234
|
-
"""Get a user by ID.
|
|
234
|
+
"""Get a user by ID or exact username (case-insensitive).
|
|
235
|
+
|
|
236
|
+
Username lookup uses /Users?username= — one round trip, no full list
|
|
237
|
+
fetch. Username match is exact (no substring); use ``list -s`` for
|
|
238
|
+
fuzzy search.
|
|
235
239
|
|
|
236
240
|
Examples:
|
|
237
|
-
bt pws users get 1 #
|
|
241
|
+
bt pws users get 1 # Lookup by ID
|
|
242
|
+
bt pws users get Administrator # Lookup by exact username
|
|
243
|
+
bt pws users get dave@example.com # If username is an email
|
|
238
244
|
bt pws users get 4 -o json # JSON output
|
|
239
245
|
"""
|
|
240
246
|
try:
|
|
241
247
|
with get_client() as client:
|
|
242
248
|
client.authenticate()
|
|
243
|
-
user
|
|
249
|
+
if user.isdigit():
|
|
250
|
+
user_obj = client.get_user(int(user))
|
|
251
|
+
else:
|
|
252
|
+
user_obj = client.get_user_by_username(user)
|
|
253
|
+
if user_obj is None:
|
|
254
|
+
console.print(f"[yellow]No user found with username '{user}'.[/yellow]")
|
|
255
|
+
raise typer.Exit(1)
|
|
244
256
|
|
|
245
257
|
if output == "json":
|
|
246
|
-
console.print_json(json.dumps(
|
|
258
|
+
console.print_json(json.dumps(user_obj, default=str))
|
|
247
259
|
else:
|
|
248
|
-
print_user_detail(
|
|
260
|
+
print_user_detail(user_obj)
|
|
249
261
|
|
|
262
|
+
except typer.Exit:
|
|
263
|
+
raise
|
|
250
264
|
except httpx.HTTPStatusError as e:
|
|
251
265
|
print_api_error(e, "get user")
|
|
252
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
|