proxcli 0.6.0__tar.gz → 0.7.0__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 (52) hide show
  1. {proxcli-0.6.0 → proxcli-0.7.0}/CHANGELOG.md +9 -0
  2. {proxcli-0.6.0 → proxcli-0.7.0}/PKG-INFO +7 -3
  3. {proxcli-0.6.0 → proxcli-0.7.0}/README.md +6 -2
  4. {proxcli-0.6.0 → proxcli-0.7.0}/TODO.md +12 -11
  5. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/tasks.py +16 -4
  6. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/client/client.py +78 -0
  7. {proxcli-0.6.0 → proxcli-0.7.0}/pyproject.toml +1 -1
  8. {proxcli-0.6.0 → proxcli-0.7.0}/uv.lock +1 -1
  9. {proxcli-0.6.0 → proxcli-0.7.0}/.env.example +0 -0
  10. {proxcli-0.6.0 → proxcli-0.7.0}/.github/workflows/ci.yml +0 -0
  11. {proxcli-0.6.0 → proxcli-0.7.0}/.gitignore +0 -0
  12. {proxcli-0.6.0 → proxcli-0.7.0}/.python-version +0 -0
  13. {proxcli-0.6.0 → proxcli-0.7.0}/AGENTS.md +0 -0
  14. {proxcli-0.6.0 → proxcli-0.7.0}/PLAN.md +0 -0
  15. {proxcli-0.6.0 → proxcli-0.7.0}/PROJECT.md +0 -0
  16. {proxcli-0.6.0 → proxcli-0.7.0}/PROMPT.md +0 -0
  17. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/__init__.py +0 -0
  18. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/__init__.py +0 -0
  19. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/auth.py +0 -0
  20. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/cluster.py +0 -0
  21. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/completion.py +0 -0
  22. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/container.py +0 -0
  23. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/firewall_helpers.py +0 -0
  24. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/main.py +0 -0
  25. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/node.py +0 -0
  26. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/pool.py +0 -0
  27. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/storage.py +0 -0
  28. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/cli/vm.py +0 -0
  29. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/client/__init__.py +0 -0
  30. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/client/auth.py +0 -0
  31. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/client/exceptions.py +0 -0
  32. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/config/__init__.py +0 -0
  33. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/config/config.py +0 -0
  34. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/config/models.py +0 -0
  35. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/output/__init__.py +0 -0
  36. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/output/formatter.py +0 -0
  37. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/output/json_fmt.py +0 -0
  38. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/output/table_fmt.py +0 -0
  39. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/output/yaml_fmt.py +0 -0
  40. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/utils/__init__.py +0 -0
  41. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/utils/helpers.py +0 -0
  42. {proxcli-0.6.0 → proxcli-0.7.0}/proxmox/utils/logging.py +0 -0
  43. {proxcli-0.6.0 → proxcli-0.7.0}/tests/__init__.py +0 -0
  44. {proxcli-0.6.0 → proxcli-0.7.0}/tests/conftest.py +0 -0
  45. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_auth.py +0 -0
  46. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_cli/__init__.py +0 -0
  47. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_cli/test_main.py +0 -0
  48. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_client.py +0 -0
  49. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_config.py +0 -0
  50. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_integration/__init__.py +0 -0
  51. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_output/__init__.py +0 -0
  52. {proxcli-0.6.0 → proxcli-0.7.0}/tests/test_output/test_formatter.py +0 -0
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-06-20
11
+
12
+ ### Added
13
+ - Task log streaming: `proxmox task log <upid> [--follow]`.
14
+ Without `--follow`, prints available log lines. With `--follow`,
15
+ polls every second until the task exits (like `tail -f`).
16
+ Also added `ProxmoxClient._extract_node_from_upid()` as a static helper.
17
+
10
18
  ## [0.6.0] - 2026-06-20
11
19
 
12
20
  ### Added
@@ -74,6 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
74
82
  - CSRF ticket auto-refresh on 401.
75
83
  - AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
76
84
 
