portacode 1.5.17.dev5__tar.gz → 1.5.18.dev0__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 (443) hide show
  1. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/PKG-INFO +1 -1
  2. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/_version.py +2 -2
  3. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/codex_app_server.py +70 -11
  4. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/codex_handlers.py +83 -10
  5. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/git_manager.py +2 -0
  6. portacode-1.5.18.dev0/portacode/link_capture/__pycache__/__init__.cpython-38.pyc +0 -0
  7. portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  8. portacode-1.5.18.dev0/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc +0 -0
  9. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_app_server.py +30 -1
  10. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_handlers.py +57 -0
  11. portacode-1.5.18.dev0/portacode/tests/test_codex_loopback_proxy.py +137 -0
  12. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/PKG-INFO +1 -1
  13. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/SOURCES.txt +4 -0
  14. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.claude/agents/communication-manager.md +0 -0
  15. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.claude/settings.local.json +0 -0
  16. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.gitignore +0 -0
  17. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/.gitmodules +0 -0
  18. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/LICENSE +0 -0
  19. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/MANIFEST.in +0 -0
  20. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/Makefile +0 -0
  21. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/README.md +0 -0
  22. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/backup.sh +0 -0
  23. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/build_android.sh +0 -0
  24. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/connect.py +0 -0
  25. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/connect.sh +0 -0
  26. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docker-compose.yaml +0 -0
  27. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
  28. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/creative-team-brief-portacode.md +0 -0
  29. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/device-health-alerting-plan.md +0 -0
  30. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/devops-messaging-ab-tests.md +0 -0
  31. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
  32. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/device-transfer-button.png +0 -0
  33. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/device-transfer-modal.png +0 -0
  34. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/pair-device-button.png +0 -0
  35. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/pairing-request.png +0 -0
  36. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/images/student-workspace.png +0 -0
  37. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/docs/template-guide.html +0 -0
  38. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/README.md +0 -0
  39. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/Dockerfile +0 -0
  40. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/README.md +0 -0
  41. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  42. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  43. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/simple_device/docker-compose.yaml +0 -0
  44. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/Dockerfile +0 -0
  45. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/README.md +0 -0
  46. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  47. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  48. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  49. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  50. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  51. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  52. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  53. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  54. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  55. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  56. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  57. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  58. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  59. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  60. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  61. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  62. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  63. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  64. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  65. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  66. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  67. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  68. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  69. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  70. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  71. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  72. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  73. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  74. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  75. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  76. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  77. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  78. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  79. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  80. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  81. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  82. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  83. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  84. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  85. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  86. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  87. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  88. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  89. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  90. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  91. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  92. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  93. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  94. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  95. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  96. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  97. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  98. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  99. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  100. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  101. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  102. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  103. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  104. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  105. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  106. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  107. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  108. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  109. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  110. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  111. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  112. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  113. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  114. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  115. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  116. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  117. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  118. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  119. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  120. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  121. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  122. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  123. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  124. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  125. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  126. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  127. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  128. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  129. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  130. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  131. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  132. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  133. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  134. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  135. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  136. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  137. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  138. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  139. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  140. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  141. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  142. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  143. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  144. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  145. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  146. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  147. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  148. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  149. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  150. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  151. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  152. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  153. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  154. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  155. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  156. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  157. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  158. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  159. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  160. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  161. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  162. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  163. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  164. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  165. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  166. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  167. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  168. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  169. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  170. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  171. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  172. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  173. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  174. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  175. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  176. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  177. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  178. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  179. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  180. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  181. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  182. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  183. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  184. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  185. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  186. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  187. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  188. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  189. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  190. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  191. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  192. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  193. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  194. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  195. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  196. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  197. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  198. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  199. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  200. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  201. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  202. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  203. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  204. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  205. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  206. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  207. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  208. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  209. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  210. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  211. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  212. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  213. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  214. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  215. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  216. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  217. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  218. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  219. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  220. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  221. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  222. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  223. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  224. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  225. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  226. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  227. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  228. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  229. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  230. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  231. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  232. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  233. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  234. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  235. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  236. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  237. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  238. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  239. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  240. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  241. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  242. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  243. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  244. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  245. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  246. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  247. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  248. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  249. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  250. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  251. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  252. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  253. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  254. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  255. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  256. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  257. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  258. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  259. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  260. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  261. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
  262. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
  263. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  264. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  265. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  266. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  267. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  268. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
  269. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  270. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  271. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  272. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  273. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  274. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  275. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  276. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  277. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  278. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  279. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  280. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  281. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/README.md +0 -0
  282. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/__init__.py +0 -0
  283. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/__main__.py +0 -0
  284. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/cli.py +0 -0
  285. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_loopback_proxy.py +0 -0
  286. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_prepare.py +0 -0
  287. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/codex_usage_limit.py +0 -0
  288. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/README.md +0 -0
  289. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/__init__.py +0 -0
  290. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/client.py +0 -0
  291. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/README.md +0 -0
  292. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  293. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/__init__.py +0 -0
  294. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
  295. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/base.py +0 -0
  296. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
  297. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
  298. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
  299. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
  300. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  301. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
  302. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  303. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
  304. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
  305. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  306. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
  307. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
  308. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
  309. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
  310. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
  311. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
  312. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/proxmox_infra.py +0 -0
  313. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/registry.py +0 -0
  314. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
  315. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/session.py +0 -0
  316. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
  317. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
  318. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  319. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
  320. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
  321. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
  322. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/handlers/update_handler.py +0 -0
  323. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/multiplex.py +0 -0
  324. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/terminal.py +0 -0
  325. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
  326. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
  327. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/data.py +0 -0
  328. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/exit_codes.py +0 -0
  329. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/keypair.py +0 -0
  330. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/__init__.py +0 -0
  331. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  332. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/elinks +0 -0
  333. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gio-open +0 -0
  334. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gnome-open +0 -0
  335. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
  336. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/kde-open +0 -0
  337. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/kfmclient +0 -0
  338. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  339. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  340. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/links +0 -0
  341. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/links2 +0 -0
  342. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/lynx +0 -0
  343. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/mate-open +0 -0
  344. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/netsurf +0 -0
  345. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
  346. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/w3m +0 -0
  347. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
  348. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/link_capture/bin/xdg-open +0 -0
  349. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/logging_categories.py +0 -0
  350. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/pairing.py +0 -0
  351. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/restart.py +0 -0
  352. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/service.py +0 -0
  353. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
  354. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
  355. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_runtime_paths.py +0 -0
  356. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_service.py +0 -0
  357. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/test_terminal_exposed_services.py +0 -0
  358. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tests/test_codex_prepare.py +0 -0
  359. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/__init__.py +0 -0
  360. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
  361. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
  362. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
  363. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/forwarding_state.py +0 -0
  364. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/get_domain.py +0 -0
  365. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/privileged.py +0 -0
  366. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/service_install.py +0 -0
  367. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/tunneling/state.py +0 -0
  368. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/updater.py +0 -0
  369. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  370. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/__init__.py +0 -0
  371. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/diff_apply.py +0 -0
  372. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/diff_renderer.py +0 -0
  373. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/ntp_clock.py +0 -0
  374. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode/utils/runtime_paths.py +0 -0
  375. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  376. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/entry_points.txt +0 -0
  377. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/requires.txt +0 -0
  378. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode.egg-info/top_level.txt +0 -0
  379. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/.gitignore +0 -0
  380. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/README.md +0 -0
  381. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
  382. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
  383. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
  384. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
  385. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
  386. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
  387. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
  388. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
  389. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
  390. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
  391. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/pyproject.toml +0 -0
  392. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/restore.sh +0 -0
  393. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/run_tests.py +0 -0
  394. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/scripts/diagnose_codex_gateway.py +0 -0
  395. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/setup.cfg +0 -0
  396. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/setup.py +0 -0
  397. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test.sh +0 -0
  398. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/README.md +0 -0
  399. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/__init__.py +0 -0
  400. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_device_online.py +0 -0
  401. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_file_operations.py +0 -0
  402. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_git_status_ui.py +0 -0
  403. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_login_flow.py +0 -0
  404. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
  405. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_play_store_screenshots.py +0 -0
  406. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
  407. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_interaction.py +0 -0
  408. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
  409. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_modules/test_terminal_start.py +0 -0
  410. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_request_id.py +0 -0
  411. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/test_session_terminal_shutdown.py +0 -0
  412. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/.env.example +0 -0
  413. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/README.md +0 -0
  414. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/__init__.py +0 -0
  415. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/cli.py +0 -0
  416. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/__init__.py +0 -0
  417. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/base_test.py +0 -0
  418. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/cli_manager.py +0 -0
  419. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
  420. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/playwright_manager.py +0 -0
  421. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/runner.py +0 -0
  422. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
  423. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/core/test_discovery.py +0 -0
  424. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/testing_framework/requirements.txt +0 -0
  425. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  426. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
  427. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/agent_context_management.md +0 -0
  428. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/django_server_time_sync.md +0 -0
  429. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/device_performance_degradation.md +0 -0
  430. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  431. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
  432. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
  433. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/premature_terminal_exit.md +0 -0
  434. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
  435. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
  436. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
  437. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  438. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
  439. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/generate_play_store_assets.py +0 -0
  440. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/pairing_tester.py +0 -0
  441. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/run_screenshot_suite.sh +0 -0
  442. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/tools/test_python_ntp_clock.py +0 -0
  443. {portacode-1.5.17.dev5 → portacode-1.5.18.dev0}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.17.dev5
