portacode 1.4.15.dev0__tar.gz → 1.4.15.dev1__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 (383) hide show
  1. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/PKG-INFO +1 -1
  2. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/_version.py +2 -2
  3. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +87 -0
  4. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/proxmox_infra.py +286 -0
  5. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/PKG-INFO +1 -1
  6. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/.claude/agents/communication-manager.md +0 -0
  7. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/.claude/settings.local.json +0 -0
  8. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/.gitignore +0 -0
  9. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/.gitmodules +0 -0
  10. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/LICENSE +0 -0
  11. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/MANIFEST.in +0 -0
  12. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/Makefile +0 -0
  13. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/README.md +0 -0
  14. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/backup.sh +0 -0
  15. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/connect.py +0 -0
  16. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/connect.sh +0 -0
  17. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docker-compose.yaml +0 -0
  18. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docs/images/device-transfer-button.png +0 -0
  19. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docs/images/device-transfer-modal.png +0 -0
  20. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docs/images/pair-device-button.png +0 -0
  21. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docs/images/pairing-request.png +0 -0
  22. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/docs/images/student-workspace.png +0 -0
  23. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/README.md +0 -0
  24. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/simple_device/Dockerfile +0 -0
  25. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/simple_device/README.md +0 -0
  26. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  27. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  28. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/simple_device/docker-compose.yaml +0 -0
  29. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/Dockerfile +0 -0
  30. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/README.md +0 -0
  31. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  32. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  33. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  34. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  35. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  36. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  37. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  38. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  39. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  40. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  41. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  42. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  43. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  44. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  45. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  46. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  47. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  48. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  49. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  50. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  51. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  52. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  53. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  54. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  55. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  56. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  57. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  58. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  59. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  60. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  61. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  62. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  63. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  64. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  65. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  66. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  67. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  68. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  69. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  70. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  71. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  72. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  73. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  74. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  75. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  76. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  77. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  78. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  79. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  80. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  81. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  82. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  83. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  84. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  85. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  86. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  87. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  88. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  89. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  90. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  91. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  92. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  93. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  94. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  95. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  96. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  97. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  98. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  99. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  100. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  101. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  102. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  103. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  104. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  105. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  106. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  107. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  108. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  109. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  110. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  111. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  112. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  113. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  114. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  115. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  116. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  117. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  118. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  119. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  120. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  121. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  122. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  123. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  124. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  125. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  126. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  127. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  128. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  129. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  130. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  131. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  132. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  133. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  134. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  135. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  136. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  137. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  138. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  139. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  140. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  141. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  142. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  143. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  144. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  145. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  146. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  147. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  148. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  149. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  150. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  151. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  152. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  153. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  154. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  155. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  156. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  157. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  158. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  159. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  160. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  161. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  162. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  163. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  164. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  165. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  166. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  167. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  168. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  169. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  170. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  171. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  172. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  173. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  174. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  175. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  176. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  177. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  178. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  179. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  180. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  181. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  182. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  183. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  184. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  185. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  186. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  187. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  188. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  189. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  190. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  191. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  192. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  193. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  194. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  195. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  196. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  197. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  198. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  199. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  200. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  201. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  202. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  203. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  204. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  205. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  206. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  207. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  208. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  209. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  210. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  211. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  212. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  213. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  214. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  215. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  216. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  217. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  218. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  219. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  220. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  221. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  222. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  223. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  224. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  225. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  226. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  227. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  228. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  229. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  230. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  231. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  232. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  233. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  234. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  235. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  236. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  237. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  238. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  239. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  240. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  241. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  242. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  243. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  244. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  245. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  246. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/docker-compose.yaml +0 -0
  247. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/README.md +0 -0
  248. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  249. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  250. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  251. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  252. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  253. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/manage.py +0 -0
  254. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  255. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  256. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  257. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  258. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  259. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  260. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  261. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  262. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  263. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  264. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  265. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  266. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/README.md +0 -0
  267. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/__init__.py +0 -0
  268. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/__main__.py +0 -0
  269. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/cli.py +0 -0
  270. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/README.md +0 -0
  271. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/__init__.py +0 -0
  272. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/client.py +0 -0
  273. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/README.md +0 -0
  274. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/__init__.py +0 -0
  275. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/base.py +0 -0
  276. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/chunked_content.py +0 -0
  277. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/diff_handlers.py +0 -0
  278. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/file_handlers.py +0 -0
  279. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  280. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/README.md +0 -0
  281. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/__init__.py +0 -0
  282. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  283. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  284. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/handlers.py +0 -0
  285. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/manager.py +0 -0
  286. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/models.py +0 -0
  287. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state/utils.py +0 -0
  288. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/project_state_handlers.py +0 -0
  289. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/registry.py +0 -0
  290. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/session.py +0 -0
  291. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/system_handlers.py +0 -0
  292. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/tab_factory.py +0 -0
  293. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/terminal_handlers.py +0 -0
  294. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
  295. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/handlers/update_handler.py +0 -0
  296. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/multiplex.py +0 -0
  297. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/connection/terminal.py +0 -0
  298. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/data.py +0 -0
  299. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/keypair.py +0 -0
  300. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/__init__.py +0 -0
  301. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  302. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  303. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/elinks +0 -0
  304. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/gio-open +0 -0
  305. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/gnome-open +0 -0
  306. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/gvfs-open +0 -0
  307. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/kde-open +0 -0
  308. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/kfmclient +0 -0
  309. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  310. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  311. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/links +0 -0
  312. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/links2 +0 -0
  313. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/lynx +0 -0
  314. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/mate-open +0 -0
  315. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/netsurf +0 -0
  316. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/sensible-browser +0 -0
  317. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/w3m +0 -0
  318. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/x-www-browser +0 -0
  319. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/link_capture/bin/xdg-open +0 -0
  320. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/logging_categories.py +0 -0
  321. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/pairing.py +0 -0
  322. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/service.py +0 -0
  323. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/static/js/test-ntp-clock.html +0 -0
  324. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/static/js/utils/ntp-clock.js +0 -0
  325. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  326. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/utils/__init__.py +0 -0
  327. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/utils/diff_apply.py +0 -0
  328. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/utils/diff_renderer.py +0 -0
  329. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode/utils/ntp_clock.py +0 -0
  330. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/SOURCES.txt +0 -0
  331. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/dependency_links.txt +0 -0
  332. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/entry_points.txt +0 -0
  333. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/requires.txt +0 -0
  334. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/portacode.egg-info/top_level.txt +0 -0
  335. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/pyproject.toml +0 -0
  336. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/restore.sh +0 -0
  337. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/run_tests.py +0 -0
  338. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/setup.cfg +0 -0
  339. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/setup.py +0 -0
  340. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test.sh +0 -0
  341. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/README.md +0 -0
  342. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/__init__.py +0 -0
  343. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_device_online.py +0 -0
  344. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_file_operations.py +0 -0
  345. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_git_status_ui.py +0 -0
  346. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_login_flow.py +0 -0
  347. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_navigate_testing_folder.py +0 -0
  348. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_play_store_screenshots.py +0 -0
  349. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_terminal_buffer_performance.py +0 -0
  350. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_terminal_interaction.py +0 -0
  351. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_terminal_loading_race_condition.py +0 -0
  352. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_modules/test_terminal_start.py +0 -0
  353. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/test_request_id.py +0 -0
  354. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/.env.example +0 -0
  355. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/README.md +0 -0
  356. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/__init__.py +0 -0
  357. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/cli.py +0 -0
  358. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/__init__.py +0 -0
  359. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/base_test.py +0 -0
  360. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/cli_manager.py +0 -0
  361. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/hierarchical_runner.py +0 -0
  362. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/playwright_manager.py +0 -0
  363. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/runner.py +0 -0
  364. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/shared_cli_manager.py +0 -0
  365. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/core/test_discovery.py +0 -0
  366. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/testing_framework/requirements.txt +0 -0
  367. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  368. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/agent_context_management.md +0 -0
  369. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/django_server_time_sync.md +0 -0
  370. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/device_performance_degradation.md +0 -0
  371. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  372. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/indefinite_resource_loading.md +0 -0
  373. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/portacode_service_silently_down.md +0 -0
  374. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/premature_terminal_exit.md +0 -0
  375. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/project_cpu_hotspots.md +0 -0
  376. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/terminals_exit_upon_starting.md +0 -0
  377. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  378. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/todo/smartphone_terminal_input_frustrations.md +0 -0
  379. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/tools/generate_play_store_assets.py +0 -0
  380. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/tools/pairing_tester.py +0 -0
  381. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/tools/run_screenshot_suite.sh +0 -0
  382. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/tools/test_python_ntp_clock.py +0 -0
  383. {portacode-1.4.15.dev0 → portacode-1.4.15.dev1}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.15.dev0
