bt-cli 0.4.12__py3-none-any.whl → 0.4.14__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """BeyondTrust Unified Admin CLI."""
2
2
 
3
- __version__ = "0.4.12"
3
+ __version__ = "0.4.13"
bt_cli/data/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
- # BT-Admin CLI
1
+ # BT-CLI
2
2
 
3
3
  BeyondTrust Platform CLI for Password Safe, Entitle, PRA, and EPM Windows.
4
4
 
5
5
  ## Setup
6
6
 
7
7
  ```bash
8
- cd /home/admin/entitl-sko/bt-cli
8
+ # From project root
9
9
  source .venv/bin/activate && source .env
10
10
  bt whoami # Test all connections
11
11
  ```
@@ -62,16 +62,22 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
62
62
  - PWS assets: Must create via workgroup endpoint
63
63
  - ECM integration: PWS system name must match PRA jump item name
64
64
 
65
- ## Key Reference IDs
65
+ ## Functional vs Managed Accounts
66
66
 
67
- | Resource | ID | Notes |
68
- |----------|-----|-------|
69
- | PWS Workgroup AWS | 3 | |
70
- | PWS Workgroup Datacenter | 2 | |
71
- | PWS Functional Account | 7 | SSH key auth |
72
- | PRA Jumpoint AWS | 3 | |
73
- | PRA Jumpoint Datacenter | 2 | |
74
- | PRA ec2-admin SSH CA | 31 | For EC2 provisioning |
67
+ **Functional accounts** (`bt pws functional`) - Service accounts used BY Password Safe to connect to systems for auto-management (password rotation, discovery). One functional account can manage many systems.
68
+
69
+ **Managed accounts** (`bt pws accounts`) - User accounts ON systems that Password Safe manages (stores/rotates passwords for). These are what users check out.
70
+
71
+ ```bash
72
+ # List functional accounts to find the right one for your platform
73
+ bt pws functional list
74
+
75
+ # List workgroups to find the right one
76
+ bt pws workgroups list
77
+
78
+ # Use functional account when onboarding a system
79
+ bt pws quick onboard -n "server" -i "10.0.1.50" -w <WORKGROUP_ID> -f <FUNC_ACCT_ID> -e "sudo"
80
+ ```
75
81
 
76
82
  ## Project Structure
77
83
 
@@ -22,9 +22,10 @@ Before executing any destructive operation:
22
22
 
23
23
  ## Setup
24
24
 
25
+ The CLI should be installed and configured. Test with:
26
+
25
27
  ```bash
26
- cd /home/admin/entitl-sko/bt-cli
27
- source .venv/bin/activate && source .env
28
+ bt whoami # Verify all products are connected
28
29
  ```
29
30
 
30
31
  ## Test All Connections
@@ -47,27 +48,32 @@ bt quick pasm-search web-server -o json
47
48
  ### PASM Onboard (PWS + PRA)
48
49
  Onboard a host to both Password Safe and PRA in one command.
49
50
 
51
+ **First, discover your environment IDs** (see "Discover Environment IDs" below).
52
+
50
53
  ```bash
51
- # Linux SSH host
52
- bt quick pasm-onboard -n "my-server" -i "10.0.1.50" -w 3 -j 3 -g 24
54
+ # Linux SSH host (use IDs from your environment)
55
+ bt quick pasm-onboard -n "my-server" -i "10.0.1.50" \
56
+ -w <workgroup_id> -j <jumpoint_id> -g <jump_group_id>
53
57
 
54
58
  # Full options
55
59
  bt quick pasm-onboard \
56
60
  --name "web-01" \
57
61
  --ip "10.0.1.100" \
58
- --workgroup 3 \
59
- --jumpoint 3 \
60
- --jump-group 24 \
61
- --functional-account 7 \
62
+ --workgroup <workgroup_id> \
63
+ --jumpoint <jumpoint_id> \
64
+ --jump-group <jump_group_id> \
65
+ --functional-account <func_acct_id> \
62
66
  --elevation "sudo" \
63
- --pra-username "ec2-admin"
67
+ --pra-username "admin"
64
68
 
65
69
  # Windows RDP host
66
- bt quick pasm-onboard -n "win-srv" -i "10.0.2.10" -w 2 -p 1 -j 3 -g 31 --jump-type rdp --port 3389
70
+ bt quick pasm-onboard -n "win-srv" -i "10.0.2.10" \
71
+ -w <workgroup_id> -p <platform_id> -j <jumpoint_id> -g <jump_group_id> \
72
+ --jump-type rdp --port 3389
67
73
 
68
74
  # Skip one product
69
- bt quick pasm-onboard -n "pra-only" -i "10.0.1.5" -j 3 -g 24 --skip-pws
70
- bt quick pasm-onboard -n "pws-only" -i "10.0.1.6" -w 3 --skip-pra
75
+ bt quick pasm-onboard -n "pra-only" -i "10.0.1.5" -j <jumpoint_id> -g <jump_group_id> --skip-pws
76
+ bt quick pasm-onboard -n "pws-only" -i "10.0.1.6" -w <workgroup_id> --skip-pra
71
77
  ```
72
78
 
73
79
  ### PASM Offboard
@@ -78,20 +84,24 @@ bt quick pasm-offboard -n "my-server"
78
84
  bt quick pasm-offboard -n "web-01" --force
79
85
  ```
80
86
 
81
- ## Environment Reference IDs
82
-
83
- | Resource | ID | Notes |
84
- |----------|-----|-------|
85
- | PWS Workgroup (AWS) | 3 | AWS_Account Workgroup |
86
- | PWS Workgroup (Datacenter) | 2 | Datacenter_West |
87
- | PWS Functional Account | 7 | pws-functional SSH key |
88
- | PWS Platform Linux | 2 | |
89
- | PWS Platform Windows | 1 | |
90
- | PRA Jumpoint (AWS) | 3 | AWS Account |
91
- | PRA Jumpoint (Datacenter) | 2 | Data Center 01 |
92
- | PRA Vault Account (ec2-admin) | 31 | SSH CA for EC2 |
93
- | Entitle PRA Integration | bb2a3c79-02a9-45d9-be7f-f209d97cb1d7 | |
94
- | Entitle Customer Access | 22f4960f-b2ee-435f-9fa2-b82baeca06b2 | Virtual integration |
87
+ ## Discover Environment IDs
88
+
89
+ Use these commands to find the IDs for your environment:
90
+
91
+ ```bash
92
+ # PWS resources
93
+ bt pws workgroups list # Find workgroup IDs
94
+ bt pws functional list # Find functional account IDs
95
+ bt pws platforms list # Find platform IDs
96
+
97
+ # PRA resources
98
+ bt pra jumpoint list # Find jumpoint IDs
99
+ bt pra jump-groups list # Find jump group IDs
100
+ bt pra vault groups list # Find vault account group IDs
101
+
102
+ # Entitle resources
103
+ bt entitle integrations list # Find integration IDs
104
+ ```
95
105
 
96
106
  ## Product-Specific Skills
97
107
 
@@ -25,10 +25,10 @@ bt entitle integrations get <integration_id>
25
25
  bt entitle resources list --integration <integration_id>
26
26
  bt entitle resources get <resource_id>
27
27
 
28
- # Virtual integration resources
28
+ # Virtual integration resources (use integration ID from `bt entitle integrations list`)
29
29
  bt entitle resources create-virtual \
30
30
  --name "Customer-05 (Bing7)" \
31
- --integration 22f4960f-b2ee-435f-9fa2-b82baeca06b2 \
31
+ --integration <virtual_integration_id> \
32
32
  --source-role-id <role_id> \
33
33
  --role-name "Start Session"
34
34
  bt entitle resources delete <resource_id>
@@ -101,18 +101,21 @@ bt entitle policies get <policy_id>
101
101
  - Or manually trigger sync in Entitle UI: Integrations → PRA → Sync Now
102
102
 
103
103
  ```bash
