portacode 1.4.11.dev4__py3-none-any.whl → 1.4.11.dev6__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.
portacode/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.4.11.dev4'
32
- __version_tuple__ = version_tuple = (1, 4, 11, 'dev4')
31
+ __version__ = version = '1.4.11.dev6'
32
+ __version_tuple__ = version_tuple = (1, 4, 11, 'dev6')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -380,6 +380,22 @@ Emitted after a successful `create_proxmox_container` action. Contains the new c
380
380
  * `container` (object): Metadata such as `vmid`, `hostname`, `template`, `storage`, `disk_gib`, `ram_mib`, and `cpus`.
381
381
  * `setup_steps` (array[object]): Detailed bootstrap step results (name, stdout/stderr, elapsed time, and status).
382
382
 
383
+ ### `proxmox_container_progress`
384
+
385
+ Sent intermittently while `create_proxmox_container` is executing so callers can display a progress indicator. Each notification describes the currently running step (validation, provisioning, or each bootstrap command) and whether it succeeded or failed.
386
+
387
+ **Event Fields:**
388
+
389
+ * `step_index` (integer): 1-based index of the current step inside the entire provisioning sequence.
390
+ * `total_steps` (integer): Total number of steps that must run before provisioning completes.
391
+ * `step_name` (string): Internal step identifier (e.g., `create_container`, `apt_update`, `portacode_connect`).
392
+ * `step_label` (string): Human-friendly label suitable for UI (e.g., `Create container`, `Apt update`).
393
+ * `status` (string): One of `in_progress`, `completed`, or `failed`.
394
+ * `phase` (string): Either `lifecycle` (environment/container lifecycle) or `bootstrap` (per-command bootstrap work).
395
+ * `message` (string): Short description of what is happening or why a failure occurred.
396
+ * `details` (object, optional): Contains `attempt` (if retries were needed) and `error_summary` when a step fails.
397
+ * `request_id` (string, optional): Mirrors the request ID from the incoming `create_proxmox_container` payload when available.
398
+
383
399
  ### `clock_sync_request`
384
400
 
