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.
- hac_client_cli/__init__.py +2 -0
- hac_client_cli/app.py +465 -0
- hac_client_cli/commands_endpoint.py +207 -0
- hac_client_cli/commands_env.py +219 -0
- hac_client_cli/commands_session.py +417 -0
- hac_client_cli/commands_update.py +582 -0
- hac_client_cli/config_loader.py +188 -0
- hac_client_cli/environment_manager.py +424 -0
- hac_client_cli-0.1.0.dist-info/METADATA +125 -0
- hac_client_cli-0.1.0.dist-info/RECORD +14 -0
- hac_client_cli-0.1.0.dist-info/WHEEL +5 -0
- hac_client_cli-0.1.0.dist-info/entry_points.txt +2 -0
- hac_client_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- hac_client_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Environment management commands."""
|
|
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 hac_client_cli.environment_manager import EnvironmentManager
|
|
11
|
+
|
|
12
|
+
env_app = typer.Typer(help="Manage HAC environments", no_args_is_help=True)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@env_app.command("list")
|
|
17
|
+
def list_environments(
|
|
18
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format: table, json"),
|
|
19
|
+
no_headers: bool = typer.Option(False, "--no-headers", help="Suppress column headers"),
|
|
20
|
+
quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output (names only)")
|
|
21
|
+
):
|
|
22
|
+
"""List all configured environments."""
|
|
23
|
+
try:
|
|
24
|
+
manager = EnvironmentManager()
|
|
25
|
+
environments = manager.list_environments()
|
|
26
|
+
default = manager.get_default_environment()
|
|
27
|
+
|
|
28
|
+
if not environments:
|
|
29
|
+
if format != "json":
|
|
30
|
+
console.print("[yellow]No environments configured[/yellow]")
|
|
31
|
+
console.print("\nAdd an environment with endpoints:")
|
|
32
|
+
console.print(" hac env add local")
|
|
33
|
+
console.print(" hac endpoint add local hac --url https://localhost:9002")
|
|
34
|
+
else:
|
|
35
|
+
print("[]")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# JSON output
|
|
39
|
+
if format == "json":
|
|
40
|
+
output = [
|
|
41
|
+
{
|
|
42
|
+
"name": env.name,
|
|
43
|
+
"endpoints": {
|
|
44
|
+
ep_name: {
|
|
45
|
+
"url": ep.url,
|
|
46
|
+
"ignore_ssl": ep.ignore_ssl,
|
|
47
|
+
"timeout": ep.timeout
|
|
48
|
+
}
|
|
49
|
+
for ep_name, ep in env.endpoints.items()
|
|
50
|
+
},
|
|
51
|
+
"default_endpoint": env.default_endpoint,
|
|
52
|
+
"is_default": env.name == default
|
|
53
|
+
}
|
|
54
|
+
for env in environments
|
|
55
|
+
]
|
|
56
|
+
print(json.dumps(output, indent=2))
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Quiet output (names only)
|
|
60
|
+
if quiet:
|
|
61
|
+
for env in environments:
|
|
62
|
+
print(env.name)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Table format (default)
|
|
66
|
+
table = Table(show_header=not no_headers)
|
|
67
|
+
table.add_column("NAME", style="cyan")
|
|
68
|
+
table.add_column("ENDPOINTS", justify="right")
|
|
69
|
+
table.add_column("DEFAULT-ENDPOINT")
|
|
70
|
+
table.add_column("DEFAULT", justify="center")
|
|
71
|
+
|
|
72
|
+
for env in environments:
|
|
73
|
+
default_marker = "✓" if env.name == default else ""
|
|
74
|
+
endpoint_count = str(len(env.endpoints))
|
|
75
|
+
default_ep = env.default_endpoint or "-"
|
|
76
|
+
|
|
77
|
+
table.add_row(
|
|
78
|
+
env.name,
|
|
79
|
+
endpoint_count,
|
|
80
|
+
default_ep,
|
|
81
|
+
default_marker
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
console.print(table)
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
console.print(f"[red]ERROR: {e}[/red]", file=sys.stderr)
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@env_app.command("show")
|
|
92
|
+
def show_environment(
|
|
93
|
+
name: str = typer.Argument(..., help="Environment name"),
|
|
94
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON")
|
|
95
|
+
):
|
|
96
|
+
"""Show details of a specific environment with all endpoints."""
|
|
97
|
+
try:
|
|
98
|
+
manager = EnvironmentManager()
|
|
99
|
+
env = manager.get_environment(name)
|
|
100
|
+
|
|
101
|
+
if not env:
|
|
102
|
+
print(f"ERROR: Environment '{name}' not found", file=sys.stderr)
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
|
|
105
|
+
default = manager.get_default_environment()
|
|
106
|
+
|
|
107
|
+
if json_output:
|
|
108
|
+
output = {
|
|
109
|
+
"name": env.name,
|
|
110
|
+
"endpoints": {
|
|
111
|
+
ep_name: {
|
|
112
|
+
"url": ep.url,
|
|
113
|
+
"username": ep.username,
|
|
114
|
+
"ignore_ssl": ep.ignore_ssl,
|
|
115
|
+
"timeout": ep.timeout
|
|
116
|
+
}
|
|
117
|
+
for ep_name, ep in env.endpoints.items()
|
|
118
|
+
},
|
|
119
|
+
"default_endpoint": env.default_endpoint,
|
|
120
|
+
"is_default": env.name == default
|
|
121
|
+
}
|
|
122
|
+
print(json.dumps(output, indent=2))
|
|
123
|
+
else:
|
|
124
|
+
marker = " (default)" if env.name == default else ""
|
|
125
|
+
print(f"Environment: {env.name}{marker}")
|
|
126
|
+
print()
|
|
127
|
+
|
|
128
|
+
if not env.endpoints:
|
|
129
|
+
print(" No endpoints configured")
|
|
130
|
+
print(f"\n Add an endpoint: hac endpoint add {env.name} hac --url https://...")
|
|
131
|
+
else:
|
|
132
|
+
print(f" Endpoints ({len(env.endpoints)}):")
|
|
133
|
+
print()
|
|
134
|
+
for ep_name, ep in sorted(env.endpoints.items()):
|
|
135
|
+
default_marker = " (default)" if ep_name == env.default_endpoint else ""
|
|
136
|
+
print(f" {ep_name}{default_marker}")
|
|
137
|
+
print(f" URL: {ep.url}")
|
|
138
|
+
print(f" Ignore SSL: {ep.ignore_ssl}")
|
|
139
|
+
print(f" Timeout: {ep.timeout}s")
|
|
140
|
+
print()
|
|
141
|
+
|
|
142
|
+
if env.default_endpoint:
|
|
143
|
+
print(f" Start session: hac session start {env.name} --username <user>")
|
|
144
|
+
else:
|
|
145
|
+
print(f" Start session: hac session start {env.name} --endpoint <endpoint-name> --username <user>")
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
149
|
+
raise typer.Exit(1)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@env_app.command("add")
|
|
153
|
+
def add_environment(
|
|
154
|
+
name: str = typer.Argument(..., help="Environment name"),
|
|
155
|
+
set_default: bool = typer.Option(False, "--set-default", "-d", help="Set as default environment")
|
|
156
|
+
):
|
|
157
|
+
"""Add a new environment (add endpoints separately with 'hac endpoint add')."""
|
|
158
|
+
try:
|
|
159
|
+
manager = EnvironmentManager()
|
|
160
|
+
manager.add_environment(
|
|
161
|
+
name=name,
|
|
162
|
+
set_default=set_default
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
marker = " (set as default)" if set_default else ""
|
|
166
|
+
print(f"✓ Environment '{name}' added{marker}")
|
|
167
|
+
print(f"\nNext: Add endpoints to this environment")
|
|
168
|
+
print(f" hac endpoint add {name} <endpoint-name> --url https://...")
|
|
169
|
+
|
|
170
|
+
except ValueError as e:
|
|
171
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
172
|
+
raise typer.Exit(1)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
175
|
+
raise typer.Exit(1)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@env_app.command("remove")
|
|
181
|
+
def remove_environment(
|
|
182
|
+
name: str = typer.Argument(..., help="Environment name")
|
|
183
|
+
):
|
|
184
|
+
"""Remove an environment."""
|
|
185
|
+
try:
|
|
186
|
+
manager = EnvironmentManager()
|
|
187
|
+
|
|
188
|
+
if not manager.get_environment(name):
|
|
189
|
+
print(f"ERROR: Environment '{name}' not found", file=sys.stderr)
|
|
190
|
+
raise typer.Exit(1)
|
|
191
|
+
|
|
192
|
+
manager.remove_environment(name)
|
|
193
|
+
print(f"✓ Environment '{name}' removed")
|
|
194
|
+
|
|
195
|
+
except ValueError as e:
|
|
196
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
197
|
+
raise typer.Exit(1)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
200
|
+
raise typer.Exit(1)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@env_app.command("set-default")
|
|
204
|
+
def set_default(
|
|
205
|
+
name: str = typer.Argument(..., help="Environment name")
|
|
206
|
+
):
|
|
207
|
+
"""Set the default environment."""
|
|
208
|
+
try:
|
|
209
|
+
manager = EnvironmentManager()
|
|
210
|
+
manager.set_default_environment(name)
|
|
211
|
+
print(f"✓ Default environment set to '{name}'")
|
|
212
|
+
|
|
213
|
+
except ValueError as e:
|
|
214
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
215
|
+
raise typer.Exit(1)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
218
|
+
raise typer.Exit(1)
|
|
219
|
+
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""Session management commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import typer
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from hac_client_core.session import SessionManager
|
|
13
|
+
from hac_client_core.client import HacClient
|
|
14
|
+
from hac_client_core.auth import BasicAuthHandler
|
|
15
|
+
|
|
16
|
+
session_app = typer.Typer(help="Manage HAC sessions", no_args_is_help=True)
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def format_duration(seconds: float) -> str:
|
|
21
|
+
"""Format duration in human-readable format."""
|
|
22
|
+
if seconds < 60:
|
|
23
|
+
return f"{int(seconds)}s"
|
|
24
|
+
elif seconds < 3600:
|
|
25
|
+
return f"{int(seconds/60)}m {int(seconds%60)}s"
|
|
26
|
+
else:
|
|
27
|
+
hours = int(seconds / 3600)
|
|
28
|
+
minutes = int((seconds % 3600) / 60)
|
|
29
|
+
return f"{hours}h {minutes}m"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@session_app.command("start")
|
|
33
|
+
def start_session(
|
|
34
|
+
environment: str = typer.Argument(..., help="Environment name"),
|
|
35
|
+
endpoint: Optional[str] = typer.Option(None, "--endpoint", "-n", help="Endpoint name (uses default if not specified)"),
|
|
36
|
+
username: Optional[str] = typer.Option(None, "--username", "-u", help="Username (or use env var)"),
|
|
37
|
+
password: Optional[str] = typer.Option(None, "--password", "-p", help="Password (or use stdin/env var)")
|
|
38
|
+
):
|
|
39
|
+
"""Start a new HAC session (authenticate and create session).
|
|
40
|
+
|
|
41
|
+
Username can be provided via:
|
|
42
|
+
- Command option: --username <user>
|
|
43
|
+
- Environment variable: HAC_USERNAME or HAC_USERNAME_<ENV>_<ENDPOINT>
|
|
44
|
+
- Interactive prompt: if not provided
|
|
45
|
+
|
|
46
|
+
Password can be provided via:
|
|
47
|
+
- Command option: --password <pass> (not recommended)
|
|
48
|
+
- Environment variable: HAC_PASSWORD or HAC_PASSWORD_<ENV>_<ENDPOINT>
|
|
49
|
+
- Standard input: echo 'password' | hac session start <env> --username <user>
|
|
50
|
+
- Interactive prompt: if none of the above
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
hac session start local --username admin
|
|
54
|
+
hac session start local --endpoint hac --username admin
|
|
55
|
+
HAC_USERNAME=admin HAC_PASSWORD=secret hac session start local
|
|
56
|
+
echo 'secret' | hac session start local --username admin
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
from hac_client_cli.config_loader import get_endpoint_config
|
|
60
|
+
|
|
61
|
+
# Get endpoint configuration
|
|
62
|
+
env_name, endpoint_name, ep_config = get_endpoint_config(environment, endpoint)
|
|
63
|
+
|
|
64
|
+
# Create session identifier
|
|
65
|
+
session_id = f"{env_name}/{endpoint_name}"
|
|
66
|
+
|
|
67
|
+
# Get username from various sources
|
|
68
|
+
if not username:
|
|
69
|
+
# Try environment variable (specific to env/endpoint, then env, then generic)
|
|
70
|
+
env_ep_var = f"HAC_USERNAME_{env_name.upper()}_{endpoint_name.upper()}"
|
|
71
|
+
env_var = f"HAC_USERNAME_{env_name.upper()}"
|
|
72
|
+
username = os.environ.get(env_ep_var) or os.environ.get(env_var) or os.environ.get("HAC_USERNAME")
|
|
73
|
+
|
|
74
|
+
if not username:
|
|
75
|
+
print("ERROR: Username not provided", file=sys.stderr)
|
|
76
|
+
print("\nProvide username via:", file=sys.stderr)
|
|
77
|
+
print(f" --username <user>", file=sys.stderr)
|
|
78
|
+
print(f" HAC_USERNAME environment variable", file=sys.stderr)
|
|
79
|
+
print(f" HAC_USERNAME_{env_name.upper()}_{endpoint_name.upper()} environment variable", file=sys.stderr)
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
# Get password from various sources
|
|
83
|
+
if not password:
|
|
84
|
+
# Try environment variable (specific to env/endpoint, then env, then generic)
|
|
85
|
+
env_ep_var = f"HAC_PASSWORD_{env_name.upper()}_{endpoint_name.upper()}"
|
|
86
|
+
env_var = f"HAC_PASSWORD_{env_name.upper()}"
|
|
87
|
+
password = os.environ.get(env_ep_var) or os.environ.get(env_var) or os.environ.get("HAC_PASSWORD")
|
|
88
|
+
|
|
89
|
+
if not password:
|
|
90
|
+
# Try stdin (non-interactive)
|
|
91
|
+
if not sys.stdin.isatty():
|
|
92
|
+
password = sys.stdin.read().strip()
|
|
93
|
+
|
|
94
|
+
if not password:
|
|
95
|
+
print("ERROR: Password not provided", file=sys.stderr)
|
|
96
|
+
print("\nProvide password via:", file=sys.stderr)
|
|
97
|
+
print(f" HAC_PASSWORD environment variable", file=sys.stderr)
|
|
98
|
+
print(f" HAC_PASSWORD_{env_name.upper()}_{endpoint_name.upper()} environment variable", file=sys.stderr)
|
|
99
|
+
print(f" stdin: echo 'password' | hac session start {env_name} --endpoint {endpoint_name} --username {username}", file=sys.stderr)
|
|
100
|
+
raise typer.Exit(1)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Create client and authenticate
|
|
104
|
+
auth = BasicAuthHandler(username, password)
|
|
105
|
+
client = HacClient(
|
|
106
|
+
base_url=ep_config.url,
|
|
107
|
+
auth_handler=auth,
|
|
108
|
+
environment=session_id, # Use composite key
|
|
109
|
+
timeout=ep_config.timeout,
|
|
110
|
+
ignore_ssl=ep_config.ignore_ssl,
|
|
111
|
+
session_persistence=True,
|
|
112
|
+
quiet=False
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Force login to create session
|
|
116
|
+
client.login()
|
|
117
|
+
|
|
118
|
+
print(f"✓ Session started for '{session_id}'")
|
|
119
|
+
print(f" User: {username}")
|
|
120
|
+
print(f" URL: {ep_config.url}")
|
|
121
|
+
|
|
122
|
+
finally:
|
|
123
|
+
# Clear password from memory immediately
|
|
124
|
+
if password:
|
|
125
|
+
password = None
|
|
126
|
+
del password
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@session_app.command("import")
|
|
134
|
+
def import_session(
|
|
135
|
+
environment: str = typer.Argument(..., help="Environment name"),
|
|
136
|
+
endpoint: Optional[str] = typer.Option(None, "--endpoint", "-n", help="Endpoint name (uses default if not specified)"),
|
|
137
|
+
username: Optional[str] = typer.Option(None, "--username", "-u", help="Username (or use env var)"),
|
|
138
|
+
session_id: Optional[str] = typer.Option(None, "--session-id", help="Session ID (JSESSIONID)"),
|
|
139
|
+
csrf_token: Optional[str] = typer.Option(None, "--csrf-token", help="CSRF token"),
|
|
140
|
+
route_cookie: Optional[str] = typer.Option(None, "--route-cookie", help="ROUTE cookie (optional)")
|
|
141
|
+
):
|
|
142
|
+
"""Import an existing HAC session from tokens.
|
|
143
|
+
|
|
144
|
+
Username can be provided via:
|
|
145
|
+
- Command option: --username <user>
|
|
146
|
+
- Environment variable: HAC_USERNAME or HAC_USERNAME_<ENV>_<ENDPOINT>
|
|
147
|
+
|
|
148
|
+
Tokens can be provided via:
|
|
149
|
+
- Command options (not recommended for security)
|
|
150
|
+
- Environment variables:
|
|
151
|
+
- HAC_SESSION_ID or HAC_SESSION_ID_<ENV>_<ENDPOINT>
|
|
152
|
+
- HAC_CSRF_TOKEN or HAC_CSRF_TOKEN_<ENV>_<ENDPOINT>
|
|
153
|
+
- HAC_ROUTE_COOKIE or HAC_ROUTE_COOKIE_<ENV>_<ENDPOINT>
|
|
154
|
+
- Standard input (JSON format with username, session_id, csrf_token fields)
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
# Via environment variables
|
|
158
|
+
HAC_USERNAME=admin HAC_SESSION_ID=abc123 HAC_CSRF_TOKEN=def456 hac session import local
|
|
159
|
+
|
|
160
|
+
# Via stdin (JSON)
|
|
161
|
+
echo '{"username":"admin","session_id":"abc","csrf_token":"def"}' | hac session import local --endpoint hac
|
|
162
|
+
|
|
163
|
+
# Via command options
|
|
164
|
+
hac session import local --endpoint hac --username admin --session-id abc123 --csrf-token def456
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
from hac_client_cli.config_loader import get_endpoint_config
|
|
168
|
+
|
|
169
|
+
# Get endpoint configuration
|
|
170
|
+
env_name, endpoint_name, ep_config = get_endpoint_config(environment, endpoint)
|
|
171
|
+
|
|
172
|
+
# Create session identifier
|
|
173
|
+
sess_id = f"{env_name}/{endpoint_name}"
|
|
174
|
+
|
|
175
|
+
# Get username from various sources
|
|
176
|
+
if not username:
|
|
177
|
+
# Try environment variables
|
|
178
|
+
env_ep_var = f"HAC_USERNAME_{env_name.upper()}_{endpoint_name.upper()}"
|
|
179
|
+
env_var = f"HAC_USERNAME_{env_name.upper()}"
|
|
180
|
+
username = os.environ.get(env_ep_var) or os.environ.get(env_var) or os.environ.get("HAC_USERNAME")
|
|
181
|
+
|
|
182
|
+
# Get tokens from various sources
|
|
183
|
+
if not session_id or not csrf_token or not username:
|
|
184
|
+
# Try stdin (JSON)
|
|
185
|
+
if not sys.stdin.isatty():
|
|
186
|
+
import json
|
|
187
|
+
data = json.load(sys.stdin)
|
|
188
|
+
username = username or data.get("username")
|
|
189
|
+
session_id = session_id or data.get("session_id")
|
|
190
|
+
csrf_token = csrf_token or data.get("csrf_token")
|
|
191
|
+
route_cookie = route_cookie or data.get("route_cookie")
|
|
192
|
+
|
|
193
|
+
if not session_id or not csrf_token:
|
|
194
|
+
# Try environment variables
|
|
195
|
+
env_ep_prefix = f"{env_name.upper()}_{endpoint_name.upper()}"
|
|
196
|
+
env_prefix = env_name.upper()
|
|
197
|
+
session_id = session_id or os.environ.get(f"HAC_SESSION_ID_{env_ep_prefix}") or os.environ.get(f"HAC_SESSION_ID_{env_prefix}") or os.environ.get("HAC_SESSION_ID")
|
|
198
|
+
csrf_token = csrf_token or os.environ.get(f"HAC_CSRF_TOKEN_{env_ep_prefix}") or os.environ.get(f"HAC_CSRF_TOKEN_{env_prefix}") or os.environ.get("HAC_CSRF_TOKEN")
|
|
199
|
+
route_cookie = route_cookie or os.environ.get(f"HAC_ROUTE_COOKIE_{env_ep_prefix}") or os.environ.get(f"HAC_ROUTE_COOKIE_{env_prefix}") or os.environ.get("HAC_ROUTE_COOKIE")
|
|
200
|
+
|
|
201
|
+
if not username:
|
|
202
|
+
print("ERROR: Username not provided", file=sys.stderr)
|
|
203
|
+
print("Provide via --username, HAC_USERNAME env var, or stdin (JSON)", file=sys.stderr)
|
|
204
|
+
raise typer.Exit(1)
|
|
205
|
+
|
|
206
|
+
if not session_id:
|
|
207
|
+
print("ERROR: Session ID not provided", file=sys.stderr)
|
|
208
|
+
print("Provide via --session-id, HAC_SESSION_ID env var, or stdin (JSON)", file=sys.stderr)
|
|
209
|
+
raise typer.Exit(1)
|
|
210
|
+
|
|
211
|
+
if not csrf_token:
|
|
212
|
+
print("ERROR: CSRF token not provided", file=sys.stderr)
|
|
213
|
+
print("Provide via --csrf-token, HAC_CSRF_TOKEN env var, or stdin (JSON)", file=sys.stderr)
|
|
214
|
+
raise typer.Exit(1)
|
|
215
|
+
|
|
216
|
+
# Import session
|
|
217
|
+
session_manager = SessionManager()
|
|
218
|
+
session_manager.save_session(
|
|
219
|
+
base_url=ep_config.url,
|
|
220
|
+
username=username,
|
|
221
|
+
environment=sess_id, # Use composite key
|
|
222
|
+
session_id=session_id,
|
|
223
|
+
csrf_token=csrf_token,
|
|
224
|
+
route_cookie=route_cookie
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
print(f"✓ Session imported for '{sess_id}'")
|
|
228
|
+
print(f" User: {username}")
|
|
229
|
+
print(f" URL: {ep_config.url}")
|
|
230
|
+
print(f" Session ID: {session_id[:16]}...")
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
234
|
+
raise typer.Exit(1)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@session_app.command("list")
|
|
238
|
+
def list_sessions(
|
|
239
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format: table, json"),
|
|
240
|
+
no_headers: bool = typer.Option(False, "--no-headers", help="Suppress column headers"),
|
|
241
|
+
quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output (environment IDs only)")
|
|
242
|
+
):
|
|
243
|
+
"""List all active sessions."""
|
|
244
|
+
try:
|
|
245
|
+
manager = SessionManager()
|
|
246
|
+
sessions = manager.list_sessions()
|
|
247
|
+
|
|
248
|
+
if not sessions:
|
|
249
|
+
if format != "json":
|
|
250
|
+
console.print("[yellow]No active sessions[/yellow]")
|
|
251
|
+
console.print("\nCreate a session: hac session start <environment> --username <user>")
|
|
252
|
+
else:
|
|
253
|
+
print("[]")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# JSON output
|
|
257
|
+
if format == "json":
|
|
258
|
+
output = [
|
|
259
|
+
{
|
|
260
|
+
"environment": s.environment,
|
|
261
|
+
"url": s.base_url,
|
|
262
|
+
"username": s.username,
|
|
263
|
+
"session_id": s.session_id[:16] + "...",
|
|
264
|
+
"created_at": s.created_at_formatted,
|
|
265
|
+
"last_used_at": s.last_used_at_formatted,
|
|
266
|
+
"age_seconds": s.age_seconds,
|
|
267
|
+
"idle_seconds": s.idle_seconds,
|
|
268
|
+
"is_authenticated": s.is_authenticated
|
|
269
|
+
}
|
|
270
|
+
for s in sessions
|
|
271
|
+
]
|
|
272
|
+
print(json.dumps(output, indent=2))
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
# Quiet output (environment IDs only)
|
|
276
|
+
if quiet:
|
|
277
|
+
for s in sessions:
|
|
278
|
+
print(s.environment)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
# Table format (default)
|
|
282
|
+
table = Table(show_header=not no_headers)
|
|
283
|
+
table.add_column("ENVIRONMENT", style="cyan")
|
|
284
|
+
table.add_column("USERNAME")
|
|
285
|
+
table.add_column("URL")
|
|
286
|
+
table.add_column("AUTH", justify="center")
|
|
287
|
+
table.add_column("AGE", justify="right")
|
|
288
|
+
table.add_column("IDLE", justify="right")
|
|
289
|
+
|
|
290
|
+
for s in sessions:
|
|
291
|
+
age = format_duration(s.age_seconds)
|
|
292
|
+
idle = format_duration(s.idle_seconds)
|
|
293
|
+
auth_marker = "[green]✓[/green]" if s.is_authenticated else "[red]✗[/red]"
|
|
294
|
+
|
|
295
|
+
table.add_row(
|
|
296
|
+
s.environment,
|
|
297
|
+
s.username,
|
|
298
|
+
s.base_url,
|
|
299
|
+
auth_marker,
|
|
300
|
+
age,
|
|
301
|
+
idle
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
console.print(table)
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
console.print(f"[red]ERROR: {e}[/red]", file=sys.stderr)
|
|
308
|
+
raise typer.Exit(1)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@session_app.command("show")
|
|
312
|
+
def show_session(
|
|
313
|
+
environment: str = typer.Argument(..., help="Environment name"),
|
|
314
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON")
|
|
315
|
+
):
|
|
316
|
+
"""Show details of a specific session."""
|
|
317
|
+
try:
|
|
318
|
+
from hac_client_cli.environment_manager import EnvironmentManager
|
|
319
|
+
|
|
320
|
+
env_manager = EnvironmentManager()
|
|
321
|
+
env = env_manager.get_environment(environment)
|
|
322
|
+
|
|
323
|
+
if not env:
|
|
324
|
+
print(f"ERROR: Environment '{environment}' not found", file=sys.stderr)
|
|
325
|
+
raise typer.Exit(1)
|
|
326
|
+
|
|
327
|
+
session_manager = SessionManager()
|
|
328
|
+
session = session_manager.load_session(env.url, env.username, environment)
|
|
329
|
+
|
|
330
|
+
if not session:
|
|
331
|
+
print(f"No active session for environment '{environment}'")
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
if json_output:
|
|
335
|
+
output = {
|
|
336
|
+
"environment": session.environment,
|
|
337
|
+
"url": session.base_url,
|
|
338
|
+
"username": session.username,
|
|
339
|
+
"session_id": session.session_id,
|
|
340
|
+
"csrf_token": session.csrf_token[:16] + "...",
|
|
341
|
+
"route_cookie": session.route_cookie,
|
|
342
|
+
"created_at": session.created_at_formatted,
|
|
343
|
+
"last_used_at": session.last_used_at_formatted,
|
|
344
|
+
"age_seconds": session.age_seconds,
|
|
345
|
+
"idle_seconds": session.idle_seconds,
|
|
346
|
+
"is_authenticated": session.is_authenticated
|
|
347
|
+
}
|
|
348
|
+
print(json.dumps(output, indent=2))
|
|
349
|
+
else:
|
|
350
|
+
age = format_duration(session.age_seconds)
|
|
351
|
+
idle = format_duration(session.idle_seconds)
|
|
352
|
+
auth_marker = "✓ authenticated" if session.is_authenticated else "✗ not authenticated"
|
|
353
|
+
|
|
354
|
+
print(f"Session: {session.environment}")
|
|
355
|
+
print(f" URL: {session.base_url}")
|
|
356
|
+
print(f" Username: {session.username}")
|
|
357
|
+
print(f" Status: {auth_marker}")
|
|
358
|
+
print(f" Session ID: {session.session_id}")
|
|
359
|
+
print(f" CSRF Token: {session.csrf_token[:32]}...")
|
|
360
|
+
if session.route_cookie:
|
|
361
|
+
print(f" Route Cookie: {session.route_cookie}")
|
|
362
|
+
print(f"\n Created: {session.created_at_formatted}")
|
|
363
|
+
print(f" Last used: {session.last_used_at_formatted}")
|
|
364
|
+
print(f" Age: {age}")
|
|
365
|
+
print(f" Idle: {idle}")
|
|
366
|
+
|
|
367
|
+
except Exception as e:
|
|
368
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
369
|
+
raise typer.Exit(1)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@session_app.command("clear")
|
|
373
|
+
def clear_session(
|
|
374
|
+
environment: str = typer.Argument(..., help="Environment name")
|
|
375
|
+
):
|
|
376
|
+
"""Clear session for a specific environment."""
|
|
377
|
+
try:
|
|
378
|
+
from hac_client_cli.environment_manager import EnvironmentManager
|
|
379
|
+
|
|
380
|
+
env_manager = EnvironmentManager()
|
|
381
|
+
env = env_manager.get_environment(environment)
|
|
382
|
+
|
|
383
|
+
if not env:
|
|
384
|
+
print(f"ERROR: Environment '{environment}' not found", file=sys.stderr)
|
|
385
|
+
raise typer.Exit(1)
|
|
386
|
+
|
|
387
|
+
session_manager = SessionManager()
|
|
388
|
+
session_manager.remove_session(env.url, env.username, environment)
|
|
389
|
+
|
|
390
|
+
print(f"✓ Session cleared for environment '{environment}'")
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
394
|
+
raise typer.Exit(1)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@session_app.command("clear-all")
|
|
398
|
+
def clear_all_sessions(
|
|
399
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force without confirmation")
|
|
400
|
+
):
|
|
401
|
+
"""Clear all sessions."""
|
|
402
|
+
try:
|
|
403
|
+
if not force:
|
|
404
|
+
confirm = typer.confirm("Clear all sessions?")
|
|
405
|
+
if not confirm:
|
|
406
|
+
print("Cancelled")
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
session_manager = SessionManager()
|
|
410
|
+
count = session_manager.clear_all_sessions()
|
|
411
|
+
|
|
412
|
+
print(f"✓ Cleared {count} session(s)")
|
|
413
|
+
|
|
414
|
+
except Exception as e:
|
|
415
|
+
print(f"ERROR: {e}", file=sys.stderr)
|
|
416
|
+
raise typer.Exit(1)
|
|
417
|
+
|