104
- # 1. Trigger PRA sync in Entitle UI (or wait for hourly sync)
104
+ # 1. Find your PRA integration ID
105
+ bt entitle integrations list | grep -i pra
105
106
 
106
- # 2. Find the new PRA resource
107
- bt entitle resources list --integration bb2a3c79-02a9-45d9-be7f-f209d97cb1d7
107
+ # 2. Trigger PRA sync in Entitle UI (or wait for hourly sync)
108
108
 
109
- # 3. Get "Start Sessions Only" role ID
109
+ # 3. Find the new PRA resource
110
+ bt entitle resources list --integration <pra_integration_id>
111
+
112
+ # 4. Get "Start Sessions Only" role ID
110
113
  bt entitle roles list --resource <pra_resource_id>
111
114
 
112
- # 4. Add to Customer Access virtual integration
115
+ # 5. Add to virtual integration (find ID with `bt entitle integrations list`)
113
116
  bt entitle resources create-virtual \
114
117
  --name "Customer-05" \
115
- --integration 22f4960f-b2ee-435f-9fa2-b82baeca06b2 \
118
+ --integration <virtual_integration_id> \
116
119
  --source-role-id <role_id> \
117
120
  --role-name "Start Session"
118
121
  ```
@@ -123,13 +126,21 @@ bt entitle resources create-virtual \
123
126
  bt entitle permissions list --user "user@example.com" -o json
124
127
  ```
125
128
 
126
- ## Key IDs
129
+ ## Discover IDs
130
+
131
+ ```bash
132
+ # Find integration IDs
133
+ bt entitle integrations list
134
+
135
+ # Find workflow IDs
136
+ bt entitle workflows list
137
+
138
+ # Find resource IDs within an integration
139
+ bt entitle resources list --integration <integration_id>
127
140
 
128
- | Resource | ID |
129
- |----------|-----|
130
- | PRA Integration | bb2a3c79-02a9-45d9-be7f-f209d97cb1d7 |
131
- | Customer Access (Virtual) | 22f4960f-b2ee-435f-9fa2-b82baeca06b2 |
132
- | AWS SandBox | (check `bt entitle integrations list`) |
141
+ # Find role IDs for a resource
142
+ bt entitle roles list --resource <resource_id>
143
+ ```
133
144
 
134
145
  ## Integrations Available
135
146
 
@@ -118,22 +118,21 @@ bt epmw audits requests list
118
118
  bt epmw audits requests get <audit_id>
119
119
  ```
120
120
 
121
- ## Key Groups
122
-
123
- | ID | Name | Computers |
124
- |----|------|-----------|
125
- | 042267ec-... | Servers - Datacenter1 | CorpMem01, CorpMem02 |
126
- | 1c8f6310-... | Workstations - Datacenter1 | CorpWS01, CorpWS02 |
127
- | 67ca23ad-... | Engineering | BenC-Skynet |
128
-
129
- ## Key Computers
130
-
131
- | Host | Domain | Group | Status |
132
- |------|--------|-------|--------|
133
- | CorpMem01 | nexusdyn.corp | Servers | Connected |
134
- | CorpMem02 | nexusdyn.corp | Servers | Connected |
135
- | CorpWS01 | nexusdyn.corp | Workstations | Connected |
136
- | CorpWS02 | nexusdyn.corp | Workstations | Connected |
121
+ ## Discover IDs
122
+
123
+ ```bash
124
+ # Find group IDs
125
+ bt epmw groups list
126
+
127
+ # Find computer IDs
128
+ bt epmw computers list
129
+
130
+ # Find policy IDs
131
+ bt epmw policies list
132
+
133
+ # Find user IDs
134
+ bt epmw users list
135
+ ```
137
136
 
138
137
  ## API Notes
139
138
 
@@ -35,28 +35,28 @@ bt pra quick search admin -o json
35
35
 
36
36
  ```bash
37
37
  bt pra jump-items shell list
38
- bt pra jump-items shell get 55
38
+ bt pra jump-items shell get <jump_item_id>
39
39
  bt pra jump-items shell create \
40
40
  --name "web-server-01" \
41
41
  --hostname "10.0.1.50" \
42
- --jumpoint 3 \
43
- --jump-group 24 \
42
+ --jumpoint <jumpoint_id> \
43
+ --jump-group <jump_group_id> \
44
44
  --protocol ssh \
45
45
  --port 22 \
46
- --username "ec2-admin"
47
- bt pra jump-items shell delete 55
46
+ --username "admin"
47
+ bt pra jump-items shell delete <jump_item_id>
48
48
  ```
49
49
 
50
50
  ### RDP Jump
51
51
 
52
52
  ```bash
53
53
  bt pra jump-items rdp list
54
- bt pra jump-items rdp get 1
54
+ bt pra jump-items rdp get <jump_item_id>
55
55
  bt pra jump-items rdp create \
56
56
  --name "win-server-01" \
57
57
  --hostname "10.0.2.10" \
58
- --jumpoint 3 \
59
- --jump-group 31 \
58
+ --jumpoint <jumpoint_id> \
59
+ --jump-group <jump_group_id> \
60
60
  --port 3389
61
61
  ```
62
62
 
@@ -67,8 +67,8 @@ bt pra jump-items tunnel list
67
67
  bt pra jump-items tunnel create \
68
68
  --name "production-k8s" \
69
69
  --hostname "k8s-api.example.com" \
70
- --jumpoint 3 \
71
- --jump-group 24 \
70
+ --jumpoint <jumpoint_id> \
71
+ --jump-group <jump_group_id> \
72
72
  --type k8s \
73
73
  --url "https://k8s-api.example.com:6443" \
74
74
  --ca-cert "$(cat /path/to/ca.crt)"
@@ -78,25 +78,25 @@ bt pra jump-items tunnel create \
78
78
 
79
79
  ```bash
80
80
  bt pra jump-groups list
81
- bt pra jump-groups get 24
81
+ bt pra jump-groups get <group_id>
82
82
  bt pra jump-groups create \
83
- --name "Customer-05 (Bing7)" \
84
- --code-name bing7 \
85
- --comments "Demo customer"
86
- bt pra jump-groups delete 30
83
+ --name "Customer-05" \
84
+ --code-name customer05 \
85
+ --comments "New customer environment"
86
+ bt pra jump-groups delete <group_id>
87
87
  ```
88
88
 
89
89
  ## Vault Accounts
90
90
 
91
91
  ```bash
92
92
  bt pra vault accounts list
93
- bt pra vault accounts get 6
94
- bt pra vault accounts checkout 6
95
- bt pra vault accounts checkin 6
96
- bt pra vault accounts rotate 6
93
+ bt pra vault accounts get <account_id>
94
+ bt pra vault accounts checkout <account_id>
95
+ bt pra vault accounts checkin <account_id>
96
+ bt pra vault accounts rotate <account_id>
97
97
 
98
98
  # SSH CA - get public key for provisioning
99
- bt pra vault accounts get-public-key 31
99
+ bt pra vault accounts get-public-key <ssh_ca_account_id>
100
100
  ```
101
101
 
102
102
  ## SSH CA Authentication
@@ -104,21 +104,18 @@ bt pra vault accounts get-public-key 31
104
104
  PRA supports SSH CA for ephemeral access - no static keys on hosts.
105
105
 
106
106
  ```bash
107
+ # Find SSH CA vault accounts
108
+ bt pra vault accounts list | grep -i ssh
109
+
107
110
  # Get CA public key (ready for authorized_keys)