3
+ Version: 1.5.18.dev0
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.dev5'
22
- __version_tuple__ = version_tuple = (1, 5, 17, 'dev5')
21
+ __version__ = version = '1.5.18.dev0'
22
+ __version_tuple__ = version_tuple = (1, 5, 18, 'dev0')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import json
7
7
  import logging
8
+ import os
8
9
  import shutil
9
10
  import uuid
10
11
  from pathlib import Path
@@ -32,6 +33,7 @@ class CodexAppServer:
32
33
  command: Optional[List[str]] = None,
33
34
  client_info: Optional[Dict[str, Any]] = None,
34
35
  on_notification: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None,
36
+ on_unexpected_exit: Optional[Callable[[Optional[int]], Awaitable[None]]] = None,
35
37
  _process_factory: Optional[Callable[..., Awaitable[Any]]] = None,
36
38
  ) -> None:
37
39
  self.command = command or list(DEFAULT_CODEX_COMMAND)
@@ -43,6 +45,7 @@ class CodexAppServer:
43
45
  client_info = {"name": "portacode", "version": _portacode_version}
44
46
  self.client_info = client_info
45
47
  self.on_notification = on_notification
48
+ self.on_unexpected_exit = on_unexpected_exit
46
49
  self._process_factory = _process_factory or asyncio.create_subprocess_exec