385
401
  Internal event that devices send to the gateway to request the authoritative server timestamp (used for adjusting `portacode.utils.ntp_clock`). The gateway responds immediately with [`clock_sync_response`](#clock_sync_response).
@@ -1075,6 +1091,22 @@ Emitted after a successful `create_proxmox_container` action to report the newly
1075
1091
  * `container` (object): Metadata such as `vmid`, `hostname`, `template`, `storage`, `disk_gib`, `ram_mib`, and `cpus`.
1076
1092
  * `setup_steps` (array[object]): Detailed bootstrap step reports including stdout/stderr, elapsed time, and pass/fail status.
1077
1093
 
1094
+ ### `proxmox_container_progress`
1095
+
1096
+ Sent continuously while `create_proxmox_container` runs so dashboards can show a progress bar tied to each lifecycle and bootstrap step.
1097
+
1098
+ **Event Fields:**
1099
+
1100
+ * `step_index` (integer): 1-based position of the step inside the entire provisioning workflow.
1101
+ * `total_steps` (integer): Total number of lifecycle and bootstrap steps for the current operation.
1102
+ * `step_name` (string): Internal identifier (e.g., `validate_environment`, `install_deps`, `portacode_connect`).
1103
+ * `step_label` (string): Friendly label suitable for the UI.
1104
+ * `status` (string): One of `in_progress`, `completed`, or `failed`.
1105
+ * `phase` (string): Either `lifecycle` (node validation/container lifecycle) or `bootstrap` (commands run inside the CT).
1106
+ * `message` (string): Short human-readable description of the action or failure.
1107
+ * `details` (object, optional): Contains `attempt` (when retries are used) and `error_summary` on failure.
1108
+ * `request_id` (string, optional): Mirrors the `create_proxmox_container` request when provided.
1109
+
1078
1110
  ### <a name="clock_sync_response"></a>`clock_sync_response`
1079
1111
 
1080
1112
  Reply sent by the gateway immediately after receiving a `clock_sync_request`. Devices use this event plus the measured round-trip time to keep their local `ntp_clock` offset accurate.
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import json
6
7
  import logging
7
8
  import os
@@ -12,7 +13,7 @@ import sys
12
13
  import time
13
14
  from datetime import datetime
14
15
  from pathlib import Path
15
- from typing import Any, Dict, Iterable, List, Optional, Tuple
16
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
16
17
 
17
18
  import platformdirs
18
19
 
@@ -39,6 +40,8 @@ IFACES_PATH = Path("/etc/network/interfaces")
39
40
  SYSCTL_PATH = Path("/etc/sysctl.d/99-portacode-forward.conf")
40
41
  UNIT_DIR = Path("/etc/systemd/system")
41
42
 
43
+ ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
44
+
42
45
 
43
46
  def _call_subprocess(cmd: List[str], **kwargs: Any) -> subprocess.CompletedProcess[str]:
44
47
  env = os.environ.copy()
@@ -245,6 +248,66 @@ def _format_rootfs(storage: str, disk_gib: int, storage_type: str) -> str:
245
248
  return f"{storage}:{disk_gib}G"
246
249
 
247
250
 
251
+ def _get_provisioning_user_info(message: Dict[str, Any]) -> Tuple[str, str, str]:
252
+ user = (message.get("username") or "svcuser").strip() if message else "svcuser"
253
+ user = user or "svcuser"
254
+ password = message.get("password") or "" if message else ""
255
+ ssh_key = (message.get("ssh_key") or "").strip() if message else ""
256
+ return user, password, ssh_key
257
+
258
+
259
+ def _friendly_step_label(step_name: str) -> str:
260
+ if not step_name:
261
+ return "Step"
262
+ normalized = step_name.replace("_", " ").strip()
263
+ return normalized.capitalize()
264
+
265
+
266
+ def _build_bootstrap_steps(user: str, password: str, ssh_key: str) -> List[Dict[str, Any]]:
267
+ steps = [
268
+ {
269
+ "name": "apt_update",
270
+ "cmd": "apt-get update -y",
271
+ "retries": 4,
272
+ "retry_delay_s": 5,
273
+ "retry_on": [
274
+ "Temporary failure resolving",
275
+ "Could not resolve",
276
+ "Failed to fetch",
277
+ ],
278
+ },
279
+ {
280
+ "name": "install_deps",
281
+ "cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
282
+ "retries": 5,
283
+ "retry_delay_s": 5,
284
+ "retry_on": [
285
+ "lock-frontend",
286
+ "Unable to acquire the dpkg frontend lock",
287
+ "Temporary failure resolving",
288
+ "Could not resolve",
289
+ "Failed to fetch",
290
+ ],
291
+ },
292
+ {"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
293
+ {"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
294
+ ]
295
+ if password:
296
+ steps.append({"name": "set_password", "cmd": f"echo '{user}:{password}' | chpasswd", "retries": 0})
297
+ if ssh_key:
298
+ steps.append({
299
+ "name": "add_ssh_key",
300
+ "cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
301
+ "retries": 0,
302
+ })
303
+ steps.extend([
304
+ {"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
305
+ {"name": "install_portacode", "cmd": "python3 -m pip install --upgrade portacode", "retries": 0},
306
+ {"name": "portacode_connect", "type": "portacode_connect", "timeout_s": 30},
307
+ ])
308
+ return steps
309
+
310
+
248
311
  def _get_storage_type(storages: Iterable[Dict[str, Any]], storage_name: str) -> str:
249
312
  for entry in storages:
250
313
  if entry.get("storage") == storage_name:
@@ -333,9 +396,7 @@ def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) ->
333
396
  if not storage:
334
397
  raise ValueError("Storage pool could not be determined.")
335
398
 
336
- user = (message.get("username") or "svcuser").strip() or "svcuser"
337
- password = message.get("password") or ""
338
- ssh_key = (message.get("ssh_key") or "").strip()
399
+ user, password, ssh_key = _get_provisioning_user_info(message)
339
400
 
340
401
  payload = {
341
402
  "template": template,
@@ -471,15 +532,31 @@ def _summarize_error(res: Dict[str, Any]) -> str:
471
532
  return "Command failed; see stdout/stderr for details."
472
533
 
473
534
 
474
- def _run_setup_steps(vmid: int, steps: List[Dict[str, Any]], user: str) -> Tuple[List[Dict[str, Any]], bool]:
535
+ def _run_setup_steps(
536
+ vmid: int,
537
+ steps: List[Dict[str, Any]],
538
+ user: str,
539
+ progress_callback: Optional[ProgressCallback] = None,
540
+ start_index: int = 1,
541
+ total_steps: Optional[int] = None,
542
+ ) -> Tuple[List[Dict[str, Any]], bool]:
475
543
  results: List[Dict[str, Any]] = []
476
- for step in steps:
544
+ computed_total = total_steps if total_steps is not None else start_index + len(steps) - 1
545
+ for offset, step in enumerate(steps):
546
+ step_index = start_index + offset
547
+ if progress_callback:
548
+ progress_callback(step_index, computed_total, step, "in_progress", None)
549
+
477
550
  if step.get("type") == "portacode_connect":
478
551
  res = _portacode_connect_and_read_key(vmid, user, timeout_s=step.get("timeout_s", 10))
479
552
  res["name"] = step["name"]
480
553
  results.append(res)
481
554
  if not res.get("ok"):
555
+ if progress_callback:
556
+ progress_callback(step_index, computed_total, step, "failed", res)
482
557
  return results, False
558
+ if progress_callback:
559
+ progress_callback(step_index, computed_total, step, "completed", res)
483
560
  continue
484
561
 
485
562
  attempts = 0
@@ -492,7 +569,11 @@ def _run_setup_steps(vmid: int, steps: List[Dict[str, Any]], user: str) -> Tuple
492
569
  res["error_summary"] = _summarize_error(res)
493
570
  results.append(res)
494
571
  if res["returncode"] == 0:
572
+ if progress_callback:
573
+ progress_callback(step_index, computed_total, step, "completed", res)
495
574
  break
575
+ if progress_callback:
576
+ progress_callback(step_index, computed_total, step, "failed", res)
496
577
  retry_on = step.get("retry_on", [])
497
578
  if attempts >= step.get("retries", 0) + 1:
498
579
  return results, False
@@ -503,50 +584,25 @@ def _run_setup_steps(vmid: int, steps: List[Dict[str, Any]], user: str) -> Tuple
503
584
  return results, True
504
585
 
505
586
 
506
- def _bootstrap_portacode(vmid: int, user: str, password: str, ssh_key: str) -> Tuple[str, List[Dict[str, Any]]]:
507
- steps = [
508
- {
509
- "name": "apt_update",
510
- "cmd": "apt-get update -y",
511
- "retries": 4,
512
- "retry_delay_s": 5,
513
- "retry_on": [
514
- "Temporary failure resolving",
515
- "Could not resolve",
516
- "Failed to fetch",
517
- ],
518
- },
519
- {
520
- "name": "install_deps",
521
- "cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
522
- "retries": 5,
523
- "retry_delay_s": 5,
524
- "retry_on": [
525
- "lock-frontend",
526
- "Unable to acquire the dpkg frontend lock",
527
- "Temporary failure resolving",
528
- "Could not resolve",
529
- "Failed to fetch",
530
- ],
531
- },
532
- {"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
533
- {"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
534
- ]
535
- if password:
536
- steps.append({"name": "set_password", "cmd": f"echo '{user}:{password}' | chpasswd", "retries": 0})
537
- if ssh_key:
538
- steps.append({
539
- "name": "add_ssh_key",
540
- "cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
541
- "retries": 0,
542
- })
543
- steps.extend([
544
- {"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
545
- {"name": "install_portacode", "cmd": "python3 -m pip install --upgrade portacode", "retries": 0},
546
- {"name": "portacode_connect", "type": "portacode_connect", "timeout_s": 30},
547
- ])
548
-
549
- results, ok = _run_setup_steps(vmid, steps, user)
587
+ def _bootstrap_portacode(
588
+ vmid: int,
589
+ user: str,
590
+ password: str,
591
+ ssh_key: str,
592
+ steps: Optional[List[Dict[str, Any]]] = None,
593
+ progress_callback: Optional[ProgressCallback] = None,
594
+ start_index: int = 1,
595
+ total_steps: Optional[int] = None,
596
+ ) -> Tuple[str, List[Dict[str, Any]]]:
597
+ actual_steps = steps if steps is not None else _build_bootstrap_steps(user, password, ssh_key)
598
+ results, ok = _run_setup_steps(
599
+ vmid,
600
+ actual_steps,
601
+ user,
602
+ progress_callback=progress_callback,
603
+ start_index=start_index,
604
+ total_steps=total_steps,
605
+ )
550
606
  if not ok:
551
607
  raise RuntimeError("Portacode bootstrap steps failed.")
552
608
  key_step = next((entry for entry in results if entry.get("name") == "portacode_connect"), None)
@@ -690,29 +746,205 @@ class CreateProxmoxContainerHandler(SyncHandler):
690
746
  def command_name(self) -> str:
691
747
  return "create_proxmox_container"
692
748
 
749
+ def _emit_progress_event(
750
+ self,
751
+ *,
752
+ step_index: int,
753
+ total_steps: int,
754
+ step_name: str,
755
+ step_label: str,
756
+ status: str,
757
+ message: str,
758
+ phase: str,
759
+ request_id: Optional[str],
760
+ details: Optional[Dict[str, Any]] = None,
761
+ ) -> None:
762
+ loop = self.context.get("event_loop")
763
+ if not loop or loop.is_closed():
764
+ logger.debug(
765
+ "progress event skipped (no event loop) step=%s status=%s",
766
+ step_name,
767
+ status,
768
+ )
769
+ return
770
+
771
+ payload: Dict[str, Any] = {
772
+ "event": "proxmox_container_progress",
773
+ "step_name": step_name,
774
+ "step_label": step_label,
775
+ "status": status,
776
+ "phase": phase,
777
+ "step_index": step_index,
778
+ "total_steps": total_steps,
779
+ "message": message,
780
+ }
781
+ if request_id:
782
+ payload["request_id"] = request_id
783
+ if details:
784
+ payload["details"] = details
785
+
786
+ future = asyncio.run_coroutine_threadsafe(self.send_response(payload), loop)
787
+ future.add_done_callback(
788
+ lambda fut: logger.warning(
789
+ "Failed to emit progress event for %s: %s", step_name, fut.exception()
790
+ )
791
+ if fut.exception()
792
+ else None
793
+ )
794
+
693
795
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
694
- if os.geteuid() != 0:
695
- raise PermissionError("Container creation requires root privileges.")
696
- config = _load_config()
697
- if not config or not config.get("token_value"):
698
- raise ValueError("Proxmox infrastructure is not configured.")
699
- if not config.get("network", {}).get("applied"):
700
- raise RuntimeError("Proxmox bridge setup must be applied before creating containers.")
701
-
702
- proxmox = _connect_proxmox(config)
703
- node = config.get("node") or DEFAULT_NODE_NAME
704
- payload = _build_container_payload(message, config)
705
- payload["cpuunits"] = max(int(payload["cpus"] * 1024), 10)
706
- payload["memory"] = int(payload["ram_mib"])
707
- payload["node"] = node
708
-
709
- vmid, _ = _instantiate_container(proxmox, node, payload)
710
- payload["vmid"] = vmid
711
- payload["created_at"] = datetime.utcnow().isoformat() + "Z"
712
- _write_container_record(vmid, payload)
713
-
714
- _start_container(proxmox, node, vmid)
715
- public_key, steps = _bootstrap_portacode(vmid, payload["username"], payload["password"], payload["ssh_public_key"])
796
+ logger.info("create_proxmox_container command received")
797
+ request_id = message.get("request_id")
798
+ bootstrap_user, bootstrap_password, bootstrap_ssh_key = _get_provisioning_user_info(message)
799
+ bootstrap_steps = _build_bootstrap_steps(bootstrap_user, bootstrap_password, bootstrap_ssh_key)
800
+ total_steps = 3 + len(bootstrap_steps)
801
+ current_step_index = 1
802
+
803
+ def _run_lifecycle_step(
804
+ step_name: str,
805
+ step_label: str,
806
+ start_message: str,
807
+ success_message: str,
808
+ action,
809
+ ):
810
+ nonlocal current_step_index
811
+ step_index = current_step_index
812
+ self._emit_progress_event(
813
+ step_index=step_index,
814
+ total_steps=total_steps,
815
+ step_name=step_name,
816
+ step_label=step_label,
817
+ status="in_progress",
818
+ message=start_message,
819
+ phase="lifecycle",
820
+ request_id=request_id,
821
+ )
822
+ try:
823
+ result = action()
824
+ except Exception as exc:
825
+ self._emit_progress_event(
826
+ step_index=step_index,
827
+ total_steps=total_steps,
828
+ step_name=step_name,
829
+ step_label=step_label,
830
+ status="failed",
831
+ message=f"{step_label} failed: {exc}",
832
+ phase="lifecycle",
833
+ request_id=request_id,
834
+ details={"error": str(exc)},
835
+ )
836
+ raise
837
+ self._emit_progress_event(
838
+ step_index=step_index,
839
+ total_steps=total_steps,
840
+ step_name=step_name,
841
+ step_label=step_label,
842
+ status="completed",
843
+ message=success_message,
844
+ phase="lifecycle",
845
+ request_id=request_id,
846
+ )
847
+ current_step_index += 1
848
+ return result
849
+
850
+ def _validate_environment():
851
+ if os.geteuid() != 0:
852
+ raise PermissionError("Container creation requires root privileges.")
853
+ config = _load_config()
854
+ if not config or not config.get("token_value"):
855
+ raise ValueError("Proxmox infrastructure is not configured.")
856
+ if not config.get("network", {}).get("applied"):
857
+ raise RuntimeError("Proxmox bridge setup must be applied before creating containers.")
858
+ return config
859
+
860
+ config = _run_lifecycle_step(
861
+ "validate_environment",
862
+ "Validating infrastructure",
863
+ "Checking token, permissions, and bridge setup…",
864
+ "Infrastructure validated.",
865
+ _validate_environment,
866
+ )
867
+
868
+ def _create_container():
869
+ proxmox = _connect_proxmox(config)
870
+ node = config.get("node") or DEFAULT_NODE_NAME
871
+ payload = _build_container_payload(message, config)
872
+ payload["cpuunits"] = max(int(payload["cpus"] * 1024), 10)
873
+ payload["memory"] = int(payload["ram_mib"])
874
+ payload["node"] = node
875
+ logger.debug(
876
+ "Provisioning container node=%s template=%s ram=%s cpu=%s storage=%s",
877
+ node,
878
+ payload["template"],
879
+ payload["ram_mib"],
880
+ payload["cpus"],
881
+ payload["storage"],
882
+ )
883
+ vmid, _ = _instantiate_container(proxmox, node, payload)
884
+ payload["vmid"] = vmid
885
+ payload["created_at"] = datetime.utcnow().isoformat() + "Z"
886
+ _write_container_record(vmid, payload)
887
+ return proxmox, node, vmid, payload
888
+
889
+ proxmox, node, vmid, payload = _run_lifecycle_step(
890
+ "create_container",
891
+ "Creating container",
892
+ "Provisioning the LXC container…",
893
+ "Container created.",
894
+ _create_container,
895
+ )
896
+
897
+ def _start_container_step():
898
+ _start_container(proxmox, node, vmid)
899
+
900
+ _run_lifecycle_step(
901
+ "start_container",
902
+ "Starting container",
903
+ "Booting the container…",
904
+ "Container startup completed.",
905
+ _start_container_step,
906
+ )
907
+
908
+ def _bootstrap_progress_callback(step_index: int, total: int, step: Dict[str, Any], status: str, result: Optional[Dict[str, Any]]):
909
+ label = step.get("display_name") or _friendly_step_label(step.get("name", "bootstrap"))
910
+ if status == "in_progress":
911
+ message_text = f"{label} is running…"
912
+ elif status == "completed":
913
+ message_text = f"{label} completed."
914
+ else:
915
+ summary = (result or {}).get("error_summary") or (result or {}).get("error")
916
+ message_text = f"{label} failed"
917
+ if summary:
918
+ message_text += f": {summary}"
919
+ details: Dict[str, Any] = {}
920
+ if status == "failed" and result:
921
+ if result.get("attempt"):
922
+ details["attempt"] = result["attempt"]
923
+ summary_detail = result.get("error_summary") or result.get("error")
924
+ if summary_detail:
925
+ details["error_summary"] = summary_detail
926
+ self._emit_progress_event(
927
+ step_index=step_index,
928
+ total_steps=total,
929
+ step_name=step.get("name", "bootstrap"),
930
+ step_label=label,
931
+ status=status,
932
+ message=message_text,
933
+ phase="bootstrap",
934
+ request_id=request_id,
935
+ details=details or None,
936
+ )
937
+
938
+ public_key, steps = _bootstrap_portacode(
939
+ vmid,
940
+ payload["username"],
941
+ payload["password"],
942
+ payload["ssh_public_key"],
943
+ steps=bootstrap_steps,
944
+ progress_callback=_bootstrap_progress_callback,
945
+ start_index=current_step_index,
946
+ total_steps=total_steps,
947
+ )
716
948
 
717
949
  return {
718
950
  "event": "proxmox_container_created",
@@ -414,6 +414,7 @@ class TerminalManager:
414
414
  "mux": mux,
415
415
  "use_content_caching": True, # Enable content caching optimization
416
416
  "debug": self.debug,
417
+ "event_loop": asyncio.get_running_loop(),
417
418
  }
418
419
 
419
420
  # Initialize command registry
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.11.dev4
3
+ Version: 1.4.11.dev6
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=hZw3vDtHj5ynUwmk9yjHWFu2qz0O0kQUQOyWyeVm45M,719
4
+ portacode/_version.py,sha256=5aWhaBfuSdCUpEwbbl75wIVr6LiHBu_Cbrnz4tRTfac,719
5
5
  portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
@@ -12,9 +12,9 @@ portacode/connection/README.md,sha256=f9rbuIEKa7cTm9C98rCiBbEtbiIXQU11esGSNhSMiJ
12
12
  portacode/connection/__init__.py,sha256=atqcVGkViIEd7pRa6cP2do07RJOM0UWpbnz5zXjGktU,250
13
13
  portacode/connection/client.py,sha256=jtLb9_YufqPkzi9t8VQH3iz_JEMisbtY6a8L9U5weiU,14181
14
14
  portacode/connection/multiplex.py,sha256=L-TxqJ_ZEbfNEfu1cwxgJ5vUdyRzZjsMy2Kx1diiZys,5237
15
- portacode/connection/terminal.py,sha256=NFkbbJe4Rz5XUi1WYEf9kXJqH8ETqqC9tcQhVORG3Y8,44599
15
+ portacode/connection/terminal.py,sha256=oImE33DgtDEEjVM9r1oUejl0miJgwZaJ9K3pAeHhzBo,44653
16
16
  portacode/connection/handlers/README.md,sha256=HsLZG1QK1JNm67HsgL6WoDg9nxzKXxwkc5fJPFJdX5g,12169
17
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=1cYtf0iJBtY_Q4dadmKSdcHd67zg1f2mf-Gnm4-oI08,90511
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=gtUP5DEoKSg5q2rO08DxM9r_fNhjXkNzy3sNc6dZArE,92824
18
18
  portacode/connection/handlers/__init__.py,sha256=iiyF3smwiI0IeDYzWQTl2PPVfW6aSp-g2CSO1ZTo9Ho,2641
19
19
  portacode/connection/handlers/base.py,sha256=oENFb-Fcfzwk99Qx8gJQriEMiwSxwygwjOiuCH36hM4,10231
20
20
  portacode/connection/handlers/chunked_content.py,sha256=h6hXRmxSeOgnIxoU8CkmvEf2Odv-ajPrpHIe_W3GKcA,9251
@@ -22,7 +22,7 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
22
22
  portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
23
23
  portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
24
24
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
25
- portacode/connection/handlers/proxmox_infra.py,sha256=eNK905IoelaFDkeHu57RUMtQzSU26CNPtJXxqPwZ6NI,28452
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=F2M7qQxllo32rdERCcYYAC6t8albYd8vAmemAXGwxPs,36836
26
26
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
27
27
  portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
28
28
  portacode/connection/handlers/system_handlers.py,sha256=AKh7IbwptlLYrbSw5f-DHigvlaKHsg9lDP-lkAUm8cE,10755
@@ -64,7 +64,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
64
64
  portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
65
65
  portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
66
66
  portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
67
- portacode-1.4.11.dev4.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
67
+ portacode-1.4.11.dev6.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
68
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
69
69
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
70
70
  test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
@@ -90,8 +90,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
90
90
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
91
91
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
92
92
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
93
- portacode-1.4.11.dev4.dist-info/METADATA,sha256=cksF5JpfpIfbUS7viC9cpAYwTIg8xwzFcu-4utJiECg,13051
94
- portacode-1.4.11.dev4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
- portacode-1.4.11.dev4.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
96
- portacode-1.4.11.dev4.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
97
- portacode-1.4.11.dev4.dist-info/RECORD,,
93
+ portacode-1.4.11.dev6.dist-info/METADATA,sha256=qREiPXfLBIuIlBTY9tHtU34IHLIeTlE0yknR5gmUMW8,13051
94
+ portacode-1.4.11.dev6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
+ portacode-1.4.11.dev6.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
96
+ portacode-1.4.11.dev6.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
97
+ portacode-1.4.11.dev6.dist-info/RECORD,,