qualys-cli 0.1.6__tar.gz → 0.1.7__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 (69) hide show
  1. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/CHANGELOG.md +14 -1
  2. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/PKG-INFO +1 -1
  3. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/pyproject.toml +1 -1
  4. qualys_cli-0.1.7/src/qualys_cli/__init__.py +1 -0
  5. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/_docs/usage.html +2 -2
  6. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/client.py +9 -8
  7. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_features.py +18 -0
  8. qualys_cli-0.1.6/src/qualys_cli/__init__.py +0 -1
  9. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/.gitignore +0 -0
  10. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/LICENSE +0 -0
  11. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/README.md +0 -0
  12. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/__main__.py +0 -0
  13. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/audit.py +0 -0
  14. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/auth.py +0 -0
  15. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/cache.py +0 -0
  16. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/cli.py +0 -0
  17. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/__init__.py +0 -0
  18. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/asset.py +0 -0
  19. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/ca.py +0 -0
  20. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/cs.py +0 -0
  21. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/csam.py +0 -0
  22. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/etm.py +0 -0
  23. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/pc.py +0 -0
  24. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/pm.py +0 -0
  25. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/scanauth.py +0 -0
  26. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/sub.py +0 -0
  27. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/tc.py +0 -0
  28. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/user.py +0 -0
  29. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/vm.py +0 -0
  30. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/commands/was.py +0 -0
  31. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/config.py +0 -0
  32. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/extras.py +0 -0
  33. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/flair.py +0 -0
  34. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/formatters.py +0 -0
  35. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/history.py +0 -0
  36. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/http_server.py +0 -0
  37. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/mcp_server.py +0 -0
  38. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/metrics.py +0 -0
  39. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/platforms.py +0 -0
  40. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/src/qualys_cli/queries.py +0 -0
  41. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/__init__.py +0 -0
  42. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/conftest.py +0 -0
  43. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_agent_mode.py +0 -0
  44. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_all_commands_smoke.py +0 -0
  45. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_asset.py +0 -0
  46. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_audit.py +0 -0
  47. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_ca.py +0 -0
  48. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_client.py +0 -0
  49. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_csam.py +0 -0
  50. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_error_handling.py +0 -0
  51. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_etm.py +0 -0
  52. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_flair.py +0 -0
  53. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_formatters.py +0 -0
  54. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_global_flags.py +0 -0
  55. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_help.py +0 -0
  56. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_high_fixes.py +0 -0
  57. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_main.py +0 -0
  58. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_med_fixes.py +0 -0
  59. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_pc.py +0 -0
  60. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_pm.py +0 -0
  61. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_resiliency.py +0 -0
  62. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_review_fixes.py +0 -0
  63. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_scanauth.py +0 -0
  64. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_security_fixes.py +0 -0
  65. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_sub.py +0 -0
  66. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_tc.py +0 -0
  67. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_user.py +0 -0
  68. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_vm.py +0 -0
  69. {qualys_cli-0.1.6 → qualys_cli-0.1.7}/tests/test_was.py +0 -0
@@ -6,6 +6,18 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.7] — 2026-07-17
10
+
11
+ ### Fixed
12
+ - `--dry-run` / `--explain` now renders JSON **array** request bodies instead
13
+ of dropping them to `null`. Commands that send a bare JSON array — e.g.
14
+ `etm report bulk-delete`, which `DELETE`s `/reports` with a raw
15
+ `["id", …]` body — previously showed `"body": null` in the explain output.
16
+ Because that JSON is the documented contract parsed by the `mcp`, `serve`,
17
+ and `batch` consumers, the actual payload was invisible to them and to
18
+ anyone auditing a request before sending it. Array bodies are now shown
19
+ (and secret-redacted) in both the human panel and the machine-readable JSON.
20
+
9
21
  ## [0.1.6] — 2026-07-17
10
22
 
11
23
  ### Security
@@ -217,7 +229,8 @@ Initial public release.
217
229
  - Ruff lint clean (E, F, I, UP, B, C4, SIM rule sets).
218
230
  - Codegen pipeline (`scripts/`) is opt-in via the `codegen` extra.
219
231
 
220
- [Unreleased]: https://github.com/qualys/qualys-cli/compare/v0.1.5...HEAD
232
+ [Unreleased]: https://github.com/qualys/qualys-cli/compare/v0.1.6...HEAD
233
+ [0.1.6]: https://github.com/qualys/qualys-cli/compare/v0.1.5...v0.1.6
221
234
  [0.1.5]: https://github.com/qualys/qualys-cli/compare/v0.1.4...v0.1.5
222
235
  [0.1.4]: https://github.com/qualys/qualys-cli/compare/v0.1.2...v0.1.4
223
236
  [0.1.2]: https://github.com/qualys/qualys-cli/compare/v0.1.1...v0.1.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qualys-cli
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Unified command-line interface for the Qualys Cloud Platform REST APIs (VM, PC, PM, ETM, WAS, TotalCloud, Cloud Agent, Container Security, CSAM, Subscription, Asset, and User management).
5
5
  Project-URL: Homepage, https://www.qualys.com
