Skip to content

Commit f304c62

Browse files
committed
CIS2 CLI - add banner and color-coded UI
Polish the CLI interface with visual improvements: - Add ASCII art banner with NHS CIS2 branding - Implement color-coded messages: - Blue for status/informational messages - Green for success messages - Red for error messages - Display banner on startup
1 parent 9b22c95 commit f304c62

File tree

1 file changed

+77
-32
lines changed
  • scripts/cis2_cli

1 file changed

+77
-32
lines changed

scripts/cis2_cli/cis2

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,56 @@ class CIS2Client:
134134
class CIS2CLI:
135135
"""Command-line interface for the CIS2 Connection Manager."""
136136

137+
# ANSI color codes
138+
BLUE = "\033[94m"
139+
WHITE = "\033[97m"
140+
GREEN = "\033[92m"
141+
RED = "\033[91m"
142+
RESET = "\033[0m"
143+
137144
def __init__(self):
138145
self.client = CIS2Client()
139146
self.last_config_hash: Optional[str] = None
140147
self.last_config_id: Optional[str] = None
141148

149+
def show_banner(self):
150+
"""Display the CLI tool banner."""
151+
print(
152+
f"""
153+
{self.BLUE} ╔═══════════════════════════════════════════════════════════════╗
154+
║ ║
155+
{self.WHITE} ███ ██ ██ ██ ███████ ██████ ██ ███████ ██████{self.BLUE}
156+
{self.WHITE} ████ ██ ██ ██ ██ ██ ██ ██ ██{self.BLUE}
157+
{self.WHITE} ██ ██ ██ ███████ ███████ ██ ██ ███████ █████{self.BLUE}
158+
{self.WHITE} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██{self.BLUE}
159+
{self.WHITE} ██ ████ ██ ██ ███████ ██████ ██ ███████ ███████{self.BLUE}
160+
║ ║
161+
{self.WHITE} Connection Manager CLI Tool {self.BLUE}
162+
║ ║
163+
╚═══════════════════════════════════════════════════════════════╝{self.RESET}
164+
"""
165+
)
166+
print("=" * 40)
167+
142168
def setup_credentials(self):
143169
"""Prompt user for secret and team ID, then authenticate."""
144170
# Get secret
145171
secret = getpass.getpass("Enter your API secret: ")
146172
if not secret:
147-
print("Error: Secret is required")
173+
print(f"{self.RED}Error: Secret is required{self.RESET}")
148174
sys.exit(1)
149175

150176
# Authenticate
151177
try:
152-
print("Authenticating with CIS2...")
178+
print(f"{self.BLUE}Authenticating with CIS2...{self.RESET}")
153179
auth_result = self.client.authenticate(secret)
154-
print("✓ Authentication successful")
180+
print(f"{self.GREEN}✓ Authenticated{self.RESET}")
155181

