bt-cli 0.4.13__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.
Files changed (121) hide show
  1. bt_cli/__init__.py +3 -0
  2. bt_cli/cli.py +830 -0
  3. bt_cli/commands/__init__.py +1 -0
  4. bt_cli/commands/configure.py +415 -0
  5. bt_cli/commands/learn.py +229 -0
  6. bt_cli/commands/quick.py +784 -0
  7. bt_cli/core/__init__.py +1 -0
  8. bt_cli/core/auth.py +213 -0
  9. bt_cli/core/client.py +313 -0
  10. bt_cli/core/config.py +393 -0
  11. bt_cli/core/config_file.py +420 -0
  12. bt_cli/core/csv_utils.py +91 -0
  13. bt_cli/core/errors.py +247 -0
  14. bt_cli/core/output.py +205 -0
  15. bt_cli/core/prompts.py +87 -0
  16. bt_cli/core/rest_debug.py +221 -0
  17. bt_cli/data/CLAUDE.md +94 -0
  18. bt_cli/data/__init__.py +0 -0
  19. bt_cli/data/skills/bt/SKILL.md +108 -0
  20. bt_cli/data/skills/entitle/SKILL.md +170 -0
  21. bt_cli/data/skills/epmw/SKILL.md +144 -0
  22. bt_cli/data/skills/pra/SKILL.md +150 -0
  23. bt_cli/data/skills/pws/SKILL.md +198 -0
  24. bt_cli/entitle/__init__.py +1 -0
  25. bt_cli/entitle/client/__init__.py +5 -0
  26. bt_cli/entitle/client/base.py +443 -0
  27. bt_cli/entitle/commands/__init__.py +24 -0
  28. bt_cli/entitle/commands/accounts.py +53 -0
  29. bt_cli/entitle/commands/applications.py +39 -0
  30. bt_cli/entitle/commands/auth.py +68 -0
  31. bt_cli/entitle/commands/bundles.py +218 -0
  32. bt_cli/entitle/commands/integrations.py +60 -0
  33. bt_cli/entitle/commands/permissions.py +70 -0
  34. bt_cli/entitle/commands/policies.py +97 -0
  35. bt_cli/entitle/commands/resources.py +131 -0
  36. bt_cli/entitle/commands/roles.py +74 -0
  37. bt_cli/entitle/commands/users.py +123 -0
  38. bt_cli/entitle/commands/workflows.py +187 -0
  39. bt_cli/entitle/models/__init__.py +31 -0
  40. bt_cli/entitle/models/bundle.py +28 -0
  41. bt_cli/entitle/models/common.py +37 -0
  42. bt_cli/entitle/models/integration.py +30 -0
  43. bt_cli/entitle/models/permission.py +27 -0
  44. bt_cli/entitle/models/policy.py +25 -0
  45. bt_cli/entitle/models/resource.py +29 -0
  46. bt_cli/entitle/models/role.py +28 -0
  47. bt_cli/entitle/models/user.py +24 -0
  48. bt_cli/entitle/models/workflow.py +55 -0
  49. bt_cli/epmw/__init__.py +1 -0
  50. bt_cli/epmw/client/__init__.py +5 -0
  51. bt_cli/epmw/client/base.py +848 -0
  52. bt_cli/epmw/commands/__init__.py +33 -0
  53. bt_cli/epmw/commands/audits.py +250 -0
  54. bt_cli/epmw/commands/auth.py +55 -0
  55. bt_cli/epmw/commands/computers.py +140 -0
  56. bt_cli/epmw/commands/events.py +233 -0
  57. bt_cli/epmw/commands/groups.py +215 -0
  58. bt_cli/epmw/commands/policies.py +673 -0
  59. bt_cli/epmw/commands/quick.py +348 -0
  60. bt_cli/epmw/commands/requests.py +224 -0
  61. bt_cli/epmw/commands/roles.py +78 -0
  62. bt_cli/epmw/commands/tasks.py +38 -0
  63. bt_cli/epmw/commands/users.py +219 -0
  64. bt_cli/epmw/models/__init__.py +1 -0
  65. bt_cli/pra/__init__.py +1 -0
  66. bt_cli/pra/client/__init__.py +5 -0
  67. bt_cli/pra/client/base.py +618 -0
  68. bt_cli/pra/commands/__init__.py +30 -0
  69. bt_cli/pra/commands/auth.py +55 -0
  70. bt_cli/pra/commands/import_export.py +442 -0
  71. bt_cli/pra/commands/jump_clients.py +139 -0
  72. bt_cli/pra/commands/jump_groups.py +146 -0
  73. bt_cli/pra/commands/jump_items.py +638 -0
  74. bt_cli/pra/commands/jumpoints.py +95 -0
  75. bt_cli/pra/commands/policies.py +197 -0
  76. bt_cli/pra/commands/quick.py +470 -0
  77. bt_cli/pra/commands/teams.py +81 -0
  78. bt_cli/pra/commands/users.py +87 -0
  79. bt_cli/pra/commands/vault.py +564 -0
  80. bt_cli/pra/models/__init__.py +27 -0
  81. bt_cli/pra/models/common.py +12 -0
  82. bt_cli/pra/models/jump_client.py +25 -0
  83. bt_cli/pra/models/jump_group.py +15 -0
  84. bt_cli/pra/models/jump_item.py +72 -0
  85. bt_cli/pra/models/jumpoint.py +19 -0
  86. bt_cli/pra/models/team.py +14 -0
  87. bt_cli/pra/models/user.py +17 -0
  88. bt_cli/pra/models/vault.py +45 -0
  89. bt_cli/pws/__init__.py +1 -0
  90. bt_cli/pws/client/__init__.py +5 -0
  91. bt_cli/pws/client/base.py +356 -0
  92. bt_cli/pws/client/beyondinsight.py +869 -0
  93. bt_cli/pws/client/passwordsafe.py +1786 -0
  94. bt_cli/pws/commands/__init__.py +33 -0
  95. bt_cli/pws/commands/accounts.py +372 -0
  96. bt_cli/pws/commands/assets.py +311 -0
  97. bt_cli/pws/commands/auth.py +166 -0
  98. bt_cli/pws/commands/clouds.py +221 -0
  99. bt_cli/pws/commands/config.py +344 -0
  100. bt_cli/pws/commands/credentials.py +347 -0
  101. bt_cli/pws/commands/databases.py +306 -0
  102. bt_cli/pws/commands/directories.py +199 -0
  103. bt_cli/pws/commands/functional.py +298 -0
  104. bt_cli/pws/commands/import_export.py +452 -0
  105. bt_cli/pws/commands/platforms.py +118 -0
  106. bt_cli/pws/commands/quick.py +1646 -0
  107. bt_cli/pws/commands/search.py +256 -0
  108. bt_cli/pws/commands/secrets.py +1343 -0
  109. bt_cli/pws/commands/systems.py +389 -0
  110. bt_cli/pws/commands/users.py +415 -0
  111. bt_cli/pws/commands/workgroups.py +166 -0
  112. bt_cli/pws/config.py +18 -0
  113. bt_cli/pws/models/__init__.py +19 -0
  114. bt_cli/pws/models/account.py +186 -0
  115. bt_cli/pws/models/asset.py +102 -0
  116. bt_cli/pws/models/common.py +132 -0
  117. bt_cli/pws/models/system.py +121 -0
  118. bt_cli-0.4.13.dist-info/METADATA +417 -0
  119. bt_cli-0.4.13.dist-info/RECORD +121 -0
  120. bt_cli-0.4.13.dist-info/WHEEL +4 -0
  121. bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,233 @@