85
+ [0.7.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.7.0
77
86
  [0.6.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.6.0
78
87
  [0.5.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.5.0
79
88
  [0.4.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.4.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxcli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: A CLI tool to interact with Proxmox VE nodes and clusters via the REST API
5
5
  Author-email: Xabi Ezpeleta <xezpeleta@gmail.com>
6
6
  License: MIT
@@ -21,7 +21,7 @@ Requires-Dist: pyyaml>=6
21
21
  Requires-Dist: rich>=13
22
22
  Description-Content-Type: text/markdown
23
23
 
24
- # proxmox
24
+ # proxcli
25
25
 
26
26
  A CLI tool to interact with [Proxmox VE](https://www.proxmox.com/) nodes and clusters via the REST API.
27
27
 
@@ -33,7 +33,7 @@ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
33
33
 
34
34
  ```bash
35
35
  # From PyPI
36
- uv tool install proxmox
36
+ uv tool install proxcli
37
37
 
38
38
  # From Git
39
39
  uv tool install git+https://github.com/xezpeleta/proxcli.git
@@ -276,8 +276,12 @@ proxmox cluster firewall refs [--type alias|ipset|group]
276
276
  ```bash
277
277
  proxmox task list [--node <node>]
278
278
  proxmox task show <upid>
279
+ proxmox task log <upid> [--follow]
279
280
  ```
280
281
 
282
+ `proxmox task log --follow` polls `/nodes/{node}/tasks/{upid}/log` every second
283
+ and streams new lines until the task completes (like `tail -f`).
284
+
281
285
  ## Output Formats
282
286
 
283
287
  ### JSON (default)
@@ -1,4 +1,4 @@
1
- # proxmox
1
+ # proxcli
2
2
 
3
3
  A CLI tool to interact with [Proxmox VE](https://www.proxmox.com/) nodes and clusters via the REST API.
4
4
 
@@ -10,7 +10,7 @@ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
10
10
 
11
11
  ```bash
12
12
  # From PyPI
13
- uv tool install proxmox
13
+ uv tool install proxcli
14
14
 
15
15
  # From Git
16
16
  uv tool install git+https://github.com/xezpeleta/proxcli.git
@@ -253,8 +253,12 @@ proxmox cluster firewall refs [--type alias|ipset|group]
253
253
  ```bash
254
254
  proxmox task list [--node <node>]
255
255
  proxmox task show <upid>
256
+ proxmox task log <upid> [--follow]
256
257
  ```
257
258
 
259
+ `proxmox task log --follow` polls `/nodes/{node}/tasks/{upid}/log` every second
260
+ and streams new lines until the task completes (like `tail -f`).
261
+
258
262
  ## Output Formats
259
263
 
260
264
  ### JSON (default)
@@ -2,15 +2,20 @@
2
2
 
3
3
  Planned improvements for future releases. Items are roughly ordered by priority.
4
4
 
5
+ Completed items are marked with a check. Implementation notes are preserved for context.
6
+
5
7
  ---
6
8
 
7
- ## v1.1 — Polish & Usability
9
+ ## Done
10
+
11
+ - [x] **Firewall management** — cluster, node, VM, and container. Options, enable/disable, policy, rules (CRUD), aliases (cluster), ipsets with CIDR mgmt (cluster), refs.
12
+ - [x] **Pool management** — `proxmox pool`: list, show, create, update, delete. Wraps `/pools`.
13
+ - [x] **Shell completions** — `proxmox completion bash|zsh|fish`. Dynamic, introspects the parser tree.
8
14
 
9
- - [ ] **Shell completions**
10
- - Add `proxmox completion bash|zsh|fish` subcommand that emits a completion script. Use argparse's built-in completion or a lightweight generator. Makes tab-completion work for all subcommands and flags.
15
+ ## v1.1 Polish & Usability
11
16
 
12
17
  - [ ] **Streaming task logs (`--follow`)**
13
- - `proxmox task log <upid>` that streams task output in real time (like `tail -f`). Requires httpx streaming support (already viable — httpx is the chosen HTTP client).
18
+ - `proxmox task log <upid> --follow` that streams task output in real time (like `tail -f`). Requires httpx streaming.
14
19
 
15
20
  - [ ] **Startup time optimization**
16
21
  - Current `proxmox --help` takes ~350ms. Lazy-load subcommand modules so only the requested resource's code is imported. Move `import rich`, `import yaml` inside formatter functions. Target: <200ms.
@@ -29,17 +34,13 @@ Planned improvements for future releases. Items are roughly ordered by priority.
29
34
  - `proxmox backup` subcommand: `list`, `create`, `show`, `delete`. Wrap `/nodes/{node}/vzdump` and `/nodes/{node}/storage/{storage}/content` for backup files.
30
35
 
31
36
  - [ ] **User & permission management**
32
- - `proxmox user` subcommand: `list`, `show`, `create`, `update`, `delete`. Wraps `/access/users`, `/access/acl`, `/access/roles`.
33
-
34
- - [ ] **Pool management**
35
- - `proxmox pool` subcommand: `list`, `show`, `create`, `update`, `delete`. Wraps `/pools`.
37
+ - `proxmox user` subcommand: `list`, `show`, `create`, `update`, `delete`.
38
+ - `proxmox role` subcommand: `list`, `show`, `create`, `update`, `delete`.
39
+ - `proxmox acl` subcommand: `list`, `show`. Wraps `/access/users`, `/access/roles`, `/access/acl`, `/access/groups`.
36
40
 
37
41
  - [ ] **Network management**
38
42
  - `proxmox network` subcommand: `list`, `show`, `update` for bridges, bonds, VLANs. Wraps `/nodes/{node}/network`.
39
43
 
40
- - [ ] **Firewall management**
41
- - `proxmox firewall` subcommand: `list`, `show`, `enable`, `disable`, `create rule`, `delete rule`. Wraps `/nodes/{node}/firewall`, `/cluster/firewall`.
42
-
43
44
  - [ ] **SDN (Software-Defined Networking)**
44
45
  - `proxmox sdn` subcommand: `zones`, `vnets`, `subnets`. Wraps `/cluster/sdn/*` endpoints.
45
46
 
@@ -22,13 +22,20 @@ def register_task_parser(subparsers: argparse._SubParsersAction) -> None:
22
22
  task_show.add_argument("upid", help="Task UPID")
23
23
  task_show.set_defaults(func=_task_show)
24
24
 
25
+ # --- task log ---
26
+ task_log = task_sub.add_parser("log", help="Show task log output")
27
+ task_log.add_argument("upid", help="Task UPID")
28
+ task_log.add_argument(
29
+ "--follow", "-f",
30
+ action="store_true",
31
+ help="Follow log output until task completes (like tail -f)",
32
+ )
33
+ task_log.set_defaults(func=_task_log)
34
+
25
35
 
26
36
  def _extract_node_from_upid(upid: str) -> str | None:
27
37
  """Parse node name from a Proxmox UPID string: UPID:{node}:..."""
28
- parts = upid.split(":")
29
- if len(parts) >= 2:
30
- return parts[1]
31
- return None
38
+ return ProxmoxClient._extract_node_from_upid(upid)
32
39
 
33
40
 
34
41
  def _task_list(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
@@ -63,3 +70,8 @@ def _task_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
63
70
  if not node:
64
71
  return {"error": f"Could not extract node from UPID: {args.upid}"}
65
72
  return client.get(f"/nodes/{node}/tasks/{args.upid}/status")
73
+
74
+
75
+ def _task_log(args: argparse.Namespace, client: ProxmoxClient) -> None:
76
+ """Stream task log (returns None so main.py skips JSON formatting)."""
77
+ client.stream_task_log(args.upid, follow=args.follow)
@@ -280,3 +280,81 @@ class ProxmoxClient:
280
280
  import sys
281
281
 
282
282
  print(f"[proxmox] {message}", file=sys.stderr)
283
+
284
+ # ------------------------------------------------------------------
285
+ # Task log streaming
286
+ # ------------------------------------------------------------------
287
+
288
+ def stream_task_log(self, upid: str, *, follow: bool = False) -> None:
289
+ """Stream task log lines to stdout.
290
+
291
+ Args:
292
+ upid: Task UPID string (e.g. UPID:pve01:00000010:...).
293
+ follow: If True, keep polling until task exits.
294
+ """
295
+ import sys
296
+
297
+ node = self._extract_node_from_upid(upid)
298
+ if not node:
299
+ print(f"Error: could not extract node from UPID: {upid}", file=sys.stderr)
300
+ return
301
+
302
+ if self._dry_run:
303
+ self._print_dry_run("GET", f"{self._base_url}/api2/json/nodes/{node}/tasks/{upid}/log", None)
304
+ if follow:
305
+ print("[dry-run] --follow would poll /tasks/{upid}/status until stopped")
306
+ return
307
+
308
+ start = 0
309
+ seen_lines: set[int] = set()
310
+
311
+ while True:
312
+ try:
313
+ log_data = self.request("GET", f"/nodes/{node}/tasks/{upid}/log", params={"start": start})
314
+ except ProxmoxAPIError:
315
+ if not follow:
316
+ break
317
+ time.sleep(1)
318
+ continue
319
+
320
+ if isinstance(log_data, dict):
321
+ log_data = log_data.get("data", []) if "data" in log_data else []
322
+
323
+ if isinstance(log_data, list):
324
+ for entry in log_data:
325
+ if not isinstance(entry, dict):
326
+ continue
327
+ n = entry.get("n", 0)
328
+ if n in seen_lines:
329
+ continue
330
+ seen_lines.add(n)
331
+ line = entry.get("t", "")
332
+ print(line)
333
+
334
+ # Check if task is done
335
+ if follow:
336
+ try:
337
+ status_data = self.request("GET", f"/nodes/{node}/tasks/{upid}/status")
338
+ if isinstance(status_data, dict):
339
+ status = status_data.get("status")
340
+ if status == "stopped":
341
+ exit_code = status_data.get("exitstatus")
342
+ if exit_code is not None:
343
+ print(f"\nTask completed with exit code: {exit_code}")
344
+ break
345
+ except ProxmoxAPIError:
346
+ pass
347
+
348
+ if not follow:
349
+ break
350
+
351
+ start = len(seen_lines)
352
+ time.sleep(1)
353
+
354
+ @staticmethod
355
+ def _extract_node_from_upid(upid: str) -> str | None:
356
+ """Parse node name from a Proxmox UPID string: UPID:{node}:..."""
357
+ parts = upid.split(":")
358
+ if len(parts) >= 2:
359
+ return parts[1]
360
+ return None
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "proxcli"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "A CLI tool to interact with Proxmox VE nodes and clusters via the REST API"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -254,7 +254,7 @@ wheels = [
254
254
 
255
255
  [[package]]
256
256
  name = "proxcli"
257
- version = "0.6.0"
257
+ version = "0.7.0"
258
258
  source = { editable = "." }
259
259
  dependencies = [
260
260
  { name = "httpx" },
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