portacode 1.5.13.dev4__tar.gz → 1.5.14.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 (438) hide show
  1. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/PKG-INFO +1 -1
  2. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/_version.py +2 -2
  3. portacode-1.5.14.dev0/portacode/connection/codex_app_server.py +326 -0
  4. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/__init__.py +15 -0
  5. portacode-1.5.14.dev0/portacode/connection/handlers/codex_handlers.py +293 -0
  6. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/terminal.py +13 -0
  7. portacode-1.5.14.dev0/portacode/tests/test_codex_app_server.py +166 -0
  8. portacode-1.5.14.dev0/portacode/tests/test_codex_handlers.py +189 -0
  9. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/PKG-INFO +1 -1
  10. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/SOURCES.txt +4 -2
  11. portacode-1.5.13.dev4/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  12. portacode-1.5.13.dev4/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  13. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/.claude/agents/communication-manager.md +0 -0
  14. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/.claude/settings.local.json +0 -0
  15. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/.gitignore +0 -0
  16. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/.gitmodules +0 -0
  17. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/LICENSE +0 -0
  18. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/MANIFEST.in +0 -0
  19. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/Makefile +0 -0
  20. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/README.md +0 -0
  21. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/backup.sh +0 -0
  22. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/build_android.sh +0 -0
  23. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/connect.py +0 -0
  24. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/connect.sh +0 -0
  25. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docker-compose.yaml +0 -0
  26. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
  27. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/creative-team-brief-portacode.md +0 -0
  28. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/device-health-alerting-plan.md +0 -0
  29. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/devops-messaging-ab-tests.md +0 -0
  30. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
  31. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/images/device-transfer-button.png +0 -0
  32. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/images/device-transfer-modal.png +0 -0
  33. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/images/pair-device-button.png +0 -0
  34. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/images/pairing-request.png +0 -0
  35. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/images/student-workspace.png +0 -0
  36. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/docs/template-guide.html +0 -0
  37. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/README.md +0 -0
  38. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/simple_device/Dockerfile +0 -0
  39. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/simple_device/README.md +0 -0
  40. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  41. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  42. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/simple_device/docker-compose.yaml +0 -0
  43. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/Dockerfile +0 -0
  44. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/README.md +0 -0
  45. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  46. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  47. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  48. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  49. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  50. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  51. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  52. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  53. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  54. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  55. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  56. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  57. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  58. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  59. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  60. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  61. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  62. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  63. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  64. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  65. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  66. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  67. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  68. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  69. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  70. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  71. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  72. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  73. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  74. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  75. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  76. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  77. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  78. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  79. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  80. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  81. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  82. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  83. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  84. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  85. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  86. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  87. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  88. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  89. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  90. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  91. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  92. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  93. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  94. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  95. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  96. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  97. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  98. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  99. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  100. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  101. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  102. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  103. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  104. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  105. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  106. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  107. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  108. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  109. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  110. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  111. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  112. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  113. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  114. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  115. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  116. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  117. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  118. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  119. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  120. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  121. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  122. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  123. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  124. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  125. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  126. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  127. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  128. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  129. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  130. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  131. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  132. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  133. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  134. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  135. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  136. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  137. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  138. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  139. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  140. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  141. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  142. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  143. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  144. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  145. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  146. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  147. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  148. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  149. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  150. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  151. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  152. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  153. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  154. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  155. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  156. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  157. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  158. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  159. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  160. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  161. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  162. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  163. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  164. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  165. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  166. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  167. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  168. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  169. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  170. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  171. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  172. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  173. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  174. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  175. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  176. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  177. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  178. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  179. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  180. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  181. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  182. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  183. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  184. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  185. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  186. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  187. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  188. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  189. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  190. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  191. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  192. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  193. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  194. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  195. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  196. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  197. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  198. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  199. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  200. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  201. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  202. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  203. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  204. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  205. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  206. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  207. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  208. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  209. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  210. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  211. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  212. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  213. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  214. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  215. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  216. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  217. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  218. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  219. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  220. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  221. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  222. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  223. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  224. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  225. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  226. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  227. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  228. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  229. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  230. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  231. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  232. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  233. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  234. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  235. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  236. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  237. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  238. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  239. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  240. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  241. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  242. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  243. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  244. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  245. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  246. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  247. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  248. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  249. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  250. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  251. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  252. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  253. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  254. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  255. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  256. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  257. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  258. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  259. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  260. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
  261. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
  262. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  263. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  264. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  265. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  266. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  267. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
  268. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  269. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  270. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  271. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  272. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  273. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  274. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  275. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  276. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  277. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  278. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  279. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  280. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/README.md +0 -0
  281. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/__init__.py +0 -0
  282. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/__main__.py +0 -0
  283. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/cli.py +0 -0
  284. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/codex_loopback_proxy.py +0 -0
  285. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/codex_prepare.py +0 -0
  286. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/README.md +0 -0
  287. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/__init__.py +0 -0
  288. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/client.py +0 -0
  289. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/README.md +0 -0
  290. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  291. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
  292. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/base.py +0 -0
  293. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
  294. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
  295. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
  296. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
  297. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  298. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
  299. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  300. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
  301. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
  302. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  303. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  304. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
  305. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
  306. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
  307. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
  308. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
  309. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
  310. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/proxmox_infra.py +0 -0
  311. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/registry.py +0 -0
  312. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
  313. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/session.py +0 -0
  314. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
  315. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
  316. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  317. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
  318. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
  319. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
  320. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/handlers/update_handler.py +0 -0
  321. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/multiplex.py +0 -0
  322. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
  323. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
  324. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/data.py +0 -0
  325. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/exit_codes.py +0 -0
  326. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/keypair.py +0 -0
  327. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/__init__.py +0 -0
  328. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/elinks +0 -0
  329. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gio-open +0 -0
  330. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gnome-open +0 -0
  331. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
  332. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/kde-open +0 -0
  333. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/kfmclient +0 -0
  334. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  335. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  336. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/links +0 -0
  337. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/links2 +0 -0
  338. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/lynx +0 -0
  339. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/mate-open +0 -0
  340. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/netsurf +0 -0
  341. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
  342. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/w3m +0 -0
  343. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
  344. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/link_capture/bin/xdg-open +0 -0
  345. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/logging_categories.py +0 -0
  346. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/pairing.py +0 -0
  347. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/restart.py +0 -0
  348. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/service.py +0 -0
  349. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
  350. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
  351. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/test_runtime_paths.py +0 -0
  352. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/test_service.py +0 -0
  353. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/test_terminal_exposed_services.py +0 -0
  354. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/__init__.py +0 -0
  355. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
  356. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
  357. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
  358. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/forwarding_state.py +0 -0
  359. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/get_domain.py +0 -0
  360. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/privileged.py +0 -0
  361. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/service_install.py +0 -0
  362. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/tunneling/state.py +0 -0
  363. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/updater.py +0 -0
  364. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  365. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/__init__.py +0 -0
  366. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/diff_apply.py +0 -0
  367. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/diff_renderer.py +0 -0
  368. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/ntp_clock.py +0 -0
  369. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode/utils/runtime_paths.py +0 -0
  370. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  371. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/entry_points.txt +0 -0
  372. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/requires.txt +0 -0
  373. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode.egg-info/top_level.txt +0 -0
  374. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/.gitignore +0 -0
  375. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/README.md +0 -0
  376. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
  377. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
  378. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
  379. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
  380. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
  381. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
  382. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
  383. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
  384. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
  385. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
  386. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/pyproject.toml +0 -0
  387. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/restore.sh +0 -0
  388. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/run_tests.py +0 -0
  389. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/scripts/diagnose_codex_gateway.py +0 -0
  390. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/setup.cfg +0 -0
  391. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/setup.py +0 -0
  392. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test.sh +0 -0
  393. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/README.md +0 -0
  394. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/__init__.py +0 -0
  395. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_device_online.py +0 -0
  396. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_file_operations.py +0 -0
  397. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_git_status_ui.py +0 -0
  398. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_login_flow.py +0 -0
  399. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
  400. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_play_store_screenshots.py +0 -0
  401. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
  402. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_terminal_interaction.py +0 -0
  403. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
  404. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_modules/test_terminal_start.py +0 -0
  405. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_request_id.py +0 -0
  406. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/test_session_terminal_shutdown.py +0 -0
  407. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/.env.example +0 -0
  408. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/README.md +0 -0
  409. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/__init__.py +0 -0
  410. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/cli.py +0 -0
  411. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/__init__.py +0 -0
  412. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/base_test.py +0 -0
  413. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/cli_manager.py +0 -0
  414. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
  415. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/playwright_manager.py +0 -0
  416. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/runner.py +0 -0
  417. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
  418. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/core/test_discovery.py +0 -0
  419. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/testing_framework/requirements.txt +0 -0
  420. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  421. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
  422. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/agent_context_management.md +0 -0
  423. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/django_server_time_sync.md +0 -0
  424. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/device_performance_degradation.md +0 -0
  425. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  426. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
  427. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
  428. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/premature_terminal_exit.md +0 -0
  429. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
  430. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
  431. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
  432. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  433. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
  434. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/tools/generate_play_store_assets.py +0 -0
  435. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/tools/pairing_tester.py +0 -0
  436. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/tools/run_screenshot_suite.sh +0 -0
  437. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/tools/test_python_ntp_clock.py +0 -0
  438. {portacode-1.5.13.dev4 → portacode-1.5.14.dev0}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.13.dev4