1
+ """EPMW events commands."""
2
+
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import httpx
7
+ import typer
8
+
9
+ from bt_cli.core.output import OutputFormat, print_api_error, print_json, print_table, print_info
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="EPM Windows events")
12
+
13
+
14
+ def _get_iso_date(hours_ago: int = 24) -> str:
15
+ """Get ISO format date string for N hours ago."""
16
+ dt = datetime.now(timezone.utc) - timedelta(hours=hours_ago)
17
+ return dt.strftime("%Y-%m-%dT%H:%M:%S.000Z")
18
+
19
+
20
+ def _get_iso_now() -> str:
21
+ """Get current time in ISO format."""
22
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
23
+
24
+
25
+ def _get_nested(obj: Dict[str, Any], path: str, default: str = "-") -> str:
26
+ """Get a nested value from a dict using dot notation."""
27
+ keys = path.split(".")
28
+ value = obj
29
+ for key in keys:
30
+ if isinstance(value, dict):
31
+ value = value.get(key)
32
+ else:
33
+ return default
34
+ if value is None:
35
+ return default
36
+ return str(value) if value is not None else default
37
+
38
+
39
+ def _flatten_events(events: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
40
+ """Flatten nested event structure for table display."""
41
+ flattened = []
42
+ for event in events:
43
+ flat = {
44
+ "timestamp": _get_nested(event, "@timestamp"),
45
+ "hostname": _get_nested(event, "host.hostname"),
46
+ "user": _get_nested(event, "user.name"),
47
+ "action": _get_nested(event, "event.action"),
48
+ "code": _get_nested(event, "event.code"),
49
+ "reason": _get_nested(event, "event.reason"),
50
+ "application": _get_nested(event, "file.name"),
51
+ "path": _get_nested(event, "file.path"),
52
+ "os": _get_nested(event, "host.os.name"),
53
+ }
54
+ flattened.append(flat)
55
+ return flattened
56
+
57
+
58
+ @app.command("list")
59
+ def list_events(
60
+ hours: int = typer.Option(
61
+ 24, "--hours", "-h", help="Hours to look back (default: 24)"
62
+ ),
63
+ limit: int = typer.Option(
64
+ 1000, "--limit", "-l", help="Max records to return (1-1000)"
65
+ ),
66
+ output: OutputFormat = typer.Option(
67
+ OutputFormat.TABLE, "--output", "-o", help="Output format"
68
+ ),
69
+ ):
70
+ """List events from the last N hours (default: 24).
71
+
72
+ Examples:
73
+
74
+ # Last 24 hours (default)
75
+ bt epmw events list
76
+
77
+ # Last 4 hours
78
+ bt epmw events list --hours 4
79
+
80
+ # Last 48 hours, max 500 records
81
+ bt epmw events list --hours 48 --limit 500
82
+ """
83
+ from bt_cli.epmw.client import get_client
84
+
85
+ try:
86
+ start_date = _get_iso_date(hours)
87
+ print_info(f"Fetching events since {start_date} (last {hours} hours)...")
88
+
89
+ client = get_client()
90
+ events = client.list_events_from_date(start_date, record_size=limit)
91
+
92
+ if output == OutputFormat.JSON:
93
+ print_json(events)
94
+ else:
95
+ if not events:
96
+ print_info("No events found in the specified time period.")
97
+ return
98
+
99
+ # Flatten nested structure for table display
100
+ flat_events = _flatten_events(events)
101
+
102
+ columns = [
103
+ ("Time", "timestamp"),
104
+ ("Computer", "hostname"),
105
+ ("User", "user"),
106
+ ("Action", "action"),
107
+ ("Application", "application"),
108
+ ("Reason", "reason"),
109
+ ]
110
+ print_table(flat_events, columns, title=f"Events (last {hours} hours)")
111
+ print_info(f"Total: {len(events)} events")
112
+ except httpx.HTTPStatusError as e:
113
+ print_api_error(e, "list events")
114
+ raise typer.Exit(1)
115
+ except httpx.RequestError as e:
116
+ print_api_error(e, "list events")
117
+ raise typer.Exit(1)
118
+ except Exception as e:
119
+ print_api_error(e, "list events")
120
+ raise typer.Exit(1)
121
+
122
+
123
+ @app.command("search")
124
+ def search_events(
125
+ hours: int = typer.Option(
126
+ 24, "--hours", "-h", help="Hours to look back (default: 24)"
127
+ ),
128
+ hostname: Optional[str] = typer.Option(
129
+ None, "--hostname", help="Filter by computer hostname"
130
+ ),
131
+ username: Optional[str] = typer.Option(
132
+ None, "--username", "-u", help="Filter by username"
133
+ ),
134
+ event_type: Optional[str] = typer.Option(
135
+ None, "--event-type", "-t", help="Filter by event type"
136
+ ),
137
+ event_action: Optional[str] = typer.Option(
138
+ None, "--event-action", "-a", help="Filter by event action"
139
+ ),
140
+ os_filter: Optional[str] = typer.Option(
141
+ None, "--os", help="Filter by operating system"
142
+ ),
143
+ workstyle: Optional[str] = typer.Option(
144
+ None, "--workstyle", help="Filter by policy workstyle name"
145
+ ),
146
+ page_size: int = typer.Option(
147
+ 100, "--page-size", help="Records per page (max 200)"
148
+ ),
149
+ page: int = typer.Option(
150
+ 1, "--page", "-p", help="Page number"
151
+ ),
152
+ output: OutputFormat = typer.Option(
153
+ OutputFormat.TABLE, "--output", "-o", help="Output format"
154
+ ),
155
+ ):
156
+ """Search events with filters.
157
+
158
+ Examples:
159
+
160
+ # Search last 24 hours for a specific user
161
+ bt epmw events search --username "john.doe"
162
+
163
+ # Search last 4 hours for a specific computer
164
+ bt epmw events search --hours 4 --hostname "WORKSTATION01"
165
+
166
+ # Search with multiple filters
167
+ bt epmw events search --hours 12 --event-type "ApplicationRun" --os "Windows"
168
+
169
+ # Get second page of results
170
+ bt epmw events search --page 2 --page-size 50
171
+ """
172
+ from bt_cli.epmw.client import get_client
173
+
174
+ try:
175
+ start_date = _get_iso_date(hours)
176
+ end_date = _get_iso_now()
177
+ print_info(f"Searching events from {start_date} to {end_date}...")
178
+
179
+ client = get_client()
180
+
181
+ # Build filter lists
182
+ event_types = [event_type] if event_type else None
183
+ event_actions = [event_action] if event_action else None
184
+
185
+ result = client.search_events(
186
+ start_date=start_date,
187
+ end_date=end_date,
188
+ operating_system=os_filter,
189
+ event_types=event_types,
190
+ event_actions=event_actions,
191
+ hostname=hostname,
192
+ username=username,
193
+ workstyle_name=workstyle,
194
+ page_size=page_size,
195
+ page_number=page,
196
+ )
197
+
198
+ if output == OutputFormat.JSON:
199
+ print_json(result)
200
+ else:
201
+ # Handle different response structures
202
+ events = result if isinstance(result, list) else result.get("data", result.get("events", []))
203
+
204
+ if not events:
205
+ print_info("No events found matching the filters.")
206
+ return
207
+
208
+ # Flatten nested structure for table display
209
+ flat_events = _flatten_events(events)
210
+
211
+ columns = [
212
+ ("Time", "timestamp"),
213
+ ("Computer", "hostname"),
214
+ ("User", "user"),
215
+ ("Action", "action"),
216
+ ("Application", "application"),
217
+ ("Reason", "reason"),
218
+ ]
219
+ print_table(flat_events, columns, title=f"Events Search Results (page {page})")
220
+
221
+ # Show pagination info if available
222
+ if isinstance(result, dict):
223
+ total = result.get("totalCount", len(events))
224
+ print_info(f"Showing {len(events)} of {total} total events")
225
+ except httpx.HTTPStatusError as e:
226
+ print_api_error(e, "search events")
227
+ raise typer.Exit(1)
228
+ except httpx.RequestError as e:
229
+ print_api_error(e, "search events")
230
+ raise typer.Exit(1)
231
+ except Exception as e:
232
+ print_api_error(e, "search events")
233
+ raise typer.Exit(1)
@@ -0,0 +1,215 @@
1
+ """EPMW groups commands."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from bt_cli.core.output import OutputFormat, print_api_error, print_error, print_json, print_table
9
+
10
+ app = typer.Typer(no_args_is_help=True, help="Computer groups management")
11
+
12
+
13
+ @app.command("list")
14
+ def list_groups(
15
+ output: OutputFormat = typer.Option(
16
+ OutputFormat.TABLE, "--output", "-o", help="Output format"
17
+ ),
18
+ ):
19
+ """List all groups."""
20
+ from bt_cli.epmw.client import get_client
21
+
22
+ try:
23
+ client = get_client()
24
+ groups = client.list_groups()
25
+
26
+ if output == OutputFormat.JSON:
27
+ print_json(groups)
28
+ else:
29
+ columns = [
30
+ ("ID", "id"),
31
+ ("Name", "name"),
32
+ ("Computers", "computerCount"),
33
+ ("Active", "activeComputers"),
34
+ ("Policy", "policyName"),
35
+ ("Revision", "revision"),
36
+ ]
37
+ print_table(groups, columns, title="Groups")
38
+ except httpx.HTTPStatusError as e:
39
+ print_api_error(e, "list groups")
40
+ raise typer.Exit(1)
41
+ except httpx.RequestError as e:
42
+ print_api_error(e, "list groups")
43
+ raise typer.Exit(1)
44
+ except Exception as e:
45
+ print_api_error(e, "list groups")
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("get")
50
+ def get_group(
51
+ group_id: str = typer.Argument(..., help="Group ID"),
52
+ output: OutputFormat = typer.Option(
53
+ OutputFormat.JSON, "--output", "-o", help="Output format"
54
+ ),
55
+ ):
56
+ """Get group details."""
57
+ from bt_cli.epmw.client import get_client
58
+
59
+ try:
60
+ client = get_client()
61
+ group = client.get_group(group_id)
62
+
63
+ if output == OutputFormat.JSON:
64
+ print_json(group)
65
+ else:
66
+ for key, value in group.items():
67
+ typer.echo(f"{key}: {value}")
68
+ except httpx.HTTPStatusError as e:
69
+ print_api_error(e, "get group")
70
+ raise typer.Exit(1)
71
+ except httpx.RequestError as e:
72
+ print_api_error(e, "get group")
73
+ raise typer.Exit(1)
74
+ except Exception as e:
75
+ print_api_error(e, "get group")
76
+ raise typer.Exit(1)
77
+
78
+
79
+ @app.command("create")
80
+ def create_group(
81
+ name: str = typer.Option(..., "--name", "-n", help="Group name"),
82
+ description: Optional[str] = typer.Option(None, "--description", "-d", help="Group description"),
83
+ output: OutputFormat = typer.Option(
84
+ OutputFormat.JSON, "--output", "-o", help="Output format"
85
+ ),
86
+ ):
87
+ """Create a new group."""
88
+ from bt_cli.epmw.client import get_client
89
+
90
+ try:
91
+ client = get_client()
92
+ data = {"name": name}
93
+ if description:
94
+ data["description"] = description
95
+ group = client.create_group(data)
96
+
97
+ typer.echo(f"Created group: {group['name']} (ID: {group['id']})")
98
+ if output == OutputFormat.JSON:
99
+ print_json(group)
100
+ except httpx.HTTPStatusError as e:
101
+ print_api_error(e, "create group")
102
+ raise typer.Exit(1)
103
+ except httpx.RequestError as e:
104
+ print_api_error(e, "create group")
105
+ raise typer.Exit(1)
106
+ except Exception as e:
107
+ print_api_error(e, "create group")
108
+ raise typer.Exit(1)
109
+
110
+
111
+ @app.command("update")
112
+ def update_group(
113
+ group_id: str = typer.Argument(..., help="Group ID"),
114
+ name: Optional[str] = typer.Option(None, "--name", "-n", help="New group name"),
115
+ description: Optional[str] = typer.Option(None, "--description", "-d", help="New description"),
116
+ output: OutputFormat = typer.Option(
117
+ OutputFormat.JSON, "--output", "-o", help="Output format"
118
+ ),
119
+ ):
120
+ """Update a group."""
121
+ from bt_cli.epmw.client import get_client
122
+
123
+ try:
124
+ client = get_client()
125
+ data = {}
126
+ if name:
127
+ data["name"] = name
128
+ if description:
129
+ data["description"] = description
130
+ if not data:
131
+ print_error("No updates specified")
132
+ raise typer.Exit(1)
133
+
134
+ group = client.update_group(group_id, data)
135
+ typer.echo(f"Updated group: {group_id}")
136
+ if output == OutputFormat.JSON and group:
137
+ print_json(group)
138
+ except httpx.HTTPStatusError as e:
139
+ print_api_error(e, "update group")
140
+ raise typer.Exit(1)
141
+ except httpx.RequestError as e:
142
+ print_api_error(e, "update group")
143
+ raise typer.Exit(1)
144
+ except Exception as e:
145
+ print_api_error(e, "update group")
146
+ raise typer.Exit(1)
147
+
148
+
149
+ @app.command("delete")
150
+ def delete_group(
151
+ group_id: str = typer.Argument(..., help="Group ID"),
152
+ ):
153
+ """Delete a group."""
154
+ from bt_cli.epmw.client import get_client
155
+
156
+ try:
157
+ client = get_client()
158
+ client.delete_group(group_id)
159
+ typer.echo(f"Deleted group: {group_id}")
160
+ except httpx.HTTPStatusError as e:
161
+ print_api_error(e, "delete group")
162
+ raise typer.Exit(1)
163
+ except httpx.RequestError as e:
164
+ print_api_error(e, "delete group")
165
+ raise typer.Exit(1)
166
+ except Exception as e:
167
+ print_api_error(e, "delete group")
168
+ raise typer.Exit(1)
169
+
170
+
171
+ @app.command("assign-policy")
172
+ def assign_policy(
173
+ group_id: str = typer.Argument(..., help="Group ID"),
174
+ policy_revision_id: str = typer.Option(..., "--policy-revision", "-p", help="Policy revision ID"),
175
+ ):
176
+ """Assign a policy revision to a group."""
177
+ from bt_cli.epmw.client import get_client
178
+
179
+ try:
180
+ client = get_client()
181
+ client.assign_policy_to_group(group_id, policy_revision_id)
182
+ typer.echo(f"Assigned policy revision {policy_revision_id} to group {group_id}")
183
+ except httpx.HTTPStatusError as e:
184
+ print_api_error(e, "assign policy")
185
+ raise typer.Exit(1)
186
+ except httpx.RequestError as e:
187
+ print_api_error(e, "assign policy")
188
+ raise typer.Exit(1)
189
+ except Exception as e:
190
+ print_api_error(e, "assign policy")
191
+ raise typer.Exit(1)
192
+
193
+
194
+ @app.command("assign-computers")
195
+ def assign_computers(
196
+ group_id: str = typer.Argument(..., help="Group ID"),
197
+ computer_ids: str = typer.Option(..., "--computers", "-c", help="Comma-separated computer IDs"),
198
+ ):
199
+ """Assign computers to a group."""
200
+ from bt_cli.epmw.client import get_client
201
+
202
+ try:
203
+ client = get_client()
204
+ ids = [c.strip() for c in computer_ids.split(",")]
205
+ client.assign_computers_to_group(group_id, ids)
206
+ typer.echo(f"Assigned {len(ids)} computer(s) to group {group_id}")
207
+ except httpx.HTTPStatusError as e:
208
+ print_api_error(e, "assign computers")
209
+ raise typer.Exit(1)
210
+ except httpx.RequestError as e:
211
+ print_api_error(e, "assign computers")
212
+ raise typer.Exit(1)
213
+ except Exception as e:
214
+ print_api_error(e, "assign computers")
215
+ raise typer.Exit(1)