codedd-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.
- codedd_cli/__init__.py +3 -0
- codedd_cli/__main__.py +19 -0
- codedd_cli/api/__init__.py +4 -0
- codedd_cli/api/client.py +118 -0
- codedd_cli/api/endpoints.py +44 -0
- codedd_cli/api/exceptions.py +25 -0
- codedd_cli/auditor/__init__.py +6 -0
- codedd_cli/auditor/architecture_analyzer.py +1241 -0
- codedd_cli/auditor/architecture_prompts.py +171 -0
- codedd_cli/auditor/complexity_analyzer.py +942 -0
- codedd_cli/auditor/dependency_scanner.py +2478 -0
- codedd_cli/auditor/file_auditor.py +572 -0
- codedd_cli/auditor/git_stats_collector.py +332 -0
- codedd_cli/auditor/response_parser.py +487 -0
- codedd_cli/auditor/vulnerability_validator.py +324 -0
- codedd_cli/auth/__init__.py +4 -0
- codedd_cli/auth/session.py +41 -0
- codedd_cli/auth/token_manager.py +87 -0
- codedd_cli/cli.py +69 -0
- codedd_cli/commands/__init__.py +1 -0
- codedd_cli/commands/audit_cmd.py +1877 -0
- codedd_cli/commands/audits_cmd.py +273 -0
- codedd_cli/commands/auth_cmd.py +230 -0
- codedd_cli/commands/config_cmd.py +454 -0
- codedd_cli/commands/scope_cmd.py +967 -0
- codedd_cli/config/__init__.py +4 -0
- codedd_cli/config/constants.py +22 -0
- codedd_cli/config/settings.py +369 -0
- codedd_cli/llm/__init__.py +1 -0
- codedd_cli/llm/key_manager.py +271 -0
- codedd_cli/models/__init__.py +5 -0
- codedd_cli/models/account.py +13 -0
- codedd_cli/models/audit.py +32 -0
- codedd_cli/models/local_directory.py +26 -0
- codedd_cli/scanner/__init__.py +18 -0
- codedd_cli/scanner/file_classifier.py +247 -0
- codedd_cli/scanner/file_walker.py +207 -0
- codedd_cli/scanner/line_counter.py +75 -0
- codedd_cli/utils/__init__.py +1 -0
- codedd_cli/utils/directory_validator.py +179 -0
- codedd_cli/utils/display.py +493 -0
- codedd_cli/utils/payload_inspector.py +180 -0
- codedd_cli/utils/security.py +14 -0
- codedd_cli/utils/validators.py +37 -0
- codedd_cli-0.1.0.dist-info/METADATA +276 -0
- codedd_cli-0.1.0.dist-info/RECORD +49 -0
- codedd_cli-0.1.0.dist-info/WHEEL +4 -0
- codedd_cli-0.1.0.dist-info/entry_points.txt +3 -0
- codedd_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``codedd audits`` sub-commands: list, select.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.prompt import IntPrompt
|
|
10
|
+
|
|
11
|
+
from codedd_cli.api.client import CodeDDClient
|
|
12
|
+
from codedd_cli.api.endpoints import Endpoints
|
|
13
|
+
from codedd_cli.auth.session import require_auth
|
|
14
|
+
from codedd_cli.config.settings import ConfigManager
|
|
15
|
+
from codedd_cli.models.audit import Audit, GroupAudit
|
|
16
|
+
from codedd_cli.utils.display import (
|
|
17
|
+
print_error,
|
|
18
|
+
print_info,
|
|
19
|
+
print_success,
|
|
20
|
+
render_audit_detail,
|
|
21
|
+
render_audits_table,
|
|
22
|
+
)
|
|
23
|
+
from codedd_cli.utils.payload_inspector import review_request
|
|
24
|
+
from codedd_cli.utils.validators import is_valid_uuid
|
|
25
|
+
|
|
26
|
+
console = Console()
|
|
27
|
+
audits_app = typer.Typer(no_args_is_help=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _fetch_audits(
|
|
31
|
+
client: CodeDDClient,
|
|
32
|
+
offset: int = 0,
|
|
33
|
+
limit: int = 20,
|
|
34
|
+
audit_type: Optional[str] = None,
|
|
35
|
+
) -> tuple[list[Audit], list[GroupAudit], int]:
|
|
36
|
+
"""
|
|
37
|
+
Call the CLI audits endpoint and parse the response into model objects.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Tuple of (single_audits, group_audits, total_count).
|
|
41
|
+
"""
|
|
42
|
+
params: dict[str, str | int] = {"offset": offset, "limit": limit}
|
|
43
|
+
if audit_type:
|
|
44
|
+
params["type"] = audit_type
|
|
45
|
+
|
|
46
|
+
resp = client.get(Endpoints.LIST_AUDITS, params=params)
|
|
47
|
+
|
|
48
|
+
if resp.status_code == 401:
|
|
49
|
+
print_error("Authentication failed. Run [bold cyan]codedd auth login[/bold cyan].")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
|
|
52
|
+
if resp.status_code != 200:
|
|
53
|
+
msg = "Failed to fetch audits"
|
|
54
|
+
try:
|
|
55
|
+
msg = resp.json().get("message", msg)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
print_error(msg)
|
|
59
|
+
raise typer.Exit(code=1)
|
|
60
|
+
|
|
61
|
+
data = resp.json()
|
|
62
|
+
if data.get("status") != "success":
|
|
63
|
+
print_error(data.get("message", "Unknown error from server"))
|
|
64
|
+
raise typer.Exit(code=1)
|
|
65
|
+
|
|
66
|
+
single_audits = [
|
|
67
|
+
Audit(
|
|
68
|
+
audit_uuid=a["audit_uuid"],
|
|
69
|
+
audit_name=a.get("audit_name", ""),
|
|
70
|
+
audit_status=a.get("audit_status", ""),
|
|
71
|
+
audit_type=a.get("audit_type", "single"),
|
|
72
|
+
ai_synthesis=a.get("ai_synthesis"),
|
|
73
|
+
repo_url=a.get("repo_url", ""),
|
|
74
|
+
number_files=a.get("number_files", 0),
|
|
75
|
+
lines_of_code=a.get("lines_of_code", 0),
|
|
76
|
+
)
|
|
77
|
+
for a in data.get("audits_data", [])
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
group_audits = [
|
|
81
|
+
GroupAudit(
|
|
82
|
+
audit_uuid=g["audit_uuid"],
|
|
83
|
+
audit_name=g.get("audit_name", ""),
|
|
84
|
+
audit_status=g.get("audit_status", ""),
|
|
85
|
+
audit_type=g.get("audit_type", "group"),
|
|
86
|
+
ai_synthesis=g.get("ai_synthesis"),
|
|
87
|
+
number_of_sub_audits=g.get("number_of_sub_audits", 0),
|
|
88
|
+
number_files=g.get("number_files", 0),
|
|
89
|
+
lines_of_code=g.get("lines_of_code", 0),
|
|
90
|
+
)
|
|
91
|
+
for g in data.get("group_audits_data", [])
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
total = data.get("total_audits", len(single_audits) + len(group_audits))
|
|
95
|
+
return single_audits, group_audits, total
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@audits_app.command("list")
|
|
99
|
+
@require_auth
|
|
100
|
+
def list_audits(
|
|
101
|
+
audit_type: Optional[str] = typer.Option(
|
|
102
|
+
None,
|
|
103
|
+
"--type",
|
|
104
|
+
"-t",
|
|
105
|
+
help="Filter by audit type: 'single' or 'group'.",
|
|
106
|
+
),
|
|
107
|
+
limit: int = typer.Option(10, "--limit", "-l", help="Number of audits per page (default 10, max 100)."),
|
|
108
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number (1-indexed)."),
|
|
109
|
+
show: bool = typer.Option(
|
|
110
|
+
False,
|
|
111
|
+
"--show",
|
|
112
|
+
"-s",
|
|
113
|
+
help="Write the API request to a file and open it for review before sending.",
|
|
114
|
+
),
|
|
115
|
+
) -> None:
|
|
116
|
+
"""
|
|
117
|
+
List your audits on the CodeDD platform.
|
|
118
|
+
|
|
119
|
+
Displays a table with audit name, status, file count, lines of code,
|
|
120
|
+
and date. Use --type to filter by single or group audits.
|
|
121
|
+
"""
|
|
122
|
+
if audit_type and audit_type not in ("single", "group"):
|
|
123
|
+
print_error("--type must be 'single' or 'group'")
|
|
124
|
+
raise typer.Exit(code=1)
|
|
125
|
+
|
|
126
|
+
if limit < 1 or limit > 100:
|
|
127
|
+
print_error("--limit must be between 1 and 100")
|
|
128
|
+
raise typer.Exit(code=1)
|
|
129
|
+
|
|
130
|
+
offset = (page - 1) * limit
|
|
131
|
+
|
|
132
|
+
cfg = ConfigManager()
|
|
133
|
+
|
|
134
|
+
params: dict[str, str | int] = {"offset": offset, "limit": limit}
|
|
135
|
+
if audit_type:
|
|
136
|
+
params["type"] = audit_type
|
|
137
|
+
|
|
138
|
+
if show:
|
|
139
|
+
confirmed = review_request(
|
|
140
|
+
"GET",
|
|
141
|
+
Endpoints.LIST_AUDITS,
|
|
142
|
+
params=params,
|
|
143
|
+
command_label="Audits List",
|
|
144
|
+
context_note="This request fetches your list of audits from CodeDD. Only pagination and optional type filter are sent.",
|
|
145
|
+
confirm_prompt="Proceed with this request to CodeDD?",
|
|
146
|
+
)
|
|
147
|
+
if not confirmed:
|
|
148
|
+
print_info("Cancelled.")
|
|
149
|
+
raise typer.Exit(code=0)
|
|
150
|
+
|
|
151
|
+
with CodeDDClient(config=cfg) as client:
|
|
152
|
+
single_audits, group_audits, total = _fetch_audits(
|
|
153
|
+
client, offset=offset, limit=limit, audit_type=audit_type
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not single_audits and not group_audits:
|
|
157
|
+
print_info("No audits found. Create an audit at [bold cyan]https://codedd.ai[/bold cyan]")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
active_uuid = (cfg.active_audit_uuid or "").strip() or None
|
|
161
|
+
render_audits_table(single_audits, group_audits, total, active_audit_uuid=active_uuid)
|
|
162
|
+
|
|
163
|
+
# Pagination info
|
|
164
|
+
total_pages = max(1, (total + limit - 1) // limit)
|
|
165
|
+
if total_pages > 1:
|
|
166
|
+
print_info(f"Page {page}/{total_pages} (use --page to navigate)")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@audits_app.command("select")
|
|
170
|
+
@require_auth
|
|
171
|
+
def select_audit(
|
|
172
|
+
audit_uuid: Optional[str] = typer.Argument(
|
|
173
|
+
None,
|
|
174
|
+
help="List index (1, 2, …) or UUID of the audit to select (omit for interactive).",
|
|
175
|
+
),
|
|
176
|
+
show: bool = typer.Option(
|
|
177
|
+
False,
|
|
178
|
+
"--show",
|
|
179
|
+
"-s",
|
|
180
|
+
help="Write the API request to a file and open it for review before sending.",
|
|
181
|
+
),
|
|
182
|
+
) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Select an audit as the active audit for subsequent CLI commands.
|
|
185
|
+
|
|
186
|
+
Pass the row number from [bold]codedd audits list[/bold] (e.g. 1, 2) or a
|
|
187
|
+
full UUID. If omitted, an interactive list is shown.
|
|
188
|
+
"""
|
|
189
|
+
cfg = ConfigManager()
|
|
190
|
+
|
|
191
|
+
list_params: dict[str, str | int] = {"offset": 0, "limit": 50}
|
|
192
|
+
if show:
|
|
193
|
+
confirmed = review_request(
|
|
194
|
+
"GET",
|
|
195
|
+
Endpoints.LIST_AUDITS,
|
|
196
|
+
params=list_params,
|
|
197
|
+
command_label="Audits Select (Fetch List)",
|
|
198
|
+
context_note="This request fetches your list of audits from CodeDD so you can select one. Only pagination params are sent.",
|
|
199
|
+
confirm_prompt="Proceed with this request to CodeDD?",
|
|
200
|
+
)
|
|
201
|
+
if not confirmed:
|
|
202
|
+
print_info("Cancelled.")
|
|
203
|
+
raise typer.Exit(code=0)
|
|
204
|
+
|
|
205
|
+
with CodeDDClient(config=cfg) as client:
|
|
206
|
+
single_audits, group_audits, total = _fetch_audits(client, limit=50)
|
|
207
|
+
|
|
208
|
+
# Build a combined ordered list: group audits first, then single
|
|
209
|
+
all_audits: list[Audit | GroupAudit] = list(group_audits) + list(single_audits)
|
|
210
|
+
|
|
211
|
+
if not all_audits:
|
|
212
|
+
print_info("No audits available.")
|
|
213
|
+
raise typer.Exit()
|
|
214
|
+
|
|
215
|
+
if audit_uuid:
|
|
216
|
+
audit_uuid = audit_uuid.strip()
|
|
217
|
+
|
|
218
|
+
# Selection by 1-based list index (e.g. "1", "2" from "codedd audits list")
|
|
219
|
+
try:
|
|
220
|
+
index = int(audit_uuid)
|
|
221
|
+
if 1 <= index <= len(all_audits):
|
|
222
|
+
_set_active(cfg, all_audits[index - 1])
|
|
223
|
+
return
|
|
224
|
+
print_error(f"Index must be between 1 and {len(all_audits)}.")
|
|
225
|
+
raise typer.Exit(code=1)
|
|
226
|
+
except ValueError:
|
|
227
|
+
pass # Not an integer; treat as UUID below
|
|
228
|
+
|
|
229
|
+
# Direct selection by UUID
|
|
230
|
+
if not is_valid_uuid(audit_uuid):
|
|
231
|
+
print_error("Invalid UUID format. Use a list index (e.g. 1) or a full UUID.")
|
|
232
|
+
raise typer.Exit(code=1)
|
|
233
|
+
|
|
234
|
+
match = next(
|
|
235
|
+
(a for a in all_audits if a.audit_uuid == audit_uuid),
|
|
236
|
+
None,
|
|
237
|
+
)
|
|
238
|
+
if not match:
|
|
239
|
+
print_error(f"Audit {audit_uuid} not found in your audits.")
|
|
240
|
+
raise typer.Exit(code=1)
|
|
241
|
+
|
|
242
|
+
_set_active(cfg, match)
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Interactive selection
|
|
246
|
+
active_uuid = (cfg.active_audit_uuid or "").strip() or None
|
|
247
|
+
render_audits_table(
|
|
248
|
+
[a for a in all_audits if isinstance(a, Audit)],
|
|
249
|
+
[a for a in all_audits if isinstance(a, GroupAudit)],
|
|
250
|
+
total,
|
|
251
|
+
active_audit_uuid=active_uuid,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
console.print()
|
|
255
|
+
choice = IntPrompt.ask(
|
|
256
|
+
"Select an audit by number",
|
|
257
|
+
default=1,
|
|
258
|
+
choices=[str(i) for i in range(1, len(all_audits) + 1)],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
selected = all_audits[choice - 1]
|
|
262
|
+
_set_active(cfg, selected)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _set_active(cfg: ConfigManager, audit: Audit | GroupAudit) -> None:
|
|
266
|
+
"""Persist the selected audit in config and render its details."""
|
|
267
|
+
cfg.set_active_audit(
|
|
268
|
+
audit_uuid=audit.audit_uuid,
|
|
269
|
+
audit_type=audit.audit_type,
|
|
270
|
+
audit_name=audit.audit_name,
|
|
271
|
+
)
|
|
272
|
+
print_success(f"Active audit set to [bold]{audit.audit_name}[/bold]")
|
|
273
|
+
render_audit_detail(audit)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``codedd auth`` sub-commands: login, logout, status.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
from codedd_cli.api.client import CodeDDClient
|
|
12
|
+
from codedd_cli.api.endpoints import Endpoints
|
|
13
|
+
from codedd_cli.auth.token_manager import TokenManager
|
|
14
|
+
from codedd_cli.config.settings import ConfigManager
|
|
15
|
+
from codedd_cli.models.account import AccountInfo
|
|
16
|
+
from codedd_cli.utils.display import print_error, print_info, print_success
|
|
17
|
+
from codedd_cli.utils.payload_inspector import review_request
|
|
18
|
+
from codedd_cli.utils.validators import is_valid_cli_token
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
auth_app = typer.Typer(no_args_is_help=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@auth_app.command("login")
|
|
25
|
+
def login(
|
|
26
|
+
token: Optional[str] = typer.Option(
|
|
27
|
+
None,
|
|
28
|
+
"--token",
|
|
29
|
+
"-t",
|
|
30
|
+
help="CLI token (omit to be prompted interactively).",
|
|
31
|
+
prompt=False,
|
|
32
|
+
),
|
|
33
|
+
show: bool = typer.Option(
|
|
34
|
+
False,
|
|
35
|
+
"--show",
|
|
36
|
+
"-s",
|
|
37
|
+
help="Write the verify request to a file and open it for review before sending.",
|
|
38
|
+
),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Authenticate with the CodeDD platform using a CLI token.
|
|
42
|
+
|
|
43
|
+
Generate a token from your account settings at https://codedd.ai
|
|
44
|
+
(Account → CLI Access → Generate Token), then run:
|
|
45
|
+
|
|
46
|
+
codedd auth login --token <your_token>
|
|
47
|
+
|
|
48
|
+
Or simply run ``codedd auth login`` to be prompted.
|
|
49
|
+
"""
|
|
50
|
+
# Interactive prompt if --token not given
|
|
51
|
+
if not token:
|
|
52
|
+
token = typer.prompt("Paste your CLI token", hide_input=True)
|
|
53
|
+
|
|
54
|
+
# Strip whitespace and newlines (handles copy-paste issues)
|
|
55
|
+
token = token.strip().replace("\n", "").replace("\r", "")
|
|
56
|
+
|
|
57
|
+
# Local format check
|
|
58
|
+
if not is_valid_cli_token(token):
|
|
59
|
+
# Provide more helpful error message
|
|
60
|
+
if not token:
|
|
61
|
+
print_error("Token cannot be empty.")
|
|
62
|
+
elif not token.startswith("codedd_cli_"):
|
|
63
|
+
print_error(
|
|
64
|
+
f"Invalid token format. Token must start with [bold]codedd_cli_[/bold].\n"
|
|
65
|
+
f"Received: [dim]{token[:20]}...[/dim]"
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
print_error(
|
|
69
|
+
f"Invalid token format. Token appears too short or contains invalid characters.\n"
|
|
70
|
+
f"Expected: [bold]codedd_cli_[/bold] followed by at least 32 base64url characters."
|
|
71
|
+
)
|
|
72
|
+
raise typer.Exit(code=1)
|
|
73
|
+
|
|
74
|
+
# Verify against server
|
|
75
|
+
console.print("[dim]Verifying token with CodeDD server…[/dim]")
|
|
76
|
+
cfg = ConfigManager()
|
|
77
|
+
|
|
78
|
+
# Temporarily store token so the client can use it
|
|
79
|
+
TokenManager.store(token)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
if show:
|
|
83
|
+
confirmed = review_request(
|
|
84
|
+
"POST",
|
|
85
|
+
Endpoints.VERIFY_TOKEN,
|
|
86
|
+
command_label="Auth Verify (Login)",
|
|
87
|
+
context_note="This request verifies your CLI token with CodeDD. The token is sent in the X-CLI-Token header (value not shown in this file).",
|
|
88
|
+
confirm_prompt="Proceed with sending this request to CodeDD?",
|
|
89
|
+
)
|
|
90
|
+
if not confirmed:
|
|
91
|
+
TokenManager.delete()
|
|
92
|
+
print_info("Cancelled.")
|
|
93
|
+
raise typer.Exit(code=0)
|
|
94
|
+
|
|
95
|
+
with CodeDDClient(config=cfg) as client:
|
|
96
|
+
resp = client.post(Endpoints.VERIFY_TOKEN)
|
|
97
|
+
|
|
98
|
+
if resp.status_code == 200:
|
|
99
|
+
data = resp.json()
|
|
100
|
+
if data.get("status") == "success":
|
|
101
|
+
account = AccountInfo(
|
|
102
|
+
account_uuid=data["account_uuid"],
|
|
103
|
+
account_name=data.get("account_name", ""),
|
|
104
|
+
email=data.get("email", ""),
|
|
105
|
+
token_name=data.get("token_name", ""),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Persist session metadata
|
|
109
|
+
cfg.account_uuid = account.account_uuid
|
|
110
|
+
cfg.account_name = account.account_name
|
|
111
|
+
cfg.token_hint = TokenManager.token_hint(token)
|
|
112
|
+
cfg.save()
|
|
113
|
+
|
|
114
|
+
print_success(f"Authenticated as [bold]{account.account_name}[/bold] ({account.email})")
|
|
115
|
+
if account.token_name:
|
|
116
|
+
print_info(f"Token: {account.token_name}")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
# Auth failed — clean up
|
|
120
|
+
TokenManager.delete()
|
|
121
|
+
error_msg = "Authentication failed"
|
|
122
|
+
try:
|
|
123
|
+
error_msg = resp.json().get("message", error_msg)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
print_error(error_msg)
|
|
127
|
+
raise typer.Exit(code=1)
|
|
128
|
+
|
|
129
|
+
except Exception as exc:
|
|
130
|
+
TokenManager.delete()
|
|
131
|
+
if isinstance(exc, typer.Exit):
|
|
132
|
+
raise
|
|
133
|
+
print_error(f"Connection error: {exc}")
|
|
134
|
+
raise typer.Exit(code=1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@auth_app.command("logout")
|
|
138
|
+
def logout() -> None:
|
|
139
|
+
"""
|
|
140
|
+
Remove stored credentials and end the current CLI session.
|
|
141
|
+
"""
|
|
142
|
+
cfg = ConfigManager()
|
|
143
|
+
|
|
144
|
+
if not cfg.is_authenticated:
|
|
145
|
+
print_info("You are not currently logged in.")
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
account_name = cfg.account_name or "user"
|
|
149
|
+
TokenManager.delete()
|
|
150
|
+
cfg.clear_session()
|
|
151
|
+
|
|
152
|
+
print_success(f"Logged out ([dim]{account_name}[/dim]).")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@auth_app.command("status")
|
|
156
|
+
def status(
|
|
157
|
+
show: bool = typer.Option(
|
|
158
|
+
False,
|
|
159
|
+
"--show",
|
|
160
|
+
"-s",
|
|
161
|
+
help="Write the verify request to a file and open it for review before sending.",
|
|
162
|
+
),
|
|
163
|
+
) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Show the current authentication state.
|
|
166
|
+
"""
|
|
167
|
+
cfg = ConfigManager()
|
|
168
|
+
token = TokenManager.retrieve()
|
|
169
|
+
|
|
170
|
+
if not cfg.is_authenticated or not token:
|
|
171
|
+
console.print(
|
|
172
|
+
Panel(
|
|
173
|
+
"[bold red]Not authenticated[/bold red]\n\n"
|
|
174
|
+
"Run [bold cyan]codedd auth login[/bold cyan] to connect.",
|
|
175
|
+
title="Auth Status",
|
|
176
|
+
expand=False,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
# Optionally verify token is still valid server-side
|
|
182
|
+
if show:
|
|
183
|
+
confirmed = review_request(
|
|
184
|
+
"POST",
|
|
185
|
+
Endpoints.VERIFY_TOKEN,
|
|
186
|
+
command_label="Auth Verify (Status)",
|
|
187
|
+
context_note="This request verifies your CLI token with CodeDD. The token is sent in the X-CLI-Token header (value not shown in this file).",
|
|
188
|
+
confirm_prompt="Proceed with sending this request to CodeDD?",
|
|
189
|
+
)
|
|
190
|
+
if not confirmed:
|
|
191
|
+
print_info("Cancelled. Skipping server verification.")
|
|
192
|
+
# Still show local state
|
|
193
|
+
panel_text = (
|
|
194
|
+
f"[bold green]Authenticated (local)[/bold green]\n\n"
|
|
195
|
+
f"[bold]Account:[/bold] {cfg.account_name}\n"
|
|
196
|
+
f"[bold]Token:[/bold] {cfg.token_hint}\n"
|
|
197
|
+
f"[bold]API URL:[/bold] {cfg.api_url}\n\n"
|
|
198
|
+
"[dim]Server verification was skipped (--show cancelled).[/dim]"
|
|
199
|
+
)
|
|
200
|
+
console.print(Panel(panel_text, title="Auth Status", expand=False))
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
with CodeDDClient(config=cfg) as client:
|
|
204
|
+
try:
|
|
205
|
+
resp = client.post(Endpoints.VERIFY_TOKEN)
|
|
206
|
+
if resp.status_code == 200 and resp.json().get("status") == "success":
|
|
207
|
+
server_info = resp.json()
|
|
208
|
+
panel_text = (
|
|
209
|
+
f"[bold green]Authenticated[/bold green]\n\n"
|
|
210
|
+
f"[bold]Account:[/bold] {server_info.get('account_name', cfg.account_name)}\n"
|
|
211
|
+
f"[bold]Email:[/bold] {server_info.get('email', '—')}\n"
|
|
212
|
+
f"[bold]Token:[/bold] {cfg.token_hint}\n"
|
|
213
|
+
f"[bold]API URL:[/bold] {cfg.api_url}"
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
panel_text = (
|
|
217
|
+
f"[bold yellow]Token invalid or expired[/bold yellow]\n\n"
|
|
218
|
+
f"[bold]Account:[/bold] {cfg.account_name}\n"
|
|
219
|
+
f"[bold]Token:[/bold] {cfg.token_hint}\n\n"
|
|
220
|
+
"Run [bold cyan]codedd auth login[/bold cyan] with a new token."
|
|
221
|
+
)
|
|
222
|
+
except Exception:
|
|
223
|
+
panel_text = (
|
|
224
|
+
f"[bold yellow]Could not reach server[/bold yellow]\n\n"
|
|
225
|
+
f"[bold]Account:[/bold] {cfg.account_name}\n"
|
|
226
|
+
f"[bold]Token:[/bold] {cfg.token_hint}\n"
|
|
227
|
+
f"[bold]API URL:[/bold] {cfg.api_url}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
console.print(Panel(panel_text, title="Auth Status", expand=False))
|