3
+ Version: 1.5.14.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.13.dev4'
22
- __version_tuple__ = version_tuple = (1, 5, 13, 'dev4')
21
+ __version__ = version = '1.5.14.dev0'
22
+ __version_tuple__ = version_tuple = (1, 5, 14, 'dev0')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,326 @@
1
+ """JSON-RPC bridge to the Codex CLI app-server over stdio."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ import shutil
9
+ import uuid
10
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
11
+
12
+ LOGGER = logging.getLogger(__name__)
13
+
14
+ DEFAULT_CODEX_COMMAND = ["codex", "app-server", "--listen", "stdio://"]
15
+
16
+
17
+ class CodexAppServerError(RuntimeError):
18
+ """Raised when the Codex app-server cannot be started or is unhealthy."""
19
+
20
+
21
+ class CodexAppServer:
22
+ """Thin asyncio JSON-RPC 2.0 client for `codex app-server --listen stdio://`.
23
+
24
+ One instance per device agent is enough: the app-server can host multiple
25
+ threads/cwds, so we keep it alive for the lifetime of the connection and
26
+ lazily spawn it on the first command.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ command: Optional[List[str]] = None,
32
+ client_info: Optional[Dict[str, Any]] = None,
33
+ on_notification: Optional[Callable[[str, Dict[str, Any]], Awaitable[None]]] = None,
34
+ _process_factory: Optional[Callable[..., Awaitable[Any]]] = None,
35
+ ) -> None:
36
+ self.command = command or list(DEFAULT_CODEX_COMMAND)
37
+ self.client_info = client_info or {"name": "portacode", "version": "1.5.13"}
38
+ self.on_notification = on_notification
39
+ self._process_factory = _process_factory or asyncio.create_subprocess_exec
40
+
41
+ self._proc: Optional[asyncio.subprocess.Process] = None
42
+ self._reader_task: Optional[asyncio.Task] = None
43
+ self._stderr_task: Optional[asyncio.Task] = None
44
+ self._next_id = 1
45
+ self._pending: Dict[int, asyncio.Future] = {}
46
+ self._lock = asyncio.Lock()
47
+ self._initialized = asyncio.Event()
48
+ self._closed = False
49
+
50
+ # ------------------------------------------------------------------
51
+ # Lifecycle
52
+ # ------------------------------------------------------------------
53
+ async def start(self) -> None:
54
+ """Start the app-server if it is not already running."""
55
+ if self._closed:
56
+ raise CodexAppServerError("CodexAppServer has been closed")
57
+ async with self._lock:
58
+ if self._proc is not None and self._proc.returncode is None:
59
+ return
60
+ await self._spawn()
61
+
62
+ async def stop(self) -> None:
63
+ """Stop the app-server and cancel background tasks."""
64
+ async with self._lock:
65
+ self._closed = True
66
+ await self._kill_locked()
67
+
68
+ async def _spawn(self) -> None:
69
+ """Spawn the subprocess and perform the JSON-RPC initialize handshake."""
70
+ exe = shutil.which(self.command[0]) or self.command[0]
71
+ if not shutil.which(self.command[0]):
72
+ raise CodexAppServerError(
73
+ f"{self.command[0]} not found on PATH. Run `portacode prepare codex` on this device."
74
+ )
75
+
76
+ LOGGER.info("Spawning Codex app-server: %s", " ".join(self.command))
77
+ try:
78
+ self._proc = await self._process_factory(
79
+ *self.command,
80
+ stdin=asyncio.subprocess.PIPE,
81
+ stdout=asyncio.subprocess.PIPE,
82
+ stderr=asyncio.subprocess.PIPE,
83
+ env=None, # Inherit the environment where `prepare codex` set OPENAI_API_KEY
84
+ )
85
+ except Exception as exc:
86
+ raise CodexAppServerError(f"Failed to spawn codex app-server: {exc}") from exc
87
+
88
+ self._reader_task = asyncio.create_task(self._read_loop(), name="codex-app-server-stdout")
89
+ self._stderr_task = asyncio.create_task(self._stderr_loop(), name="codex-app-server-stderr")
90
+
91
+ await self._initialize()
92
+
93
+ async def _kill_locked(self) -> None:
94
+ """Tear down the subprocess and reader tasks."""
95
+ for future in list(self._pending.values()):
96
+ if not future.done():
97
+ future.set_exception(CodexAppServerError("Codex app-server stopped"))
98
+ self._pending.clear()
99
+
100
+ if self._reader_task is not None:
101
+ self._reader_task.cancel()
102
+ self._reader_task = None
103
+ if self._stderr_task is not None:
104
+ self._stderr_task.cancel()
105
+ self._stderr_task = None
106
+
107
+ if self._proc is not None and self._proc.returncode is None:
108
+ try:
109
+ self._proc.terminate()
110
+ await asyncio.wait_for(self._proc.wait(), timeout=3.0)
111
+ except asyncio.TimeoutError:
112
+ self._proc.kill()
113
+ await self._proc.wait()
114
+ except ProcessLookupError:
115
+ pass
116
+ self._proc = None
117
+ self._initialized.clear()
118
+
119
+ # ------------------------------------------------------------------
120
+ # JSON-RPC I/O
121
+ # ------------------------------------------------------------------
122
+ async def _initialize(self) -> None:
123
+ """Send the initialize handshake and wait for its response."""
124
+ response = await self._request(
125
+ "initialize",
126
+ {
127
+ "clientInfo": self.client_info,
128
+ "capabilities": {"streaming": True},
129
+ },
130
+ timeout=10.0,
131
+ )
132
+ LOGGER.info("Codex app-server initialized: %s", response.get("result", {}))
133
+ self._initialized.set()
134
+
135
+ async def _request(
136
+ self, method: str, params: Dict[str, Any], timeout: float = 60.0
137
+ ) -> Dict[str, Any]:
138
+ """Send a JSON-RPC request and return the response object."""
139
+ await self.start()
140
+ assert self._proc is not None and self._proc.stdin is not None
141
+
142
+ request_id = self._next_id
143
+ self._next_id += 1
144
+ message = {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}
145
+ payload = json.dumps(message, ensure_ascii=False)
146
+
147
+ loop = asyncio.get_running_loop()
148
+ future: asyncio.Future = loop.create_future()
149
+ self._pending[request_id] = future
150
+
151
+ try:
152
+ self._proc.stdin.write((payload + "\n").encode("utf-8"))
153
+ await self._proc.stdin.drain()
154
+ except Exception as exc:
155
+ self._pending.pop(request_id, None)
156
+ raise CodexAppServerError(f"Failed to write to codex app-server: {exc}") from exc
157
+
158
+ try:
159
+ return await asyncio.wait_for(future, timeout=timeout)
160
+ except asyncio.TimeoutError as exc:
161
+ self._pending.pop(request_id, None)
162
+ raise CodexAppServerError(f"codex app-server request timed out: {method}") from exc
163
+
164
+ async def _send_notification(self, method: str, params: Dict[str, Any]) -> None:
165
+ """Fire a JSON-RPC notification (no response expected)."""
166
+ await self.start()
167
+ assert self._proc is not None and self._proc.stdin is not None
168
+ message = {"jsonrpc": "2.0", "method": method, "params": params}
169
+ payload = json.dumps(message, ensure_ascii=False)
170
+ self._proc.stdin.write((payload + "\n").encode("utf-8"))
171
+ await self._proc.stdin.drain()
172
+
173
+ async def _send_error_response(self, request_id: Any, code: int, message: str) -> None:
174
+ """Reply to a server->client request with a JSON-RPC error."""
175
+ assert self._proc is not None and self._proc.stdin is not None
176
+ response = {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}}
177
+ payload = json.dumps(response, ensure_ascii=False)
178
+ self._proc.stdin.write((payload + "\n").encode("utf-8"))
179
+ await self._proc.stdin.drain()
180
+
181
+ # ------------------------------------------------------------------
182
+ # Reading loops
183
+ # ------------------------------------------------------------------
184
+ async def _read_loop(self) -> None:
185
+ """Read newline-delimited JSON-RPC messages from app-server stdout."""
186
+ assert self._proc is not None and self._proc.stdout is not None
187
+ try:
188
+ while True:
189
+ line = await self._proc.stdout.readline()
190
+ if not line:
191
+ break
192
+ line = line.decode("utf-8", errors="replace").strip()
193
+ if not line:
194
+ continue
195
+ try:
196
+ message = json.loads(line)
197
+ except json.JSONDecodeError as exc:
198
+ LOGGER.warning("Ignoring non-JSON line from codex app-server: %r (%s)", line, exc)
199
+ continue
200
+ await self._dispatch_message(message)
201
+ except asyncio.CancelledError:
202
+ raise
203
+ except Exception:
204
+ LOGGER.exception("codex app-server read loop failed")
205
+ finally:
206
+ # Process exited; schedule a restart if not closed.
207
+ if not self._closed:
208
+ asyncio.create_task(self._on_reader_exit())
209
+
210
+ async def _stderr_loop(self) -> None:
211
+ """Forward app-server stderr lines to the logger."""
212
+ assert self._proc is not None and self._proc.stderr is not None
213
+ try:
214
+ while True:
215
+ line = await self._proc.stderr.readline()
216
+ if not line:
217
+ break
218
+ text = line.decode("utf-8", errors="replace").rstrip()
219
+ if text:
220
+ LOGGER.debug("codex app-server stderr: %s", text)
221
+ except asyncio.CancelledError:
222
+ raise
223
+ except Exception:
224
+ LOGGER.exception("codex app-server stderr loop failed")
225
+
226
+ async def _dispatch_message(self, message: Dict[str, Any]) -> None:
227
+ """Route a single JSON-RPC message to pending response, notification, or request handler."""
228
+ request_id = message.get("id")
229
+ has_result = "result" in message
230
+ has_error = "error" in message
231
+ method = message.get("method")
232
+ params = message.get("params", {})
233
+
234
+ if has_result or has_error:
235
+ if request_id is not None and request_id in self._pending:
236
+ future = self._pending.pop(request_id)
237
+ if not future.done():
238
+ future.set_result(message)
239
+ return
240
+
241
+ if request_id is not None and method is not None:
242
+ # The server sent us a request. We currently auto-decline/unsupported everything
243
+ # because the portacode config sets approval_policy="never" and danger-full-access.
244
+ LOGGER.debug("Declining codex app-server request: %s", method)
245
+ await self._send_error_response(request_id, -32601, f"Method not supported by portacode: {method}")
246
+ return
247
+
248
+ if method is not None:
249
+ # Notification from app-server.
250
+ LOGGER.debug("codex app-server notification: %s", method)
251
+ if self.on_notification is not None:
252
+ try:
253
+ await self.on_notification(method, params)
254
+ except Exception:
255
+ LOGGER.exception("codex notification handler failed for %s", method)
256
+ return
257
+
258
+ LOGGER.debug("Ignoring unexpected codex app-server message: %s", message)
259
+
260
+ async def _on_reader_exit(self) -> None:
261
+ """Restart the app-server once after an unexpected exit."""
262
+ async with self._lock:
263
+ if self._closed or self._proc is None:
264
+ return
265
+ LOGGER.warning("Codex app-server process exited (code %s); restarting", self._proc.returncode)
266
+ await self._kill_locked()
267
+ try:
268
+ await self.start()
269
+ except Exception:
270
+ LOGGER.exception("Failed to restart codex app-server")
271
+
272
+ # ------------------------------------------------------------------
273
+ # Public API
274
+ # ------------------------------------------------------------------
275
+ async def call(
276
+ self, method: str, params: Optional[Dict[str, Any]] = None, timeout: float = 60.0
277
+ ) -> Dict[str, Any]:
278
+ """Call a Codex app-server method and return the `result` field."""
279
+ params = params or {}
280
+ response = await self._request(method, params, timeout)
281
+ if "error" in response:
282
+ error = response["error"]
283
+ raise CodexAppServerError(
284
+ f"codex app-server error ({method}): {error.get('message', error)}"
285
+ )
286
+ return response.get("result", {})
287
+
288
+ async def notify(self, method: str, params: Optional[Dict[str, Any]] = None) -> None:
289
+ """Send a JSON-RPC notification."""
290
+ await self._send_notification(method, params or {})
291
+
292
+ async def healthy(self) -> bool:
293
+ """Return True if the app-server is initialized and the process is alive."""
294
+ if self._proc is None or self._proc.returncode is not None:
295
+ return False
296
+ return self._initialized.is_set()
297
+
298
+ @staticmethod
299
+ def get_binary_path() -> Optional[str]:
300
+ return shutil.which("codex")
301
+
302
+ @staticmethod
303
+ def version() -> Optional[str]:
304
+ import subprocess
305
+
306
+ exe = shutil.which("codex")
307
+ if not exe:
308
+ return None
309
+ try:
310
+ result = subprocess.run(
311
+ [exe, "--version"], capture_output=True, text=True, timeout=10
312
+ )
313
+ return result.stdout.strip() or None
314
+ except Exception:
315
+ return None
316
+
317
+ def get_running_thread_id(self, turn_id: Optional[str] = None) -> Optional[str]:
318
+ """Hook for the manager to resolve turn_id -> thread_id if needed."""
319
+ return None
320
+
321
+ def __del__(self) -> None:
322
+ if self._proc is not None and self._proc.returncode is None:
323
+ try:
324
+ self._proc.kill()
325
+ except Exception:
326
+ pass
@@ -61,6 +61,14 @@ from .automation_v2_handlers import (
61
61
  AutomationV2StateHandler,
62
62
  AutomationV2CancelHandler,
63
63
  )
