portacode 1.5.17.dev0__tar.gz → 1.5.17.dev2__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 (441) hide show
  1. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/PKG-INFO +1 -1
  2. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/_version.py +2 -2
  3. portacode-1.5.17.dev2/portacode/codex_loopback_proxy.py +468 -0
  4. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/codex_prepare.py +230 -6
  5. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/client.py +24 -3
  6. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/codex_app_server.py +55 -4
  7. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/codex_handlers.py +121 -17
  8. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/runtime_user.py +76 -4
  9. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/test_runtime_user.py +25 -0
  10. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/keypair.py +28 -10
  11. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tests/test_codex_app_server.py +61 -0
  12. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tests/test_codex_handlers.py +62 -14
  13. portacode-1.5.17.dev2/portacode/tests/test_codex_prepare.py +84 -0
  14. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/PKG-INFO +1 -1
  15. portacode-1.5.17.dev0/portacode/codex_loopback_proxy.py +0 -248
  16. portacode-1.5.17.dev0/portacode/tests/test_codex_prepare.py +0 -29
  17. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/.claude/agents/communication-manager.md +0 -0
  18. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/.claude/settings.local.json +0 -0
  19. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/.gitignore +0 -0
  20. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/.gitmodules +0 -0
  21. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/LICENSE +0 -0
  22. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/MANIFEST.in +0 -0
  23. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/Makefile +0 -0
  24. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/README.md +0 -0
  25. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/backup.sh +0 -0
  26. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/build_android.sh +0 -0
  27. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/connect.py +0 -0
  28. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/connect.sh +0 -0
  29. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docker-compose.yaml +0 -0
  30. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
  31. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/creative-team-brief-portacode.md +0 -0
  32. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/device-health-alerting-plan.md +0 -0
  33. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/devops-messaging-ab-tests.md +0 -0
  34. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/homepage-dashboard-positioning-fixes.md +0 -0
  35. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/images/device-transfer-button.png +0 -0
  36. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/images/device-transfer-modal.png +0 -0
  37. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/images/pair-device-button.png +0 -0
  38. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/images/pairing-request.png +0 -0
  39. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/images/student-workspace.png +0 -0
  40. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/docs/template-guide.html +0 -0
  41. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/README.md +0 -0
  42. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/simple_device/Dockerfile +0 -0
  43. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/simple_device/README.md +0 -0
  44. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  45. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  46. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/simple_device/docker-compose.yaml +0 -0
  47. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/Dockerfile +0 -0
  48. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/README.md +0 -0
  49. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  50. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  51. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  52. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  53. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  54. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  55. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  56. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  57. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  58. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  59. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  60. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  61. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  62. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  63. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  64. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  65. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  66. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  67. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  68. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  69. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  70. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  71. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  72. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  73. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  74. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  75. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  76. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  77. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  78. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  79. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  80. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  81. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  82. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  83. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  84. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  85. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  86. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  87. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  88. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  89. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  90. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  91. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  92. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  93. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  94. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  95. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  96. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  97. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  98. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  99. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  100. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  101. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  102. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  103. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  104. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  105. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  106. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  107. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  108. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  109. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  110. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  111. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  112. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  113. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  114. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  115. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  116. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  117. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  118. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  119. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  120. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  121. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  122. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  123. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  124. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  125. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  126. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  127. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  128. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  129. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  130. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  131. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  132. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  133. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  134. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  135. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  136. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  137. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  138. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  139. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  140. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  141. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  142. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  143. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  144. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  145. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  146. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  147. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  148. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  149. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  150. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  151. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  152. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  153. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  154. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  155. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  156. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  157. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  158. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  159. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  160. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  161. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  162. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  163. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  164. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  165. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  166. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  167. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  168. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  169. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  170. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  171. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  172. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  173. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  174. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  175. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  176. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  177. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  178. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  179. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  180. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  181. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  182. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  183. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  184. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  185. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  186. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  187. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  188. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  189. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  190. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  191. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  192. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  193. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  194. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  195. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  196. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  197. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  198. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  199. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  200. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  201. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  202. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  203. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  204. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  205. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  206. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  207. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  208. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  209. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  210. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  211. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  212. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  213. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  214. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  215. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  216. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  217. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  218. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  219. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  220. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  221. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  222. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  223. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  224. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  225. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  226. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  227. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  228. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  229. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  230. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  231. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  232. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  233. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  234. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  235. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  236. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  237. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  238. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  239. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  240. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  241. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  242. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  243. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  244. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  245. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  246. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  247. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  248. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  249. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  250. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  251. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  252. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  253. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  254. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  255. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  256. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  257. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  258. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  259. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  260. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  261. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  262. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  263. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  264. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/docker-compose.yaml +0 -0
  265. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/README.md +0 -0
  266. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  267. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  268. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  269. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  270. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  271. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/manage.py +0 -0
  272. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  273. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  274. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  275. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  276. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  277. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  278. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  279. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  280. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  281. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  282. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  283. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  284. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/README.md +0 -0
  285. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/__init__.py +0 -0
  286. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/__main__.py +0 -0
  287. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/cli.py +0 -0
  288. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/codex_usage_limit.py +0 -0
  289. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/README.md +0 -0
  290. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/__init__.py +0 -0
  291. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/README.md +0 -0
  292. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  293. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/__init__.py +0 -0
  294. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
  295. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/base.py +0 -0
  296. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/chunked_content.py +0 -0
  297. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
  298. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
  299. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/diff_handlers.py +0 -0
  300. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
  301. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
  302. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  303. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/README.md +0 -0
  304. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/__init__.py +0 -0
  305. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  306. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  307. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/handlers.py +0 -0
  308. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/manager.py +0 -0
  309. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/models.py +0 -0
  310. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
  311. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state/utils.py +0 -0
  312. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/project_state_handlers.py +0 -0
  313. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/proxmox_infra.py +0 -0
  314. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/registry.py +0 -0
  315. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/session.py +0 -0
  316. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
  317. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/tab_factory.py +0 -0
  318. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
  319. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
  320. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
  321. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/handlers/update_handler.py +0 -0
  322. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/multiplex.py +0 -0
  323. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/terminal.py +0 -0
  324. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/test_webmin_proxy_config.py +0 -0
  325. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/connection/webmin_proxy_config.py +0 -0
  326. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/data.py +0 -0
  327. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/exit_codes.py +0 -0
  328. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/__init__.py +0 -0
  329. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  330. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/elinks +0 -0
  331. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/gio-open +0 -0
  332. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/gnome-open +0 -0
  333. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/gvfs-open +0 -0
  334. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/kde-open +0 -0
  335. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/kfmclient +0 -0
  336. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  337. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  338. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/links +0 -0
  339. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/links2 +0 -0
  340. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/lynx +0 -0
  341. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/mate-open +0 -0
  342. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/netsurf +0 -0
  343. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/sensible-browser +0 -0
  344. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/w3m +0 -0
  345. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/x-www-browser +0 -0
  346. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/link_capture/bin/xdg-open +0 -0
  347. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/logging_categories.py +0 -0
  348. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/pairing.py +0 -0
  349. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/restart.py +0 -0
  350. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/service.py +0 -0
  351. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/static/js/test-ntp-clock.html +0 -0
  352. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/static/js/utils/ntp-clock.js +0 -0
  353. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/test_runtime_paths.py +0 -0
  354. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/test_service.py +0 -0
  355. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/test_terminal_exposed_services.py +0 -0
  356. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/__init__.py +0 -0
  357. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/cloudflared_login.py +0 -0
  358. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/ensure_cloudflared.py +0 -0
  359. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/ensure_pyyaml.py +0 -0
  360. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/forwarding_state.py +0 -0
  361. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/get_domain.py +0 -0
  362. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/privileged.py +0 -0
  363. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/service_install.py +0 -0
  364. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/tunneling/state.py +0 -0
  365. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/updater.py +0 -0
  366. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  367. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/__init__.py +0 -0
  368. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/diff_apply.py +0 -0
  369. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/diff_renderer.py +0 -0
  370. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/ntp_clock.py +0 -0
  371. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode/utils/runtime_paths.py +0 -0
  372. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/SOURCES.txt +0 -0
  373. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/dependency_links.txt +0 -0
  374. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/entry_points.txt +0 -0
  375. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/requires.txt +0 -0
  376. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode.egg-info/top_level.txt +0 -0
  377. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/.gitignore +0 -0
  378. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/README.md +0 -0
  379. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
  380. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/persistent_workspace/README.md +0 -0
  381. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
  382. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
  383. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
  384. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/README.md +0 -0
  385. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
  386. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
  387. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
  388. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
  389. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/pyproject.toml +0 -0
  390. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/restore.sh +0 -0
  391. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/run_tests.py +0 -0
  392. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/scripts/diagnose_codex_gateway.py +0 -0
  393. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/setup.cfg +0 -0
  394. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/setup.py +0 -0
  395. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test.sh +0 -0
  396. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/README.md +0 -0
  397. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/__init__.py +0 -0
  398. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_device_online.py +0 -0
  399. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_file_operations.py +0 -0
  400. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_git_status_ui.py +0 -0
  401. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_login_flow.py +0 -0
  402. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_navigate_testing_folder.py +0 -0
  403. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_play_store_screenshots.py +0 -0
  404. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_terminal_buffer_performance.py +0 -0
  405. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_terminal_interaction.py +0 -0
  406. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_terminal_loading_race_condition.py +0 -0
  407. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_modules/test_terminal_start.py +0 -0
  408. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_request_id.py +0 -0
  409. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/test_session_terminal_shutdown.py +0 -0
  410. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/.env.example +0 -0
  411. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/README.md +0 -0
  412. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/__init__.py +0 -0
  413. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/cli.py +0 -0
  414. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/__init__.py +0 -0
  415. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/base_test.py +0 -0
  416. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/cli_manager.py +0 -0
  417. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/hierarchical_runner.py +0 -0
  418. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/playwright_manager.py +0 -0
  419. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/runner.py +0 -0
  420. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/shared_cli_manager.py +0 -0
  421. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/core/test_discovery.py +0 -0
  422. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/testing_framework/requirements.txt +0 -0
  423. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  424. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
  425. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/agent_context_management.md +0 -0
  426. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/django_server_time_sync.md +0 -0
  427. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/device_performance_degradation.md +0 -0
  428. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  429. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/indefinite_resource_loading.md +0 -0
  430. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/portacode_service_silently_down.md +0 -0
  431. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/premature_terminal_exit.md +0 -0
  432. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/project_cpu_hotspots.md +0 -0
  433. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/terminals_exit_upon_starting.md +0 -0
  434. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/websocket_client_silently_dead.md +0 -0
  435. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  436. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/todo/smartphone_terminal_input_frustrations.md +0 -0
  437. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/tools/generate_play_store_assets.py +0 -0
  438. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/tools/pairing_tester.py +0 -0
  439. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/tools/run_screenshot_suite.sh +0 -0
  440. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/tools/test_python_ntp_clock.py +0 -0
  441. {portacode-1.5.17.dev0 → portacode-1.5.17.dev2}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.17.dev0
