hac-client-cli 0.1.0__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.
@@ -0,0 +1,2 @@
1
+ """HAC Client CLI."""
2
+
hac_client_cli/app.py ADDED
@@ -0,0 +1,465 @@
1
+ """HAC Client CLI application.
2
+
3
+ Thin adapter over hac-client-core for basic HAC operations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import sys
9
+ import json
10
+ import typer
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ from hac_client_cli.environment_manager import EnvironmentManager
15
+ from hac_client_cli.commands_env import env_app
16
+ from hac_client_cli.commands_endpoint import app as endpoint_app
17
+ from hac_client_cli.commands_session import session_app
18
+ from hac_client_cli.commands_update import update_app
19
+ from hac_client_core.client import HacClient, HacClientError
20
+ from hac_client_core.auth import BasicAuthHandler
21
+
22
+ app = typer.Typer(
23
+ help="SAP Commerce HAC client",
24
+ no_args_is_help=True,
25
+ add_completion=False
26
+ )
27
+
28
+ # Add environment, endpoint, and session management
29
+ app.add_typer(env_app, name="env")
30
+ app.add_typer(endpoint_app, name="endpoint")
31
+ app.add_typer(session_app, name="session")
32
+ app.add_typer(update_app, name="update")
33
+
34
+
35
+ def create_client(environment: Optional[str] = None, endpoint: Optional[str] = None, quiet: bool = False) -> HacClient:
36
+ """Create HAC client from configuration.
37
+
38
+ Requires an active session. Use 'hac session start' to create one.
39
+
40
+ Args:
41
+ environment: Environment name (uses default if None)
42
+ endpoint: Endpoint name (uses environment default if None)
43
+ quiet: Suppress informational messages
44
+
45
+ Returns:
46
+ Configured HacClient instance
47
+ """
48
+ from hac_client_core.session import SessionManager
49
+ from hac_client_cli.config_loader import get_endpoint_config
50
+
51
+ # Get endpoint configuration
52
+ env_name, endpoint_name, ep_config = get_endpoint_config(environment, endpoint)
53
+
54
+ # Create session identifier
55
+ session_id = f"{env_name}/{endpoint_name}"
56
+
57
+ # Check for existing session
58
+ # Note: We need to find a session for this endpoint, but we don't know the username yet
59
+ # Sessions are keyed by (base_url, username, environment), so we need to list all sessions
60
+ session_manager = SessionManager()
61
+ all_sessions = session_manager.list_sessions()
62
+
63
+ # Find a session for this endpoint
64
+ # Normalize URLs for comparison (remove trailing slashes)
65
+ config_url_normalized = ep_config.url.rstrip('/')
66
+ session = None
67
+ for s in all_sessions:
68
+ session_url_normalized = s.base_url.rstrip('/')
69
+ if s.environment == session_id and session_url_normalized == config_url_normalized:
70
+ session = s
71
+ break
72
+
73
+ if not session:
74
+ print(f"ERROR: No active session for '{session_id}'", file=sys.stderr)
75
+ print(f"\nStart a session:", file=sys.stderr)
76
+ print(f" hac session start {env_name} --endpoint {endpoint_name} --username <user>", file=sys.stderr)
77
+ print(f"\nOr import existing session:", file=sys.stderr)
78
+ print(f" hac session import {env_name} --endpoint {endpoint_name} --username <user> --session-id <id> --csrf-token <token>", file=sys.stderr)
79
+ raise typer.Exit(1)
80
+
81
+ # Create client with existing session (no auto-login)
82
+ # We need a dummy password since BasicAuthHandler requires it, but it won't be used
83
+ auth = BasicAuthHandler(session.username, "dummy")
84
+
85
+ client = HacClient(
86
+ base_url=ep_config.url,
87
+ auth_handler=auth,
88
+ environment=session_id, # Use composite key
89
+ timeout=ep_config.timeout,
90
+ ignore_ssl=ep_config.ignore_ssl,
91
+ session_persistence=True,
92
+ quiet=quiet
93
+ )
94
+
95
+ # Manually set session info from loaded session
96
+ from hac_client_core.models import SessionInfo
97
+ client.session_info = SessionInfo(
98
+ session_id=session.session_id,
99
+ csrf_token=session.csrf_token,
100
+ route_cookie=session.route_cookie,
101
+ is_authenticated=session.is_authenticated
102
+ )
103
+
104
+ # Also set cookies in the http_session so they're sent with requests
105
+ # Extract domain from base URL
106
+ from urllib.parse import urlparse
107
+ parsed_url = urlparse(ep_config.url)
108
+ domain = parsed_url.hostname
109
+
110
+ client.http_session.cookies.set('JSESSIONID', session.session_id, domain=domain, path='/')
111
+ if session.route_cookie:
112
+ # Extract value from "ROUTE=value" format
113
+ route_value = session.route_cookie.split('=', 1)[1] if '=' in session.route_cookie else session.route_cookie
114
+ client.http_session.cookies.set('ROUTE', route_value, domain=domain, path='/')
115
+
116
+ return client
117
+
118
+
119
+ @app.command("groovy")
120
+ def groovy_command(
121
+ script: str = typer.Argument(..., help="Groovy script or path to .groovy file"),
122
+ file: bool = typer.Option(False, "--file", "-f", help="Treat script as file path"),
123
+ commit: bool = typer.Option(False, "--commit", "-c", help="Enable commit mode"),
124
+ environment: Optional[str] = typer.Option(None, "--environment", "-e", help="Environment name"),
125
+ endpoint: Optional[str] = typer.Option(None, "--endpoint", "-n", help="Endpoint name"),
126
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
127
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress informational messages")
128
+ ):
129
+ """Execute Groovy script in HAC."""
130
+ try:
131
+ # Read script from file if needed
132
+ script_content = script
133
+ if file or script.endswith('.groovy'):
134
+ script_path = Path(script)
135
+ if not script_path.exists():
136
+ print(f"ERROR: Script file not found: {script}", file=sys.stderr)
137
+ raise typer.Exit(1)
138
+ script_content = script_path.read_text()
139
+
140
+ # Create client and execute
141
+ client = create_client(environment, endpoint, quiet)
142
+ result = client.execute_groovy(script_content, commit=commit)
143
+
144
+ if not result.success:
145
+ print(f"ERROR: Script execution failed\n{result.stacktrace_text}", file=sys.stderr)
146
+ raise typer.Exit(1)
147
+
148
+ if json_output:
149
+ output = {
150
+ "success": result.success,
151
+ "output_text": result.output_text,
152
+ "execution_result": result.execution_result,
153
+ "commit_mode": result.commit_mode,
154
+ "execution_time_ms": result.execution_time_ms
155
+ }
156
+ print(json.dumps(output, indent=2))
157
+ else:
158
+ # Print script output to stderr, result to stdout
159
+ if result.output_text:
160
+ print(result.output_text, file=sys.stderr)
161
+ print(result.execution_result)
162
+
163
+ except HacClientError as e:
164
+ print(f"ERROR: {e}", file=sys.stderr)
165
+ raise typer.Exit(1)
166
+
167
+
168
+ @app.command("flexsearch")
169
+ def flexsearch_command(
170
+ query: str = typer.Argument(..., help="FlexibleSearch query"),
171
+ max_count: int = typer.Option(200, "--max-count", "-m", help="Maximum number of results"),
172
+ locale: str = typer.Option("en", "--locale", "-l", help="Locale for the query"),
173
+ environment: Optional[str] = typer.Option(None, "--environment", "-e", help="Environment name"),
174
+ endpoint: Optional[str] = typer.Option(None, "--endpoint", "-n", help="Endpoint name"),
175
+ csv: bool = typer.Option(False, "--csv", help="Output as CSV"),
176
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
177
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress informational messages")
178
+ ):
179
+ """Execute FlexibleSearch query in HAC."""
180
+ try:
181
+ client = create_client(environment, endpoint, quiet)
182
+ result = client.execute_flexiblesearch(query, max_count=max_count, locale=locale)
183
+
184
+ if not result.success:
185
+ print(f"ERROR: Query failed\n{result.exception}", file=sys.stderr)
186
+ raise typer.Exit(1)
187
+
188
+ if json_output:
189
+ output = {
190
+ "success": result.success,
191
+ "headers": result.headers,
192
+ "rows": result.rows,
193
+ "result_count": result.result_count,
194
+ "execution_time_ms": result.execution_time_ms
195
+ }
196
+ print(json.dumps(output, indent=2))
197
+ elif csv:
198
+ # Output as CSV
199
+ if result.headers:
200
+ print(",".join(result.headers))
201
+ for row in result.rows:
202
+ print(",".join(str(cell) for cell in row))
203
+ else:
204
+ # Human-readable table format
205
+ if result.headers:
206
+ print("\t".join(result.headers))
207
+ for row in result.rows:
208
+ print("\t".join(str(cell) for cell in row))
209
+
210
+ if not quiet:
211
+ print(f"\n{result.result_count} results", file=sys.stderr)
212
+
213
+ except HacClientError as e:
214
+ print(f"ERROR: {e}", file=sys.stderr)
215
+ raise typer.Exit(1)
216
+
217
+
218
+ @app.command("impex")
219
+ def impex_command(
220
+ file: Path = typer.Option(..., "--file", "-f", help="Impex file to import"),
221
+ validation: str = typer.Option("import_strict", "--validation", "-v", help="Validation mode (import_strict, import_relaxed, strict, relaxed)"),
222
+ environment: Optional[str] = typer.Option(None, "--environment", "-e", help="Environment name"),
223
+ endpoint: Optional[str] = typer.Option(None, "--endpoint", "-n", help="Endpoint name"),
224
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
225
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress informational messages")
226
+ ):
227
+ """Import Impex data in HAC."""
228
+ try:
229
+ if not file.exists():
230
+ print(f"ERROR: Impex file not found: {file}", file=sys.stderr)
231
+ raise typer.Exit(1)
232
+
233
+ impex_content = file.read_text()
234
+
235
+ client = create_client(environment, endpoint, quiet)
236
+ result = client.import_impex(impex_content, validation_mode=validation)
237
+
238
+ if not result.success:
239
+ print(f"ERROR: Impex import failed\n{result.error}", file=sys.stderr)
240
+ raise typer.Exit(1)
241
+
242
+ if json_output:
243
+ output = {
244
+ "success": result.success,
245
+ "output": result.output,
246
+ "validation_errors": result.validation_errors
247
+ }
248
+ print(json.dumps(output, indent=2))
249
+ else:
250
+ print(result.output)
251
+
252
+ except HacClientError as e:
253
+ print(f"ERROR: {e}", file=sys.stderr)
254
+ raise typer.Exit(1)
255
+
256
+
257
+ @app.command("config")
258
+ def config_command(
259
+ list_environments: bool = typer.Option(False, "--list", "-l", help="List configured environments"),
260
+ validate: bool = typer.Option(False, "--validate", "-v", help="Validate configuration"),
261
+ show_path: bool = typer.Option(False, "--path", "-p", help="Show config file path"),
262
+ show_example: bool = typer.Option(False, "--example", "-x", help="Show example configuration"),
263
+ environment: Optional[str] = typer.Option(None, "--env", "-e", help="Show specific environment"),
264
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON")
265
+ ):
266
+ """Discover, inspect, and manage HAC client configuration.
267
+
268
+ Examples:
269
+ hac config # Show all configuration
270
+ hac config -l # List environments
271
+ hac config -e local # Show specific environment
272
+ hac config -v # Validate configuration
273
+ hac config -p # Show config file path
274
+ hac config -x # Show example config
275
+ """
276
+ from hac_client_cli.config_loader import get_config_path
277
+
278
+ config_path = get_config_path()
279
+
280
+ # Show path only
281
+ if show_path:
282
+ if json_output:
283
+ print(json.dumps({"config_path": str(config_path), "exists": config_path.exists()}, indent=2))
284
+ else:
285
+ print(f"Configuration file: {config_path}")
286
+ print(f"Status: {'exists' if config_path.exists() else 'not found'}")
287
+ return
288
+
289
+ # Show example
290
+ if show_example:
291
+ print("""# HAC Client Configuration
292
+ # Save to: ~/.config/hac-client/config.toml
293
+ # Or set: export HAC_CLIENT_CONFIG_PATH=/path/to/config.toml
294
+
295
+ # Default environment to use
296
+ default_environment = "local"
297
+
298
+ # Environment definitions
299
+ [environments.local]
300
+ url = "https://localhost:9002"
301
+ username = "admin"
302
+ # password = "nimda" # Or use HAC_PASSWORD env var
303
+ ignore_ssl = true
304
+ timeout = 30
305
+
306
+ [environments.dev]
307
+ url = "https://dev.example.com"
308
+ username = "admin"
309
+ # Password from HAC_PASSWORD_DEV or HAC_PASSWORD env var
310
+ ignore_ssl = false
311
+ timeout = 60
312
+
313
+ [environments.prod]
314
+ url = "https://prod.example.com"
315
+ username = "admin"
316
+ # ALWAYS use env var for prod: HAC_PASSWORD_PROD
317
+ ignore_ssl = false
318
+ timeout = 120
319
+ """)
320
+ return
321
+
322
+ # Load and validate config
323
+ try:
324
+ config = load_config()
325
+
326
+ # Validate mode
327
+ if validate:
328
+ issues = []
329
+
330
+ # Check if config file exists
331
+ if not config_path.exists():
332
+ issues.append("Configuration file does not exist")
333
+
334
+ # Check if we have environments
335
+ if not config.environments:
336
+ issues.append("No environments configured")
337
+
338
+ # Check default environment exists
339
+ if config.default_environment not in config.environments:
340
+ issues.append(f"Default environment '{config.default_environment}' not found in configured environments")
341
+
342
+ # Check each environment
343
+ for name, env in config.environments.items():
344
+ if not env.password:
345
+ issues.append(f"Environment '{name}': password not configured (set in config or HAC_PASSWORD_{name.upper()} env var)")
346
+ if not env.url.startswith('http'):
347
+ issues.append(f"Environment '{name}': URL should start with http:// or https://")
348
+
349
+ if json_output:
350
+ print(json.dumps({"valid": len(issues) == 0, "issues": issues}, indent=2))
351
+ else:
352
+ if issues:
353
+ print("❌ Configuration has issues:\n")
354
+ for issue in issues:
355
+ print(f" - {issue}")
356
+ print(f"\nConfiguration file: {config_path}")
357
+ print("Run 'hac config --example' to see example configuration")
358
+ raise typer.Exit(1)
359
+ else:
360
+ print("✅ Configuration is valid")
361
+ print(f" - {len(config.environments)} environment(s) configured")
362
+ print(f" - Default: {config.default_environment}")
363
+ return
364
+
365
+ # Show specific environment
366
+ if environment:
367
+ if environment not in config.environments:
368
+ print(f"ERROR: Environment '{environment}' not found", file=sys.stderr)
369
+ print("\nAvailable environments:", file=sys.stderr)
370
+ for env_name in config.environments:
371
+ marker = " (default)" if env_name == config.default_environment else ""
372
+ print(f" - {env_name}{marker}", file=sys.stderr)
373
+ raise typer.Exit(1)
374
+
375
+ env = config.environments[environment]
376
+ if json_output:
377
+ output = {
378
+ "name": environment,
379
+ "url": env.url,
380
+ "username": env.username,
381
+ "password_configured": env.password is not None,
382
+ "ignore_ssl": env.ignore_ssl,
383
+ "timeout": env.timeout,
384
+ "is_default": environment == config.default_environment
385
+ }
386
+ print(json.dumps(output, indent=2))
387
+ else:
388
+ marker = " (default)" if environment == config.default_environment else ""
389
+ print(f"Environment: {environment}{marker}")
390
+ print(f" URL: {env.url}")
391
+ print(f" Username: {env.username}")
392
+ print(f" Password: {'✓ configured' if env.password else '✗ NOT configured'}")
393
+ print(f" Ignore SSL: {env.ignore_ssl}")
394
+ print(f" Timeout: {env.timeout}s")
395
+ return
396
+
397
+ # List environments
398
+ if list_environments:
399
+ if json_output:
400
+ envs = list(config.environments.keys())
401
+ print(json.dumps({"environments": envs, "default": config.default_environment}, indent=2))
402
+ else:
403
+ print("Configured environments:")
404
+ for env_name in sorted(config.environments.keys()):
405
+ marker = " (default)" if env_name == config.default_environment else ""
406
+ print(f" - {env_name}{marker}")
407
+ return
408
+
409
+ # Show full config (default)
410
+ if json_output:
411
+ output = {
412
+ "config_path": str(config_path),
413
+ "default_environment": config.default_environment,
414
+ "environments": {
415
+ name: {
416
+ "url": env.url,
417
+ "username": env.username,
418
+ "password_configured": env.password is not None,
419
+ "ignore_ssl": env.ignore_ssl,
420
+ "timeout": env.timeout
421
+ }
422
+ for name, env in config.environments.items()
423
+ }
424
+ }
425
+ print(json.dumps(output, indent=2))
426
+ else:
427
+ print(f"Configuration file: {config_path}")
428
+ print(f"Status: {'✓ exists' if config_path.exists() else '✗ not found (using defaults)'}")
429
+ print(f"\nDefault environment: {config.default_environment}")
430
+ print(f"\nEnvironments ({len(config.environments)}):")
431
+ for name in sorted(config.environments.keys()):
432
+ env = config.environments[name]
433
+ marker = " ← default" if name == config.default_environment else ""
434
+ pwd_status = "✓" if env.password else "✗"
435
+ print(f" {name}{marker}")
436
+ print(f" URL: {env.url}")
437
+ print(f" Username: {env.username}")
438
+ print(f" Password: {pwd_status}")
439
+ print(f" SSL: {'ignore' if env.ignore_ssl else 'verify'}")
440
+
441
+ print("\nCommands:")
442
+ print(" hac config -l # List environments")
443
+ print(" hac config -e local # Show environment details")
444
+ print(" hac config -v # Validate configuration")
445
+ print(" hac config -x # Show example config")
446
+
447
+ except ValueError as e:
448
+ print(f"ERROR: Configuration validation failed", file=sys.stderr)
449
+ print(f" {e}", file=sys.stderr)
450
+ print(f"\nConfiguration file: {config_path}", file=sys.stderr)
451
+ print("\nRun 'hac config --example' to see correct format", file=sys.stderr)
452
+ raise typer.Exit(1)
453
+ except Exception as e:
454
+ print(f"ERROR: {e}", file=sys.stderr)
455
+ raise typer.Exit(1)
456
+
457
+
458
+ def main():
459
+ """Entry point for the HAC client CLI."""
460
+ app()
461
+
462
+
463
+ if __name__ == "__main__":
464
+ main()
465
+
@@ -0,0 +1,207 @@
1
+ """Endpoint management commands for HAC client CLI."""
2
+
3
+ import sys
4
+ import json
5
+ import typer
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ from .environment_manager import EnvironmentManager
11
+ from .config_loader import get_config_path
12
+
13
+
14
+ app = typer.Typer(help="Manage HAC endpoints")
15
+ console = Console()
16
+
17
+
18
+ @app.command("list")
19
+ def list_endpoints(
20
+ environment: str = typer.Argument(..., help="Environment name"),
21
+ format: str = typer.Option("table", "--format", "-f", help="Output format: table, json"),
22
+ no_headers: bool = typer.Option(False, "--no-headers", help="Suppress column headers"),
23
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output (names only)")
24
+ ):
25
+ """List all endpoints in an environment."""
26
+ manager = EnvironmentManager(get_config_path())
27
+
28
+ try:
29
+ endpoints = manager.list_endpoints(environment)
30
+ env = manager.get_environment(environment)
31
+
32
+ if not endpoints:
33
+ if format != "json":
34
+ console.print(f"[yellow]No endpoints configured for environment '{environment}'[/yellow]")
35
+ else:
36
+ print("[]")
37
+ return
38
+
39
+ # JSON output
40
+ if format == "json":
41
+ output = [
42
+ {
43
+ "name": ep.name,
44
+ "url": ep.url,
45
+ "ignore_ssl": ep.ignore_ssl,
46
+ "timeout": ep.timeout,
47
+ "is_default": env and ep.name == env.default_endpoint
48
+ }
49
+ for ep in endpoints
50
+ ]
51
+ print(json.dumps(output, indent=2))
52
+ return
53
+
54
+ # Quiet output (names only)
55
+ if quiet:
56
+ for ep in endpoints:
57
+ print(ep.name)
58
+ return
59
+
60
+ # Table format (default)
61
+ table = Table(show_header=not no_headers)
62
+ table.add_column("NAME", style="cyan")
63
+ table.add_column("URL")
64
+ table.add_column("SSL")
65
+ table.add_column("TIMEOUT", justify="right")
66
+ table.add_column("DEFAULT", justify="center")
67
+
68
+ for endpoint in endpoints:
69
+ default_marker = "✓" if env and endpoint.name == env.default_endpoint else ""
70
+ ssl_status = "[yellow]ignore[/yellow]" if endpoint.ignore_ssl else "[green]verify[/green]"
71
+
72
+ table.add_row(
73
+ endpoint.name,
74
+ endpoint.url,
75
+ ssl_status,
76
+ f"{endpoint.timeout}s",
77
+ default_marker
78
+ )
79
+
80
+ console.print(table)
81
+
82
+ except ValueError as e:
83
+ console.print(f"[red]ERROR: {e}[/red]", file=sys.stderr)
84
+ raise typer.Exit(1)
85
+
86
+
87
+ @app.command("show")
88
+ def show_endpoint(
89
+ environment: str = typer.Argument(..., help="Environment name"),
90
+ endpoint: str = typer.Argument(..., help="Endpoint name")
91
+ ):
92
+ """Show details of a specific endpoint."""
93
+ manager = EnvironmentManager(get_config_path())
94
+
95
+ ep = manager.get_endpoint(environment, endpoint)
96
+
97
+ if not ep:
98
+ print(f"ERROR: Endpoint '{endpoint}' not found in environment '{environment}'")
99
+ raise typer.Exit(1)
100
+
101
+ env = manager.get_environment(environment)
102
+ is_default = env and ep.name == env.default_endpoint
103
+
104
+ print(f"Endpoint: {environment}/{endpoint}")
105
+ if is_default:
106
+ print("Status: default")
107
+ print()
108
+ print(f"URL: {ep.url}")
109
+ print(f"Ignore SSL: {ep.ignore_ssl}")
110
+ print(f"Timeout: {ep.timeout}s")
111
+
112
+
113
+ @app.command("add")
114
+ def add_endpoint(
115
+ environment: str = typer.Argument(..., help="Environment name"),
116
+ endpoint: str = typer.Argument(..., help="Endpoint name"),
117
+ url: str = typer.Option(..., "--url", "-u", help="HAC base URL"),
118
+ ignore_ssl: bool = typer.Option(False, "--ignore-ssl", help="Ignore SSL certificate errors"),
119
+ timeout: int = typer.Option(30, "--timeout", help="HTTP timeout in seconds"),
120
+ set_default: bool = typer.Option(False, "--set-default", help="Set as default endpoint")
121
+ ):
122
+ """Add a new endpoint to an environment.
123
+
124
+ Note: Username is provided during session creation, not endpoint configuration.
125
+ An endpoint is just infrastructure (URL, connection settings).
126
+ """
127
+ manager = EnvironmentManager(get_config_path())
128
+
129
+ try:
130
+ manager.add_endpoint(
131
+ env_name=environment,
132
+ endpoint_name=endpoint,
133
+ url=url,
134
+ ignore_ssl=ignore_ssl,
135
+ timeout=timeout,
136
+ set_default=set_default
137
+ )
138
+
139
+ default_msg = " (set as default)" if set_default else ""
140
+ print(f"✓ Added endpoint '{endpoint}' to environment '{environment}'{default_msg}")
141
+ print(f"\nStart session: hac session start {environment} --endpoint {endpoint} --username <user>")
142
+
143
+ except ValueError as e:
144
+ print(f"ERROR: {e}")
145
+ raise typer.Exit(1)
146
+
147
+
148
+ @app.command("update")
149
+ def update_endpoint(
150
+ environment: str = typer.Argument(..., help="Environment name"),
151
+ endpoint: str = typer.Argument(..., help="Endpoint name"),
152
+ url: Optional[str] = typer.Option(None, "--url", "-u", help="HAC base URL"),
153
+ ignore_ssl: Optional[bool] = typer.Option(None, "--ignore-ssl/--no-ignore-ssl", help="Ignore SSL errors"),
154
+ timeout: Optional[int] = typer.Option(None, "--timeout", help="HTTP timeout in seconds")
155
+ ):
156
+ """Update an existing endpoint."""
157
+ manager = EnvironmentManager(get_config_path())
158
+
159
+ try:
160
+ manager.update_endpoint(
161
+ env_name=environment,
162
+ endpoint_name=endpoint,
163
+ url=url,
164
+ ignore_ssl=ignore_ssl,
165
+ timeout=timeout
166
+ )
167
+
168
+ print(f"✓ Updated endpoint '{endpoint}' in environment '{environment}'")
169
+
170
+ except ValueError as e:
171
+ print(f"ERROR: {e}")
172
+ raise typer.Exit(1)
173
+
174
+
175
+ @app.command("remove")
176
+ def remove_endpoint(
177
+ environment: str = typer.Argument(..., help="Environment name"),
178
+ endpoint: str = typer.Argument(..., help="Endpoint name")
179
+ ):
180
+ """Remove an endpoint from an environment."""
181
+ manager = EnvironmentManager(get_config_path())
182
+
183
+ try:
184
+ manager.remove_endpoint(environment, endpoint)
185
+ print(f"✓ Removed endpoint '{endpoint}' from environment '{environment}'")
186
+
187
+ except ValueError as e:
188
+ print(f"ERROR: {e}")
189
+ raise typer.Exit(1)
190
+
191
+
192
+ @app.command("set-default")
193
+ def set_default_endpoint(
194
+ environment: str = typer.Argument(..., help="Environment name"),
195
+ endpoint: str = typer.Argument(..., help="Endpoint name to set as default")
196
+ ):
197
+ """Set the default endpoint for an environment."""
198
+ manager = EnvironmentManager(get_config_path())
199
+
200
+ try:
201
+ manager.set_default_endpoint(environment, endpoint)
202
+ print(f"✓ Set '{endpoint}' as default endpoint for environment '{environment}'")
203
+
204
+ except ValueError as e:
205
+ print(f"ERROR: {e}")
206
+ raise typer.Exit(1)
207
+