47
50
 
48
51
  self._proc: Optional[asyncio.subprocess.Process] = None
@@ -74,12 +77,6 @@ class CodexAppServer:
74
77
 
75
78
  async def _spawn(self) -> None:
76
79
  """Spawn the subprocess and perform the JSON-RPC initialize handshake."""
77
- exe = shutil.which(self.command[0]) or self.command[0]
78
- if not shutil.which(self.command[0]):
79
- raise CodexAppServerError(
80
- f"{self.command[0]} not found on PATH. Run `portacode prepare codex` on this device."
81
- )
82
-
83
80
  # systemd services do not load shell profile env; merge /etc/portacode/codex.env.
84
81
  from portacode.codex_prepare import build_codex_subprocess_env, ensure_codex_home
85
82
  from portacode.connection.handlers.runtime_user import (
@@ -93,9 +90,16 @@ class CodexAppServer:
93
90
  # (e.g. bishoy), not as the root agent process.
94
91
  runtime_user = get_default_runtime_user()
95
92
  runtime_home = get_runtime_user_home()
93
+ exe = self._resolve_command_path(self.command[0], runtime_home)
94
+ if not exe:
95
+ raise CodexAppServerError(
96
+ f"{self.command[0]} not found for runtime user {runtime_user}. "
97
+ "Run `portacode prepare codex` on this device."
98
+ )
96
99
  child_env["HOME"] = runtime_home
97
100
  child_env["USER"] = runtime_user
98
101
  child_env["LOGNAME"] = runtime_user
102
+ self._prepend_executable_directory(child_env, exe)
99
103
  try:
100
104
  child_env["CODEX_HOME"] = str(ensure_codex_home())
101
105
  except Exception:
@@ -123,7 +127,7 @@ class CodexAppServer:
123
127
  "no_proxy",
124
128
  ]
125
129
  spawn_cmd = wrap_argv_for_user(
126
- self.command,
130
+ [exe, *self.command[1:]],
127
131
  runtime_user,
128
132
  preserve_env_names=preserve,
129
133
  login=False,
@@ -382,6 +386,11 @@ class CodexAppServer:
382
386
  )
383
387
  return
384
388
  LOGGER.warning("Codex app-server process exited (code %s); restarting", self._proc.returncode)
389
+ if self.on_unexpected_exit is not None:
390
+ try:
391
+ await self.on_unexpected_exit(self._proc.returncode)
392
+ except Exception:
393
+ LOGGER.exception("Codex app-server exit handler failed")
385
394
  await self._kill_locked()
386
395
  try:
387
396
  await self.start()
@@ -415,19 +424,69 @@ class CodexAppServer:
415
424
  return self._initialized.is_set()
416
425
 
417
426
  @staticmethod