3
+ Version: 1.5.17.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.5.17.dev0'
22
- __version_tuple__ = version_tuple = (1, 5, 17, 'dev0')
21
+ __version__ = version = '1.5.17.dev2'
22
+ __version_tuple__ = version_tuple = (1, 5, 17, 'dev2')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,468 @@
1
+ """Loopback-only Codex Responses proxy authenticated by the device key.
2
+
3
+ Runs on a dedicated thread + event loop so long-lived SSE streams and crypto
4
+ never starve the Portacode websocket / terminal asyncio loop.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import base64
11
+ import hashlib
12
+ import json
13
+ import logging
14
+ import os
15
+ import secrets
16
+ import threading
17
+ import time
18
+ import uuid
19
+ from concurrent.futures import Future
20
+ from typing import Dict, Optional, Tuple
21
+
22
+ import httpx
23
+
24
+ from .codex_usage_limit import note_usage_limit_resets_at
25
+ from .keypair import KeyPair
26
+
27
+ LOGGER = logging.getLogger(__name__)
28
+
29
+ CODEX_LOOPBACK_HOST = "127.0.0.1"
30
+ CODEX_LOOPBACK_PORT = 61789
31
+ DEFAULT_GATEWAY_URL = "https://codexapi.portacode.com/v1"
32
+ MAX_REQUEST_BYTES = 10 * 1024 * 1024
33
+ ALLOWED_PATHS = {"/health", "/v1/models", "/v1/responses"}
34
+ MAX_CONCURRENT_UPSTREAM = 8
35
+
36
+ # Streaming read can idle between SSE chunks; connect/write stay bounded.
37
+ _UPSTREAM_TIMEOUT = httpx.Timeout(connect=20.0, read=600.0, write=60.0, pool=20.0)
38
+
39
+ FORWARD_RESPONSE_HEADERS = frozenset(
40
+ {
41
+ "content-type",
42
+ "cache-control",
43
+ "x-codex-active-limit",
44
+ "x-codex-primary-reset-at",
45
+ "x-codex-secondary-reset-at",
46
+ "x-codex-primary-used-percent",
47
+ "x-codex-secondary-used-percent",
48
+ "x-codex-primary-window-minutes",
49
+ "x-codex-secondary-window-minutes",
50
+ "x-codex-promo-message",
51
+ "x-codex-credits-available",
52
+ "x-codex-credits-balance",
53
+ "x-portacode-resets-at",
54
+ "x-request-id",
55
+ "cf-ray",
56
+ }
57
+ )
58
+
59
+
60
+ class CodexLoopbackProxy:
61
+ """Expose the narrow OpenAI-compatible surface that Codex CLI needs locally."""
62
+
63
+ def __init__(self, keypair: KeyPair, gateway_url: Optional[str] = None) -> None:
64
+ self.keypair = keypair
65
+ self.gateway_url = (
66
+ gateway_url
67
+ or os.getenv("PORTACODE_CODEX_DEVICE_GATEWAY_URL")
68
+ or DEFAULT_GATEWAY_URL
69
+ ).rstrip("/")
70
+ self._server: Optional[asyncio.AbstractServer] = None
71
+ self._client: Optional[httpx.AsyncClient] = None
72
+ self._upstream_sem: Optional[asyncio.Semaphore] = None
73
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
74
+ self._thread: Optional[threading.Thread] = None
75
+ self._stop_event: Optional[threading.Event] = None
76
+ self._active_requests = 0
77
+ self._active_lock = threading.Lock()
78
+
79
+ async def start(self) -> None:
80
+ """Start the proxy thread (idempotent). Awaitable from the agent loop."""
81
+ if self._thread is not None and self._thread.is_alive() and self._server is not None:
82
+ return
83
+
84
+ ready: Future = Future()
85
+ stop_event = threading.Event()
86
+ self._stop_event = stop_event
87
+
88
+ def _thread_main() -> None:
89
+ loop = asyncio.new_event_loop()
90
+ asyncio.set_event_loop(loop)
91
+ self._loop = loop
92
+ try:
93
+ loop.run_until_complete(self._async_start())
94
+ ready.set_result(True)
95
+ while not stop_event.is_set():
96
+ loop.run_forever()
97
+ if stop_event.is_set():
98
+ break
99
+ except Exception as exc:
100
+ if not ready.done():
101
+ ready.set_exception(exc)
102
+ else:
103
+ LOGGER.exception("Codex loopback proxy thread crashed")
104
+ finally:
105
+ try:
106
+ loop.run_until_complete(self._async_stop())
107
+ except Exception:
108
+ LOGGER.exception("Codex loopback proxy cleanup failed")
109
+ try:
110
+ loop.close()
111
+ except Exception:
112
+ pass
113
+ self._loop = None
114
+
115
+ self._thread = threading.Thread(
116
+ target=_thread_main,
117
+ name="portacode-codex-loopback",
118
+ daemon=True,
119
+ )
120
+ self._thread.start()
121
+ await asyncio.wrap_future(ready)
122
+
123
+ async def stop(self) -> None:
124
+ thread = self._thread
125
+ loop = self._loop
126
+ stop_event = self._stop_event
127
+ if thread is None:
128
+ return
129
+ if stop_event is not None:
130
+ stop_event.set()
131
+ if loop is not None and loop.is_running():
132
+ loop.call_soon_threadsafe(loop.stop)
133
+ # Prefer to_thread when available; keep a 3.8-safe fallback.
134
+ try:
135
+ await asyncio.to_thread(thread.join, 5.0)
136
+ except AttributeError:
137
+ loop = asyncio.get_running_loop()
138
+ await loop.run_in_executor(None, thread.join, 5.0)
139
+ self._thread = None
140
+ self._stop_event = None
141
+
142
+ async def _async_start(self) -> None:
143
+ if self._server is not None:
144
+ return
145
+ self._upstream_sem = asyncio.Semaphore(MAX_CONCURRENT_UPSTREAM)
146
+ self._client = httpx.AsyncClient(
147
+ timeout=_UPSTREAM_TIMEOUT,
148
+ follow_redirects=False,
149
+ http2=False,
150
+ )
151
+ try:
152
+ self._server = await asyncio.start_server(
153
+ self._handle_client,
154
+ host=CODEX_LOOPBACK_HOST,
155
+ port=CODEX_LOOPBACK_PORT,
156
+ limit=MAX_REQUEST_BYTES + 16 * 1024,
157
+ reuse_address=True,
158
+ )
159
+ except OSError:
160
+ LOGGER.exception(
161
+ "Failed to bind Codex loopback proxy on %s:%s",
162
+ CODEX_LOOPBACK_HOST,
163
+ CODEX_LOOPBACK_PORT,
164
+ )
165
+ await self._async_stop()
166
+ raise
167
+ LOGGER.info(
168
+ "Codex loopback proxy listening on http://%s:%s/v1 (isolated thread, gateway=%s)",
169
+ CODEX_LOOPBACK_HOST,
170
+ CODEX_LOOPBACK_PORT,
171
+ self.gateway_url,
172
+ )
173
+
174
+ async def _async_stop(self) -> None:
175
+ server = self._server
176
+ self._server = None
177
+ if server is not None:
178
+ server.close()
179
+ try:
180
+ await server.wait_closed()
181
+ except Exception:
182
+ LOGGER.debug("wait_closed for loopback server failed", exc_info=True)
183
+ client = self._client
184
+ self._client = None
185
+ if client is not None:
186
+ try:
187
+ await client.aclose()
188
+ except Exception:
189
+ LOGGER.debug("Closing upstream httpx client failed", exc_info=True)
190
+
191
+ def _signature_headers(self, method: str, path: str, body: bytes) -> Dict[str, str]:
192
+ timestamp = str(int(time.time()))
193
+ nonce = secrets.token_urlsafe(24)
194
+ body_hash = hashlib.sha256(body).hexdigest()
195
+ canonical = f"{method}\n{path}\n{timestamp}\n{nonce}\n{body_hash}".encode("ascii")
196
+ signature = base64.b64encode(self.keypair.sign_bytes(canonical)).decode("ascii")
197
+ return {
198
+ "X-Portacode-Device-Key": self.keypair.public_key_der_b64(),
199
+ "X-Portacode-Timestamp": timestamp,
200
+ "X-Portacode-Nonce": nonce,
201
+ "X-Portacode-Signature": signature,
202
+ }
203
+
204
+ def _bump_active(self, delta: int) -> int:
205
+ with self._active_lock:
206
+ self._active_requests = max(0, self._active_requests + delta)
207
+ return self._active_requests
208
+
209
+ async def _handle_client(
210
+ self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
211
+ ) -> None:
212
+ req_id = uuid.uuid4().hex[:10]
213
+ peer = writer.get_extra_info("peername")
214
+ started = time.monotonic()
215
+ active = self._bump_active(1)
216
+ method = path = "-"
217
+ try:
218
+ method, path, headers, body = await self._read_request(reader)
219
+ LOGGER.info(
220
+ "codex-proxy[%s] %s %s bytes=%s peer=%s active=%s",
221
+ req_id,
222
+ method,
223
+ path,
224
+ len(body),
225
+ peer,
226
+ active,
227
+ )
228
+ if method == "GET" and path == "/health":
229
+ payload = {
230
+ "ok": True,
231
+ "service": "portacode-codex-loopback",
232
+ "active_requests": active,
233
+ "gateway": self.gateway_url,
234
+ }
235
+ await self._write_json(
236
+ writer, 200, json.dumps(payload, separators=(",", ":")).encode("utf-8")
237
+ )
238
+ return
239
+ if method not in {"GET", "POST"} or path not in ALLOWED_PATHS:
240
+ await self._write_error(writer, 404, "Unsupported local Codex endpoint")
241
+ return
242
+ if method == "GET" and path != "/v1/models":
243
+ await self._write_error(writer, 405, "Method not allowed")
244
+ return
245
+ if method == "POST" and path != "/v1/responses":
246
+ await self._write_error(writer, 405, "Method not allowed")
247
+ return
248
+ await self._forward(writer, method, path, headers, body, req_id=req_id)
249
+ except asyncio.TimeoutError:
250
+ LOGGER.error(
251
+ "codex-proxy[%s] timed out after %.1fs method=%s path=%s",
252
+ req_id,
253
+ time.monotonic() - started,
254
+ method,
255
+ path,
256
+ )
257
+ try:
258
+ await self._write_error(writer, 504, "Portacode Codex proxy timed out talking to gateway")
259
+ except Exception:
260
+ pass
261
+ except ValueError as exc:
262
+ LOGGER.warning("codex-proxy[%s] bad request: %s", req_id, exc)
263
+ try:
264
+ await self._write_error(writer, 400, str(exc))
265
+ except Exception:
266
+ pass
267
+ except Exception:
268
+ LOGGER.exception(
269
+ "codex-proxy[%s] request failed after %.1fs method=%s path=%s",
270
+ req_id,
271
+ time.monotonic() - started,
272
+ method,
273
+ path,
274
+ )
275
+ try:
276
+ await self._write_error(writer, 502, "Portacode Codex proxy request failed")
277
+ except Exception:
278
+ pass
279
+ finally:
280
+ remaining = self._bump_active(-1)
281
+ LOGGER.info(
282
+ "codex-proxy[%s] done in %.1fs active=%s",
283
+ req_id,
284
+ time.monotonic() - started,
285
+ remaining,
286
+ )
287
+ try:
288
+ writer.close()
289
+ await asyncio.wait_for(writer.wait_closed(), timeout=2.0)
290
+ except Exception:
291
+ pass
292
+
293
+ async def _read_request(
294
+ self, reader: asyncio.StreamReader
295
+ ) -> Tuple[str, str, Dict[str, str], bytes]:
296
+ request_line = (await asyncio.wait_for(reader.readline(), timeout=30.0)).decode("latin-1").strip()
297
+ parts = request_line.split(" ")
298
+ if len(parts) != 3:
299
+ raise ValueError("Malformed HTTP request")
300
+ method, target, _version = parts
301
+ path = target.split("?", 1)[0]
302
+ headers: Dict[str, str] = {}
303
+ while True:
304
+ line = await asyncio.wait_for(reader.readline(), timeout=30.0)
305
+ if line in {b"\r\n", b"\n", b""}:
306
+ break
307
+ name, separator, value = line.decode("latin-1").partition(":")
308
+ if not separator:
309
+ raise ValueError("Malformed HTTP header")
310
+ headers[name.strip().lower()] = value.strip()
311
+ if headers.get("transfer-encoding", "").lower() == "chunked":
312
+ raise ValueError("Chunked request bodies are not supported")
313
+ length = int(headers.get("content-length", "0"))
314
+ if length < 0 or length > MAX_REQUEST_BYTES:
315
+ raise ValueError("Request body exceeds the local Codex proxy limit")
316
+ body = (
317
+ await asyncio.wait_for(reader.readexactly(length), timeout=60.0) if length else b""
318
+ )
319
+ return method.upper(), path, headers, body
320
+
321
+ def _collect_response_headers(self, response: httpx.Response) -> Dict[str, str]:
322
+ out: Dict[str, str] = {
323
+ "Content-Type": response.headers.get("content-type", "application/json"),
324
+ "Cache-Control": "no-store",
325
+ "Connection": "close",
326
+ }
327
+ for name, value in response.headers.items():
328
+ key = name.lower()
329
+ if key in FORWARD_RESPONSE_HEADERS and key not in {"content-type", "cache-control"}:
330
+ out[name] = value
331
+ return out
332
+
333
+ def _note_resets_from_error(self, response: httpx.Response, body: bytes) -> None:
334
+ resets_at = None
335
+ header_val = response.headers.get("x-portacode-resets-at") or response.headers.get(
336
+ "x-codex-primary-reset-at"
337
+ )
338
+ if header_val:
339
+ try:
340
+ resets_at = int(header_val)
341
+ except ValueError:
342
+ resets_at = None
343
+ if resets_at is None and body:
344
+ try:
345
+ payload = json.loads(body.decode("utf-8", errors="replace"))
346
+ except json.JSONDecodeError:
347
+ payload = None
348
+ if isinstance(payload, dict):
349
+ err = payload.get("error") if isinstance(payload.get("error"), dict) else {}
350
+ for key in ("resets_at", "resetsAt"):
351
+ if key in err:
352
+ try:
353
+ resets_at = int(err[key])
354
+ break
355
+ except (TypeError, ValueError):
356
+ pass
357
+ note_usage_limit_resets_at(resets_at)
358
+
359
+ async def _forward(
360
+ self,
361
+ writer: asyncio.StreamWriter,
362
+ method: str,
363
+ path: str,
364
+ request_headers: Dict[str, str],
365
+ body: bytes,
366
+ *,
367
+ req_id: str,
368
+ ) -> None:
369
+ headers = self._signature_headers(method, path, body)
370
+ content_type = request_headers.get("content-type")
371
+ if content_type:
372
+ headers["Content-Type"] = content_type
373
+ gateway_path = path[3:] if path.startswith("/v1") else path
374
+ upstream_url = f"{self.gateway_url}/device{gateway_path}"
375
+ client = self._client
376
+ if client is None:
377
+ raise RuntimeError("Upstream HTTP client is not started")
378
+ sem = self._upstream_sem
379
+ if sem is None:
380
+ raise RuntimeError("Upstream semaphore is not started")
381
+
382
+ async with sem:
383
+ LOGGER.info(
384
+ "codex-proxy[%s] upstream %s %s",
385
+ req_id,
386
+ method,
387
+ upstream_url,
388
+ )
389
+ try:
390
+ async with client.stream(
391
+ method,
392
+ upstream_url,
393
+ content=body,
394
+ headers=headers,
395
+ ) as response:
396
+ response_headers = self._collect_response_headers(response)
397
+ status_text = response.reason_phrase or "OK"
398
+ LOGGER.info(
399
+ "codex-proxy[%s] upstream status=%s content-type=%s",
400
+ req_id,
401
+ response.status_code,
402
+ response.headers.get("content-type", ""),
403
+ )
404
+
405
+ if response.status_code >= 400:
406
+ err_body = await response.aread()
407
+ self._note_resets_from_error(response, err_body)
408
+ LOGGER.warning(
409
+ "codex-proxy[%s] upstream error status=%s body_prefix=%r",
410
+ req_id,
411
+ response.status_code,
412
+ err_body[:300],
413
+ )
414
+ response_headers["Content-Length"] = str(len(err_body))
415
+ writer.write(
416
+ f"HTTP/1.1 {response.status_code} {status_text}\r\n".encode("ascii")
417
+ )
418
+ for name, value in response_headers.items():
419
+ writer.write(f"{name}: {value}\r\n".encode("latin-1"))
420
+ writer.write(b"\r\n")
421
+ writer.write(err_body)
422
+ await writer.drain()
423
+ return
424
+
425
+ if response.headers.get("content-length"):
426
+ response_headers["Content-Length"] = response.headers["content-length"]
427
+ writer.write(
428
+ f"HTTP/1.1 {response.status_code} {status_text}\r\n".encode("ascii")
429
+ )
430
+ for name, value in response_headers.items():
431
+ writer.write(f"{name}: {value}\r\n".encode("latin-1"))
432
+ writer.write(b"\r\n")
433
+ bytes_out = 0
434
+ async for chunk in response.aiter_raw():
435
+ if not chunk:
436
+ continue
437
+ bytes_out += len(chunk)
438
+ writer.write(chunk)
439
+ await writer.drain()
440
+ LOGGER.info(
441
+ "codex-proxy[%s] streamed %s bytes to client",
442
+ req_id,
443
+ bytes_out,
444
+ )
445
+ except httpx.TimeoutException as exc:
446
+ LOGGER.error("codex-proxy[%s] upstream timeout: %s", req_id, exc)
447
+ raise asyncio.TimeoutError(str(exc)) from exc
448
+ except httpx.HTTPError as exc:
449
+ LOGGER.error("codex-proxy[%s] upstream http error: %s", req_id, exc)
450
+ raise
451
+
452
+ async def _write_error(self, writer: asyncio.StreamWriter, status: int, message: str) -> None:
453
+ body = ('{"error":{"message":"' + message.replace('"', "'") + '"}}').encode("utf-8")
454
+ await self._write_json(writer, status, body)
455
+
456
+ async def _write_json(self, writer: asyncio.StreamWriter, status: int, body: bytes) -> None:
457
+ reason = "OK" if 200 <= status < 300 else "Error"
458
+ writer.write(
459
+ (
460
+ f"HTTP/1.1 {status} {reason}\r\n"
461
+ "Content-Type: application/json\r\n"
462
+ "Cache-Control: no-store\r\n"
463
+ f"Content-Length: {len(body)}\r\n"
464
+ "Connection: close\r\n\r\n"
465
+ ).encode("ascii")
466
+ + body
467
+ )
468
+ await writer.drain()