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,638 @@
1
+ """Jump Item commands (shell, RDP, VNC, web, tunnels)."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from bt_cli.core.output import OutputFormat, print_table, print_json, print_error, print_success, print_api_error
9
+
10
+ app = typer.Typer(no_args_is_help=True)
11
+
12
+ # Shell Jump subcommands
13
+ shell_app = typer.Typer(no_args_is_help=True, help="Shell Jump items (SSH/Telnet)")
14
+ app.add_typer(shell_app, name="shell")
15
+
16
+
17
+ @shell_app.command("list")
18
+ def list_shell_jumps(
19
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
20
+ jumpoint_id: Optional[int] = typer.Option(None, "--jumpoint", "-j", help="Filter by Jumpoint"),
21
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
22
+ ):
23
+ """List Shell Jump items."""
24
+ from bt_cli.pra.client import get_client
25
+
26
+ try:
27
+ client = get_client()
28
+ items = client.list_shell_jumps(jump_group_id=jump_group_id, jumpoint_id=jumpoint_id)
29
+
30
+ if output == OutputFormat.JSON:
31
+ print_json(items)
32
+ else:
33
+ columns = [
34
+ ("ID", "id"),
35
+ ("Name", "name"),
36
+ ("Hostname", "hostname"),
37
+ ("Protocol", "protocol"),
38
+ ("Port", "port"),
39
+ ("Username", "username"),
40
+ ("Jumpoint", "jumpoint_id"),
41
+ ]
42
+ print_table(items, columns, title="Shell Jump Items")
43
+ except httpx.HTTPStatusError as e:
44
+ print_api_error(e, "list shell jumps")
45
+ raise typer.Exit(1)
46
+ except httpx.RequestError as e:
47
+ print_api_error(e, "list shell jumps")
48
+ raise typer.Exit(1)
49
+ except Exception as e:
50
+ print_api_error(e, "list shell jumps")
51
+ raise typer.Exit(1)
52
+
53
+
54
+ @shell_app.command("get")
55
+ def get_shell_jump(
56
+ item_id: int = typer.Argument(..., help="Shell Jump ID"),
57
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
58
+ ):
59
+ """Get Shell Jump details."""
60
+ from bt_cli.pra.client import get_client
61
+ from rich.console import Console
62
+ from rich.panel import Panel
63
+
64
+ console = Console()
65
+
66
+ try:
67
+ client = get_client()
68
+ item = client.get_shell_jump(item_id)
69
+
70
+ if output == OutputFormat.JSON:
71
+ print_json(item)
72
+ else:
73
+ name = item.get("name", "")
74
+ hostname = item.get("hostname", "")
75
+ protocol = item.get("protocol", "")
76
+ port = item.get("port", "")
77
+ username = item.get("username", "") or "-"
78
+ jumpoint = item.get("jumpoint_id", "")
79
+ jump_group = item.get("jump_group_id", "")
80
+ tag = item.get("tag", "") or "-"
81
+ comments = item.get("comments", "") or "-"
82
+
83
+ console.print(Panel(
84
+ f"[bold]{name}[/bold]\n\n"
85
+ f"[dim]Hostname:[/dim] {hostname}\n"
86
+ f"[dim]Protocol:[/dim] {protocol}\n"
87
+ f"[dim]Port:[/dim] {port}\n"
88
+ f"[dim]Username:[/dim] {username}\n"
89
+ f"[dim]Jumpoint ID:[/dim] {jumpoint}\n"
90
+ f"[dim]Jump Group ID:[/dim] {jump_group}\n"
91
+ f"[dim]Tag:[/dim] {tag}\n"
92
+ f"[dim]Comments:[/dim] {comments}",
93
+ title="Shell Jump Details",
94
+ subtitle=f"ID: {item.get('id', '')}",
95
+ ))
96
+ except httpx.HTTPStatusError as e:
97
+ print_api_error(e, "get shell jump")
98
+ raise typer.Exit(1)
99
+ except httpx.RequestError as e:
100
+ print_api_error(e, "get shell jump")
101
+ raise typer.Exit(1)
102
+ except Exception as e:
103
+ print_api_error(e, "get shell jump")
104
+ raise typer.Exit(1)
105
+
106
+
107
+ @shell_app.command("create")
108
+ def create_shell_jump(
109
+ name: str = typer.Option(..., "--name", "-n", help="Jump item name"),
110
+ hostname: str = typer.Option(..., "--hostname", "-h", help="Target hostname or IP"),
111
+ jumpoint_id: int = typer.Option(..., "--jumpoint", "-j", help="Jumpoint ID"),
112
+ jump_group_id: int = typer.Option(..., "--jump-group", "-g", help="Jump Group ID"),
113
+ protocol: str = typer.Option("ssh", "--protocol", "-p", help="ssh or telnet"),
114
+ port: int = typer.Option(22, "--port", help="Port number"),
115
+ username: Optional[str] = typer.Option(None, "--username", "-u", help="Default username"),
116
+ tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Tag"),
117
+ comments: Optional[str] = typer.Option(None, "--comments", "-c", help="Comments"),
118
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
119
+ ):
120
+ """Create a Shell Jump item."""
121
+ from bt_cli.pra.client import get_client
122
+
123
+ try:
124
+ client = get_client()
125
+ item = client.create_shell_jump(
126
+ name=name,
127
+ hostname=hostname,
128
+ jumpoint_id=jumpoint_id,
129
+ jump_group_id=jump_group_id,
130
+ protocol=protocol,
131
+ port=port,
132
+ username=username,
133
+ tag=tag,
134
+ comments=comments,
135
+ )
136
+ print_success(f"Created shell jump: {item.get('name')} (ID: {item.get('id')})")
137
+ if output == OutputFormat.JSON:
138
+ print_json(item)
139
+ except httpx.HTTPStatusError as e:
140
+ print_api_error(e, "create shell jump")
141
+ raise typer.Exit(1)
142
+ except httpx.RequestError as e:
143
+ print_api_error(e, "create shell jump")
144
+ raise typer.Exit(1)
145
+ except Exception as e:
146
+ print_api_error(e, "create shell jump")
147
+ raise typer.Exit(1)
148
+
149
+
150
+ @shell_app.command("update")
151
+ def update_shell_jump(
152
+ item_id: int = typer.Argument(..., help="Shell Jump ID"),
153
+ name: Optional[str] = typer.Option(None, "--name", "-n", help="New name"),
154
+ hostname: Optional[str] = typer.Option(None, "--hostname", "-h", help="New hostname or IP"),
155
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="New Jump Group ID"),
156
+ protocol: Optional[str] = typer.Option(None, "--protocol", "-p", help="New protocol (ssh or telnet)"),
157
+ port: Optional[int] = typer.Option(None, "--port", help="New port number"),
158
+ username: Optional[str] = typer.Option(None, "--username", "-u", help="New username"),
159
+ tag: Optional[str] = typer.Option(None, "--tag", "-t", help="New tag"),
160
+ comments: Optional[str] = typer.Option(None, "--comments", "-c", help="New comments"),
161
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
162
+ ):
163
+ """Update a Shell Jump item."""
164
+ from bt_cli.pra.client import get_client
165
+
166
+ # Check if at least one field is provided
167
+ if all(v is None for v in [name, hostname, jump_group_id, protocol, port, username, tag, comments]):
168
+ print_error("At least one field must be provided to update")
169
+ raise typer.Exit(1)
170
+
171
+ try:
172
+ client = get_client()
173
+ item = client.update_shell_jump(
174
+ item_id=item_id,
175
+ name=name,
176
+ hostname=hostname,
177
+ jump_group_id=jump_group_id,
178
+ protocol=protocol,
179
+ port=port,
180
+ username=username,
181
+ tag=tag,
182
+ comments=comments,
183
+ )
184
+ print_success(f"Updated shell jump: {item.get('name')} (ID: {item.get('id')})")
185
+ if output == OutputFormat.JSON:
186
+ print_json(item)
187
+ except httpx.HTTPStatusError as e:
188
+ print_api_error(e, "update shell jump")
189
+ raise typer.Exit(1)
190
+ except httpx.RequestError as e:
191
+ print_api_error(e, "update shell jump")
192
+ raise typer.Exit(1)
193
+ except Exception as e:
194
+ print_api_error(e, "update shell jump")
195
+ raise typer.Exit(1)
196
+
197
+
198
+ @shell_app.command("delete")
199
+ def delete_shell_jump(
200
+ item_id: int = typer.Argument(..., help="Shell Jump ID"),
201
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
202
+ ):
203
+ """Delete a Shell Jump item."""
204
+ from bt_cli.pra.client import get_client
205
+
206
+ if not force:
207
+ typer.confirm(f"Delete shell jump {item_id}?", abort=True)
208
+
209
+ try:
210
+ client = get_client()
211
+ client.delete_shell_jump(item_id)
212
+ print_success(f"Deleted shell jump {item_id}")
213
+ except httpx.HTTPStatusError as e:
214
+ print_api_error(e, "delete shell jump")
215
+ raise typer.Exit(1)
216
+ except httpx.RequestError as e:
217
+ print_api_error(e, "delete shell jump")
218
+ raise typer.Exit(1)
219
+ except Exception as e:
220
+ print_api_error(e, "delete shell jump")
221
+ raise typer.Exit(1)
222
+
223
+
224
+ # RDP Jump subcommands
225
+ rdp_app = typer.Typer(no_args_is_help=True, help="Remote RDP Jump items")
226
+ app.add_typer(rdp_app, name="rdp")
227
+
228
+
229
+ @rdp_app.command("list")
230
+ def list_rdp_jumps(
231
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
232
+ jumpoint_id: Optional[int] = typer.Option(None, "--jumpoint", "-j", help="Filter by Jumpoint"),
233
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
234
+ ):
235
+ """List Remote RDP Jump items."""
236
+ from bt_cli.pra.client import get_client
237
+
238
+ try:
239
+ client = get_client()
240
+ items = client.list_rdp_jumps(jump_group_id=jump_group_id, jumpoint_id=jumpoint_id)
241
+
242
+ if output == OutputFormat.JSON:
243
+ print_json(items)
244
+ else:
245
+ columns = [
246
+ ("ID", "id"),
247
+ ("Name", "name"),
248
+ ("Hostname", "hostname"),
249
+ ("Username", "rdp_username"),
250
+ ("Domain", "domain"),
251
+ ("Jumpoint", "jumpoint_id"),
252
+ ]
253
+ print_table(items, columns, title="RDP Jump Items")
254
+ except httpx.HTTPStatusError as e:
255
+ print_api_error(e, "list RDP jumps")
256
+ raise typer.Exit(1)
257
+ except httpx.RequestError as e:
258
+ print_api_error(e, "list RDP jumps")
259
+ raise typer.Exit(1)
260
+ except Exception as e:
261
+ print_api_error(e, "list RDP jumps")
262
+ raise typer.Exit(1)
263
+
264
+
265
+ @rdp_app.command("get")
266
+ def get_rdp_jump(
267
+ item_id: int = typer.Argument(..., help="RDP Jump ID"),
268
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
269
+ ):
270
+ """Get Remote RDP Jump details."""
271
+ from bt_cli.pra.client import get_client
272
+ from rich.console import Console
273
+ from rich.panel import Panel
274
+
275
+ console = Console()
276
+
277
+ try:
278
+ client = get_client()
279
+ item = client.get_rdp_jump(item_id)
280
+
281
+ if output == OutputFormat.JSON:
282
+ print_json(item)
283
+ else:
284
+ name = item.get("name", "")
285
+ hostname = item.get("hostname", "")
286
+ port = item.get("rdp_port", item.get("port", ""))
287
+ domain = item.get("domain", "") or "-"
288
+ jumpoint = item.get("jumpoint_id", "")
289
+ jump_group = item.get("jump_group_id", "")
290
+ tag = item.get("tag", "") or "-"
291
+ comments = item.get("comments", "") or "-"
292
+
293
+ console.print(Panel(
294
+ f"[bold]{name}[/bold]\n\n"
295
+ f"[dim]Hostname:[/dim] {hostname}\n"
296
+ f"[dim]Port:[/dim] {port}\n"
297
+ f"[dim]Domain:[/dim] {domain}\n"
298
+ f"[dim]Jumpoint ID:[/dim] {jumpoint}\n"
299
+ f"[dim]Jump Group ID:[/dim] {jump_group}\n"
300
+ f"[dim]Tag:[/dim] {tag}\n"
301
+ f"[dim]Comments:[/dim] {comments}",
302
+ title="RDP Jump Details",
303
+ subtitle=f"ID: {item.get('id', '')}",
304
+ ))
305
+ except httpx.HTTPStatusError as e:
306
+ print_api_error(e, "get RDP jump")
307
+ raise typer.Exit(1)
308
+ except httpx.RequestError as e:
309
+ print_api_error(e, "get RDP jump")
310
+ raise typer.Exit(1)
311
+ except Exception as e:
312
+ print_api_error(e, "get RDP jump")
313
+ raise typer.Exit(1)
314
+
315
+
316
+ @rdp_app.command("create")
317
+ def create_rdp_jump(
318
+ name: str = typer.Option(..., "--name", "-n", help="Jump item name"),
319
+ hostname: str = typer.Option(..., "--hostname", "-h", help="Target hostname or IP"),
320
+ jumpoint_id: int = typer.Option(..., "--jumpoint", "-j", help="Jumpoint ID"),
321
+ jump_group_id: int = typer.Option(..., "--jump-group", "-g", help="Jump Group ID"),
322
+ rdp_username: Optional[str] = typer.Option(None, "--username", "-u", help="RDP username"),
323
+ domain: Optional[str] = typer.Option(None, "--domain", "-d", help="Domain"),
324
+ tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Tag"),
325
+ comments: Optional[str] = typer.Option(None, "--comments", "-c", help="Comments"),
326
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
327
+ ):
328
+ """Create a Remote RDP Jump item."""
329
+ from bt_cli.pra.client import get_client
330
+
331
+ try:
332
+ client = get_client()
333
+ item = client.create_rdp_jump(
334
+ name=name,
335
+ hostname=hostname,
336
+ jumpoint_id=jumpoint_id,
337
+ jump_group_id=jump_group_id,
338
+ rdp_username=rdp_username,
339
+ domain=domain,
340
+ tag=tag,
341
+ comments=comments,
342
+ )
343
+ print_success(f"Created RDP jump: {item.get('name')} (ID: {item.get('id')})")
344
+ if output == OutputFormat.JSON:
345
+ print_json(item)
346
+ except httpx.HTTPStatusError as e:
347
+ print_api_error(e, "create RDP jump")
348
+ raise typer.Exit(1)
349
+ except httpx.RequestError as e:
350
+ print_api_error(e, "create RDP jump")
351
+ raise typer.Exit(1)
352
+ except Exception as e:
353
+ print_api_error(e, "create RDP jump")
354
+ raise typer.Exit(1)
355
+
356
+
357
+ @rdp_app.command("delete")
358
+ def delete_rdp_jump(
359
+ item_id: int = typer.Argument(..., help="RDP Jump ID"),
360
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
361
+ ):
362
+ """Delete a Remote RDP Jump item."""
363
+ from bt_cli.pra.client import get_client
364
+
365
+ if not force:
366
+ typer.confirm(f"Delete RDP jump {item_id}?", abort=True)
367
+
368
+ try:
369
+ client = get_client()
370
+ client.delete_rdp_jump(item_id)
371
+ print_success(f"Deleted RDP jump {item_id}")
372
+ except httpx.HTTPStatusError as e:
373
+ print_api_error(e, "delete RDP jump")
374
+ raise typer.Exit(1)
375
+ except httpx.RequestError as e:
376
+ print_api_error(e, "delete RDP jump")
377
+ raise typer.Exit(1)
378
+ except Exception as e:
379
+ print_api_error(e, "delete RDP jump")
380
+ raise typer.Exit(1)
381
+
382
+
383
+ # VNC Jump subcommands
384
+ vnc_app = typer.Typer(no_args_is_help=True, help="Remote VNC Jump items")
385
+ app.add_typer(vnc_app, name="vnc")
386
+
387
+
388
+ @vnc_app.command("list")
389
+ def list_vnc_jumps(
390
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
391
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
392
+ ):
393
+ """List Remote VNC Jump items."""
394
+ from bt_cli.pra.client import get_client
395
+
396
+ try:
397
+ client = get_client()
398
+ items = client.list_vnc_jumps(jump_group_id=jump_group_id)
399
+
400
+ if output == OutputFormat.JSON:
401
+ print_json(items)
402
+ else:
403
+ columns = [
404
+ ("ID", "id"),
405
+ ("Name", "name"),
406
+ ("Hostname", "hostname"),
407
+ ("Port", "port"),
408
+ ("Jumpoint", "jumpoint_id"),
409
+ ]
410
+ print_table(items, columns, title="VNC Jump Items")
411
+ except httpx.HTTPStatusError as e:
412
+ print_api_error(e, "list VNC jumps")
413
+ raise typer.Exit(1)
414
+ except httpx.RequestError as e:
415
+ print_api_error(e, "list VNC jumps")
416
+ raise typer.Exit(1)
417
+ except Exception as e:
418
+ print_api_error(e, "list VNC jumps")
419
+ raise typer.Exit(1)
420
+
421
+
422
+ # Web Jump subcommands
423
+ web_app = typer.Typer(no_args_is_help=True, help="Web Jump items")
424
+ app.add_typer(web_app, name="web")
425
+
426
+
427
+ @web_app.command("list")
428
+ def list_web_jumps(
429
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
430
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
431
+ ):
432
+ """List Web Jump items."""
433
+ from bt_cli.pra.client import get_client
434
+
435
+ try:
436
+ client = get_client()
437
+ items = client.list_web_jumps(jump_group_id=jump_group_id)
438
+
439
+ if output == OutputFormat.JSON:
440
+ print_json(items)
441
+ else:
442
+ columns = [
443
+ ("ID", "id"),
444
+ ("Name", "name"),
445
+ ("URL", "url"),
446
+ ("Username", "username"),
447
+ ("Jumpoint", "jumpoint_id"),
448
+ ]
449
+ print_table(items, columns, title="Web Jump Items")
450
+ except httpx.HTTPStatusError as e:
451
+ print_api_error(e, "list web jumps")
452
+ raise typer.Exit(1)
453
+ except httpx.RequestError as e:
454
+ print_api_error(e, "list web jumps")
455
+ raise typer.Exit(1)
456
+ except Exception as e:
457
+ print_api_error(e, "list web jumps")
458
+ raise typer.Exit(1)
459
+
460
+
461
+ # Protocol Tunnel subcommands (TCP, MSSQL, PostgreSQL, MySQL, K8s)
462
+ tunnel_app = typer.Typer(no_args_is_help=True, help="Protocol Tunnel Jump items (TCP, MSSQL, PostgreSQL, MySQL, K8s)")
463
+ app.add_typer(tunnel_app, name="tunnel")
464
+
465
+
466
+ @tunnel_app.command("list")
467
+ def list_protocol_tunnels(
468
+ jump_group_id: Optional[int] = typer.Option(None, "--jump-group", "-g", help="Filter by Jump Group"),
469
+ tunnel_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by type: tcp, mssql, psql, mysql, k8s"),
470
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
471
+ ):
472
+ """List Protocol Tunnel Jump items."""
473
+ from bt_cli.pra.client import get_client
474
+
475
+ try:
476
+ client = get_client()
477
+ items = client.list_protocol_tunnels(jump_group_id=jump_group_id, tunnel_type=tunnel_type)
478
+
479
+ if output == OutputFormat.JSON:
480
+ print_json(items)
481
+ else:
482
+ columns = [
483
+ ("ID", "id"),
484
+ ("Name", "name"),
485
+ ("Hostname", "hostname"),
486
+ ("Type", "tunnel_type"),
487
+ ("URL", "url"),
488
+ ("Jumpoint", "jumpoint_id"),
489
+ ]
490
+ print_table(items, columns, title="Protocol Tunnel Jump Items")
491
+ except httpx.HTTPStatusError as e:
492
+ print_api_error(e, "list protocol tunnels")
493
+ raise typer.Exit(1)
494
+ except httpx.RequestError as e:
495
+ print_api_error(e, "list protocol tunnels")
496
+ raise typer.Exit(1)
497
+ except Exception as e:
498
+ print_api_error(e, "list protocol tunnels")
499
+ raise typer.Exit(1)
500
+
501
+
502
+ @tunnel_app.command("get")
503
+ def get_protocol_tunnel(
504
+ item_id: int = typer.Argument(..., help="Protocol Tunnel ID"),
505
+ output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o"),
506
+ ):
507
+ """Get Protocol Tunnel details."""
508
+ from bt_cli.pra.client import get_client
509
+ from rich.console import Console
510
+ from rich.panel import Panel
511
+
512
+ console = Console()
513
+
514
+ try:
515
+ client = get_client()
516
+ item = client.get_protocol_tunnel(item_id)
517
+
518
+ if output == OutputFormat.JSON:
519
+ print_json(item)
520
+ else:
521
+ name = item.get("name", "")
522
+ hostname = item.get("hostname", "")
523
+ tunnel_type = item.get("tunnel_type", "")
524
+ username = item.get("username", "") or "-"
525
+ database = item.get("database", "") or "-"
526
+ url = item.get("url", "") or "-"
527
+ jumpoint = item.get("jumpoint_id", "")
528
+ jump_group = item.get("jump_group_id", "")
529
+ tag = item.get("tag", "") or "-"
530
+ comments = item.get("comments", "") or "-"
531
+
532
+ content = (
533
+ f"[bold]{name}[/bold]\n\n"
534
+ f"[dim]Hostname:[/dim] {hostname}\n"
535
+ f"[dim]Type:[/dim] {tunnel_type}\n"
536
+ )
537
+ # Show database fields for database tunnel types
538
+ if tunnel_type in ("mssql", "psql", "mysql"):
539
+ content += f"[dim]Username:[/dim] {username}\n"
540
+ content += f"[dim]Database:[/dim] {database}\n"
541
+ if tunnel_type == "k8s":
542
+ content += f"[dim]URL:[/dim] {url}\n"
543
+ content += (
544
+ f"[dim]Jumpoint ID:[/dim] {jumpoint}\n"
545
+ f"[dim]Jump Group ID:[/dim] {jump_group}\n"
546
+ f"[dim]Tag:[/dim] {tag}\n"
547
+ f"[dim]Comments:[/dim] {comments}"
548
+ )
549
+
550
+ console.print(Panel(
551
+ content,
552
+ title="Protocol Tunnel Details",
553
+ subtitle=f"ID: {item.get('id', '')}",
554
+ ))
555
+ except httpx.HTTPStatusError as e:
556
+ print_api_error(e, "get protocol tunnel")
557
+ raise typer.Exit(1)
558
+ except httpx.RequestError as e:
559
+ print_api_error(e, "get protocol tunnel")
560
+ raise typer.Exit(1)
561
+ except Exception as e:
562
+ print_api_error(e, "get protocol tunnel")
563
+ raise typer.Exit(1)
564
+
565
+
566
+ @tunnel_app.command("create")
567
+ def create_protocol_tunnel(
568
+ name: str = typer.Option(..., "--name", "-n", help="Jump item name"),
569
+ hostname: str = typer.Option(..., "--hostname", "-h", help="Target hostname (auto-set for k8s)"),
570
+ jumpoint_id: int = typer.Option(..., "--jumpoint", "-j", help="Jumpoint ID (Linux for k8s)"),
571
+ jump_group_id: int = typer.Option(..., "--jump-group", "-g", help="Jump Group ID"),
572
+ tunnel_type: str = typer.Option("tcp", "--type", "-t", help="tcp, mssql, psql, mysql, or k8s"),
573
+ tunnel_definitions: Optional[str] = typer.Option(None, "--ports", help="TCP port pairs (e.g., '22;24;26;28')"),
574
+ username: Optional[str] = typer.Option(None, "--username", "-u", help="Database username (for mssql, psql, mysql)"),
575
+ database: Optional[str] = typer.Option(None, "--database", "-d", help="Database name (for mssql, psql, mysql)"),
576
+ url: Optional[str] = typer.Option(None, "--url", help="K8s API URL (required for k8s)"),
577
+ ca_certificates: Optional[str] = typer.Option(None, "--ca-cert", help="K8s CA cert (required for k8s)"),
578
+ tag: Optional[str] = typer.Option(None, "--tag", help="Tag"),
579
+ comments: Optional[str] = typer.Option(None, "--comments", "-c", help="Comments"),
580
+ output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
581
+ ):
582
+ """Create a Protocol Tunnel Jump item (TCP, MSSQL, PostgreSQL, MySQL, or K8s)."""
583
+ from bt_cli.pra.client import get_client
584
+
585
+ try:
586
+ client = get_client()
587
+ item = client.create_protocol_tunnel(
588
+ name=name,
589
+ hostname=hostname,
590
+ jumpoint_id=jumpoint_id,
591
+ jump_group_id=jump_group_id,
592
+ tunnel_type=tunnel_type,
593
+ tunnel_definitions=tunnel_definitions,
594
+ username=username,
595
+ database=database,
596
+ url=url,
597
+ ca_certificates=ca_certificates,
598
+ tag=tag,
599
+ comments=comments,
600
+ )
601
+ print_success(f"Created protocol tunnel: {item.get('name')} (ID: {item.get('id')})")
602
+ if output == OutputFormat.JSON:
603
+ print_json(item)
604
+ except httpx.HTTPStatusError as e:
605
+ print_api_error(e, "create protocol tunnel")
606
+ raise typer.Exit(1)
607
+ except httpx.RequestError as e:
608
+ print_api_error(e, "create protocol tunnel")
609
+ raise typer.Exit(1)
610
+ except Exception as e:
611
+ print_api_error(e, "create protocol tunnel")
612
+ raise typer.Exit(1)
613
+
614
+
615
+ @tunnel_app.command("delete")
616
+ def delete_protocol_tunnel(
617
+ item_id: int = typer.Argument(..., help="Protocol Tunnel ID"),
618
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
619
+ ):
620
+ """Delete a Protocol Tunnel Jump item."""
621
+ from bt_cli.pra.client import get_client
622
+
623
+ if not force:
624
+ typer.confirm(f"Delete protocol tunnel {item_id}?", abort=True)
625
+
626
+ try:
627
+ client = get_client()
628
+ client.delete_protocol_tunnel(item_id)
629
+ print_success(f"Deleted protocol tunnel {item_id}")
630
+ except httpx.HTTPStatusError as e:
631
+ print_api_error(e, "delete protocol tunnel")
632
+ raise typer.Exit(1)
633
+ except httpx.RequestError as e:
634
+ print_api_error(e, "delete protocol tunnel")
635
+ raise typer.Exit(1)
636
+ except Exception as e:
637
+ print_api_error(e, "delete protocol tunnel")
638
+ raise typer.Exit(1)