418
- def get_binary_path() -> Optional[str]:
419
- return shutil.which("codex")
427
+ def get_binary_path(runtime_home: Optional[str] = None) -> Optional[str]:
428
+ """Find Codex for the service's configured runtime user.
429
+
430
+ A systemd agent normally has a deliberately small PATH. That should
431
+ not hide a Codex CLI installed by the runtime user through nvm or a
432
+ local npm prefix, which is common on developer machines.
433
+ """
434
+ from portacode.connection.handlers.runtime_user import get_runtime_user_home
435
+
436
+ return CodexAppServer._resolve_command_path(
437
+ "codex", runtime_home or get_runtime_user_home()
438
+ )
439
+
440
+ @staticmethod
441
+ def _resolve_command_path(command: str, runtime_home: Optional[str] = None) -> Optional[str]:
442
+ if not command:
443
+ return None
444
+ resolved = shutil.which(command)
445
+ if resolved:
446
+ return resolved
447
+
448
+ candidate = Path(command).expanduser()
449
+ if candidate.is_absolute() or candidate.parent != Path("."):
450
+ if candidate.is_file() and os.access(str(candidate), os.X_OK):
451
+ return str(candidate)
452
+ return None
453
+ if command not in {"codex", "codex.cmd"}:
454
+ return None
455
+
456
+ home = Path(runtime_home or Path.home()).expanduser()
457
+ candidates = [
458
+ home / ".local" / "bin" / command,
459
+ home / ".npm" / "bin" / command,
460
+ ]
461
+ nvm_root = home / ".nvm" / "versions" / "node"
462
+ if nvm_root.is_dir():
463
+ candidates.extend(sorted(nvm_root.glob("*/bin/" + command), reverse=True))
464
+ for candidate in candidates:
465
+ if candidate.is_file() and os.access(str(candidate), os.X_OK):
466
+ return str(candidate)
467
+ return None
468
+
469
+ @staticmethod
470
+ def _prepend_executable_directory(env: Dict[str, str], executable: str) -> None:
471
+ """Make the Node interpreter beside an npm launcher available to env."""
472
+ executable_dir = str(Path(executable).parent)
473
+ current_path = env.get("PATH") or os.defpath
474
+ entries = current_path.split(os.pathsep)
475
+ if executable_dir not in entries:
476
+ env["PATH"] = os.pathsep.join([executable_dir, current_path])
420
477
 
421
478
  @staticmethod
422
479
  def version() -> Optional[str]:
423
480
  import subprocess
424
481
 
425
- exe = shutil.which("codex")
482
+ exe = CodexAppServer.get_binary_path()
426
483
  if not exe:
427
484
  return None
428
485
  try:
486
+ env = dict(os.environ)
487
+ CodexAppServer._prepend_executable_directory(env, exe)
429
488
  result = subprocess.run(
430
- [exe, "--version"], capture_output=True, text=True, timeout=10
489
+ [exe, "--version"], capture_output=True, text=True, timeout=10, env=env
431
490
  )
432
491
  return result.stdout.strip() or None
433
492
  except Exception:
@@ -87,17 +87,23 @@ class _SessionAwareSender:
87
87
  self.control_channel = control_channel
88
88
  self.context = context
89
89
 
90
- async def send(self, payload: Dict[str, Any], project_id: Optional[str] = None) -> None:
90
+ async def send(
91
+ self,
92
+ payload: Dict[str, Any],
93
+ project_id: Optional[str] = None,
94
+ explicit_sessions: Optional[List[str]] = None,
95
+ ) -> None:
96
+ target_sessions = set(explicit_sessions or [])
91
97
  client_session_manager = self.context.get("client_session_manager")
92
98
  if client_session_manager and client_session_manager.has_interested_clients():
93
- target_sessions = client_session_manager.get_target_sessions(project_id)
94
- if target_sessions:
95
- payload = dict(payload)
96
- payload["client_sessions"] = target_sessions
99
+ target_sessions.update(client_session_manager.get_target_sessions(project_id))
97
100
  reply_channel = client_session_manager.get_reply_channel_for_compatibility()
98
101
  if reply_channel:
99
102
  payload = dict(payload)
100
103
  payload["reply_channel"] = reply_channel
104
+ if target_sessions:
105
+ payload = dict(payload)
106
+ payload["client_sessions"] = sorted(target_sessions)
101
107
 
102
108
  await self.control_channel.send(payload)
103
109
 
@@ -107,14 +113,46 @@ class CodexChatManager:
107
113
 
108
114
  def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
109
115
  self.sender = _SessionAwareSender(control_channel, context)
110
- self.bridge = CodexAppServer(on_notification=self._on_notification)
116
+ self.bridge = CodexAppServer(
117
+ on_notification=self._on_notification,
118
+ on_unexpected_exit=self._on_bridge_exit,
119
+ )
111
120
  self._thread_project: Dict[str, str] = {}
112
121
  self._cwd_project: Dict[str, str] = {}
122
+ self._thread_active_turn: Dict[str, str] = {}
123
+ self._thread_sessions: Dict[str, set[str]] = {}
113
124
  # Survives page reloads via codex_status so the IDE can restore UI.
114
125
  self._prepare_running = False
115
126
  self._prepare_step: Optional[str] = None
116
127
  self._prepare_error: Optional[str] = None
117
128
 
