exploitsynth 0.3.2__tar.gz → 0.3.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exploitsynth
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: ExploitSynth scanner CLI — AI port-identification from your terminal
5
5
  Project-URL: Homepage, https://scan.exploitsynth.com
6
6
  Project-URL: Documentation, https://scan.exploitsynth.com/docs
@@ -1,3 +1,3 @@
1
1
  """ExploitSynth scanner CLI."""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.3"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import re
6
7
  import sys
7
8
  from pathlib import Path
@@ -63,6 +64,68 @@ def _client() -> Client:
63
64
  output.fail(str(e))
64
65
 
65
66
 
67
+ def _resolve_scan_id(client: Client, partial: str) -> str:
68
+ """Accept a full UUID or the short prefix that `scans` prints (e.g. 004bafa9)."""
69
+ p = partial.strip()
70
+ if len(p) >= 32: # already a full UUID
71
+ return p
72
+ try:
73
+ rows = client.list_scans(limit=100)
74
+ except ApiError as e:
75
+ output.fail(str(e))
76
+ matches = [str(r["id"]) for r in rows if str(r.get("id", "")).startswith(p)]
77
+ if not matches:
78
+ output.fail(f"No recent scan matching '{partial}'.")
79
+ if len(matches) > 1:
80
+ output.fail(f"'{partial}' matches several scans; use more characters.")
81
+ return matches[0]
82
+
83
+
84
+ def _render_scan(
85
+ client: Client,
86
+ scan_id: str,
87
+ *,
88
+ reasoning: bool = False,
89
+ output_path: "Optional[Path]" = None,
90
+ json_output: bool = False,
91
+ ) -> None:
92
+ """Fetch one scan and print its results; optionally save the full payload to a file."""
93
+ full_id = _resolve_scan_id(client, scan_id)
94
+ try:
95
+ data = client.get_scan(full_id)
96
+ except ApiError as e:
97
+ output.fail(str(e))
98
+
99
+ if output_path is not None:
100
+ output_path.write_text(json.dumps(data, indent=2) + "\n")
101
+ output.note(f"Saved results to {output_path}")
102
+
103
+ if json_output:
104
+ output.emit_json(data)
105
+ return
106
+
107
+ scan = data["scan"]
108
+ results = data["results"]
109
+
110
+ header = Text()
111
+ header.append(scan.get("ip", "?"), style="bold white")
112
+ header.append(f" {scan.get('status', '?')}", style="dim")
113
+ out.print(header)
114
+
115
+ if not results:
116
+ out.print(Text("No services identified.", style="dim"))
117
+ else:
118
+ out.print(results_table(results))
119
+ if reasoning:
120
+ for r in sorted(results, key=lambda x: x.get("port", 0)):
121
+ if r.get("extra_info"):
122
+ out.print(Text(f"\n:{r.get('port')} {r.get('extra_info')}", style="dim"))
123
+
124
+ if scan.get("summary"):
125
+ out.print(Text("\nSummary", style="bold"))
126
+ out.print(Text(scan["summary"], style="dim"))
127
+
128
+
66
129
  # ─────────────────────────── login ───────────────────────────
67
130
  @app.command()
68
131
  def login(
@@ -324,17 +387,24 @@ def scan(
324
387
  # ─────────────────────────── scans (list) ─────────────────────
325
388
  @app.command(name="scans")
326
389
  def list_scans(
390
+ scan_id: Optional[str] = typer.Argument(None, help="A scan id (full or short) to view its results; omit to list."),
327
391
  project: Optional[str] = typer.Option(None, "--project", help="Filter by project name."),
328
392
  limit: int = typer.Option(20, "--limit", "-n", help="Max rows."),
393
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="When viewing one scan, save its results as JSON here."),
329
394
  json_output: bool = JSON_OPT,
330
395
  ):
331
- """List recent scans.
396
+ """List recent scans, or view one: `exploitsynth scans <id>`.
332
397
 
333
398
  Example:
334
399
  exploitsynth scans --json | jq '.[] | {id, status}'
335
400
  """
336
401
  output.set_json(json_output)
337
402
  client = _client()
403
+
404
+ if scan_id:
405
+ _render_scan(client, scan_id, output_path=output_path, json_output=json_output)
406
+ return
407
+
338
408
  try:
339
409
  rows = client.list_scans(project=project, limit=limit)
340
410
  except ApiError as e:
@@ -370,43 +440,15 @@ def list_scans(
370
440
  # ─────────────────────────── show ────────────────────────────
371
441
  @app.command()
372
442
  def show(
373
- scan_id: str = typer.Argument(..., help="Scan id (full UUID)."),
443
+ scan_id: str = typer.Argument(..., help="Scan id (full UUID or the short prefix `scans` prints)."),
374
444
  reasoning: bool = typer.Option(False, "--reasoning", "-r", help="Include the agent's reasoning per port."),
445
+ output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Save the full results as JSON to this file."),
375
446
  json_output: bool = JSON_OPT,
376
447
  ):
377
448
  """Show one scan's results."""
378
449
  output.set_json(json_output)
379
450
  client = _client()
380
- try:
381
- data = client.get_scan(scan_id)
382
- except ApiError as e:
383
- output.fail(str(e))
384
-
385
- if json_output:
386
- output.emit_json(data)
387
- return
388
-
389
- scan = data["scan"]
390
- results = data["results"]
391
-
392
- header = Text()
393
- header.append(scan.get("ip", "?"), style="bold white")
394
- header.append(f" {scan.get('status', '?')}", style="dim")
395
- out.print(header)
396
-
397
- if not results:
398
- out.print(Text("No services identified.", style="dim"))
399
- else:
400
- out.print(results_table(results))
401
-
402
- if reasoning:
403
- for r in sorted(results, key=lambda x: x.get("port", 0)):
404
- if r.get("extra_info"):
405
- out.print(Text(f"\n:{r.get('port')} — {r.get('extra_info')}", style="dim"))
406
-
407
- if scan.get("summary"):
408
- out.print(Text("\nSummary", style="bold"))
409
- out.print(Text(scan["summary"], style="dim"))
451
+ _render_scan(client, scan_id, reasoning=reasoning, output_path=output_path, json_output=json_output)
410
452
 
411
453
 
412
454
  # ─────────────────────────── credits ─────────────────────────
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "exploitsynth"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "ExploitSynth scanner CLI — AI port-identification from your terminal"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
File without changes
File without changes