3
+ Version: 1.4.15.dev1
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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.15.dev0'
32
- __version_tuple__ = version_tuple = (1, 4, 15, 'dev0')
31
+ __version__ = version = '1.4.15.dev1'
32
+ __version_tuple__ = version_tuple = (1, 4, 15, 'dev1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -331,6 +331,8 @@ Configures a Proxmox node for Portacode infrastructure usage (API token validati
331
331
  * `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`. When omitted, the last configured value is preserved.
332
332
  * `cloudflare_api_token` (string, optional): Cloudflare API token the host can reuse later to provision tunnels.
333
333
 
334
+ The setup command also installs `cloudflared` on the node so Cloudflare tunnels can be created afterward.
335
+
334
336
  **Responses:**
335
337
 
336
338
  * On success, the device will emit a [`proxmox_infra_configured`](#proxmox_infra_configured-event) event with the persisted infra snapshot.
@@ -410,6 +412,51 @@ Deletes a managed container from Proxmox (stopping it first if necessary) and re
410
412
  * Emits a [`proxmox_container_action`](#proxmox_container_action-event) event with `action="remove"` and the refreshed infra snapshot after deletion.
411
413
  * Emits an [`error`](#error) event on failure.
412
414
 
415
+ ### `create_cloudflare_tunnel`
416
+
417
+ Creates or updates a Cloudflare tunnel in front of a managed container. The container must be running and a Cloudflare API token must already be configured via `setup_proxmox_infra`.
418
+
419
+ **Payload Fields:**
420
+
421
+ * `ctid` (string, required): Identifier of the container.
422
+ * `container_port` (integer, required): Port inside the container to expose through Cloudflare.
423
+ * `cloudflare_url` (string, required): Hostname (e.g., `app.example.com`) that the tunnel should serve.
424
+ * `protocol` (string, optional): One of `http`, `https`, or `tcp` (defaults to `http`).
425
+
426
+ **Responses:**
427
+
428
+ * Emits a [`cloudflare_tunnel_created`](#cloudflare_tunnel_created-event) event with the tunnel metadata.
429
+ * Emits an [`error`](#error) event on failure.
430
+
431
+ ### `update_cloudflare_tunnel`
432
+
433
+ Refreshes the tunnel configuration for an existing tunnel (different port, URL, or protocol).
434
+
435
+ **Payload Fields:**
436
+
437
+ * `ctid` (string, required): Identifier of the container.
438
+ * `container_port` (integer, optional): New container port.
439
+ * `cloudflare_url` (string, optional): New hostname.
440
+ * `protocol` (string, optional): New protocol (`http`, `https`, or `tcp`).
441
+
442
+ **Responses:**
443
+
444
+ * Emits a [`cloudflare_tunnel_updated`](#cloudflare_tunnel_updated-event) event with the refreshed tunnel metadata.
445
+ * Emits an [`error`](#error) event on failure.
446
+
447
+ ### `remove_cloudflare_tunnel`
448
+
449
+ Stops and removes any tunnel metadata associated with a container.
450
+
451
+ **Payload Fields:**
452
+
453
+ * `ctid` (string, required): Identifier of the container.
454
+
455
+ **Responses:**
456
+
457
+ * Emits a [`cloudflare_tunnel_removed`](#cloudflare_tunnel_removed-event) event.
458
+ * Emits an [`error`](#error) event on failure.
459
+
413
460
  ### `proxmox_container_created`
414
461
 
415
462
  Emitted after a successful `create_proxmox_container` action. Contains the new container ID, the Portacode public key produced inside the container, and the bootstrap logs.
@@ -1146,6 +1193,11 @@ Provides system information in response to a `system_info` action. Handled by [`
1146
1193
  * `cpu_share` (number): vCPU-equivalent share requested at creation.
1147
1194
  * `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
1148
1195
  * `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
1196
+ * `tunnel` (object|null): Tunnel metadata that includes:
1197
+ * `url` (string): Public hostname assigned for this tunnel.
1198
+ * `container_port` (integer): Container port exposed via the tunnel.
1199
+ * `protocol` (string): Protocol advertised when the tunnel was configured.
1200
+ * `status` (string): `running`, `stopped`, or `unknown`.
1149
1201
  * `portacode_version` (string): Installed CLI version returned by `portacode.__version__`.
1150
1202
 
1151
1203
  ### `proxmox_infra_configured`
@@ -1211,6 +1263,41 @@ Emitted after `start_proxmox_container`, `stop_proxmox_container`, or `remove_pr
1211
1263
  * `details` (object, optional): Exit status information (e.g., `exitstatus`, `stop_exitstatus`, `delete_exitstatus`).
1212
1264
  * `infra` (object): Same snapshot described under [`system_info`](#system_info-event) `proxmox.infra`, including the updated `managed_containers` summary.
1213
1265
 
1266
+ ### `cloudflare_tunnel_created`
1267
+
1268
+ Submitted after a successful `create_cloudflare_tunnel` action.
1269
+
1270
+ **Event Fields:**
1271
+
1272
+ * `event` (string): `cloudflare_tunnel_created`.
1273
+ * `ctid` (string): Container ID associated with the tunnel.
1274
+ * `success` (boolean): True when the tunnel is running.
1275
+ * `message` (string): Summary text.
1276
+ * `tunnel` (object): Tunnel metadata matching the manager view.
1277
+
1278
+ ### `cloudflare_tunnel_updated`
1279
+
1280
+ Sent when `update_cloudflare_tunnel` completes.
1281
+
1282
+ **Event Fields:**
1283
+
1284
+ * `event` (string): `cloudflare_tunnel_updated`.
1285
+ * `ctid` (string): Container ID associated with the tunnel.
1286
+ * `success` (boolean): True on success.
1287
+ * `message` (string): Summary text.
1288
+ * `tunnel` (object): Updated tunnel metadata.
1289
+
1290
+ ### `cloudflare_tunnel_removed`
1291
+
1292
+ Sent after a tunnel has been removed from a container record.
1293
+
1294
+ **Event Fields:**
1295
+
1296
+ * `event` (string): `cloudflare_tunnel_removed`.
1297
+ * `ctid` (string): Container ID that no longer has a tunnel.
1298
+ * `success` (boolean): True on success.
1299
+ * `message` (string): Summary text.
1300
+
1214
1301
  ### <a name="clock_sync_response"></a>`clock_sync_response`
1215
1302
 
1216
1303
  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.
@@ -15,6 +15,7 @@ import sys
15
15
  import tempfile
16
16
  import time
17
17
  import threading
18
+ import urllib.request
18
19
  from datetime import datetime
19
20
  from pathlib import Path
20
21
  from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple
@@ -40,12 +41,15 @@ BRIDGE_IP = SUBNET_CIDR.split("/", 1)[0]
40
41
  DHCP_START = "10.10.0.100"
41
42
  DHCP_END = "10.10.0.200"
42
43
  DNS_SERVER = "1.1.1.1"
44
+ CLOUDFLARE_DEB_URL = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb"
43
45
  IFACES_PATH = Path("/etc/network/interfaces")
44
46
  SYSCTL_PATH = Path("/etc/sysctl.d/99-portacode-forward.conf")
45
47
  UNIT_DIR = Path("/etc/systemd/system")
46
48
  _MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
47
49
  _MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
48
50
  _MANAGED_CONTAINERS_CACHE_LOCK = threading.Lock()
51
+ _CLOUDFLARE_TUNNEL_PROCESSES: Dict[str, subprocess.Popen] = {}
52
+ _CLOUDFLARE_TUNNELS_LOCK = threading.Lock()
49
53
 
50
54
  ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
51
55
 
@@ -279,6 +283,25 @@ def _ensure_bridge(bridge: str = DEFAULT_BRIDGE) -> Dict[str, Any]:
279
283
  return {"applied": True, "bridge": bridge, "message": f"Bridge {bridge} configured"}
280
284
 
281
285
 
286
+ def _ensure_cloudflared_installed() -> None:
287
+ if shutil.which("cloudflared"):
288
+ return
289
+ apt = shutil.which("apt-get")
290
+ if not apt:
291
+ raise RuntimeError("cloudflared is missing and apt-get is unavailable to install it")
292
+ download_dir = Path(tempfile.mkdtemp())
293
+ deb_path = download_dir / "cloudflared.deb"
294
+ try:
295
+ urllib.request.urlretrieve(CLOUDFLARE_DEB_URL, deb_path)
296
+ try:
297
+ _call_subprocess(["dpkg", "-i", str(deb_path)], check=True)
298
+ except subprocess.CalledProcessError:
299
+ _call_subprocess([apt, "install", "-f", "-y"], check=True)
300
+ _call_subprocess(["dpkg", "-i", str(deb_path)], check=True)
301
+ finally:
302
+ shutil.rmtree(download_dir, ignore_errors=True)
303
+
304
+
282
305
  def _verify_connectivity(timeout: float = 5.0) -> bool:
283
306
  try:
284
307
  _call_subprocess(["/bin/ping", "-c", "2", "1.1.1.1"], check=True, timeout=timeout)
@@ -355,6 +378,7 @@ def _build_managed_containers_summary(records: List[Dict[str, Any]]) -> Dict[str
355
378
  "cpu_share": cpu_share,
356
379
  "created_at": record.get("created_at"),
357
380
  "status": status,
381
+ "tunnel": record.get("tunnel"),
358
382
  }
359
383
  )
360
384
 
@@ -592,6 +616,88 @@ def _remove_container_record(vmid: int) -> None:
592
616
  _invalidate_managed_containers_cache()
593
617
 
594
618
 
619
+ def _update_container_tunnel(vmid: int, tunnel: Optional[Dict[str, Any]]) -> None:
620
+ record = _read_container_record(vmid)
621
+ if tunnel:
622
+ record["tunnel"] = tunnel
623
+ else:
624
+ record.pop("tunnel", None)
625
+ _write_container_record(vmid, record)
626
+
627
+
628
+ def _ensure_cloudflare_token(config: Dict[str, Any]) -> str:
629
+ cloudflare = config.get("cloudflare") or {}
630
+ token = cloudflare.get("api_token")
631
+ if not token:
632
+ raise RuntimeError("Cloudflare API token is not configured.")
633
+ return token
634
+
635
+
636
+ def _launch_container_tunnel(proxmox: Any, node: str, vmid: int, tunnel: Dict[str, Any]) -> Dict[str, Any]:
637
+ port = int(tunnel.get("container_port") or 0)
638
+ if not port:
639
+ raise ValueError("container_port is required to create a tunnel.")
640
+ hostname = tunnel.get("url")
641
+ if not hostname:
642
+ raise ValueError("cloudflare_url is required to expose the tunnel.")
643
+ protocol = (tunnel.get("protocol") or "http").lower()
644
+ ip_address = _resolve_container_ip(proxmox, node, vmid)
645
+ target_url = f"{protocol}://{ip_address}:{port}"
646
+ name = tunnel.get("name") or _build_tunnel_name(vmid, port)
647
+ _stop_cloudflare_process(name)
648
+ proc = _start_cloudflare_process(name, hostname, target_url)
649
+ updated = {
650
+ "name": name,
651
+ "container_port": port,
652
+ "url": hostname,
653
+ "protocol": protocol,
654
+ "status": "running",
655
+ "pid": proc.pid,
656
+ "target_ip": ip_address,
657
+ "target_url": target_url,
658
+ "last_updated": datetime.utcnow().isoformat() + "Z",
659
+ }
660
+ _update_container_tunnel(vmid, updated)
661
+ return updated
662
+
663
+
664
+ def _stop_container_tunnel(vmid: int) -> None:
665
+ try:
666
+ record = _read_container_record(vmid)
667
+ except FileNotFoundError:
668
+ return
669
+ tunnel = record.get("tunnel")
670
+ if not tunnel:
671
+ return
672
+ name = tunnel.get("name") or _build_tunnel_name(vmid, int(tunnel.get("container_port") or 0))
673
+ stopped = _stop_cloudflare_process(name)
674
+ if stopped or tunnel.get("status") != "stopped":
675
+ tunnel_update = {
676
+ **tunnel,
677
+ "status": "stopped",
678
+ "pid": None,
679
+ "last_updated": datetime.utcnow().isoformat() + "Z",
680
+ }
681
+ _update_container_tunnel(vmid, tunnel_update)
682
+
683
+
684
+ def _remove_container_tunnel_state(vmid: int) -> None:
685
+ _stop_container_tunnel(vmid)
686
+ _update_container_tunnel(vmid, None)
687
+
688
+
689
+ def _ensure_container_tunnel_running(proxmox: Any, node: str, vmid: int) -> None:
690
+ try:
691
+ record = _read_container_record(vmid)
692
+ except FileNotFoundError:
693
+ return
694
+ tunnel = record.get("tunnel")
695
+ if not tunnel:
696
+ return
697
+ _ensure_cloudflare_token(_load_config())
698
+ _launch_container_tunnel(proxmox, node, vmid, tunnel)
699
+
700
+
595
701
  def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]:
596
702
  templates = config.get("templates") or []
597
703
  default_template = templates[0] if templates else ""
@@ -707,6 +813,74 @@ def _run_pct_push(vmid: int, src: str, dest: str) -> subprocess.CompletedProcess
707
813
  return _call_subprocess(["pct", "push", str(vmid), src, dest])
708
814
 
709
815
 
816
+ def _build_tunnel_name(vmid: int, port: int) -> str:
817
+ return f"portacode-ct{vmid}-{port}"
818
+
819
+
820
+ def _get_cloudflared_binary() -> str:
821
+ binary = shutil.which("cloudflared")
822
+ if not binary:
823
+ raise RuntimeError(
824
+ "cloudflared is required for Cloudflare tunnels but was not found on PATH. "
825
+ "Install cloudflared and run 'cloudflared tunnel login' before creating tunnels."
826
+ )
827
+ return binary
828
+
829
+
830
+ def _start_cloudflare_process(name: str, hostname: str, target_url: str) -> subprocess.Popen:
831
+ binary = _get_cloudflared_binary()
832
+ cmd = [
833
+ binary,
834
+ "tunnel",
835
+ "--hostname",
836
+ hostname,
837
+ "--url",
838
+ target_url,
839
+ "--no-autoupdate",
840
+ ]
841
+ proc = subprocess.Popen(
842
+ cmd,
843
+ stdout=subprocess.DEVNULL,
844
+ stderr=subprocess.DEVNULL,
845
+ )
846
+ with _CLOUDFLARE_TUNNELS_LOCK:
847
+ _CLOUDFLARE_TUNNEL_PROCESSES[name] = proc
848
+ return proc
849
+
850
+
851
+ def _stop_cloudflare_process(name: str) -> bool:
852
+ with _CLOUDFLARE_TUNNELS_LOCK:
853
+ proc = _CLOUDFLARE_TUNNEL_PROCESSES.pop(name, None)
854
+ if not proc:
855
+ return False
856
+ try:
857
+ proc.terminate()
858
+ proc.wait(timeout=5)
859
+ except subprocess.TimeoutExpired:
860
+ proc.kill()
861
+ proc.wait()
862
+ return True
863
+
864
+
865
+ def _resolve_container_ip(proxmox: Any, node: str, vmid: int) -> str:
866
+ status = proxmox.nodes(node).lxc(vmid).status.current.get()
867
+ if status:
868
+ ip_field = status.get("ip")
869
+ if isinstance(ip_field, list):
870
+ for entry in ip_field:
871
+ if isinstance(entry, str) and "." in entry:
872
+ return entry.split("/")[0]
873
+ elif isinstance(ip_field, str) and "." in ip_field:
874
+ return ip_field.split("/")[0]
875
+ res = _run_pct_exec(vmid, ["ip", "-4", "-o", "addr", "show", "eth0"])
876
+ line = res.stdout.splitlines()[0] if res.stdout else ""
877
+ parts = line.split()
878
+ if len(parts) >= 4:
879
+ addr = parts[3]
880
+ return addr.split("/")[0]
881
+ raise RuntimeError("Unable to determine container IP address")
882
+
883
+
710
884
  def _push_bytes_to_container(
711
885
  vmid: int, user: str, path: str, data: bytes, mode: int = 0o600
712
886
  ) -> None:
@@ -1091,6 +1265,7 @@ def configure_infrastructure(
1091
1265
  default_storage = _pick_storage(storages)
1092
1266
  templates = _list_templates(client, node, storages)
1093
1267
  network = dict(existing.get("network", {}) or {})
1268
+ _ensure_cloudflared_installed()
1094
1269
  if not network.get("applied"):
1095
1270
  try:
1096
1271
  network = _ensure_bridge()
@@ -1602,6 +1777,10 @@ class StartProxmoxContainerHandler(SyncHandler):
1602
1777
 
1603
1778
  status, elapsed = _start_container(proxmox, node, vmid)
1604
1779
  _update_container_record(vmid, {"status": "running"})
1780
+ try:
1781
+ _ensure_container_tunnel_running(proxmox, node, vmid)
1782
+ except Exception as exc:
1783
+ raise RuntimeError(f"Failed to start Cloudflare tunnel for container {vmid}: {exc}") from exc
1605
1784
 
1606
1785
  infra = get_infra_snapshot()
1607
1786
  return {
@@ -1631,6 +1810,7 @@ class StopProxmoxContainerHandler(SyncHandler):
1631
1810
  _ensure_container_managed(proxmox, node, vmid)
1632
1811
 
1633
1812
  status, elapsed = _stop_container(proxmox, node, vmid)
1813
+ _stop_container_tunnel(vmid)
1634
1814
  final_status = status.get("status") or "stopped"
1635
1815
  _update_container_record(vmid, {"status": final_status})
1636
1816
 
@@ -1666,8 +1846,13 @@ class RemoveProxmoxContainerHandler(SyncHandler):
1666
1846
  node = _get_node_from_config(config)
1667
1847
  _ensure_container_managed(proxmox, node, vmid)
1668
1848
 
1849
+ _stop_container_tunnel(vmid)
1669
1850
  stop_status, stop_elapsed = _stop_container(proxmox, node, vmid)
1670
1851
  delete_status, delete_elapsed = _delete_container(proxmox, node, vmid)
1852
+ try:
1853
+ _update_container_tunnel(vmid, None)
1854
+ except FileNotFoundError:
1855
+ pass
1671
1856
  _remove_container_record(vmid)
1672
1857
 
1673
1858
  infra = get_infra_snapshot()
@@ -1686,6 +1871,107 @@ class RemoveProxmoxContainerHandler(SyncHandler):
1686
1871
  }
1687
1872
 
1688
1873
 
1874
+ class CreateCloudflareTunnelHandler(SyncHandler):
1875
+ """Create a Cloudflare tunnel for a container."""
1876
+
1877
+ @property
1878
+ def command_name(self) -> str:
1879
+ return "create_cloudflare_tunnel"
1880
+
1881
+ def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1882
+ vmid = _parse_ctid(message)
1883
+ container_port = int(message.get("container_port") or 0)
1884
+ if container_port <= 0:
1885
+ raise ValueError("container_port is required and must be greater than zero.")
1886
+ hostname = (message.get("cloudflare_url") or message.get("hostname") or "").strip()
1887
+ if not hostname:
1888
+ raise ValueError("cloudflare_url is required.")
1889
+ protocol = (message.get("protocol") or "http").strip().lower()
1890
+ if protocol not in {"http", "https", "tcp"}:
1891
+ raise ValueError("protocol must be one of http, https, or tcp.")
1892
+ config = _ensure_infra_configured()
1893
+ _ensure_cloudflare_token(config)
1894
+ proxmox = _connect_proxmox(config)
1895
+ node = _get_node_from_config(config)
1896
+ _ensure_container_managed(proxmox, node, vmid)
1897
+ status = proxmox.nodes(node).lxc(vmid).status.current.get().get("status")
1898
+ if status != "running":
1899
+ raise RuntimeError("Container must be running to create a tunnel.")
1900
+ tunnel = {
1901
+ "container_port": container_port,
1902
+ "url": hostname,
1903
+ "protocol": protocol,
1904
+ }
1905
+ created = _launch_container_tunnel(proxmox, node, vmid, tunnel)
1906
+ return {
1907
+ "event": "cloudflare_tunnel_created",
1908
+ "ctid": str(vmid),
1909
+ "success": True,
1910
+ "message": f"Created Cloudflare tunnel for container {vmid}.",
1911
+ "tunnel": created,
1912
+ }
1913
+
1914
+
1915
+ class UpdateCloudflareTunnelHandler(SyncHandler):
1916
+ """Update an existing Cloudflare tunnel for a container."""
1917
+
1918
+ @property
1919
+ def command_name(self) -> str:
1920
+ return "update_cloudflare_tunnel"
1921
+
1922
+ def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1923
+ vmid = _parse_ctid(message)
1924
+ config = _ensure_infra_configured()
1925
+ _ensure_cloudflare_token(config)
1926
+ proxmox = _connect_proxmox(config)
1927
+ node = _get_node_from_config(config)
1928
+ _ensure_container_managed(proxmox, node, vmid)
1929
+ record = _read_container_record(vmid)
1930
+ tunnel = record.get("tunnel")
1931
+ if not tunnel:
1932
+ raise RuntimeError("No Cloudflare tunnel configured for this container.")
1933
+ container_port = int(message.get("container_port") or tunnel.get("container_port") or 0)
1934
+ if container_port <= 0:
1935
+ raise ValueError("container_port must be greater than zero.")
1936
+ hostname = (message.get("cloudflare_url") or tunnel.get("url") or "").strip()
1937
+ if not hostname:
1938
+ raise ValueError("cloudflare_url is required.")
1939
+ protocol = (message.get("protocol") or tunnel.get("protocol") or "http").strip().lower()
1940
+ if protocol not in {"http", "https", "tcp"}:
1941
+ raise ValueError("protocol must be one of http, https, or tcp.")
1942
+ updated_tunnel = {
1943
+ "container_port": container_port,
1944
+ "url": hostname,
1945
+ "protocol": protocol,
1946
+ }
1947
+ result = _launch_container_tunnel(proxmox, node, vmid, updated_tunnel)
1948
+ return {
1949
+ "event": "cloudflare_tunnel_updated",
1950
+ "ctid": str(vmid),
1951
+ "success": True,
1952
+ "message": f"Updated Cloudflare tunnel for container {vmid}.",
1953
+ "tunnel": result,
1954
+ }
1955
+
1956
+
1957
+ class RemoveCloudflareTunnelHandler(SyncHandler):
1958
+ """Remove any Cloudflare tunnel associated with a container."""
1959
+
1960
+ @property
1961
+ def command_name(self) -> str:
1962
+ return "remove_cloudflare_tunnel"
1963
+
1964
+ def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1965
+ vmid = _parse_ctid(message)
1966
+ _remove_container_tunnel_state(vmid)
1967
+ return {
1968
+ "event": "cloudflare_tunnel_removed",
1969
+ "ctid": str(vmid),
1970
+ "success": True,
1971
+ "message": f"Removed Cloudflare tunnel state for container {vmid}.",
1972
+ }
1973
+
1974
+
1689
1975
  class ConfigureProxmoxInfraHandler(SyncHandler):
1690
1976
  @property
1691
1977
  def command_name(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.15.dev0
3
+ Version: 1.4.15.dev1
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
File without changes