6
6
  Author-email: Qualys <support@qualys.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "qualys-cli"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "Unified command-line interface for the Qualys Cloud Platform REST APIs (VM, PC, PM, ETM, WAS, TotalCloud, Cloud Agent, Container Security, CSAM, Subscription, Asset, and User management)."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1 @@
1
+ __version__ = "0.1.7"
@@ -612,7 +612,7 @@ body.filtering.no-match section.module{display:none}
612
612
  <aside class="sidebar" aria-label="Documentation navigation">
613
613
  <div class="side-header">
614
614
  <img src="https://ik.imagekit.io/qualys/image/logo/qualys.svg" alt="Qualys">
615
- <span class="ver">v0.1.3</span>
615
+ <span class="ver">v0.1.7</span>
616
616
  </div>
617
617
  <div class="side-search">
618
618
  <input type="search" id="filter" placeholder="Search commands…"
@@ -696,7 +696,7 @@ body.filtering.no-match section.module{display:none}
696
696
  <div class="hero-stats">
697
697
  <div class="hero-stat"><span class="n">28</span><span class="l">Modules</span></div>
698
698
  <div class="hero-stat"><span class="n">228</span><span class="l">Commands</span></div>
699
- <div class="hero-stat"><span class="n">v0.1.3</span><span class="l">Version</span></div>
699
+ <div class="hero-stat"><span class="n">v0.1.7</span><span class="l">Version</span></div>
700
700
  <div class="hero-stat"><span class="n">3.12+</span><span class="l">Python</span></div>
701
701
  <div class="hero-stat"><span class="n">MIT</span><span class="l">License</span></div>
702
702
  </div>
@@ -408,11 +408,12 @@ class QualysClient:
408
408
  url fully-qualified request URL (profile + path)
409
409
  auth "basic" or "jwt"
410
410
  params query string as a JSON object (redacted)
411
- body request body normalised to a JSON object whenever
412
- possible. If the original body was a JSON string we
413
- parse it back so callers see structure, not a string.
414
- If the body is non-JSON (XML, form-encoded), it is
415
- surfaced as a string preview. ``null`` if no body.
411
+ body request body normalised to a JSON value (object or
412
+ array) whenever possible. If the original body was a
413
+ JSON string we parse it back so callers see structure,
414
+ not a string. If the body is non-JSON (XML,
415
+ form-encoded), it is surfaced as a string preview.
416
+ ``null`` if no body.
416
417
  headers request headers (Authorization redacted)
417
418
 
418
419
  This contract is the public surface that MCP / serve / batch
@@ -432,7 +433,7 @@ class QualysClient:
432
433
 
433
434
  # Redact secrets in the rendered body / params
434
435
  params = _audit.redact_mapping(dict(kwargs.get("params") or {}))
435
- if isinstance(body, dict):
436
+ if isinstance(body, (dict, list)):
436
437
  body = _audit.redact_mapping(body)
437
438
  elif isinstance(body, str):
438
439
  # Try to round-trip a string body back into a dict — ``post_xml``
@@ -457,7 +458,7 @@ class QualysClient:
457
458
  lines.append(f"[dim]auth:[/dim] {self.auth_type}")
458
459
  if params:
459
460
  lines.append(f"[dim]params:[/dim] {_json.dumps(params, default=str)}")
460
- if isinstance(body, dict):
461
+ if isinstance(body, (dict, list)):
461
462
  lines.append(f"[dim]body:[/dim] {_json.dumps(body, default=str, indent=2)}")
462
463
  elif isinstance(body, str):
463
464
  preview = body if len(body) < 400 else body[:400] + "…"
@@ -468,7 +469,7 @@ class QualysClient:
468
469
  if not _console.is_terminal:
469
470
  payload = {
470
471
  "method": method, "url": url, "auth": self.auth_type,
471
- "params": params, "body": body if isinstance(body, (dict, str)) else None,
472
+ "params": params, "body": body if isinstance(body, (dict, list, str)) else None,
472
473
  "headers": hdrs,
473
474
  }
474
475
  print(_json.dumps(payload, default=str))
@@ -180,6 +180,24 @@ def test_dry_run_via_cli(monkeypatch):
180
180
  assert not bad.called
181
181
 
182
182
 
183
+ @respx.mock
184
+ def test_dry_run_renders_list_body(monkeypatch):
185
+ """A JSON *array* request body (e.g. `etm report bulk-delete`) must appear
186
+ in the --explain output, not be flattened to null.
187
+
188
+ The dry-run JSON is the contract MCP/serve/batch callers parse; a list
189
+ body silently rendered as null hid the actual payload. Regression guard.
190
+ """
191
+ _env(monkeypatch)
192
+ respx.post(f"{BASE}/auth").mock(return_value=httpx.Response(200, content=b"tok"))
193
+ result = runner.invoke(
194
+ app, ["--dry-run", "etm", "report", "bulk-delete", "101,102,103", "--yes"]
195
+ )
196
+ assert result.exit_code == 0, result.output
197
+ payload = json.loads(result.output.strip().splitlines()[-1])
198
+ assert payload["body"] == ["101", "102", "103"], payload
199
+
200
+
183
201
  # ── Audit HMAC chain ────────────────────────────────────────────────────────
184
202
 
185
203
  def test_audit_chain_intact(monkeypatch, tmp_path):
@@ -1 +0,0 @@
1
- __version__ = "0.1.6"
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