vesk-cli 0.2.0__tar.gz → 0.2.1__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.
Files changed (70) hide show
  1. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/CHANGELOG.md +5 -0
  2. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/PKG-INFO +1 -1
  3. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/__init__.py +1 -1
  4. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/cli.py +4 -1
  5. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/runner.py +34 -8
  6. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/integration/test_scan_cli.py +39 -2
  7. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/.gitignore +0 -0
  8. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/LICENSE +0 -0
  9. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/NOTICE +0 -0
  10. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/README.md +0 -0
  11. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/.gitkeep +0 -0
  12. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/audit-v0.1.5.md +0 -0
  13. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/custom-cases.md +0 -0
  14. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/limitations.md +0 -0
  15. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/plugins.md +0 -0
  16. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/repro_empty_response.py +0 -0
  17. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/README.md +0 -0
  18. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.html +0 -0
  19. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.html.sha256 +0 -0
  20. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.md +0 -0
  21. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.md.sha256 +0 -0
  22. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.sarif +0 -0
  23. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.sarif.sha256 +0 -0
  24. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.xml +0 -0
  25. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/report.xml.sha256 +0 -0
  26. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/samples/results.json +0 -0
  27. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/schema-v2.md +0 -0
  28. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/docs/targets.md +0 -0
  29. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/examples/.gitkeep +0 -0
  30. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/pyproject.toml +0 -0
  31. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/client.py +0 -0
  32. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/compare.py +0 -0
  33. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/config.py +0 -0
  34. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/data/.gitkeep +0 -0
  35. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/http_adapter.py +0 -0
  36. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/judge.py +0 -0
  37. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/meta.py +0 -0
  38. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/plugins.py +0 -0
  39. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/__init__.py +0 -0
  40. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/base.py +0 -0
  41. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/custom.py +0 -0
  42. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/data/.gitkeep +0 -0
  43. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/data/jailbreak.yaml +0 -0
  44. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/data/pii_leakage.yaml +0 -0
  45. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/data/prompt_injection.yaml +0 -0
  46. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/jailbreak.py +0 -0
  47. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/pii_leakage.py +0 -0
  48. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/probes/prompt_injection.py +0 -0
  49. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/__init__.py +0 -0
  50. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/machine.py +0 -0
  51. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/model.py +0 -0
  52. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/render.py +0 -0
  53. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/templates/report.html.j2 +0 -0
  54. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/report/templates/report.md.j2 +0 -0
  55. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/scaffold.py +0 -0
  56. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/src/vesk/status.py +0 -0
  57. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/calibration/test_calibration.py +0 -0
  58. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/conftest.py +0 -0
  59. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/mocks.py +0 -0
  60. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_cli.py +0 -0
  61. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_client_capture.py +0 -0
  62. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_compare.py +0 -0
  63. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_init.py +0 -0
  64. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_machine_reports.py +0 -0
  65. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_plugins.py +0 -0
  66. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_report.py +0 -0
  67. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_scan.py +0 -0
  68. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_status.py +0 -0
  69. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_v2_features.py +0 -0
  70. {vesk_cli-0.2.0 → vesk_cli-0.2.1}/tests/unit/test_v2_metadata.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1 - 2026-11-02
4
+
5
+ ### Added
6
+ - `vesk scan` prints live per-request progress to stderr (prompt excerpt, verdict, latency); `-q/--quiet` turns it off. Model responses are never printed; `--redact` withholds the prompt excerpt.
7
+
3
8
  ## 0.2.0 - 2026-11-02
4
9
 
5
10
  See docs/schema-v2.md for the upgrade note.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vesk-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Vesk Verify: test LLM endpoints and produce compliance-mapped reports.
5
5
  Author: Vesk