156182
# Handle team ID selection
157183
if "team_ids" not in auth_result or not auth_result["team_ids"]:
158-
print("✗ Error: No team IDs available for this account")
184+
print(
185+
f"{self.RED}✗ Error: No team IDs available for this account{self.RESET}"
186+
)
159187
print(
160188
"Please contact your administrator to ensure your account has team access."
161189
)
@@ -175,7 +203,9 @@ class CIS2CLI:
175203
if 1 <= choice_num <= len(team_ids):
176204
selected_team_id = team_ids[choice_num - 1]
177205
self.client.team_id = selected_team_id
178-
print(f"✓ Team ID set to: {selected_team_id}")
206+
print(
207+
f"{self.GREEN}✓ Team ID set to: {selected_team_id}{self.RESET}"
208+
)
179209
break
180210
else:
181211
print(
@@ -188,7 +218,7 @@ class CIS2CLI:
188218
sys.exit(1)
189219

190220
except Exception as e:
191-
print(f"✗ Authentication failed: {e}")
221+
print(f"{self.RED}✗ Authentication failed: {e}{self.RESET}")
192222
sys.exit(1)
193223

194224
print()
@@ -207,14 +237,18 @@ class CIS2CLI:
207237
"""Display details of a specific configuration."""
208238
try:
209239
# First, get the list of available configurations
210-
print(f"Fetching configs for team {self.client.team_id}...")
240+
print(
241+
f"{self.BLUE}Fetching configs for team {self.client.team_id}...{self.RESET}"
242+
)
211243
configs_response = self.client.get_configs(self.client.team_id)
212244

213245
# Extract config IDs from the response
214246
config_ids = configs_response.get("configs", [])
215247

216248
if not config_ids:
217-
print("✗ Error: No configurations available for this team")
249+
print(
250+
f"{self.RED}✗ Error: No configurations available for this team{self.RESET}"
251+
)
218252
return
219253

220254
# Show numbered menu for selection
@@ -244,21 +278,23 @@ class CIS2CLI:
244278
return
245279

246280
# Fetch and display the selected configuration
247-
print(f"Fetching config '{selected_config_id}'...")
281+
print(f"{self.BLUE}Fetching config '{selected_config_id}'...{self.RESET}")
248282
config = self.client.get_config(self.client.team_id, selected_config_id)
249283

250284
# Store config hash and ID for future updates
251285
if isinstance(config, dict) and "hash" in config:
252286
self.last_config_hash = config["hash"]
253287
self.last_config_id = selected_config_id
254-
print(f"✓ Cached hash for updates: {self.last_config_hash[:8]}...")
288+
print(
289+
f"{self.GREEN}✓ Cached hash for updates: {self.last_config_hash[:8]}...{self.RESET}"
290+
)
255291

256292
print(f"\nConfiguration Details for '{selected_config_id}':")
257293
print("=" * 40)
258294
print(json.dumps(config, indent=2))
259295

260296
except Exception as e:
261-
print(f"✗ Error displaying config: {e}")
297+
print(f"{self.RED}✗ Error displaying config: {e}{self.RESET}")
262298

263299
def create_config(self):
264300
"""Create a new configuration."""
@@ -268,14 +304,16 @@ class CIS2CLI:
268304

269305
client_name = input("Enter client name: ").strip()
270306
if not client_name:
271-
print("Error: Client name is required")
307+
print(f"{self.RED}Error: Client name is required{self.RESET}")
272308
return
273309

274310
redirect_uris_input = input(
275311
"Enter redirect URIs (comma-separated): "
276312
).strip()
277313
if not redirect_uris_input:
278-
print("Error: At least one redirect URI is required")
314+
print(
315+
f"{self.RED}Error: At least one redirect URI is required{self.RESET}"
316+
)
279317
return
280318

281319
redirect_uris = [uri.strip() for uri in redirect_uris_input.split(",")]
@@ -304,7 +342,9 @@ class CIS2CLI:
304342
"Enter JWKS URI (required for private_key_jwt): "
305343
).strip()
306344
if not jwks_uri:
307-
print("Error: JWKS URI is required when using private_key_jwt")
345+
print(
346+
f"{self.RED}Error: JWKS URI is required when using private_key_jwt{self.RESET}"
347+
)
308348
return
309349

310350
jwks_uri_signing_algorithm = (
@@ -329,32 +369,34 @@ class CIS2CLI:
329369
payload["jwks_uri_signing_algorithm"] = jwks_uri_signing_algorithm
330370

331371
print()
332-
print(f"Creating config for team {self.client.team_id}...")
333-
print("Payload:")
372+
print(
373+
f"{self.BLUE}Creating config for team {self.client.team_id}...{self.RESET}"
374+
)
375+
print(f"{self.BLUE}Payload:{self.RESET}")
334376
print(json.dumps(payload, indent=2))
335377
print()
336378

337379
result = self.client.create_config(self.client.team_id, payload)
338380

339-
print("✓ Config created")
381+
print(f"{self.GREEN}✓ Config created{self.RESET}")
340382
print(json.dumps(result, indent=2))
341383

342384
except Exception as e:
343-
print(f"✗ Error creating config: {e}")
385+
print(f"{self.RED}✗ Error creating config: {e}{self.RESET}")
344386

345387
def update_config(self):
346388
"""Update an existing configuration."""
347389
# Check if we have a cached hash from displaying a config
348390
if not self.last_config_hash or not self.last_config_id:
349-
print("Error: No config hash available for update.")
391+
print(f"{self.RED}Error: No config hash available for update.{self.RESET}")
350392
print("Please display a configuration first to cache its hash.")
351393
return
352394

353395
try:
354396
print("Update existing configuration")
355397
print("-" * 30)
356-
print(f"Config: {self.last_config_id}")
357-
print(f"Hash: {self.last_config_hash[:8]}...")
398+
print(f"{self.BLUE}Config: {self.last_config_id}{self.RESET}")
399+
print(f"{self.BLUE}Hash: {self.last_config_hash[:8]}...{self.RESET}")
358400
print()
359401

360402
print("Enter the new client_config as JSON:")
@@ -370,7 +412,7 @@ class CIS2CLI:
370412
json_lines.append(line)
371413

372414
if not json_lines:
373-
print("Error: JSON payload is required")
415+
print(f"{self.RED}Error: JSON payload is required{self.RESET}")
374416
return
375417

376418
json_text = "\n".join(json_lines)
@@ -379,13 +421,13 @@ class CIS2CLI:
379421
try:
380422
client_config = json.loads(json_text)
381423
except json.JSONDecodeError as e:
382-
print(f"Error: Invalid JSON format - {e}")
424+
print(f"{self.RED}Error: Invalid JSON format - {e}{self.RESET}")
383425
return
384426

385427
print(
386-
f"Updating config '{self.last_config_id}' for team {self.client.team_id}..."
428+
f"{self.BLUE}Updating config '{self.last_config_id}' for team {self.client.team_id}...{self.RESET}"
387429
)
388-
print("Payload:")
430+
print(f"{self.BLUE}Payload:{self.RESET}")
389431
print(json.dumps(client_config, indent=2))
390432
print()
391433

@@ -396,33 +438,36 @@ class CIS2CLI:
396438
self.last_config_hash,
397439
)
398440

399-
print("✓ Config updated")
441+
print(f"{self.GREEN}✓ Config updated{self.RESET}")
400442
print(json.dumps(result, indent=2))
401443

402444
# Update the cached hash if provided in response
403445
if isinstance(result, dict) and "hash" in result:
404446
self.last_config_hash = result["hash"]
405-
print(f"✓ Cached new hash: {self.last_config_hash[:8]}...")
447+
print(
448+
f"{self.GREEN}✓ Cached new hash: {self.last_config_hash[:8]}...{self.RESET}"
449+
)
406450

407451
except Exception as e:
408-
print(f"✗ Error updating config: {e}")
452+
print(f"{self.RED}✗ Error updating config: {e}{self.RESET}")
409453

410454
def re_authenticate(self):
411455
"""Re-authenticate with a new secret."""
412456
secret = getpass.getpass("Enter your API secret: ")
413457
if not secret:
414-
print("Error: Secret is required")
458+
print(f"{self.RED}Error: Secret is required{self.RESET}")
415459
return
416460

417461
try:
418-
print("Re-authenticating with CIS2...")
462+
print(f"{self.BLUE}Re-authenticating with CIS2...{self.RESET}")
419463
self.client.authenticate(secret)
420-
print("✓ Re-authenticated")
464+
print(f"{self.GREEN}✓ Re-authenticated{self.RESET}")
421465
except Exception as e:
422-
print(f"✗ Re-authentication failed: {e}")
466+
print(f"{self.RED}✗ Re-authentication failed: {e}{self.RESET}")
423467

424468
def run(self):
425469
"""Run the CLI application."""
470+
self.show_banner()
426471
# Initial setup
427472
self.setup_credentials()
428473

0 commit comments

Comments
 (0)