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,218 @@
1
+ """Bundle commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_success, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage bundles")
12
+
13
+
14
+ @app.command("list")
15
+ def list_bundles(
16
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
17
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
18
+ ) -> None:
19
+ """List all bundles with full details."""
20
+ try:
21
+ with get_client() as client:
22
+ # List API only returns id/name, so fetch full details for each
23
+ bundles_list = client.list_bundles(search=search)
24
+
25
+ # Fetch full details for each bundle to get description, category, tags
26
+ data = []
27
+ for bundle in bundles_list:
28
+ bundle_id = bundle.get("id")
29
+ if bundle_id:
30
+ response = client.get_bundle(bundle_id)
31
+ # API returns {"result": {...}}, extract the result
32
+ full_bundle = response.get("result", response)
33
+ data.append(full_bundle)
34
+
35
+ if output == "json":
36
+ print_json(data)
37
+ else:
38
+ # Format for display - truncate description, format tags
39
+ display_data = []
40
+ for bundle in data:
41
+ desc = bundle.get("description", "") or ""
42
+ tags = bundle.get("tags", []) or []
43
+ display_data.append({
44
+ "id": bundle.get("id"),
45
+ "name": bundle.get("name"),
46
+ "category": bundle.get("category"),
47
+ "description": desc[:50] + "..." if len(desc) > 50 else desc,
48
+ "tags": ", ".join(tags[:3]) + (f" (+{len(tags)-3})" if len(tags) > 3 else "") if tags else "-",
49
+ })
50
+
51
+ print_table(
52
+ display_data,
53
+ [
54
+ ("ID", "id"),
55
+ ("Name", "name"),
56
+ ("Category", "category"),
57
+ ("Description", "description"),
58
+ ("Tags", "tags"),
59
+ ],
60
+ title="Bundles",
61
+ )
62
+ except httpx.HTTPStatusError as e:
63
+ print_api_error(e, "list bundles")
64
+ raise typer.Exit(1)
65
+ except httpx.RequestError as e:
66
+ print_api_error(e, "list bundles")
67
+ raise typer.Exit(1)
68
+ except Exception as e:
69
+ print_api_error(e, "list bundles")
70
+ raise typer.Exit(1)
71
+
72
+
73
+ @app.command("get")
74
+ def get_bundle(
75
+ bundle_id: str = typer.Argument(..., help="Bundle ID"),
76
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
77
+ ) -> None:
78
+ """Get a bundle by ID with full details."""
79
+ try:
80
+ with get_client() as client:
81
+ response = client.get_bundle(bundle_id)
82
+
83
+ bundle = response.get("result", response)
84
+
85
+ if output == "json":
86
+ print_json(response)
87
+ else:
88
+ from rich.panel import Panel
89
+ from rich.table import Table
90
+
91
+ # Header info
92
+ name = bundle.get("name", "")
93
+ desc = bundle.get("description", "") or "-"
94
+ category = bundle.get("category", "") or "-"
95
+ tags = bundle.get("tags", []) or []
96
+ durations = bundle.get("allowedDurations", [])
97
+ workflow = bundle.get("workflow", {})
98
+
99
+ # Format durations as human-readable
100
+ def fmt_duration(secs: int) -> str:
101
+ if secs >= 86400:
102
+ return f"{secs // 86400}d"
103
+ elif secs >= 3600:
104
+ return f"{secs // 3600}h"
105
+ else:
106
+ return f"{secs // 60}m"
107
+
108
+ duration_str = ", ".join(fmt_duration(d) for d in durations) if durations else "-"
109
+
110
+ console.print(Panel(
111
+ f"[bold]{name}[/bold]\n\n"
112
+ f"[dim]Category:[/dim] {category}\n"
113
+ f"[dim]Tags:[/dim] {', '.join(tags) if tags else '-'}\n"
114
+ f"[dim]Durations:[/dim] {duration_str}\n"
115
+ f"[dim]Workflow:[/dim] {workflow.get('name', '-')}",
116
+ title="Bundle Details",
117
+ subtitle=f"ID: {bundle.get('id', '')}",
118
+ ))
119
+
120
+ console.print(f"\n[dim]{desc}[/dim]\n")
121
+
122
+ # Roles table
123
+ roles = bundle.get("roles", [])
124
+ if roles:
125
+ table = Table(title="Included Roles")
126
+ table.add_column("Role", style="cyan")
127
+ table.add_column("Resource", style="green")
128
+ table.add_column("Integration", style="yellow")
129
+
130
+ for role in roles:
131
+ resource = role.get("resource", {})
132
+ integration = resource.get("integration", {})
133
+ app_name = integration.get("application", {}).get("name", "")
134
+ int_name = integration.get("name", "")
135
+ int_display = f"{int_name} ({app_name})" if app_name else int_name
136
+
137
+ table.add_row(
138
+ role.get("name", "-"),
139
+ resource.get("name", "-"),
140
+ int_display or "-",
141
+ )
142
+ console.print(table)
143
+ else:
144
+ console.print("[yellow]No roles defined[/yellow]")
145
+
146
+ except httpx.HTTPStatusError as e:
147
+ print_api_error(e, "get bundle")
148
+ raise typer.Exit(1)
149
+ except httpx.RequestError as e:
150
+ print_api_error(e, "get bundle")
151
+ raise typer.Exit(1)
152
+ except Exception as e:
153
+ print_api_error(e, "get bundle")
154
+ raise typer.Exit(1)
155
+
156
+
157
+ @app.command("create")
158
+ def create_bundle(
159
+ name: str = typer.Option(..., "--name", "-n", help="Bundle name"),
160
+ description: str = typer.Option(..., "--description", "-d", help="Bundle description"),
161
+ workflow_id: str = typer.Option(..., "--workflow", "-w", help="Workflow ID"),
162
+ role_ids: list[str] = typer.Option([], "--role", "-r", help="Role IDs to include"),
163
+ durations: list[int] = typer.Option([3600], "--duration", help="Allowed durations in seconds"),
164
+ category: Optional[str] = typer.Option(None, "--category", "-c", help="Category"),
165
+ tags: list[str] = typer.Option([], "--tag", "-t", help="Tags"),
166
+ ) -> None:
167
+ """Create a new bundle."""
168
+ try:
169
+ data = {
170
+ "name": name,
171
+ "description": description,
172
+ "workflow": {"id": workflow_id},
173
+ "roles": [{"id": rid} for rid in role_ids],
174
+ "allowedDurations": durations,
175
+ }
176
+ if category:
177
+ data["category"] = category
178
+ if tags:
179
+ data["tags"] = tags
180
+
181
+ with get_client() as client:
182
+ result = client.create_bundle(data)
183
+
184
+ print_success("Bundle created successfully!")
185
+ print_json(result)
186
+ except httpx.HTTPStatusError as e:
187
+ print_api_error(e, "create bundle")
188
+ raise typer.Exit(1)
189
+ except httpx.RequestError as e:
190
+ print_api_error(e, "create bundle")
191
+ raise typer.Exit(1)
192
+ except Exception as e:
193
+ print_api_error(e, "create bundle")
194
+ raise typer.Exit(1)
195
+
196
+
197
+ @app.command("delete")
198
+ def delete_bundle(
199
+ bundle_id: str = typer.Argument(..., help="Bundle ID"),
200
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
201
+ ) -> None:
202
+ """Delete a bundle."""
203
+ if not confirm:
204
+ typer.confirm(f"Are you sure you want to delete bundle {bundle_id}?", abort=True)
205
+
206
+ try:
207
+ with get_client() as client:
208
+ client.delete_bundle(bundle_id)
209
+ print_success("Bundle deleted successfully!")
210
+ except httpx.HTTPStatusError as e:
211
+ print_api_error(e, "delete bundle")
212
+ raise typer.Exit(1)
213
+ except httpx.RequestError as e:
214
+ print_api_error(e, "delete bundle")
215
+ raise typer.Exit(1)
216
+ except Exception as e:
217
+ print_api_error(e, "delete bundle")
218
+ raise typer.Exit(1)
@@ -0,0 +1,60 @@
1
+ """Integration commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage integrations")
12
+
13
+
14
+ @app.command("list")
15
+ def list_integrations(
16
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
17
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
18
+ ) -> None:
19
+ """List all integrations."""
20
+ try:
21
+ with get_client() as client:
22
+ data = client.list_integrations(search=search)
23
+
24
+ if output == "json":
25
+ print_json(data)
26
+ else:
27
+ print_table(
28
+ data,
29
+ [("ID", "id"), ("Name", "name"), ("Application", "application"), ("Owner", "owner")],
30
+ title="Integrations",
31
+ )
32
+ except httpx.HTTPStatusError as e:
33
+ print_api_error(e, "list integrations")
34
+ raise typer.Exit(1)
35
+ except httpx.RequestError as e:
36
+ print_api_error(e, "list integrations")
37
+ raise typer.Exit(1)
38
+ except Exception as e:
39
+ print_api_error(e, "list integrations")
40
+ raise typer.Exit(1)
41
+
42
+
43
+ @app.command("get")
44
+ def get_integration(
45
+ integration_id: str = typer.Argument(..., help="Integration ID"),
46
+ ) -> None:
47
+ """Get an integration by ID."""
48
+ try:
49
+ with get_client() as client:
50
+ data = client.get_integration(integration_id)
51
+ print_json(data)
52
+ except httpx.HTTPStatusError as e:
53
+ print_api_error(e, "get integration")
54
+ raise typer.Exit(1)
55
+ except httpx.RequestError as e:
56
+ print_api_error(e, "get integration")
57
+ raise typer.Exit(1)
58
+ except Exception as e:
59
+ print_api_error(e, "get integration")
60
+ raise typer.Exit(1)
@@ -0,0 +1,70 @@
1
+ """Permission commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_success, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage permissions")
12
+
13
+
14
+ @app.command("list")
15
+ def list_permissions(
16
+ user_id: Optional[str] = typer.Option(None, "--user", "-u", help="User ID filter"),
17
+ integration_id: Optional[str] = typer.Option(None, "--integration", "-i", help="Integration ID filter"),
18
+ resource_id: Optional[str] = typer.Option(None, "--resource", "-r", help="Resource ID filter"),
19
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
20
+ ) -> None:
21
+ """List permissions."""
22
+ try:
23
+ with get_client() as client:
24
+ data = client.list_permissions(
25
+ user_id=user_id,
26
+ integration_id=integration_id,
27
+ resource_id=resource_id,
28
+ )
29
+
30
+ if output == "json":
31
+ print_json(data)
32
+ else:
33
+ print_table(
34
+ data,
35
+ [("ID", "id"), ("User", "user"), ("Role", "role"), ("Resource", "resource"), ("Status", "status"), ("Expires", "expires_at")],
36
+ title="Permissions",
37
+ )
38
+ except httpx.HTTPStatusError as e:
39
+ print_api_error(e, "list permissions")
40
+ raise typer.Exit(1)
41
+ except httpx.RequestError as e:
42
+ print_api_error(e, "list permissions")
43
+ raise typer.Exit(1)
44
+ except Exception as e:
45
+ print_api_error(e, "list permissions")
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("revoke")
50
+ def revoke_permission(
51
+ permission_id: str = typer.Argument(..., help="Permission ID"),
52
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
53
+ ) -> None:
54
+ """Revoke a permission."""
55
+ if not confirm:
56
+ typer.confirm(f"Are you sure you want to revoke permission {permission_id}?", abort=True)
57
+
58
+ try:
59
+ with get_client() as client:
60
+ client.revoke_permission(permission_id)
61
+ print_success("Permission revoked successfully!")
62
+ except httpx.HTTPStatusError as e:
63
+ print_api_error(e, "revoke permission")
64
+ raise typer.Exit(1)
65
+ except httpx.RequestError as e:
66
+ print_api_error(e, "revoke permission")
67
+ raise typer.Exit(1)
68
+ except Exception as e:
69
+ print_api_error(e, "revoke permission")
70
+ raise typer.Exit(1)
@@ -0,0 +1,97 @@
1
+ """Policy commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage policies")
12
+
13
+
14
+ @app.command("list")
15
+ def list_policies(
16
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
17
+ ) -> None:
18
+ """List all policies with details."""
19
+ try:
20
+ with get_client() as client:
21
+ # List API only returns id/number/sortOrder, fetch full details
22
+ policies_list = client.list_policies()
23
+
24
+ data = []
25
+ for policy in policies_list:
26
+ policy_id = policy.get("id")
27
+ if policy_id:
28
+ response = client.get_policy(policy_id)
29
+ full_policy = response.get("result", response)
30
+ data.append(full_policy)
31
+
32
+ if output == "json":
33
+ print_json(data)
34
+ else:
35
+ # Format for display - show group names and counts
36
+ display_data = []
37
+ for policy in data:
38
+ in_groups = policy.get("inGroups", [])
39
+ bundles = policy.get("bundles", [])
40
+ roles = policy.get("roles", [])
41
+
42
+ # Show first group name + count
43
+ if in_groups:
44
+ first_group = in_groups[0].get("name", "")[:25]
45
+ groups_display = first_group + (f" (+{len(in_groups)-1})" if len(in_groups) > 1 else "")
46
+ else:
47
+ groups_display = "-"
48
+
49
+ # Show bundle names
50
+ if bundles:
51
+ bundle_names = [b.get("name", "")[:20] for b in bundles[:2]]
52
+ bundles_display = ", ".join(bundle_names) + (f" (+{len(bundles)-2})" if len(bundles) > 2 else "")
53
+ else:
54
+ bundles_display = "-"
55
+
56
+ display_data.append({
57
+ "number": policy.get("number"),
58
+ "inGroups": groups_display,
59
+ "bundles": bundles_display,
60
+ "roles": len(roles) if roles else "-",
61
+ "sortOrder": policy.get("sortOrder"),
62
+ })
63
+
64
+ print_table(
65
+ display_data,
66
+ [("#", "number"), ("Groups", "inGroups"), ("Bundles", "bundles"), ("Roles", "roles"), ("Order", "sortOrder")],
67
+ title="Policies",
68
+ )
69
+ except httpx.HTTPStatusError as e:
70
+ print_api_error(e, "list policies")
71
+ raise typer.Exit(1)
72
+ except httpx.RequestError as e:
73
+ print_api_error(e, "list policies")
74
+ raise typer.Exit(1)
75
+ except Exception as e:
76
+ print_api_error(e, "list policies")
77
+ raise typer.Exit(1)
78
+
79
+
80
+ @app.command("get")
81
+ def get_policy(
82
+ policy_id: str = typer.Argument(..., help="Policy ID"),
83
+ ) -> None:
84
+ """Get a policy by ID."""
85
+ try:
86
+ with get_client() as client:
87
+ data = client.get_policy(policy_id)
88
+ print_json(data)
89
+ except httpx.HTTPStatusError as e:
90
+ print_api_error(e, "get policy")
91
+ raise typer.Exit(1)
92
+ except httpx.RequestError as e:
93
+ print_api_error(e, "get policy")
94
+ raise typer.Exit(1)
95
+ except Exception as e:
96
+ print_api_error(e, "get policy")
97
+ raise typer.Exit(1)
@@ -0,0 +1,131 @@
1
+ """Resource commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage resources")
12
+
13
+
14
+ @app.command("list")
15
+ def list_resources(
16
+ integration_id: str = typer.Option(..., "--integration", "-i", help="Integration ID (required)"),
17
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
18
+ limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
19
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
20
+ ) -> None:
21
+ """List resources for an integration.
22
+
23
+ The Entitle API requires an integration ID to list resources.
24
+ Use 'bt entitle integrations list' to find integration IDs.
25
+
26
+ Examples:
27
+ bt entitle resources list -i <integration_id>
28
+ bt entitle resources list -i <integration_id> -s "search term"
29
+ bt entitle resources list -i <integration_id> -l 50 -o json
30
+ """
31
+ try:
32
+ with get_client() as client:
33
+ data = client.list_resources(integration_id=integration_id, search=search, limit=limit)
34
+
35
+ if output == "json":
36
+ print_json(data)
37
+ else:
38
+ print_table(
39
+ data,
40
+ [("ID", "id"), ("Name", "name"), ("Integration", "integration"), ("Requestable", "requestable")],
41
+ title="Resources",
42
+ )
43
+ except httpx.HTTPStatusError as e:
44
+ print_api_error(e, "list resources")
45
+ raise typer.Exit(1)
46
+ except httpx.RequestError as e:
47
+ print_api_error(e, "list resources")
48
+ raise typer.Exit(1)
49
+ except Exception as e:
50
+ print_api_error(e, "list resources")
51
+ raise typer.Exit(1)
52
+
53
+
54
+ @app.command("get")
55
+ def get_resource(
56
+ resource_id: str = typer.Argument(..., help="Resource ID"),
57
+ ) -> None:
58
+ """Get a resource by ID."""
59
+ try:
60
+ with get_client() as client:
61
+ data = client.get_resource(resource_id)
62
+ print_json(data)
63
+ except httpx.HTTPStatusError as e:
64
+ print_api_error(e, "get resource")
65
+ raise typer.Exit(1)
66
+ except httpx.RequestError as e:
67
+ print_api_error(e, "get resource")
68
+ raise typer.Exit(1)
69
+ except Exception as e:
70
+ print_api_error(e, "get resource")
71
+ raise typer.Exit(1)
72
+
73
+
74
+ @app.command("create-virtual")
75
+ def create_virtual_resource(
76
+ name: str = typer.Option(..., "--name", "-n", help="Resource name"),
77
+ integration_id: str = typer.Option(..., "--integration", "-i", help="Virtual integration ID"),
78
+ source_role_id: str = typer.Option(..., "--source-role-id", "-r", help="Source role ID from the linked integration"),
79
+ role_name: str = typer.Option("Start Session", "--role-name", help="Role name in the virtual integration"),
80
+ ) -> None:
81
+ """Create a resource in a virtual integration.
82
+
83
+ Virtual integrations link resources from other integrations. This command
84
+ creates a new resource that maps to a role from another integration.
85
+
86
+ Example:
87
+ bt entitle resources create-virtual \\
88
+ --name "Customer-05 (Bing7)" \\
89
+ --integration 22f4960f-b2ee-435f-9fa2-b82baeca06b2 \\
90
+ --source-role-id f0d60f41-be50-4402-b474-56e581ff7f50
91
+ """
92
+ try:
93
+ with get_client() as client:
94
+ result = client.create_virtual_resource(
95
+ integration_id=integration_id,
96
+ name=name,
97
+ source_role_id=source_role_id,
98
+ role_name=role_name,
99
+ )
100
+ resource = result.get("result", result)
101
+ console.print(f"Created virtual resource: {resource.get('name')} (ID: {resource.get('id')})")
102
+ print_json(result)
103
+ except httpx.HTTPStatusError as e:
104
+ print_api_error(e, "create virtual resource")
105
+ raise typer.Exit(1)
106
+ except httpx.RequestError as e:
107
+ print_api_error(e, "create virtual resource")
108
+ raise typer.Exit(1)
109
+ except Exception as e:
110
+ print_api_error(e, "create virtual resource")
111
+ raise typer.Exit(1)
112
+
113
+
114
+ @app.command("delete")
115
+ def delete_resource(
116
+ resource_id: str = typer.Argument(..., help="Resource ID"),
117
+ ) -> None:
118
+ """Delete a resource by ID."""
119
+ try:
120
+ with get_client() as client:
121
+ client.delete_resource(resource_id)
122
+ console.print(f"Deleted resource: {resource_id}")
123
+ except httpx.HTTPStatusError as e:
124
+ print_api_error(e, "delete resource")
125
+ raise typer.Exit(1)
126
+ except httpx.RequestError as e:
127
+ print_api_error(e, "delete resource")
128
+ raise typer.Exit(1)
129
+ except Exception as e:
130
+ print_api_error(e, "delete resource")
131
+ raise typer.Exit(1)
@@ -0,0 +1,74 @@
1
+ """Role commands for Entitle."""
2
+
3
+ from typing import Optional
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..client.base import get_client
9
+ from ...core.output import console, print_table, print_json, print_error, print_api_error
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Manage roles")
12
+
13
+
14
+ @app.command("list")
15
+ def list_roles(
16
+ resource_id: str = typer.Option(..., "--resource", "-r", help="Resource ID (required)"),
17
+ search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
18
+ limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
19
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
20
+ ) -> None:
21
+ """List roles for a resource.
22
+
23
+ The Entitle API requires a resource ID to list roles.
24
+ Use 'bt entitle resources list -i <integration_id>' to find resource IDs.
25
+
26
+ Examples:
27
+ bt entitle roles list -r <resource_id>
28
+ bt entitle roles list -r <resource_id> -s "admin"
29
+ """
30
+ try:
31
+ with get_client() as client:
32
+ data = client.list_roles(
33
+ resource_id=resource_id,
34
+ search=search,
35
+ limit=limit,
36
+ )
37
+
38
+ if output == "json":
39
+ print_json(data)
40
+ else:
41
+ print_table(
42
+ data,
43
+ [("ID", "id"), ("Name", "name"), ("Resource", "resource"), ("Requestable", "requestable")],
44
+ title="Roles",
45
+ )
46
+ except httpx.HTTPStatusError as e:
47
+ print_api_error(e, "list roles")
48
+ raise typer.Exit(1)
49
+ except httpx.RequestError as e:
50
+ print_api_error(e, "list roles")
51
+ raise typer.Exit(1)
52
+ except Exception as e:
53
+ print_api_error(e, "list roles")
54
+ raise typer.Exit(1)
55
+
56
+
57
+ @app.command("get")
58
+ def get_role(
59
+ role_id: str = typer.Argument(..., help="Role ID"),
60
+ ) -> None:
61
+ """Get a role by ID."""
62
+ try:
63
+ with get_client() as client:
64
+ data = client.get_role(role_id)
65
+ print_json(data)
66
+ except httpx.HTTPStatusError as e:
67
+ print_api_error(e, "get role")
68
+ raise typer.Exit(1)
69
+ except httpx.RequestError as e:
70
+ print_api_error(e, "get role")
71
+ raise typer.Exit(1)
72
+ except Exception as e:
73
+ print_api_error(e, "get role")
74
+ raise typer.Exit(1)