129
+ async def _on_bridge_exit(self, returncode: Optional[int]) -> None:
130
+ """Turn an app-server crash into terminal events for waiting browsers."""
131
+ active = list(self._thread_active_turn.items())
132
+ self._thread_active_turn.clear()
133
+ for thread_id, turn_id in active:
134
+ project_id = self._thread_project.get(thread_id)
135
+ if not project_id:
136
+ continue
137
+ suffix = f" (exit code {returncode})" if returncode is not None else ""
138
+ params = {
139
+ "threadId": thread_id,
140
+ "turnId": turn_id,
141
+ "error": {
142
+ "message": f"The local Codex app-server stopped unexpectedly{suffix}.",
143
+ "additionalDetails": "Portacode is restarting it. Retry the message after it reconnects.",
144
+ },
145
+ }
146
+ await self.sender.send(
147
+ {
148
+ "event": "codex_event",
149
+ "notification": {"method": "error", "params": params},
150
+ "project_id": project_id,
151
+ },
152
+ project_id=project_id,
153
+ explicit_sessions=list(self._thread_sessions.get(thread_id, set())),
154
+ )
155
+
118
156
  async def _on_notification(self, method: str, params: Dict[str, Any]) -> None:
119
157
  # Attach usage-limit reset timestamp captured by the loopback proxy.
120
158
  if method == "error" or (
@@ -131,6 +169,13 @@ class CodexChatManager:
131
169
  params["turn"] = turn
132
170
 
133
171
  self._remember_thread_from_params(params)
172
+ thread_id = params.get("threadId") or (params.get("thread") or {}).get("id")
173
+ turn = params.get("turn") if isinstance(params.get("turn"), dict) else {}
174
+ turn_id = params.get("turnId") or turn.get("id")
175
+ if thread_id and method == "turn/started" and turn_id:
176
+ self._thread_active_turn[str(thread_id)] = str(turn_id)
177
+ elif thread_id and method == "turn/completed":
178
+ self._thread_active_turn.pop(str(thread_id), None)
134
179
  project_id = self._resolve_project_id(params)
135
180
  if not project_id:
136
181
  # Unresolved project_id would fan out to every open project tab.
@@ -144,7 +189,11 @@ class CodexChatManager:
144
189
  "notification": {"method": method, "params": params},
145
190
  "project_id": project_id,
146
191
  }
147
- await self.sender.send(event_payload, project_id=project_id)
192
+ await self.sender.send(
193
+ event_payload,
194
+ project_id=project_id,
195
+ explicit_sessions=list(self._thread_sessions.get(str(thread_id), set())) if thread_id else None,
196
+ )
148
197
 
149
198
  def _remember_thread_from_params(self, params: Dict[str, Any]) -> None:
150
199
  """Opportunistically map threadId → project when cwd is already known."""
@@ -175,6 +224,16 @@ class CodexChatManager:
175
224
  if cwd and project_id:
176
225
  self._cwd_project[cwd] = project_id
177
226
 
227
+ def subscribe_thread(self, thread_id: str, client_session: Optional[str]) -> None:
228
+ """Route future thread events to a tab even before session discovery catches up."""
229
+ if not thread_id or not client_session:
230
+ return
231
+ sessions = self._thread_sessions.setdefault(str(thread_id), set())
232
+ sessions.add(str(client_session))
233
+ # Avoid an unbounded collection of stale channel names after many reconnects.
234
+ while len(sessions) > 32:
235
+ sessions.pop()
236
+
178
237
  async def ensure_started(self) -> None:
179
238
  await self.bridge.start()
180
239
 
@@ -544,6 +603,7 @@ class CodexThreadStartHandler(CodexAsyncHandler):
544
603
  raise CodexAppServerError("thread/start did not return a thread id")
545
604
 
546
605
  manager.record_thread(thread_id, cwd, project_id or cwd)
606
+ manager.subscribe_thread(thread_id, message.get("source_client_session"))
547
607
 