108
- bt pra vault accounts get-public-key 31
111
+ bt pra vault accounts get-public-key <ssh_ca_account_id>
109
112
  # Output: cert-authority ssh-rsa AAAAB3NzaC1yc2E...
110
113
 
111
114
  # Provision EC2 with SSH CA
112
- PRA_CA_KEY=$(bt pra vault accounts get-public-key 31)
115
+ PRA_CA_KEY=$(bt pra vault accounts get-public-key <ssh_ca_account_id>)
113
116
  # Embed in user-data script for EC2
114
117
  ```
115
118
 
116
- **SSH CA Vault Accounts:**
117
- | ID | Name | Username |
118
- |----|------|----------|
119
- | 3 | Ephemeral Admin SSH CA | admin-ephemeral |
120
- | 31 | ec2-admin | ec2-admin |
121
-
122
119
  ## CSV Import/Export
123
120
 
124
121
  ```bash
@@ -128,17 +125,21 @@ bt pra import jump-items --file jump-items.csv --dry-run
128
125
  bt pra import jump-items --file jump-items.csv
129
126
  ```
130
127
 
131
- ## Key IDs
132
-
133
- | Resource | ID |
134
- |----------|-----|
135
- | Jumpoint: Data Center 01 | 2 |
136
- | Jumpoint: AWS Account | 3 |
137
- | Jump Group: Datacenter 01 (West) | 1 |
138
- | Jump Group: Customer-01 (Axion) | 24 |
139
- | Jump Group: Customer-02 (Meridian) | 25 |
140
- | Jump Group: Cloud Containers | 26 |
141
- | Vault: ec2-admin (SSH CA) | 31 |
128
+ ## Discover IDs
129
+
130
+ ```bash
131
+ # Find jumpoint IDs
132
+ bt pra jumpoint list
133
+
134
+ # Find jump group IDs
135
+ bt pra jump-groups list
136
+
137
+ # Find vault account IDs
138
+ bt pra vault accounts list
139
+
140
+ # Find vault account group IDs
141
+ bt pra vault groups list
142
+ ```
142
143
 
143
144
  ## API Notes
144
145
 
@@ -39,8 +39,9 @@ bt pws quick search root -o json
39
39
  bt pws quick rotate -s "axion-finapp-01" -a "root"
40
40
 
41
41
  # Onboard system (asset + system + account)
42
- bt pws quick onboard -n "my-server" -i "10.0.1.50" -w 3
43
- bt pws quick onboard -n "web-01" -i "10.0.1.100" -w 3 -f 7 -e "sudo"
42
+ # First find your workgroup and functional account IDs (see "Discover IDs" below)
43
+ bt pws quick onboard -n "my-server" -i "10.0.1.50" -w <workgroup_id>
44
+ bt pws quick onboard -n "web-01" -i "10.0.1.100" -w <workgroup_id> -f <func_acct_id> -e "sudo"
44
45
 
45
46
  # Offboard system
46
47
  bt pws quick offboard -s "my-server"
@@ -52,15 +53,15 @@ bt pws quick offboard -s "web-01" --force
52
53
  ```bash
53
54
  # List systems
54
55
  bt pws systems list
55
- bt pws systems list --workgroup 3
56
+ bt pws systems list --workgroup <workgroup_id>
56
57
  bt pws systems list -o json
57
58
 
58
59
  # Get system details
59
- bt pws systems get 22
60
+ bt pws systems get <system_id>
60
61
 
61
62
  # List accounts on a system
62
- bt pws accounts list --system 22
63
- bt pws accounts get 45
63
+ bt pws accounts list --system <system_id>
64
+ bt pws accounts get <account_id>
64
65
  ```
65
66
 
66
67
  ## Secrets Safe
@@ -128,13 +129,13 @@ bt pws quick user-entitlements dave
128
129
 
129
130
  ```bash
130
131
  # 1. Find system
131
- bt pws systems list -o json | jq '.[] | select(.SystemName=="axion-finapp-01")'
132
+ bt pws systems list -o json | jq '.[] | select(.SystemName=="my-server")'
132
133
 
133
134
  # 2. Find account
134
- bt pws accounts list --system 22
135
+ bt pws accounts list --system <system_id>
135
136
 
136
137
  # 3. Checkout
137
- bt pws credentials checkout --system "axion-finapp-01" --account "root"
138
+ bt pws credentials checkout --system "my-server" --account "root"
138
139
 
139
140
  # 4. Get password
140
141
  bt pws credentials show <request_id>
@@ -158,18 +159,18 @@ bt pws import systems --file systems.csv
158
159
  bt pws import secrets --file secrets.csv