64
+ from .codex_handlers import (
65
+ CodexStatusHandler,
66
+ CodexThreadListHandler,
67
+ CodexThreadStartHandler,
68
+ CodexThreadResumeHandler,
69
+ CodexTurnStartHandler,
70
+ CodexTurnInterruptHandler,
71
+ )
64
72
 
65
73
  __all__ = [
66
74
  "BaseHandler",
@@ -112,4 +120,11 @@ __all__ = [
112
120
  "AutomationV2StartHandler",
113
121
  "AutomationV2StateHandler",
114
122
  "AutomationV2CancelHandler",
123
+ # Codex chat handlers
124
+ "CodexStatusHandler",
125
+ "CodexThreadListHandler",
126
+ "CodexThreadStartHandler",
127
+ "CodexThreadResumeHandler",
128
+ "CodexTurnStartHandler",
129
+ "CodexTurnInterruptHandler",
115
130
  ]
@@ -0,0 +1,293 @@
1
+ """Device-side command handlers for the Codex chat UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, Dict, Optional
7
+
8
+ from portacode.connection.codex_app_server import CodexAppServer, CodexAppServerError
9
+ from portacode.connection.handlers.base import AsyncHandler
10
+
11
+ LOGGER = logging.getLogger(__name__)
12
+
13
+
14
+ class _SessionAwareSender:
15
+ """Helper to push events to the browser sessions interested in a project."""
16
+
17
+ def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
18
+ self.control_channel = control_channel
19
+ self.context = context
20
+
21
+ async def send(self, payload: Dict[str, Any], project_id: Optional[str] = None) -> None:
22
+ client_session_manager = self.context.get("client_session_manager")
23
+ if client_session_manager and client_session_manager.has_interested_clients():
24
+ target_sessions = client_session_manager.get_target_sessions(project_id)
25
+ if target_sessions:
26
+ payload = dict(payload)
27
+ payload["client_sessions"] = target_sessions
28
+ reply_channel = client_session_manager.get_reply_channel_for_compatibility()
29
+ if reply_channel:
30
+ payload = dict(payload)
31
+ payload["reply_channel"] = reply_channel
32
+
33
+ await self.control_channel.send(payload)
34
+
35
+
36
+ class CodexChatManager:
37
+ """Owns one CodexAppServer bridge and routes its events to Portacode client sessions."""
38
+
39
+ def __init__(self, control_channel: Any, context: Dict[str, Any]) -> None:
40
+ self.sender = _SessionAwareSender(control_channel, context)
41
+ self.bridge = CodexAppServer(on_notification=self._on_notification)
42
+ self._thread_project: Dict[str, str] = {}
43
+ self._cwd_project: Dict[str, str] = {}
44
+
45
+ async def _on_notification(self, method: str, params: Dict[str, Any]) -> None:
46
+ project_id = self._resolve_project_id(params)
47
+ event_payload = {
48
+ "event": "codex_event",
49
+ "notification": {"method": method, "params": params},
50
+ }
51
+ if project_id:
52
+ event_payload["project_id"] = project_id
53
+ await self.sender.send(event_payload, project_id=project_id)
54
+
55
+ def _resolve_project_id(self, params: Dict[str, Any]) -> Optional[str]:
56
+ # Most notifications contain a threadId. Try that first.
57
+ thread_id = params.get("threadId") or (params.get("thread") or {}).get("id")
58
+ if thread_id and thread_id in self._thread_project:
59
+ return self._thread_project[thread_id]
60
+
61
+ # Some notifications include cwd directly; fall back to the cwd map.
62
+ cwd = params.get("cwd") or (params.get("thread") or {}).get("cwd")
63
+ if cwd and cwd in self._cwd_project:
64
+ return self._cwd_project[cwd]
65
+
66
+ return None
67
+
68
+ def record_thread(self, thread_id: str, cwd: str, project_id: str) -> None:
69
+ self._thread_project[thread_id] = project_id
70
+ self._cwd_project[cwd] = project_id
71
+
72
+ async def ensure_started(self) -> None:
73
+ await self.bridge.start()
74
+
75
+
76
+ # A single manager is stored per device agent context.
77
+ def _get_manager(handler: AsyncHandler) -> CodexChatManager:
78
+ context = handler.context
79
+ if "codex_manager" not in context:
80
+ context["codex_manager"] = CodexChatManager(handler.control_channel, context)
81
+ return context["codex_manager"]
82
+
83
+
84
+ def _project_id(message: Dict[str, Any]) -> Optional[str]:
85
+ return message.get("project_id")
86
+
87
+
88
+ class CodexStatusHandler(AsyncHandler):
89
+ """Report whether Codex is installed and the app-server is healthy."""
90
+
91
+ def command_name(self) -> str:
92
+ return "codex_status"
93
+
94
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
95
+ installed = bool(CodexAppServer.get_binary_path())
96
+ version = CodexAppServer.version()
97
+ ready = False
98
+ error_message: Optional[str] = None
99
+
100
+ if installed:
101
+ try:
102
+ manager = _get_manager(self)
103
+ await manager.ensure_started()
104
+ ready = await manager.bridge.healthy()
105
+ except Exception as exc:
106
+ LOGGER.debug("codex_status: app-server not healthy: %s", exc)
107
+ error_message = str(exc)
108
+
109
+ payload = {
110
+ "event": "codex_status",
111
+ "installed": installed,
112
+ "version": version,
113
+ "ready": ready,
114
+ }
115
+ if error_message:
116
+ payload["error_message"] = error_message
117
+ project_id = _project_id(message)
118
+ if project_id:
119
+ payload["project_id"] = project_id
120
+ return payload
121
+
122
+
123
+ class CodexThreadListHandler(AsyncHandler):
124
+ """List Codex threads for a given project cwd."""
125
+
126
+ def command_name(self) -> str:
127
+ return "codex_thread_list"
128
+
129
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
130
+ cwd = message.get("cwd")
131
+ if not cwd:
132
+ raise ValueError("cwd is required")
133
+
134
+ manager = _get_manager(self)
135
+ await manager.ensure_started()
136
+
137
+ params: Dict[str, Any] = {"cwd": cwd, "useStateDbOnly": True}
138
+ limit = message.get("limit")
139
+ cursor = message.get("cursor")
140
+ if limit is not None:
141
+ params["limit"] = limit
142
+ if cursor is not None:
143
+ params["cursor"] = cursor
144
+
145
+ result = await manager.bridge.call("thread/list", params)
146
+ payload = {
147
+ "event": "codex_thread_list",
148
+ "threads": result.get("threads", []),
149
+ "nextCursor": result.get("nextCursor"),
150
+ }
151
+ project_id = _project_id(message)
152
+ if project_id:
153
+ payload["project_id"] = project_id
154
+ return payload
155
+
156
+
157
+ class CodexThreadStartHandler(AsyncHandler):
158
+ """Start a new Codex thread in the project directory."""
159
+
160
+ def command_name(self) -> str:
161
+ return "codex_thread_start"
162
+
163
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
164
+ cwd = message.get("cwd")
165
+ project_id = _project_id(message)
166
+ if not cwd:
167
+ raise ValueError("cwd is required")
168
+
169
+ manager = _get_manager(self)
170
+ await manager.ensure_started()
171
+
172
+ params = {"cwd": cwd}
173
+ # Optional overrides from the UI (e.g. model picker later).
174
+ if message.get("model"):
175
+ params["model"] = message["model"]
176
+ if message.get("modelProvider"):
177
+ params["modelProvider"] = message["modelProvider"]
178
+
179
+ result = await manager.bridge.call("thread/start", params)
180
+ thread_id = result.get("id") or result.get("threadId")
181
+ if not thread_id:
182
+ raise CodexAppServerError("thread/start did not return a thread id")
183
+
184
+ manager.record_thread(thread_id, cwd, project_id or cwd)
185
+
186
+ payload = {
187
+ "event": "codex_thread_started",
188
+ "threadId": thread_id,
189
+ "thread": result,
190
+ }
191
+ if project_id:
192
+ payload["project_id"] = project_id
193
+ return payload
194
+
195
+
196
+ class CodexThreadResumeHandler(AsyncHandler):
197
+ """Resume a Codex thread and return its history."""
198
+
199
+ def command_name(self) -> str:
200
+ return "codex_thread_resume"
201
+
202
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
203
+ thread_id = message.get("threadId")
204
+ cwd = message.get("cwd")
205
+ project_id = _project_id(message)
206
+ if not thread_id:
207
+ raise ValueError("threadId is required")
208
+
209
+ manager = _get_manager(self)
210
+ await manager.ensure_started()
211
+
212
+ resume_params: Dict[str, Any] = {"threadId": thread_id}
213
+ if cwd:
214
+ resume_params["cwd"] = cwd
215
+ await manager.bridge.call("thread/resume", resume_params)
216
+
217
+ read_result = await manager.bridge.call("thread/read", {"threadId": thread_id})
218
+ items = read_result.get("items", read_result.get("messages", []))
219
+
220
+ if cwd and project_id:
221
+ manager.record_thread(thread_id, cwd, project_id)
222
+
223
+ payload = {
224
+ "event": "codex_thread_resumed",
225
+ "threadId": thread_id,
226
+ "items": items,
227
+ }
228
+ if project_id:
229
+ payload["project_id"] = project_id
230
+ return payload
231
+
232
+
233
+ class CodexTurnStartHandler(AsyncHandler):
234
+ """Send a user turn to a Codex thread."""
235
+
236
+ def command_name(self) -> str:
237
+ return "codex_turn_start"
238
+
239
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
240
+ thread_id = message.get("threadId")
241
+ text = message.get("text")
242
+ if not thread_id:
243
+ raise ValueError("threadId is required")
244
+ if text is None or text == "":
245
+ raise ValueError("text is required")
246
+
247
+ manager = _get_manager(self)
248
+ await manager.ensure_started()
249
+
250
+ params = {
251
+ "threadId": thread_id,
252
+ "input": [{"type": "text", "text": text}],
253
+ }
254
+ # Optional reasoning controls.
255
+ if message.get("effort"):
256
+ params["effort"] = message["effort"]
257
+ if message.get("summary"):
258
+ params["summary"] = message["summary"]
259
+
260
+ result = await manager.bridge.call("turn/start", params)
261
+ project_id = _project_id(message)
262
+
263
+ payload = {
264
+ "event": "codex_turn_started",
265
+ "threadId": thread_id,
266
+ "turn": result,
267
+ }
268
+ if project_id:
269
+ payload["project_id"] = project_id
270
+ return payload
271
+
272
+
273
+ class CodexTurnInterruptHandler(AsyncHandler):
274
+ """Interrupt a running Codex turn."""
275
+
276
+ def command_name(self) -> str:
277
+ return "codex_turn_interrupt"
278
+
279
+ async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
280
+ thread_id = message.get("threadId")
281
+ turn_id = message.get("turnId")
282
+ if not thread_id or not turn_id:
283
+ raise ValueError("threadId and turnId are required")
284
+
285
+ manager = _get_manager(self)
286
+ await manager.ensure_started()
287
+ await manager.bridge.call("turn/interrupt", {"threadId": thread_id, "turnId": turn_id})
288
+
289
+ project_id = _project_id(message)
290
+ payload = {"event": "codex_turn_interrupted", "threadId": thread_id, "turnId": turn_id}
291
+ if project_id:
292
+ payload["project_id"] = project_id
293
+ return payload
@@ -68,6 +68,12 @@ from .handlers import (
68
68
  AutomationV2StartHandler,
69
69
  AutomationV2StateHandler,
70
70
  AutomationV2CancelHandler,
71
+ CodexStatusHandler,
72
+ CodexThreadListHandler,
73
+ CodexThreadStartHandler,
74
+ CodexThreadResumeHandler,
75
+ CodexTurnStartHandler,
76
+ CodexTurnInterruptHandler,
71
77
  )
72
78
  from .handlers.project_aware_file_handlers import (
73
79
  ProjectAwareFileWriteHandler,
@@ -546,6 +552,13 @@ class TerminalManager:
546
552
  self._command_registry.register(AutomationV2StartHandler)
547
553
  self._command_registry.register(AutomationV2StateHandler)
548
554
  self._command_registry.register(AutomationV2CancelHandler)
555
+ # Codex chat handlers
556
+ self._command_registry.register(CodexStatusHandler)
557
+ self._command_registry.register(CodexThreadListHandler)
558
+ self._command_registry.register(CodexThreadStartHandler)
559
+ self._command_registry.register(CodexThreadResumeHandler)
560
+ self._command_registry.register(CodexTurnStartHandler)
561
+ self._command_registry.register(CodexTurnInterruptHandler)
549
562
 
550
563
  # ---------------------------------------------------------------------
551
564
  # Control loop – receives commands from gateway