548
608
  payload = {
549
609
  "event": "codex_thread_started",
@@ -576,6 +636,7 @@ class CodexThreadResumeHandler(CodexAsyncHandler):
576
636
  # those notifications to the requesting project from the outset.
577
637
  if project_id:
578
638
  manager.record_thread(thread_id, cwd, project_id)
639
+ manager.subscribe_thread(thread_id, message.get("source_client_session"))
579
640
 
580
641
  resume_result, items, active_turn = await _resume_thread_history(
581
642
  manager, thread_id=thread_id, cwd=cwd
@@ -590,6 +651,9 @@ class CodexThreadResumeHandler(CodexAsyncHandler):
590
651
  }
591
652
  if active_turn:
592
653
  payload["activeTurn"] = active_turn
654
+ active_turn_id = active_turn.get("id") or active_turn.get("turnId")
655
+ if active_turn_id:
656
+ manager._thread_active_turn[str(thread_id)] = str(active_turn_id)
593
657
  if project_id:
594
658
  payload["project_id"] = project_id
595
659
  return payload
@@ -623,6 +687,7 @@ class CodexTurnStartHandler(CodexAsyncHandler):
623
687
  # so the browser cannot miss the event that clears its streaming state.
624
688
  if project_id:
625
689
  manager.record_thread(thread_id, cwd, project_id)
690
+ manager.subscribe_thread(thread_id, message.get("source_client_session"))
626
691
 
627
692
  params = {
628
693
  "threadId": thread_id,
@@ -637,11 +702,15 @@ class CodexTurnStartHandler(CodexAsyncHandler):
637
702
  params["summary"] = message["summary"]
638
703
 
639
704
  result = await manager.bridge.call("turn/start", params)
705
+ turn = _turn_from_result(result)
706
+ turn_id = turn.get("id") or turn.get("turnId")
707
+ if turn_id:
708
+ manager._thread_active_turn[str(thread_id)] = str(turn_id)
640
709
 
641
710
  payload = {
642
711
  "event": "codex_turn_started",
643
712
  "threadId": thread_id,
644
- "turn": _turn_from_result(result),
713
+ "turn": turn,
645
714
  }
646
715
  if project_id:
647
716
  payload["project_id"] = project_id
@@ -658,12 +727,16 @@ class CodexTurnInterruptHandler(CodexAsyncHandler):
658
727
  async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
659
728
  thread_id = message.get("threadId")
660
729
  turn_id = message.get("turnId")
661
- if not thread_id or not turn_id:
662
- raise ValueError("threadId and turnId are required")
730
+ if not thread_id:
731
+ raise ValueError("threadId is required")
663
732
 
664
733
  manager = _get_manager(self)
734
+ turn_id = turn_id or manager._thread_active_turn.get(str(thread_id))
735
+ if not turn_id:
736
+ raise ValueError("No active turn was found for this thread")
665
737
  await manager.ensure_started()
666
738
  await manager.bridge.call("turn/interrupt", {"threadId": thread_id, "turnId": turn_id})
739
+ manager._thread_active_turn.pop(str(thread_id), None)
667
740
 
668
741
  project_id = _project_id(message)
669
742
  payload = {"event": "codex_turn_interrupted", "threadId": thread_id, "turnId": turn_id}
@@ -5,6 +5,8 @@ including status checking, diff generation, file content retrieval, and Git comm
5
5
  like staging, unstaging, and reverting files.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import asyncio
9
11
  import hashlib
10
12
  import logging
@@ -193,11 +193,40 @@ async def test_spawn_runs_as_runtime_user(tmp_path, monkeypatch):
193
193
  await bridge.start()
194
194
  assert seen
195
195
  assert seen[0][:5] == ("/usr/sbin/runuser", "-u", "bishoy", "--preserve-environment", "--")
196
- assert "codex" in seen[0]
196
+ assert "/usr/bin/codex" in seen[0]
197
197
  assert seen_kwargs[0].get("cwd") == str(home)
198
198
  await bridge.stop()
199
199
 
200
200
 
201
+ @pytest.mark.asyncio
202
+ async def test_spawn_finds_nvm_codex_when_service_path_does_not(tmp_path, monkeypatch):
203
+ """A systemd PATH must not hide a runtime user's nvm-installed Codex."""
204
+ home = tmp_path / "home"
205
+ codex_bin = home / ".nvm" / "versions" / "node" / "v24.4.1" / "bin"
206
+ codex_bin.mkdir(parents=True)
207
+ codex = codex_bin / "codex"
208
+ codex.write_text("#!/usr/bin/env node\n", encoding="utf-8")
209
+ codex.chmod(0o755)
210
+ monkeypatch.setenv("PATH", "/usr/bin:/bin")
211
+ monkeypatch.setattr(
212
+ "portacode.connection.handlers.runtime_user.get_default_runtime_user",
213
+ lambda message=None: "menas",
214
+ )
215
+ monkeypatch.setattr(
216
+ "portacode.connection.handlers.runtime_user.get_runtime_user_home",
217
+ lambda message=None: str(home),
218
+ )
219
+ monkeypatch.setattr("portacode.codex_prepare.ensure_codex_home", lambda: home / ".codex")
220
+
221
+ proc = FakeProcess([{"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "v2"}}])
222
+ bridge = CodexAppServer(_process_factory=_factory_for(proc))
223
+ await bridge.start()
224
+
225
+ assert CodexAppServer.get_binary_path(str(home)) == str(codex)
226
+ assert proc.spawn_kwargs["env"]["PATH"].split(":")[0] == str(codex_bin)
227
+ await bridge.stop()
228
+
229
+
201
230
  def test_build_codex_subprocess_env_reads_managed_file(tmp_path):
202
231
  from portacode.codex_prepare import build_codex_subprocess_env
203
232
 
@@ -330,6 +330,47 @@ async def test_codex_turn_interrupt():
330
330
  assert manager.bridge.calls[-1] == ("turn/interrupt", {"threadId": "th-1", "turnId": "turn-1"})
331
331
 
332
332
 
333
+ @pytest.mark.asyncio
334
+ async def test_codex_turn_interrupt_resolves_active_turn_when_browser_missed_id():
335
+ channel = DummyControlChannel()
336
+ context = _context()
337
+ manager = CodexChatManager(channel, context)
338
+ manager.bridge = FakeBridge()
339
+ manager._thread_active_turn["th-1"] = "turn-active"
340
+ context["codex_manager"] = manager
341
+
342
+ handler = CodexTurnInterruptHandler(channel, context)
343
+ await handler.handle(
344
+ {"cmd": "codex_turn_interrupt", "project_id": "p-1", "threadId": "th-1"},
345
+ reply_channel="rc-1",
346
+ )
347
+
348
+ assert manager.bridge.calls[-1] == (
349
+ "turn/interrupt",
350
+ {"threadId": "th-1", "turnId": "turn-active"},
351
+ )
352
+ assert "th-1" not in manager._thread_active_turn
353
+
354
+
355
+ @pytest.mark.asyncio
356
+ async def test_codex_bridge_exit_terminates_active_browser_turn():
357
+ channel = DummyControlChannel()
358
+ context = _context()
359
+ manager = CodexChatManager(channel, context)
360
+ manager.record_thread("th-1", "/tmp/proj", "p-1")
361
+ manager._thread_active_turn["th-1"] = "turn-1"
362
+
363
+ await manager._on_bridge_exit(137)
364
+
365
+ payload = channel.sent[-1]
366
+ assert payload["event"] == "codex_event"
367
+ assert payload["project_id"] == "p-1"
368
+ assert payload["notification"]["method"] == "error"
369
+ assert payload["notification"]["params"]["turnId"] == "turn-1"
370
+ assert "exit code 137" in payload["notification"]["params"]["error"]["message"]
371
+ assert not manager._thread_active_turn
372
+
373
+
333
374
  @pytest.mark.asyncio
334
375
  async def test_notification_forwarding_targets_project():
335
376
  channel = DummyControlChannel()
@@ -346,6 +387,22 @@ async def test_notification_forwarding_targets_project():
346
387
  assert payload["client_sessions"] == ["sess-1"]
347
388
 
348
389
 
390
+ @pytest.mark.asyncio
391
+ async def test_resumed_thread_events_include_new_tab_before_session_registry_updates():
392
+ channel = DummyControlChannel()
393
+ context = _context()
394
+ manager = CodexChatManager(channel, context)
395
+ manager.record_thread("th-1", "/tmp/proj", "p-1")
396
+ manager.subscribe_thread("th-1", "new-tab-session")
397
+
398
+ await manager._on_notification(
399
+ "item/agentMessage/delta",
400
+ {"threadId": "th-1", "delta": "live"},
401
+ )
402
+
403
+ assert channel.sent[-1]["client_sessions"] == ["new-tab-session", "sess-1"]
404
+
405
+
349
406
  @pytest.mark.asyncio
350
407
  async def test_notification_without_project_mapping_is_dropped():
351
408
  channel = DummyControlChannel()
@@ -0,0 +1,137 @@
1
+ """Tests for the isolated Codex loopback proxy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+ import pytest
11
+ from cryptography.hazmat.primitives import serialization
12
+ from cryptography.hazmat.primitives.asymmetric import rsa
13
+ from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
14
+
15
+ from portacode.codex_loopback_proxy import CodexLoopbackProxy
16
+ from portacode.keypair import KeyPair
17
+
18
+
19
+ def _write_keypair(tmp_path: Path) -> KeyPair:
20
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
21
+ priv_path = tmp_path / "id_portacode"
22
+ pub_path = tmp_path / "id_portacode.pub"
23
+ priv_path.write_bytes(
24
+ private_key.private_bytes(
25
+ encoding=Encoding.PEM,
26
+ format=PrivateFormat.TraditionalOpenSSL,
27
+ encryption_algorithm=NoEncryption(),
28
+ )
29
+ )
30
+ pub_path.write_bytes(
31
+ private_key.public_key().public_bytes(
32
+ Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
33
+ )
34
+ )
35
+ return KeyPair(priv_path, pub_path)
36
+
37
+
38
+ class _FakeStreamResponse:
39
+ def __init__(self, status_code=200, headers=None, chunks=None, reason="OK"):
40
+ self.status_code = status_code
41
+ self.headers = httpx.Headers(headers or {"content-type": "text/event-stream"})
42
+ self.reason_phrase = reason
43
+ self._chunks = chunks or [b"data: hello\n\n"]
44
+
45
+ async def aread(self) -> bytes:
46
+ return b"".join(self._chunks)
47
+
48
+ async def aiter_raw(self):
49
+ for chunk in self._chunks:
50
+ yield chunk
51
+
52
+ async def __aenter__(self):
53
+ return self
54
+
55
+ async def __aexit__(self, exc_type, exc, tb):
56
+ return False
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_loopback_proxy_health_on_isolated_thread(tmp_path, monkeypatch):
61
+ keypair = _write_keypair(tmp_path)
62
+ # Bind an ephemeral port to avoid colliding with a live agent.
63
+ import portacode.codex_loopback_proxy as proxy_mod
64
+
65
+ monkeypatch.setattr(proxy_mod, "CODEX_LOOPBACK_PORT", 0)
66
+
67
+ proxy = CodexLoopbackProxy(keypair, gateway_url="https://example.test/v1")
68
+ # start_server with port 0 needs us to discover the port — patch after start.
69
+ await proxy.start()
70
+ assert proxy._server is not None
71
+ sockets = proxy._server.sockets or []
72
+ assert sockets
73
+ port = sockets[0].getsockname()[1]
74
+
75
+ async with httpx.AsyncClient() as client:
76
+ response = await client.get(f"http://127.0.0.1:{port}/health", timeout=5.0)
77
+ assert response.status_code == 200
78
+ body = response.json()
79
+ assert body["ok"] is True
80
+ assert body["service"] == "portacode-codex-loopback"
81
+ assert "active_requests" in body
82
+
83
+ await proxy.stop()
84
+ assert proxy._thread is None or not proxy._thread.is_alive()
85
+
86
+
87
+ @pytest.mark.asyncio
88
+ async def test_loopback_proxy_forwards_and_logs_errors(tmp_path, monkeypatch):
89
+ keypair = _write_keypair(tmp_path)
90
+ import portacode.codex_loopback_proxy as proxy_mod
91
+
92
+ monkeypatch.setattr(proxy_mod, "CODEX_LOOPBACK_PORT", 0)
93
+
94
+ proxy = CodexLoopbackProxy(keypair, gateway_url="https://example.test/v1")
95
+ await proxy.start()
96
+ port = proxy._server.sockets[0].getsockname()[1]
97
+
98
+ fake = _FakeStreamResponse(
99
+ status_code=429,
100
+ headers={"content-type": "application/json", "x-portacode-resets-at": "1700000000"},
101
+ chunks=[b'{"error":{"message":"quota"}}'],
102
+ reason="Too Many Requests",
103
+ )
104
+
105
+ class _Client:
106
+ def stream(self, method, url, content=None, headers=None):
107
+ assert method == "POST"
108
+ assert url.endswith("/device/responses")
109
+ assert "X-Portacode-Signature" in headers
110
+ return fake
111
+
112
+ async def aclose(self):
113
+ return None
114
+
115
+ proxy._client = _Client()
116
+
117
+ async with httpx.AsyncClient() as client:
118
+ response = await client.post(
119
+ f"http://127.0.0.1:{port}/v1/responses",
120
+ content=b'{"model":"gpt-5","input":"hi"}',
121
+ headers={"content-type": "application/json"},
122
+ timeout=5.0,
123
+ )
124
+ assert response.status_code == 429
125
+ assert response.json()["error"]["message"] == "quota"
126
+ await proxy.stop()
127
+
128
+
129
+ @pytest.mark.asyncio
130
+ async def test_keypair_caches_private_key(tmp_path):
131
+ keypair = _write_keypair(tmp_path)
132
+ first = keypair.sign_bytes(b"abc")
133
+ # Corrupt the on-disk key after first load — cached key should still work.
134
+ keypair.private_key_path.write_bytes(b"not-a-key")
135
+ second = keypair.sign_bytes(b"abc")
136
+ assert first == second
137
+ assert keypair.public_key_der_b64()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.17.dev5
3
+ Version: 1.5.18.dev0
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -342,6 +342,7 @@ portacode/connection/handlers/project_state/test_handlers.py
342
342
  portacode/connection/handlers/project_state/utils.py
343
343
  portacode/link_capture/__init__.py
344
344
  portacode/link_capture/__pycache__/__init__.cpython-311.pyc
345
+ portacode/link_capture/__pycache__/__init__.cpython-38.pyc
345
346
  portacode/link_capture/bin/elinks
346
347
  portacode/link_capture/bin/gio-open
347
348
  portacode/link_capture/bin/gnome-open
@@ -359,10 +360,13 @@ portacode/link_capture/bin/sensible-browser
359
360
  portacode/link_capture/bin/w3m
360
361
  portacode/link_capture/bin/x-www-browser
361
362
  portacode/link_capture/bin/xdg-open
363
+ portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc
364
+ portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc
362
365
  portacode/static/js/test-ntp-clock.html
363
366
  portacode/static/js/utils/ntp-clock.js
364
367
  portacode/tests/test_codex_app_server.py
365
368
  portacode/tests/test_codex_handlers.py
369
+ portacode/tests/test_codex_loopback_proxy.py
366
370
  portacode/tests/test_codex_prepare.py
367
371
  portacode/tunneling/__init__.py
368
372
  portacode/tunneling/cloudflared_login.py
File without changes