159
160
  ```
160
161
 
161
- ## Key IDs
162
+ ## Discover IDs
162
163
 
163
- | Resource | ID |
164
- |----------|-----|
165
- | Workgroup: Default | 1 |
166
- | Workgroup: Datacenter_West | 2 |
167
- | Workgroup: AWS_Account | 3 |
168
- | Platform: Windows | 1 |
169
- | Platform: Linux | 2 |
170
- | Platform: MySQL | 10 |
171
- | Platform: PostgreSQL | 79 |
172
- | Functional Account | 7 |
164
+ ```bash
165
+ # Find workgroup IDs
166
+ bt pws workgroups list
167
+
168
+ # Find platform IDs
169
+ bt pws platforms list
170
+
171
+ # Find functional account IDs (for auto-management)
172
+ bt pws functional list
173
+ ```
173
174
 
174
175
  ## EC2 Systems in AWS
175
176
 
@@ -179,7 +180,7 @@ When onboarding EC2 instances to Password Safe, use the **internal AWS DNS name*
179
180
  # Use internal DNS for EC2 systems
180
181
  bt pws quick onboard -n "web-prod-01" \
181
182
  -i "ip-10-0-12-45.us-east-1.compute.internal" \
182
- -w 3 -f 7
183
+ -w <workgroup_id> -f <func_acct_id>
183
184
 
184
185
  # Or set DNS separately
185
186
  bt pws systems update <system_id> --dns "ip-10-0-12-45.us-east-1.compute.internal"
@@ -288,15 +288,29 @@ def revert_policy(
288
288
  @app.command("download")
289
289
  def download_policy(
290
290
  policy_id: str = typer.Argument(..., help="Policy ID (UUID)"),
291
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="Save to file instead of stdout"),
291
292
  ):
292
- """Download policy content (XML format)."""
293
+ """Download policy content (XML format).
294
+
295
+ Examples:
296
+ bt epmw policies download <policy_id>
297
+ bt epmw policies download <policy_id> --file policy.xml
298
+ bt epmw policies download <policy_id> > policy.xml
299
+ """
293
300
  from bt_cli.epmw.client import get_client
294
301
 
295
302
  try:
296
303
  client = get_client()
297
304
  content = client.download_policy(policy_id)
298
- # Policy content is XML
299
- typer.echo(content)
305
+
306
+ if file:
307
+ # Write to file
308
+ with open(file, "w", encoding="utf-8") as f:
309
+ f.write(content)
310
+ print_success(f"Policy saved to: {file}")
311
+ else:
312
+ # Output to stdout
313
+ typer.echo(content)
300
314
  except httpx.HTTPStatusError as e:
301
315
  print_api_error(e, "download policy")
302
316
  raise typer.Exit(1)
@@ -16,10 +16,10 @@ import_app = typer.Typer(no_args_is_help=True, help="Import resources from CSV f
16
16
  export_app = typer.Typer(no_args_is_help=True, help="Export sample CSV templates")
17
17
  console = Console()
18
18
 
19
- # Column definitions for CSV formats
19
+ # Column definitions for CSV formats - using NAMES not IDs for readability
20
20
  SYSTEMS_COLUMNS = [
21
- "name", "ip_address", "workgroup_id", "platform_id", "port",
22
- "functional_account_id", "elevation_command", "auto_manage",
21
+ "system_name", "ip_address", "workgroup", "platform", "port",
22
+ "functional_account", "elevation_command", "auto_manage",
23
23
  "account_name", "account_password", "account_description"
24
24
  ]
25
25
 
@@ -27,31 +27,36 @@ SECRETS_COLUMNS = [
27
27
  "folder_path", "title", "username", "password", "description", "notes"
28
28
  ]
29
29
 
30
- # Sample data for templates
30
+ # Sample data for templates - uses names that will be resolved to IDs
31
+ # NOTE: account_password only required if auto_manage=false
32
+ # If auto_manage=true with functional_account, PWS rotates password automatically
31
33
  SYSTEMS_SAMPLE = [
32
34
  {
33
- "name": "web-server-01", "ip_address": "10.0.1.50", "workgroup_id": "3",
34
- "platform_id": "2", "port": "22", "functional_account_id": "7",
35
+ "system_name": "linux-managed-01", "ip_address": "10.0.1.50",
36
+ "workgroup": "Default", # Use workgroup NAME (run: bt pws workgroups list)
37
+ "platform": "Linux SSH", # Use platform NAME (run: bt pws platforms list)
38
+ "port": "22",
39
+ "functional_account": "Linux - Local", # FA DisplayName (run: bt pws functional list)
35
40
  "elevation_command": "sudo", "auto_manage": "true",
36
- "account_name": "root", "account_password": "", "account_description": "Root account"
41
+ "account_name": "root", "account_password": "", "account_description": "Auto-managed root"
37
42
  },
38
43
  {
39
- "name": "web-server-01", "ip_address": "10.0.1.50", "workgroup_id": "3",
40
- "platform_id": "2", "port": "22", "functional_account_id": "7",
41
- "elevation_command": "sudo", "auto_manage": "true",
42
- "account_name": "svc-backup", "account_password": "Backup#2026!", "account_description": "Backup service"
44
+ "system_name": "linux-managed-01", "ip_address": "10.0.1.50",
45
+ "workgroup": "Default", "platform": "Linux SSH", "port": "22",
46
+ "functional_account": "Linux - Local", "elevation_command": "sudo", "auto_manage": "true",
47
+ "account_name": "appuser", "account_password": "", "account_description": "Auto-managed app account"
43
48
  },
44
49
  {
45
- "name": "db-server-01", "ip_address": "10.0.1.60", "workgroup_id": "3",
46
- "platform_id": "2", "port": "22", "functional_account_id": "7",
47
- "elevation_command": "sudo", "auto_manage": "true",
48
- "account_name": "postgres", "account_password": "", "account_description": "Database admin"
50
+ "system_name": "linux-manual-01", "ip_address": "10.0.1.60",
51
+ "workgroup": "Default", "platform": "Linux SSH", "port": "22",
52
+ "functional_account": "", "elevation_command": "", "auto_manage": "false",
53
+ "account_name": "dbadmin", "account_password": "InitialP@ss123!", "account_description": "Manual - password required"
49
54
  },
50
55
  {
51
- "name": "win-server-01", "ip_address": "10.0.2.10", "workgroup_id": "2",
52
- "platform_id": "1", "port": "5985", "functional_account_id": "",
53
- "elevation_command": "", "auto_manage": "false",
54
- "account_name": "Administrator", "account_password": "InitialPass123!", "account_description": "Local admin"
56
+ "system_name": "win-manual-01", "ip_address": "10.0.2.10",
57
+ "workgroup": "Default", "platform": "Windows", "port": "5985",
58
+ "functional_account": "", "elevation_command": "", "auto_manage": "false",
59
+ "account_name": "Administrator", "account_password": "WinP@ss456!", "account_description": "Manual - password required"
55
60
  },
56
61
  ]
57
62
 
@@ -74,25 +79,89 @@ SECRETS_SAMPLE = [
74
79
  ]
75
80
 
76
81
 
82
+ def _resolve_name_to_id(name: str, lookup: dict, resource_type: str) -> Optional[int]:
83
+ """Resolve a name to ID using case-insensitive partial matching."""
84
+ if not name:
85
+ return None
86
+
87
+ # Try exact match first (case-insensitive)
88
+ name_lower = name.lower().strip()
89
+ for key, value in lookup.items():
90
+ if key.lower() == name_lower:
91
+ return value
92
+
93
+ # Try partial match (name contains search term)
94
+ for key, value in lookup.items():
95
+ if name_lower in key.lower():
96
+ return value
97
+
98
+ return None
99
+
100
+
101
+ def _build_folder_path_map(safes: list, folders: list) -> dict[str, str]:
102
+ """Build a map of folder paths to folder IDs.
103
+
104
+ Safes are top-level containers, folders can be nested.
105
+ Returns a dict like {"SafeName/FolderName": "folder-guid", ...}
106
+ """
107
+ # Build ID -> object lookups
108
+ safe_by_id = {s.get("Id"): s for s in safes}
109
+ folder_by_id = {f.get("Id"): f for f in folders}
110
+
111
+ # Build path for each folder
112
+ path_map: dict[str, str] = {}
113
+
114
+ def get_path(folder_id: str) -> str:
115
+ """Recursively build path for a folder."""
116
+ if folder_id in safe_by_id:
117
+ # It's a safe (top-level)
118
+ return safe_by_id[folder_id].get("Name", "")
119
+
120
+ folder = folder_by_id.get(folder_id)
121
+ if not folder:
122
+ return ""
123
+
124
+ parent_id = folder.get("ParentId")
125
+ folder_name = folder.get("Name", "")
126
+
127
+ if parent_id:
128
+ parent_path = get_path(parent_id)
129
+ if parent_path:
130
+ return f"{parent_path}/{folder_name}"
131
+
132
+ return folder_name
133
+
134
+ # Map all folders
135
+ for folder in folders:
136
+ folder_id = folder.get("Id")
137
+ path = get_path(folder_id)
138
+ if path:
139
+ path_map[path.lower()] = folder_id
140
+
141
+ return path_map
142
+
143
+
77
144
  @import_app.command("systems")
78
145
  def import_systems(
79
146
  file: str = typer.Option(..., "--file", "-f", help="CSV file path"),
80
147
  dry_run: bool = typer.Option(False, "--dry-run", help="Validate without creating"),
81
- workgroup: Optional[int] = typer.Option(None, "--workgroup", "-w", help="Override workgroup ID for all rows"),
148
+ workgroup_override: Optional[str] = typer.Option(None, "--workgroup", "-w", help="Override workgroup for all rows (name or ID)"),
82
149
  ) -> None:
83
150
  """Import managed systems and accounts from CSV.
84
151
 
85
152
  Each row creates a system + account. Multiple accounts per system use
86
153
  multiple rows with the same system name (system is created only once).
87
154
 
88
- Required columns: name, ip_address, workgroup_id, account_name
89
- Optional: platform_id, port, functional_account_id, elevation_command,
155
+ Uses NAMES for workgroup, platform, functional_account - resolved to IDs automatically.
156
+
157
+ Required columns: system_name, ip_address, workgroup, account_name
158
+ Optional: platform, port, functional_account, elevation_command,
90
159
  auto_manage, account_password, account_description
91
160
 
92
161
  Examples:
93
162
  bt pws import systems --file systems.csv --dry-run
94
163
  bt pws import systems --file systems.csv
95
- bt pws import systems --file systems.csv --workgroup 3
164
+ bt pws import systems --file systems.csv --workgroup "Default"
96
165
  """
97
166
  try:
98
167
  rows = read_csv(file)
@@ -102,73 +171,148 @@ def import_systems(
102
171
 
103
172
  console.print(f"[dim]Read {len(rows)} rows from {file}[/dim]")
104
173
 
105
- # Validate all rows first
106
- errors = []
107
- required = ["name", "ip_address", "account_name"]
108
- if not workgroup:
109
- required.append("workgroup_id")
110
-
111
- for i, row in enumerate(rows, 1):
112
- row_errors = validate_required_fields(row, required, i)
113
- errors.extend(row_errors)
114
-
115
- if errors:
116
- print_error("Validation errors:")
117
- for err in errors[:20]:
118
- console.print(f" [red]{err}[/red]")
119
- if len(errors) > 20:
120
- console.print(f" [red]... and {len(errors) - 20} more errors[/red]")
121
- raise typer.Exit(1)
122
-
123
- # Group rows by system name
124
- systems_rows: dict[str, list[dict]] = {}
125
- for row in rows:
126
- name = row["name"].strip()
127
- if name not in systems_rows:
128
- systems_rows[name] = []
129
- systems_rows[name].append(row)
130
-
131
- console.print(f"[dim]Found {len(systems_rows)} unique systems with {len(rows)} total accounts[/dim]")
132
-
133
- if dry_run:
134
- console.print("\n[yellow]DRY RUN - No changes will be made[/yellow]\n")
135
- table = Table(title="Systems to Create")
136
- table.add_column("System", style="green")
137
- table.add_column("IP", style="cyan")
138
- table.add_column("Workgroup", style="yellow")
139
- table.add_column("Platform", style="magenta")
140
- table.add_column("Accounts", style="blue")
141
-
142
- for name, sys_rows in systems_rows.items():
143
- first = sys_rows[0]
144
- wg = workgroup or parse_int(first.get("workgroup_id", ""))
145
- accounts = ", ".join(r["account_name"] for r in sys_rows)
146
- table.add_row(
147
- name,
148
- first.get("ip_address", ""),
149
- str(wg),
150
- first.get("platform_id", "2"),
151
- accounts
152
- )
153
-
154
- console.print(table)
155
- console.print(f"\n[dim]Would create {len(systems_rows)} systems with {len(rows)} accounts[/dim]")
156
- return
157
-
158
- # Actually import
174
+ # Connect and build lookup tables for name resolution
159
175
  with get_client() as client:
160
176
  client.authenticate()
161
177
 
178
+ console.print("[dim]Loading workgroups, platforms, functional accounts...[/dim]")
179
+
180
+ # Build name -> ID lookup tables
181
+ workgroups = client.list_workgroups()
182
+ workgroup_lookup = {w.get("Name", ""): w.get("ID") for w in workgroups}
183
+
184
+ platforms = client.list_platforms()
185
+ platform_lookup = {p.get("Name", ""): p.get("PlatformID") for p in platforms}
186
+
187
+ func_accounts = client.list_functional_accounts()
188
+ # Use DisplayName as the primary lookup key, also add AccountName as fallback
189
+ func_acct_lookup = {}
190
+ for f in func_accounts:
191
+ fa_id = f.get("FunctionalAccountID")
192
+ # Primary: DisplayName (e.g., "Linux - Local")
193
+ if f.get("DisplayName"):
194
+ func_acct_lookup[f["DisplayName"]] = fa_id
195
+ # Fallback: AccountName (e.g., "svc_passwordsafe")
196
+ if f.get("AccountName"):
197
+ func_acct_lookup[f["AccountName"]] = fa_id
198
+
199
+ # Resolve workgroup override
200
+ wg_override_id = None
201
+ if workgroup_override:
202
+ # Try as ID first
203
+ try:
204
+ wg_override_id = int(workgroup_override)
205
+ except ValueError:
206
+ wg_override_id = _resolve_name_to_id(workgroup_override, workgroup_lookup, "workgroup")
207
+ if not wg_override_id:
208
+ print_error(f"Workgroup not found: {workgroup_override}")
209
+ console.print("[dim]Available workgroups:[/dim]")
210
+ for name in workgroup_lookup.keys():
211
+ console.print(f" - {name}")
212
+ raise typer.Exit(1)
213
+
214
+ # Validate all rows first
215
+ errors = []
216
+ # Support both old column names (backward compat) and new names
217
+ name_col = "system_name" if "system_name" in rows[0] else "name"
218
+ wg_col = "workgroup" if "workgroup" in rows[0] else "workgroup_id"
219
+
220
+ required = [name_col, "ip_address", "account_name"]
221
+ if not wg_override_id:
222
+ required.append(wg_col)
223
+
224
+ for i, row in enumerate(rows, 1):
225
+ row_errors = validate_required_fields(row, required, i)
226
+ errors.extend(row_errors)
227
+
228
+ if errors:
229
+ print_error("Validation errors:")
230
+ for err in errors[:20]:
231
+ console.print(f" [red]{err}[/red]")
232
+ if len(errors) > 20:
233
+ console.print(f" [red]... and {len(errors) - 20} more errors[/red]")
234
+ raise typer.Exit(1)
235
+
236
+ # Group rows by system name
237
+ systems_rows: dict[str, list[dict]] = {}
238
+ for row in rows:
239
+ name = row.get(name_col, "").strip()
240
+ if name not in systems_rows:
241
+ systems_rows[name] = []
242
+ systems_rows[name].append(row)
243
+
244
+ console.print(f"[dim]Found {len(systems_rows)} unique systems with {len(rows)} total accounts[/dim]")
245
+
246
+ if dry_run:
247
+ console.print("\n[yellow]DRY RUN - No changes will be made[/yellow]\n")
248
+ table = Table(title="Systems to Create")
249
+ table.add_column("System", style="green")
250
+ table.add_column("IP", style="cyan")
251
+ table.add_column("Workgroup", style="yellow")
252
+ table.add_column("Platform", style="magenta")
253
+ table.add_column("Accounts", style="blue")
254
+
255
+ for name, sys_rows in systems_rows.items():
256
+ first = sys_rows[0]
257
+ wg_name = first.get(wg_col, "")
258
+ plat_name = first.get("platform", first.get("platform_id", "Linux"))
259
+ accounts = ", ".join(r["account_name"] for r in sys_rows)
260
+ table.add_row(name, first.get("ip_address", ""), wg_name, plat_name, accounts)
261
+
262
+ console.print(table)
263
+ console.print(f"\n[dim]Would create {len(systems_rows)} systems with {len(rows)} accounts[/dim]")
264
+ return
265
+
266
+ # Actually import
162
267
  created_systems = 0
163
268
  created_accounts = 0
164
- system_ids: dict[str, int] = {} # Map system name to ID
269
+ system_ids: dict[str, int] = {}
165
270
 
166
271
  for name, sys_rows in systems_rows.items():
167
272
  first = sys_rows[0]
168
- wg_id = workgroup or parse_int(first.get("workgroup_id", ""))
273
+
274
+ # Resolve workgroup
275
+ if wg_override_id:
276
+ wg_id = wg_override_id
277
+ else:
278
+ wg_val = first.get(wg_col, "").strip()
279
+ try:
280
+ wg_id = int(wg_val)
281
+ except ValueError:
282
+ wg_id = _resolve_name_to_id(wg_val, workgroup_lookup, "workgroup")
169
283
 
170
284
  if not wg_id:
171
- print_warning(f"Skipping {name}: No workgroup ID")
285
+ print_warning(f"Skipping {name}: Could not resolve workgroup '{first.get(wg_col, '')}'")
286
+ continue
287
+
288
+ # Resolve platform
289
+ plat_val = first.get("platform", first.get("platform_id", "")).strip()
290
+ try:
291
+ platform_id = int(plat_val) if plat_val else 2
292
+ except ValueError:
293
+ platform_id = _resolve_name_to_id(plat_val, platform_lookup, "platform") or 2
294
+
295
+ # Resolve functional account
296
+ func_val = first.get("functional_account", first.get("functional_account_id", "")).strip()
297
+ func_acct_id = None
298
+ if func_val:
299
+ try:
300
+ func_acct_id = int(func_val)
301
+ except ValueError:
302
+ func_acct_id = _resolve_name_to_id(func_val, func_acct_lookup, "functional account")
303
+
304
+ # Get auto_manage flag early for validation
305
+ auto_manage = parse_bool(first.get("auto_manage", ""))
306
+
307
+ # Validate auto_manage + functional account combination
308
+ if auto_manage and func_val and not func_acct_id:
309
+ print_warning(f"Skipping {name}: auto_manage=true but functional account '{func_val}' not found")
310
+ console.print("[dim]Available functional accounts:[/dim]")
311
+ for fa_name in func_acct_lookup.keys():
312
+ console.print(f" - {fa_name}")
313
+ continue
314
+ if auto_manage and not func_val:
315
+ print_warning(f"Skipping {name}: auto_manage=true requires a functional_account")
172
316
  continue
173
317
 
174
318
  try:
@@ -183,10 +327,7 @@ def import_systems(
183
327
 
184
328
  # Create managed system
185
329
  console.print(f"[dim]Creating managed system '{name}'...[/dim]")
186
- platform_id = parse_int(first.get("platform_id", ""), 2)
187
330
  port = parse_int(first.get("port", ""), 22)
188
- func_acct = parse_int(first.get("functional_account_id", ""))
189
- auto_manage = parse_bool(first.get("auto_manage", ""))
190
331
  elevation = first.get("elevation_command", "").strip() or None
191
332
 
192
333
  system = client.create_managed_system(
@@ -194,8 +335,8 @@ def import_systems(
194
335
  platform_id=platform_id,
195
336
  asset_id=asset_id,
196
337
  port=port,
197
- functional_account_id=func_acct,
198
- auto_management_flag=auto_manage if func_acct else False,
338
+ functional_account_id=func_acct_id,
339
+ auto_management_flag=auto_manage if func_acct_id else False,
199
340
  elevation_command=elevation,
200
341
  )
201
342
  system_id = system.get("ManagedSystemID")
@@ -214,7 +355,7 @@ def import_systems(
214
355
  system_id=system_id,
215
356
  account_name=account_name,
216
357
  password=password,
217
- auto_management_flag=auto_manage if func_acct else False,
358
+ auto_management_flag=auto_manage if func_acct_id else False,
218
359
  )
219
360
  account_id = account.get("ManagedAccountID")
220
361
  created_accounts += 1
@@ -301,13 +442,11 @@ def import_secrets(
301
442
  with get_client() as client:
302
443
  client.authenticate()
303
444
 
304
- # Get all folders to map paths to IDs
445
+ # Build folder path -> ID map from safes and folders
446
+ console.print("[dim]Loading safes and folders...[/dim]")
447
+ safes = client.list_safes()
305
448
  all_folders = client.list_folders()
306
- folder_map: dict[str, int] = {}
307
- for f in all_folders:
308
- path = f.get("FolderPath", "") or f.get("Name", "")
309
- if path:
310
- folder_map[path.lower()] = f.get("Id")
449
+ folder_map = _build_folder_path_map(safes, all_folders)
311
450
 
312
451
  created = 0
313
452
  for row in rows:
@@ -318,6 +457,11 @@ def import_secrets(
318
457
  folder_id = folder_map.get(folder_path.lower())
319
458
  if not folder_id:
320
459
  print_warning(f"Folder not found: {folder_path}, skipping {title}")
460
+ console.print("[dim]Available folder paths:[/dim]")
461
+ for path in sorted(folder_map.keys())[:10]:
462
+ console.print(f" - {path}")
463
+ if len(folder_map) > 10:
464
+ console.print(f" ... and {len(folder_map) - 10} more")
321
465
  continue
322
466
 
323
467
  try:
@@ -355,22 +499,46 @@ def export_systems(
355
499
  file: str = typer.Option("pws-systems-template.csv", "--file", "-f", help="Output file path"),
356
500
  sample: bool = typer.Option(True, "--sample/--no-sample", help="Export sample template (default) or current data"),
357
501
  ) -> None:
358
- """Export sample systems CSV template.
502
+ """Export systems CSV template or current data.
503
+
504
+ Uses human-readable NAMES for workgroup, platform, functional_account.
359
505
 
360
506
  Examples:
361
507
  bt pws export systems --file systems-template.csv
362
- bt pws export systems --file systems-template.csv --sample
508
+ bt pws export systems --file current-systems.csv --no-sample
363
509
  """
364
510
  try:
365
511
  if sample:
366
512
  write_csv(file, SYSTEMS_SAMPLE, SYSTEMS_COLUMNS)
367
513
  print_success(f"Sample systems template exported to: {file}")
368
514
  console.print(f"[dim]Contains {len(SYSTEMS_SAMPLE)} example rows[/dim]")
515
+ console.print("\n[dim]Column descriptions:[/dim]")
516
+ console.print(" system_name - Unique name for the managed system")
517
+ console.print(" ip_address - IP or hostname")
518
+ console.print(" workgroup - Workgroup NAME (run: bt pws workgroups list)")
519
+ console.print(" platform - Platform NAME, partial match OK (run: bt pws platforms list)")
520
+ console.print(" port - Connection port (22 for SSH, 5985 for WinRM)")
521
+ console.print(" functional_account - Functional account NAME for auto-management (bt pws functional list)")
522
+ console.print(" elevation_command - 'sudo' or other elevation command (optional)")
523
+ console.print(" auto_manage - true=PWS rotates password, false=manual management")
524
+ console.print(" account_name - Account username to manage")
525
+ console.print(" account_password - Required if auto_manage=false, empty if auto_manage=true")
526
+ console.print(" account_description- Description for the account")
369
527
  else:
370
- # Export actual data from API
528
+ # Export actual data from API with names resolved
371
529
  with get_client() as client:
372
530
  client.authenticate()
373
531
 
532
+ # Build ID -> name lookup tables
533
+ workgroups = client.list_workgroups()
534
+ wg_names = {w.get("ID"): w.get("Name", "") for w in workgroups}
535
+
536
+ platforms = client.list_platforms()
537
+ plat_names = {p.get("PlatformID"): p.get("Name", "") for p in platforms}
538
+
539
+ func_accounts = client.list_functional_accounts()
540
+ func_names = {f.get("FunctionalAccountID"): f.get("FunctionalAccountName", "") for f in func_accounts}
541
+
374
542
  systems = client.list_managed_systems()
375
543
  rows = []
376
544
 
@@ -378,14 +546,18 @@ def export_systems(
378
546
  system_id = sys.get("ManagedSystemID")
379
547
  accounts = client.list_managed_accounts(system_id=system_id)
380
548
 
549
+ wg_id = sys.get("WorkgroupID")
550
+ plat_id = sys.get("PlatformID")
551
+ func_id = sys.get("FunctionalAccountID")
552
+
381
553
  for acc in accounts:
382
554
  rows.append({
383
- "name": sys.get("SystemName", ""),
555
+ "system_name": sys.get("SystemName", ""),
384
556
  "ip_address": sys.get("IPAddress", ""),
385
- "workgroup_id": str(sys.get("WorkgroupID", "")),
386
- "platform_id": str(sys.get("PlatformID", "")),
557
+ "workgroup": wg_names.get(wg_id, str(wg_id or "")),
558
+ "platform": plat_names.get(plat_id, str(plat_id or "")),
387
559
  "port": str(sys.get("Port", "")),
388
- "functional_account_id": str(sys.get("FunctionalAccountID", "") or ""),
560
+ "functional_account": func_names.get(func_id, "") if func_id else "",
389
561
  "elevation_command": sys.get("ElevationCommand", "") or "",
390
562
  "auto_manage": "true" if sys.get("AutoManagementFlag") else "false",
391
563
  "account_name": acc.get("AccountName", ""),
@@ -419,22 +591,25 @@ def export_secrets(
419
591
  write_csv(file, SECRETS_SAMPLE, SECRETS_COLUMNS)
420
592
  print_success(f"Sample secrets template exported to: {file}")
421
593
  console.print(f"[dim]Contains {len(SECRETS_SAMPLE)} example rows[/dim]")
594
+ console.print("\n[dim]Column descriptions:[/dim]")
595
+ console.print(" folder_path - Full path: SafeName/FolderName (run: bt pws secrets folders list)")
596
+ console.print(" title - Unique title for the secret")
597
+ console.print(" username - Username/identifier (optional)")
598
+ console.print(" password - Password/secret value")
599
+ console.print(" description - Description of the secret")
600
+ console.print(" notes - Additional notes (can be JSON)")
422
601
  else:
423
602
  # Export actual data from API
424
603
  with get_client() as client:
425
604
  client.authenticate()
426
605
 
427
606
  secrets = client.list_secrets()
428
- folders = client.list_folders()
429
-
430
- # Map folder IDs to paths
431
- folder_paths = {f.get("Id"): f.get("FolderPath", "") or f.get("Name", "") for f in folders}
432
607
 
433
608
  rows = []
434
609
  for s in secrets:
435
- folder_id = s.get("FolderId")
610
+ # Secrets have FolderPath directly (e.g., "SafeName/FolderName")
436
611
  rows.append({
437
- "folder_path": folder_paths.get(folder_id, ""),
612
+ "folder_path": s.get("FolderPath", "") or s.get("Folder", ""),
438
613
  "title": s.get("Title", ""),
439
614
  "username": s.get("Username", "") or "",
440
615
  "password": "", # Don't export passwords
@@ -568,7 +568,7 @@ def print_secrets_table(secrets: list[dict], title: str = "Secrets") -> None:
568
568
  table.add_column("ID", style="cyan", no_wrap=True, max_width=36)
569
569
  table.add_column("Title", style="green")
570
570
  table.add_column("Username", style="yellow")
571
- table.add_column("Folder", style="blue")
571
+ table.add_column("Folder Path", style="blue")
572
572
  table.add_column("Modified", style="dim")
573
573
 
574
574
  for secret in secrets:
@@ -576,7 +576,7 @@ def print_secrets_table(secrets: list[dict], title: str = "Secrets") -> None:
576
576
  secret.get("Id", "")[:36],
577
577
  secret.get("Title", ""),
578
578
  secret.get("Username", "-"),
579
- secret.get("Folder", secret.get("FolderPath", "-")),
579
+ secret.get("FolderPath", secret.get("Folder", "-")),
580
580
  str(secret.get("ModifiedOn", "-"))[:19],
581
581
  )
582
582
 
@@ -653,7 +653,7 @@ def get_secret(
653
653
  console.print(f" Password: {secret.get('Password', '-')}")
654
654
  else:
655
655
  console.print(" Password: ********")
656
- console.print(f" Folder: {secret.get('Folder', secret.get('FolderPath', '-'))}")
656
+ console.print(f" Folder Path: {secret.get('FolderPath', secret.get('Folder', '-'))}")
657
657
  console.print(f" Description: {secret.get('Description', '-') or '-'}")
658
658
  if secret.get("Notes"):
659
659
  console.print(f" Notes: {secret.get('Notes')}")
@@ -961,7 +961,7 @@ def get_text_secret(
961
961
  else:
962
962
  console.print(f"\n[bold cyan]Text Secret: {secret.get('Title', 'Unknown')}[/bold cyan]\n")
963
963
  console.print(f" ID: {secret.get('Id', 'N/A')}")
964
- console.print(f" Folder: {secret.get('Folder', secret.get('FolderPath', '-'))}")
964
+ console.print(f" Folder Path: {secret.get('FolderPath', secret.get('Folder', '-'))}")
965
965
  console.print(f" Description: {secret.get('Description', '-') or '-'}")
966
966
  if show_text:
967
967
  console.print(f" Text Content:\n{secret.get('Password', '-')}")
@@ -1065,7 +1065,7 @@ def get_file_secret(
1065
1065
  console.print(f"\n[bold cyan]File Secret: {secret.get('Title', 'Unknown')}[/bold cyan]\n")
1066
1066
  console.print(f" ID: {secret.get('Id', 'N/A')}")
1067
1067
  console.print(f" FileName: {secret.get('FileName', '-')}")
1068
- console.print(f" Folder: {secret.get('Folder', secret.get('FolderPath', '-'))}")
1068
+ console.print(f" Folder Path: {secret.get('FolderPath', secret.get('Folder', '-'))}")
1069
1069
  console.print(f" Description: {secret.get('Description', '-') or '-'}")
1070
1070
  console.print(f" Created: {secret.get('CreatedOn', '-')}")
1071
1071
  console.print(f" Modified: {secret.get('ModifiedOn', '-')}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.12
3
+ Version: 0.4.14
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
@@ -1,4 +1,4 @@
1
- bt_cli/__init__.py,sha256=Vx-1AZjCFkmnQmwbnX-8YebWHOTk6RvdrT7gxkVn404,61
1
+ bt_cli/__init__.py,sha256=2Ec_fVFIfrTcwoI2dlXG3AashEKBkbaNUp9YUdyZiOw,61
2
2
  bt_cli/cli.py,sha256=-V7Q_DcP7Ryw-mRBLMyj-iwlFpof8_eLgFAx-fN5seM,29640
3
3
  bt_cli/commands/__init__.py,sha256=Wrf3ZV1sf7JCilbv93VqoWWTyj0d-y4saAaVFD5apU8,38
4
4
  bt_cli/commands/configure.py,sha256=f3tn09eRDqlGQIq1gpuxj984S4CARYbmKI4XrqxPAAM,14270
@@ -14,13 +14,13 @@ bt_cli/core/errors.py,sha256=-LWipqdn8w-FMAC1W1jFbTMAlooiJqmyuR5j1VWMPyM,8783
14
14
  bt_cli/core/output.py,sha256=ACazDf3XAfJhAlns5fh6bQXkRIIGlBFuq1z2M-IBrwQ,5156
15
15
  bt_cli/core/prompts.py,sha256=VeB8on5VN_D_4r85rJBO6AP_yHJUxBVwWqwZHCoOiqs,2387
16
16
  bt_cli/core/rest_debug.py,sha256=7KsqsrsPXUqaYLePjZbwnoqMcg5ivJgYGS35ygaeLfM,5973
17
- bt_cli/data/CLAUDE.md,sha256=I0ZvD4x_N20QTNF9-R66nwdWMqtXlquQ-fLz3fZfgew,2829
17
+ bt_cli/data/CLAUDE.md,sha256=3Y_afs1RGTKLkn4Vr9wMKiReIr4EEtBdjFrqSs2NymQ,3228
18
18
  bt_cli/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- bt_cli/data/skills/bt/SKILL.md,sha256=fIbkCkM7mwbFhty_FpGbYQq4Ah8fU5fZVVwvqMi1XR4,2808
20
- bt_cli/data/skills/entitle/SKILL.md,sha256=n2RifQl-RwRoPW341w7VKxGIDYjZ9HEbZvJ52ap6RQk,4215
21
- bt_cli/data/skills/epmw/SKILL.md,sha256=WMz6FlqfSwVFmiF7W_p3SypQnKq0kS5elhTqa8z06cU,3902
22
- bt_cli/data/skills/pra/SKILL.md,sha256=SvbTgwYofb1xJ4Up_Jqrcm6ddQiDugmoWDvvjuTr6CM,3693
23
- bt_cli/data/skills/pws/SKILL.md,sha256=zWKttbIL-UeOLe2iCBQS6CXLcAu_9K4SofuAaqTDlQc,5298
19
+ bt_cli/data/skills/bt/SKILL.md,sha256=DZTvRtSYjiDxDbg7WTpgklYs5Dn-2Z-9w3X8RQJbGNo,3077
20
+ bt_cli/data/skills/entitle/SKILL.md,sha256=yOHP9U_1vKh1yLmrAqKl4rNo16n4f3hia0xj_ZJRRYM,4413
21
+ bt_cli/data/skills/epmw/SKILL.md,sha256=brfIXR9MnB5_FwRa-SyX1TwqVRyefgtr2foQfErpVVA,3541
22
+ bt_cli/data/skills/pra/SKILL.md,sha256=-LTUJvjvLvZn0rMmy0KOeEBtUsotjbHvXX3RCk7mDBw,3749
23
+ bt_cli/data/skills/pws/SKILL.md,sha256=lrOIN06hVrbHfph5rdu78W5N4UKzjrjuHxcJG4eK5hA,5401
24
24
  bt_cli/entitle/__init__.py,sha256=l2fasFHO_0zgCS1FgmGdAikumU8QimpaFD9Gvh3Jyqg,30
25
25
  bt_cli/entitle/client/__init__.py,sha256=kIQohXlEFGrRAyVWMO9GdESrnAg5J4dmoyJQ6q9NxSo,114
26
26
  bt_cli/entitle/client/base.py,sha256=Jov6v7iM8DZ6o_DL7esBpdnYyM-WVFzI8u_0Bjh9MgA,14623
@@ -55,7 +55,7 @@ bt_cli/epmw/commands/auth.py,sha256=2oC3h01_eh4XyEvsar_ffJAqGKXGE_PrhHQJWFswlN8,
55
55
  bt_cli/epmw/commands/computers.py,sha256=K0eBd8HOaVCe8hhHPlUuqCrZduhPuvwlkxdYt6YjJxM,4146
56
56
  bt_cli/epmw/commands/events.py,sha256=not-9_poxQWRBB4sqZSzJo6031A3IZTdg6uAl2Agk2g,7707
57
57
  bt_cli/epmw/commands/groups.py,sha256=PaMcjqfr2b3xS3W3V2A9CFTlMOAs38UmJbi5LKXbbg0,6759
58
- bt_cli/epmw/commands/policies.py,sha256=mCewGU2Sr9H628JIrW9fMJfG0xeI137NaVIPRdQTmkM,24103
58
+ bt_cli/epmw/commands/policies.py,sha256=0jFQ-ZLzTAC6rPbMWL3DfWm0jdgvE2iwR_dHUHI-QG4,24600
59
59
  bt_cli/epmw/commands/quick.py,sha256=X2iTwnDgAvatW8wVY4nqQztLmIEoGmbn1YeCVwHyvzY,13876
60
60
  bt_cli/epmw/commands/requests.py,sha256=qcx6MsPXVm2UkaBIkhpf3RZh6Lo-BBz2OPF4Tl3h4p8,8251
61
61
  bt_cli/epmw/commands/roles.py,sha256=WYAelLUwzJkBB7rlKoR4FqSxfdoDjV310KcNl6ixCCM,2396
@@ -102,11 +102,11 @@ bt_cli/pws/commands/credentials.py,sha256=p1efDBCI35exGS-6iHQ5IHBQIo0TJMfWuCBoek
102
102
  bt_cli/pws/commands/databases.py,sha256=hHGg7hNtE5mwClCpWoSmVSB1-LdY3PORu9iMRuhbxOg,11961
103
103
  bt_cli/pws/commands/directories.py,sha256=NBZfivK-gh3BlC7nXyM_iWeE-qjPIPMlsptkNGtsQJk,7985
104
104
  bt_cli/pws/commands/functional.py,sha256=C9iMSjNfG2VF-kDAM8a3rlMwp_wMePZpGRPNWwkSTBI,12705
105
- bt_cli/pws/commands/import_export.py,sha256=JVYdul4geotAmwkfV04cMHuQD_KVyJ7EYIPbf2FibXg,18536
105
+ bt_cli/pws/commands/import_export.py,sha256=jGrGqInxmWAGWVdUxVBt1hHtu_CqmXO65oV5RoUsaAA,27395
106
106
  bt_cli/pws/commands/platforms.py,sha256=1NZM8BFMG8-AL3AzRZjU0X8zZwt584Z4FOxQf6mGemc,3999
107
107
  bt_cli/pws/commands/quick.py,sha256=3i985r7A8_0zhXTDNxG9NLbGHXlXAAxZL2tMyAAIUyg,70769
108
108
  bt_cli/pws/commands/search.py,sha256=6AHJgk30mLneviODOYX4xL9VwPZo1bDUoix3oC99LMM,9875
109
- bt_cli/pws/commands/secrets.py,sha256=ig3V-CzenF-8YCle_LbszA6-cOj96LCwghUPDd1_Veg,51644
109
+ bt_cli/pws/commands/secrets.py,sha256=-8WInmkRxhq2kESDyMwvtx3hS7OvB-60-puj_2GnH_4,51664
110
110
  bt_cli/pws/commands/systems.py,sha256=UFWNgyfI--NjKXqSWVbw_g_Ar_td4-Roa0tK1AJSuQE,16905
111
111
  bt_cli/pws/commands/users.py,sha256=yMMC1I32QOjsMr2taU4qD7WajdJ7W3mNDQITbqKCEP4,14078
112
112
  bt_cli/pws/commands/workgroups.py,sha256=fvjGniHE6T_oktQSvGhjFXeeDxeubkJXTwR4RNtrA4E,6080
@@ -115,7 +115,7 @@ bt_cli/pws/models/account.py,sha256=OSCMyULPOH1Yu2WOzK0ZQhSRrggGpb2JPHScwGLqUgI,
115
115
  bt_cli/pws/models/asset.py,sha256=Fl0AlR4_9Yyyu36FL1eKF29DNsxsB-r7FaOBRlfOg2Q,4081
116
116
  bt_cli/pws/models/common.py,sha256=D9Ah4ob5CIiFhTt_IR9nF2cBWRHS2z9OyBR2Sss5yzw,3487
117
117
  bt_cli/pws/models/system.py,sha256=D_J0x1A92H2n6BsaBEK9PSAAcs3BTifA5-M9SQqQFGA,5856
118
- bt_cli-0.4.12.dist-info/METADATA,sha256=EO3jvIX-6RdBdCDkTKxUq2NjgmO0GN1j1lUC9AVF52c,11803
119
- bt_cli-0.4.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
120
- bt_cli-0.4.12.dist-info/entry_points.txt,sha256=NCOEqTI-XKpJOux0JKKhbRElz0B7upayh_d99X5hoLs,38
121
- bt_cli-0.4.12.dist-info/RECORD,,
118
+ bt_cli-0.4.14.dist-info/METADATA,sha256=1Xp74W_sSHvwqqEe6JPwvZpkU0ENadn1PfS4ZGK_ySM,11803
119
+ bt_cli-0.4.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
120
+ bt_cli-0.4.14.dist-info/entry_points.txt,sha256=NCOEqTI-XKpJOux0JKKhbRElz0B7upayh_d99X5hoLs,38
121
+ bt_cli-0.4.14.dist-info/RECORD,,