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.
Files changed (49) hide show
  1. codedd_cli/__init__.py +3 -0
  2. codedd_cli/__main__.py +19 -0
  3. codedd_cli/api/__init__.py +4 -0
  4. codedd_cli/api/client.py +118 -0
  5. codedd_cli/api/endpoints.py +44 -0
  6. codedd_cli/api/exceptions.py +25 -0
  7. codedd_cli/auditor/__init__.py +6 -0
  8. codedd_cli/auditor/architecture_analyzer.py +1241 -0
  9. codedd_cli/auditor/architecture_prompts.py +171 -0
  10. codedd_cli/auditor/complexity_analyzer.py +942 -0
  11. codedd_cli/auditor/dependency_scanner.py +2478 -0
  12. codedd_cli/auditor/file_auditor.py +572 -0
  13. codedd_cli/auditor/git_stats_collector.py +332 -0
  14. codedd_cli/auditor/response_parser.py +487 -0
  15. codedd_cli/auditor/vulnerability_validator.py +324 -0
  16. codedd_cli/auth/__init__.py +4 -0
  17. codedd_cli/auth/session.py +41 -0
  18. codedd_cli/auth/token_manager.py +87 -0
  19. codedd_cli/cli.py +69 -0
  20. codedd_cli/commands/__init__.py +1 -0
  21. codedd_cli/commands/audit_cmd.py +1877 -0
  22. codedd_cli/commands/audits_cmd.py +273 -0
  23. codedd_cli/commands/auth_cmd.py +230 -0
  24. codedd_cli/commands/config_cmd.py +454 -0
  25. codedd_cli/commands/scope_cmd.py +967 -0
  26. codedd_cli/config/__init__.py +4 -0
  27. codedd_cli/config/constants.py +22 -0
  28. codedd_cli/config/settings.py +369 -0
  29. codedd_cli/llm/__init__.py +1 -0
  30. codedd_cli/llm/key_manager.py +271 -0
  31. codedd_cli/models/__init__.py +5 -0
  32. codedd_cli/models/account.py +13 -0
  33. codedd_cli/models/audit.py +32 -0
  34. codedd_cli/models/local_directory.py +26 -0
  35. codedd_cli/scanner/__init__.py +18 -0
  36. codedd_cli/scanner/file_classifier.py +247 -0
  37. codedd_cli/scanner/file_walker.py +207 -0
  38. codedd_cli/scanner/line_counter.py +75 -0
  39. codedd_cli/utils/__init__.py +1 -0
  40. codedd_cli/utils/directory_validator.py +179 -0
  41. codedd_cli/utils/display.py +493 -0
  42. codedd_cli/utils/payload_inspector.py +180 -0
  43. codedd_cli/utils/security.py +14 -0
  44. codedd_cli/utils/validators.py +37 -0
  45. codedd_cli-0.1.0.dist-info/METADATA +276 -0
  46. codedd_cli-0.1.0.dist-info/RECORD +49 -0
  47. codedd_cli-0.1.0.dist-info/WHEEL +4 -0
  48. codedd_cli-0.1.0.dist-info/entry_points.txt +3 -0
  49. 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))