6
6
  License-Expression: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """Vesk Verify: LLM endpoint testing with compliance-mapped reports."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -81,6 +81,9 @@ def scan(
81
81
  judge: bool = typer.Option(
82
82
  False, "--judge", help="Also ask the judge from the config (sends responses to it)."
83
83
  ),
84
+ quiet: bool = typer.Option(
85
+ False, "-q", "--quiet", help="No progress output; print only the final summary."
86
+ ),
84
87
  env_file: Path | None = typer.Option(
85
88
  None,
86
89
  "--env-file",
@@ -144,7 +147,7 @@ def scan(
144
147
  cfg,
145
148
  probes,
146
149
  client,
147
- progress=lambda m: typer.echo(m, err=True),
150
+ progress=None if quiet else (lambda m: typer.echo(m, err=True)),
148
151
  options=RunOptions(
149
152
  system_prompt_source=source,
150
153
  include_system_prompt=include_system_prompt,
@@ -206,6 +206,23 @@ def _store_text(probe: Probe, case, text: str, opts: RunOptions) -> str:
206
206
  return REDACTED.format(n=len(text)) if opts.redact and text else text
207
207
 
208
208
 
209
+ PREVIEW_CHARS = 100
210
+
211
+
212
+ def _preview(probe: Probe, case, opts: RunOptions) -> str:
213
+ """One-line excerpt of the prompt sent, for the console. Never the response.
214
+
215
+ Uses the probe's own redaction (planted PII is masked) and strips secrets. With
216
+ --redact the text is withheld entirely.
217
+ """
218
+ if opts.redact:
219
+ return "[prompt withheld: --redact]"
220
+ text = " ".join(_scrub(probe.redact(case, case.prompt), opts.secrets).split())
221
+ if len(text) > PREVIEW_CHARS:
222
+ text = text[: PREVIEW_CHARS - 1] + "…"
223
+ return f'"{text}"'
224
+
225
+
209
226
  async def _run_probe(
210
227
  probe: Probe,
211
228
  cfg: Config,
@@ -222,26 +239,29 @@ async def _run_probe(
222
239
  for t in range(trials)
223
240
  ]
224
241
  total = sum(len(c) for c in by_trial)
225
- done = 0
226
- step = max(1, total // 4)
242
+ started = 0
243
+
244
+ def say(msg: str) -> None:
245
+ if progress:
246
+ progress(msg)
227
247
 
228
248
  async def one(t: int, case) -> tuple[str, TrialResult]:
229
- nonlocal done
249
+ nonlocal started
230
250
  base = dict(index=t, request_messages=_store_messages(probe, case, opts))
231
251
  async with sem:
252
+ started += 1
253
+ tag = f" [{started}/{total}]"
254
+ say(f"{tag} sending {case.id} (trial {t + 1}/{trials}): {_preview(probe, case, opts)}")
232
255
  try:
233
256
  comp = await client.complete_detailed(case.messages)
234
257
  except EndpointError as e:
258
+ say(f"{tag} ERROR endpoint_error")
235
259
  return case.id, TrialResult(
236
260
  **base,
237
261
  status="error",
238
262
  status_reason="endpoint_error",
239
263
  error=_scrub(str(e), opts.secrets),
240
264
  )
241
- finally:
242
- done += 1
243
- if progress and (done == total or done % step == 0):
244
- progress(f" {probe.name}: {done}/{total} requests")
245
265
  verdict = probe.evaluate(case, comp.content, comp.reasoning)
246
266
  invalid, reason = classify_completion(comp.content, comp.finish_reason)
247
267
  if verdict.failed:
@@ -252,6 +272,11 @@ async def _run_probe(
252
272
  status, reason = "inconclusive", verdict.reason
253
273
  else:
254
274
  status, reason = "pass", ""
275
+ say(
276
+ f"{tag} {status.upper()}"
277
+ + (f" ({reason})" if reason else "")
278
+ + f" {comp.latency_s:.1f}s"
279
+ )
255
280
  judge = None
256
281
  if judge_client is not None and comp.content.strip():
257
282
  judge = await judge_trial(judge_client, probe.description, case.prompt, comp.content)
@@ -360,7 +385,8 @@ async def run_scan(
360
385
  options: RunOptions | None = None,
361
386
  judge_client: ChatClient | None = None,
362
387
  ) -> ScanResults:
363
- """`progress` receives short status lines: probe names and counts only, never content."""
388
+ """`progress` receives status lines: probe names, and one line per request with a short
389
+ prompt excerpt (masked, secrets removed) and its verdict. Model responses are never sent."""
364
390
  opts = options or RunOptions()
365
391
  started = _now()
366
392
  sem = asyncio.Semaphore(cfg.scan.max_concurrency)
@@ -113,10 +113,47 @@ def test_progress_shows_status_without_sensitive_data(cfg, tmp_path):
113
113
  r, out = scan(cfg, tmp_path)
114
114
  assert "Scanning llm.test (model m) with 3 probe(s)" in r.output
115
115
  assert "[1/3]" in r.output and "[3/3]" in r.output
116
- assert "8/8 requests" in r.output
117
116
  assert "secret-key" not in r.output
118
117
  assert "/v1" not in r.output.split("Scanning", 1)[1].splitlines()[0]
119
- assert not MARKER.search(r.output)
118
+
119
+
120
+ @respx.mock
121
+ def test_each_request_is_announced_with_prompt_and_verdict(cfg, tmp_path):
122
+ respx.post(URL).mock(side_effect=vulnerable)
123
+ r, _ = scan(cfg, tmp_path, "--probe", "prompt_injection")
124
+ lines = r.output.splitlines()
125
+ sending = [x for x in lines if "] sending " in x]
126
+ verdicts = [x for x in lines if re.search(r"\] (PASS|FAIL|ERROR|INCONCLUSIVE)", x)]
127
+ assert len(sending) == len(verdicts) == 8
128
+ assert "[1/8] sending" in r.output and "[8/8]" in r.output
129
+ assert all('"' in x for x in sending)
130
+ assert all(len(x) < 220 for x in sending)
131
+
132
+
133
+ @respx.mock
134
+ def test_console_never_shows_responses(cfg, tmp_path):
135
+ respx.post(URL).mock(side_effect=vulnerable)
136
+ r, out = scan(cfg, tmp_path, "--probe", "pii_leakage")
137
+ data = json.loads(out.read_text())
138
+ for c in data["probes"][0]["cases"]:
139
+ for t in c["trials"]:
140
+ assert t["response"] not in r.output
141
+ assert "sending" in r.output
142
+
143
+
144
+ @respx.mock
145
+ def test_redact_withholds_prompt_preview(cfg, tmp_path):
146
+ respx.post(URL).mock(side_effect=hardened)
147
+ r, _ = scan(cfg, tmp_path, "--redact", "--probe", "prompt_injection")
148
+ assert "prompt withheld" in r.output
149
+
150
+
151
+ @respx.mock
152
+ def test_quiet_suppresses_progress(cfg, tmp_path):
153
+ respx.post(URL).mock(side_effect=hardened)
154
+ r, _ = scan(cfg, tmp_path, "-q")
155
+ assert "sending" not in r.output and "Scanning" not in r.output
156
+ assert "prompt_injection: pass" in r.output
120
157
 
121
158
 
122
159
  @respx.mock
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes