portacode 1.5.28.dev3__tar.gz → 1.5.29.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.
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/PKG-INFO +1 -1
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/_version.py +2 -2
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -9
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/__init__.py +0 -2
- portacode-1.5.29.dev0/portacode/connection/handlers/browser_handlers.py +287 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/proxmox_infra.py +108 -181
- portacode-1.5.29.dev0/portacode/connection/handlers/test_browser_handlers.py +62 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/test_proxmox_infra.py +73 -1
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/terminal.py +0 -2
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/github_credential.py +17 -1
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/provisioning_cache.py +1 -1
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_github_credential.py +11 -1
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/PKG-INFO +1 -1
- portacode-1.5.28.dev3/portacode/connection/handlers/browser_handlers.py +0 -135
- portacode-1.5.28.dev3/portacode/connection/handlers/test_browser_handlers.py +0 -20
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/.claude/settings.local.json +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/.gitignore +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/.gitmodules +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/AGENTS.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/LICENSE +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/MANIFEST.in +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/Makefile +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/build_android.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/connect.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/connect.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/creative-team-brief-portacode.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/device-health-alerting-plan.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/devops-messaging-ab-tests.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/docs/template-guide.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/simple_device/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/__main__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/cli.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/codex_loopback_proxy.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/codex_prepare.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/codex_usage_limit.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/client.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/codex_app_server.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/codex_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/resumable_transfer_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/test_file_move_copy.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/data.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/exit_codes.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/keypair.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/__pycache__/__init__.cpython-38.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/links +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/logging_categories.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/pairing.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/restart.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/service.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/singleton.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/test_runtime_paths.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/test_service.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/test_singleton.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/test_terminal_exposed_services.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_codex_app_server.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_codex_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_codex_loopback_proxy.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_codex_prepare.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_image_read_handler.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_resumable_transfer_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tests/test_system_handlers.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/forwarding_state.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/get_domain.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/privileged.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/service_install.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/tunneling/state.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/updater.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/utils/runtime_paths.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/.gitignore +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/pyproject.toml +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/run_tests.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/scripts/diagnose_codex_gateway.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/setup.cfg +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/setup.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_device_online.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_file_operations.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_login_flow.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_request_id.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/test_session_terminal_shutdown.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/.env.example +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/README.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/cli.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/__init__.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/base_test.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/runner.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/testing_framework/requirements.txt +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/agent_context_management.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/django_server_time_sync.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/tools/pairing_tester.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/validate.sh +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 5,
|
|
21
|
+
__version__ = version = '1.5.29.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 29, 'dev0')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{portacode-1.5.28.dev3 → portacode-1.5.29.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -449,15 +449,6 @@ Creates a Portacode-managed LXC container, starts it, and bootstraps the Portaco
|
|
|
449
449
|
* On success, the device will emit a [`proxmox_container_created`](#proxmox_container_created-event) event that includes the Portacode auth key produced inside the container.
|
|
450
450
|
* On failure, the device will emit an [`error`](#error) event.
|
|
451
451
|
|
|
452
|
-
### `proxmox_resource_audit`
|
|
453
|
-
|
|
454
|
-
Returns a credential-free live inventory for staff infrastructure reconciliation. The response includes node and storage capacity, every LXC/QEMU resource, Portacode description markers, managed Device IDs, cache-template generation/source metadata, linked-clone parent and dependent VMIDs, local records whose Proxmox guests are missing, deletion tombstones, runtime state, and consistency warnings. API tokens and device key material are never included.
|
|
455
|
-
|
|
456
|
-
**Responses:**
|
|
457
|
-
|
|
458
|
-
* On success, the device emits `proxmox_resource_audit` with `resources`, `missing_resources`, `storages`, and host capacity data.
|
|
459
|
-
* On failure, the device emits an [`error`](#error) event.
|
|
460
|
-
|
|
461
452
|
### `start_proxmox_container`
|
|
462
453
|
|
|
463
454
|
Starts a previously provisioned, Portacode-managed LXC container. Handled by [`StartProxmoxContainerHandler`](./proxmox_infra.py).
|
|
@@ -61,7 +61,6 @@ from .proxmox_infra import (
|
|
|
61
61
|
StartProxmoxContainerHandler,
|
|
62
62
|
StopProxmoxContainerHandler,
|
|
63
63
|
RemoveProxmoxContainerHandler,
|
|
64
|
-
ProxmoxResourceAuditHandler,
|
|
65
64
|
)
|
|
66
65
|
from .cloudflare_tunnel import CloudflareTunnelSetupHandler
|
|
67
66
|
from .cloudflare_forwarding import (
|
|
@@ -134,7 +133,6 @@ __all__ = [
|
|
|
134
133
|
"StartProxmoxContainerHandler",
|
|
135
134
|
"StopProxmoxContainerHandler",
|
|
136
135
|
"RemoveProxmoxContainerHandler",
|
|
137
|
-
"ProxmoxResourceAuditHandler",
|
|
138
136
|
"UpdatePortacodeHandler",
|
|
139
137
|
"RevertProxmoxInfraHandler",
|
|
140
138
|
"CloudflareTunnelSetupHandler",
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Bounded Playwright browser automation for Dashboard AI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
from .base import AsyncHandler
|
|
15
|
+
from .runtime_user import get_default_runtime_user, get_runtime_user_home, mkdir_with_owner, wrap_argv_for_user
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$")
|
|
19
|
+
_WORKERS: dict[tuple[str, str], "_BrowserWorker"] = {}
|
|
20
|
+
_WORKERS_LOCK: asyncio.Lock | None = None
|
|
21
|
+
_NODE_PATH: str | None = None
|
|
22
|
+
_NODE_PATH_LOCK: asyncio.Lock | None = None
|
|
23
|
+
_WORKER_IDLE_SECONDS = 300.0
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
_RUNNER = r"""
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const readline = require('readline');
|
|
28
|
+
const { chromium } = require('playwright');
|
|
29
|
+
const locator = (p,s) => {
|
|
30
|
+
let l;
|
|
31
|
+
if (s.role) l=p.getByRole(s.role, s.name === undefined ? {} : {name:s.name, exact:!!s.exact});
|
|
32
|
+
else if (s.label) l=p.getByLabel(s.label, {exact:!!s.exact});
|
|
33
|
+
else if (s.text) l=p.getByText(s.text, {exact:!!s.exact});
|
|
34
|
+
else if (s.testid) l=p.getByTestId(s.testid);
|
|
35
|
+
else l=p.locator(s.css || s.selector);
|
|
36
|
+
return s.nth === undefined ? l : l.nth(s.nth);
|
|
37
|
+
};
|
|
38
|
+
let context=null, page=null;
|
|
39
|
+
const closeContext = async () => {
|
|
40
|
+
if(context) await context.close().catch(()=>{});
|
|
41
|
+
context=null; page=null;
|
|
42
|
+
};
|
|
43
|
+
const ensureContext = async cfg => {
|
|
44
|
+
if(context && cfg.video_dir) await closeContext();
|
|
45
|
+
if(!context) {
|
|
46
|
+
const launch = {headless:true, viewport:cfg.viewport};
|
|
47
|
+
if (cfg.executable_path) launch.executablePath = cfg.executable_path;
|
|
48
|
+
if (cfg.video_dir) launch.recordVideo = {dir:cfg.video_dir, size:cfg.video_size};
|
|
49
|
+
context = await chromium.launchPersistentContext(cfg.profile_dir, launch);
|
|
50
|
+
const pages=context.pages(); page=pages[0] || await context.newPage();
|
|
51
|
+
context.on('close',()=>{context=null;page=null;});
|
|
52
|
+
}
|
|
53
|
+
if(!page || page.isClosed()) {const pages=context.pages();page=pages[0] || await context.newPage();}
|
|
54
|
+
await page.setViewportSize(cfg.viewport);
|
|
55
|
+
return page;
|
|
56
|
+
};
|
|
57
|
+
const run = async cfg => {
|
|
58
|
+
const out = {ok:false, session_id:cfg.session_id, steps:[], screenshots:[]};
|
|
59
|
+
let video;
|
|
60
|
+
try {
|
|
61
|
+
await ensureContext(cfg);
|
|
62
|
+
video=page.video(); const started=Date.now();
|
|
63
|
+
page.setDefaultTimeout(cfg.timeout_ms);
|
|
64
|
+
if (cfg.start_url) await page.goto(cfg.start_url, {waitUntil:'domcontentloaded'});
|
|
65
|
+
for (let i=0;i<cfg.steps.length;i++) {
|
|
66
|
+
if(cfg.deadline_ms && Date.now()-started > cfg.deadline_ms) throw Error('recording duration limit reached');
|
|
67
|
+
const s=cfg.steps[i], op=s.op; let value=null;
|
|
68
|
+
try {
|
|
69
|
+
const l = ['click','fill','press','select','check','uncheck','assert','read'].includes(op) ? locator(page,s) : null;
|
|
70
|
+
if(op==='goto') await page.goto(s.url,{waitUntil:s.wait_until||'domcontentloaded'});
|
|
71
|
+
else if(op==='click') await l.click();
|
|
72
|
+
else if(op==='fill') await l.fill(String(s.value??''));
|
|
73
|
+
else if(op==='press') await l.press(s.key);
|
|
74
|
+
else if(op==='select') value=await l.selectOption(s.value);
|
|
75
|
+
else if(op==='check') await l.check();
|
|
76
|
+
else if(op==='uncheck') await l.uncheck();
|
|
77
|
+
else if(op==='wait') s.ms ? await page.waitForTimeout(s.ms) : await page.waitForLoadState(s.state||'domcontentloaded');
|
|
78
|
+
else if(op==='assert') { const kind=s.kind||'visible'; if(kind==='visible') await l.waitFor({state:'visible'}); else if(kind==='hidden') await l.waitFor({state:'hidden'}); else if(kind==='text'){const got=await l.textContent();if(!String(got||'').includes(String(s.value)))throw Error(`expected text ${s.value}`); } else if(kind==='url'){if(!page.url().includes(String(s.value)))throw Error(`expected URL ${s.value}`);} else throw Error('unsupported assertion'); }
|
|
79
|
+
else if(op==='read') { if(s.attribute) value=await l.getAttribute(s.attribute); else if(s.kind==='value') value=await l.inputValue(); else if(s.kind==='count') value=await l.count(); else value=(await l.innerText()).slice(0,8000); }
|
|
80
|
+
else if(op==='screenshot') { const b=await page.screenshot({type:'jpeg',quality:cfg.image_quality,fullPage:!!s.full_page}); out.screenshots.push({step:i,data:b.toString('base64'),mime_type:'image/jpeg'}); }
|
|
81
|
+
else throw Error(`unsupported operation: ${op}`);
|
|
82
|
+
out.steps.push({i,op,ok:true,...(value===null?{}:{value})});
|
|
83
|
+
} catch(e) { out.steps.push({i,op,ok:false,error:String(e.message||e).slice(0,500)}); if(cfg.screenshot_on_failure){const b=await page.screenshot({type:'jpeg',quality:cfg.image_quality});out.screenshots.push({step:i,data:b.toString('base64'),mime_type:'image/jpeg',failure:true});} throw e; }
|
|
84
|
+
}
|
|
85
|
+
if(cfg.final_screenshot){const b=await page.screenshot({type:'jpeg',quality:cfg.image_quality});out.screenshots.push({step:'final',data:b.toString('base64'),mime_type:'image/jpeg'});}
|
|
86
|
+
out.ok=true; out.url=page.url(); out.title=await page.title();
|
|
87
|
+
} catch(e) {out.error=String(e.message||e).slice(0,1000);}
|
|
88
|
+
finally {
|
|
89
|
+
if(cfg.video_dir) await closeContext();
|
|
90
|
+
if(video && cfg.video_dir){try{out.video_path=await video.path();}catch{}}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
};
|
|
94
|
+
const rl=readline.createInterface({input:process.stdin,crlfDelay:Infinity});
|
|
95
|
+
rl.on('line', line => {
|
|
96
|
+
rl.pause();
|
|
97
|
+
(async()=>run(JSON.parse(line)))()
|
|
98
|
+
.then(out=>process.stdout.write(JSON.stringify(out)+'\n'))
|
|
99
|
+
.catch(e=>process.stdout.write(JSON.stringify({ok:false,error:String(e)})+'\n'))
|
|
100
|
+
.finally(()=>rl.resume());
|
|
101
|
+
});
|
|
102
|
+
rl.on('close', async()=>{await closeContext(); process.exit(0);});
|
|
103
|
+
for(const signal of ['SIGTERM','SIGINT']) process.on(signal,async()=>{await closeContext();process.exit(0);});
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _loop_lock(current: asyncio.Lock | None) -> asyncio.Lock:
|
|
108
|
+
"""Return a lock belonging to the active event loop (tests create many loops)."""
|
|
109
|
+
if current is None or getattr(current, "_loop", None) not in (None, asyncio.get_running_loop()):
|
|
110
|
+
return asyncio.Lock()
|
|
111
|
+
return current
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def _node_path() -> str:
|
|
115
|
+
global _NODE_PATH, _NODE_PATH_LOCK
|
|
116
|
+
if _NODE_PATH is not None:
|
|
117
|
+
return _NODE_PATH
|
|
118
|
+
_NODE_PATH_LOCK = _loop_lock(_NODE_PATH_LOCK)
|
|
119
|
+
async with _NODE_PATH_LOCK:
|
|
120
|
+
if _NODE_PATH is None:
|
|
121
|
+
npm = await asyncio.create_subprocess_exec(
|
|
122
|
+
"npm", "root", "-g", stdout=asyncio.subprocess.PIPE,
|
|
123
|
+
stderr=asyncio.subprocess.PIPE,
|
|
124
|
+
)
|
|
125
|
+
npm_out, npm_err = await npm.communicate()
|
|
126
|
+
if npm.returncode:
|
|
127
|
+
raise RuntimeError(
|
|
128
|
+
f"Unable to locate global Node modules: {npm_err.decode(errors='replace')[:300]}"
|
|
129
|
+
)
|
|
130
|
+
_NODE_PATH = os.pathsep.join(filter(None, [
|
|
131
|
+
"/opt/portacode-playwright/node_modules", npm_out.decode().strip(),
|
|
132
|
+
]))
|
|
133
|
+
return _NODE_PATH
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class _BrowserWorker:
|
|
137
|
+
def __init__(self, *, user: str, session_id: str, argv: list[str], env: dict[str, str]):
|
|
138
|
+
self.user = user
|
|
139
|
+
self.session_id = session_id
|
|
140
|
+
self.argv = argv
|
|
141
|
+
self.env = env
|
|
142
|
+
self.process: asyncio.subprocess.Process | None = None
|
|
143
|
+
self.stderr_task: asyncio.Task | None = None
|
|
144
|
+
self.stderr_tail = bytearray()
|
|
145
|
+
self.lock = asyncio.Lock()
|
|
146
|
+
self.idle_task: asyncio.Task | None = None
|
|
147
|
+
self.last_used = time.monotonic()
|
|
148
|
+
|
|
149
|
+
async def _start(self) -> None:
|
|
150
|
+
if self.process and self.process.returncode is None:
|
|
151
|
+
return
|
|
152
|
+
self.process = await asyncio.create_subprocess_exec(
|
|
153
|
+
*self.argv, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
|
|
154
|
+
stderr=asyncio.subprocess.PIPE, env=self.env,
|
|
155
|
+
)
|
|
156
|
+
self.stderr_task = asyncio.create_task(self._drain_stderr(self.process))
|
|
157
|
+
|
|
158
|
+
async def _drain_stderr(self, process: asyncio.subprocess.Process) -> None:
|
|
159
|
+
if not process.stderr:
|
|
160
|
+
return
|
|
161
|
+
try:
|
|
162
|
+
while chunk := await process.stderr.read(4096):
|
|
163
|
+
self.stderr_tail.extend(chunk)
|
|
164
|
+
del self.stderr_tail[:-65536]
|
|
165
|
+
except (asyncio.CancelledError, ValueError):
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def _schedule_idle_close(self) -> None:
|
|
169
|
+
if self.idle_task:
|
|
170
|
+
self.idle_task.cancel()
|
|
171
|
+
self.last_used = time.monotonic()
|
|
172
|
+
self.idle_task = asyncio.create_task(self._close_when_idle())
|
|
173
|
+
|
|
174
|
+
async def _close_when_idle(self) -> None:
|
|
175
|
+
try:
|
|
176
|
+
await asyncio.sleep(_WORKER_IDLE_SECONDS)
|
|
177
|
+
async with self.lock:
|
|
178
|
+
if time.monotonic() - self.last_used >= _WORKER_IDLE_SECONDS:
|
|
179
|
+
await self._stop_locked()
|
|
180
|
+
_WORKERS.pop((self.user, self.session_id), None)
|
|
181
|
+
except asyncio.CancelledError:
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
async def run(self, config: dict[str, Any], timeout: float) -> dict[str, Any]:
|
|
185
|
+
async with self.lock:
|
|
186
|
+
await self._start()
|
|
187
|
+
assert self.process and self.process.stdin and self.process.stdout
|
|
188
|
+
try:
|
|
189
|
+
self.process.stdin.write((json.dumps(config) + "\n").encode())
|
|
190
|
+
await self.process.stdin.drain()
|
|
191
|
+
line = await asyncio.wait_for(self.process.stdout.readline(), timeout=timeout)
|
|
192
|
+
if not line:
|
|
193
|
+
detail = bytes(self.stderr_tail).decode(errors="replace")[-500:]
|
|
194
|
+
await self._stop_locked()
|
|
195
|
+
raise RuntimeError(
|
|
196
|
+
f"Playwright worker exited unexpectedly: {detail}"
|
|
197
|
+
)
|
|
198
|
+
result = json.loads(line)
|
|
199
|
+
except (asyncio.TimeoutError, BrokenPipeError, json.JSONDecodeError) as exc:
|
|
200
|
+
await self._stop_locked()
|
|
201
|
+
if isinstance(exc, asyncio.TimeoutError):
|
|
202
|
+
raise RuntimeError("Browser run exceeded its bounded execution time") from exc
|
|
203
|
+
raise RuntimeError(f"Playwright worker failed: {exc}") from exc
|
|
204
|
+
self._schedule_idle_close()
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
async def _stop_locked(self) -> None:
|
|
208
|
+
process, self.process = self.process, None
|
|
209
|
+
stderr_task, self.stderr_task = self.stderr_task, None
|
|
210
|
+
if not process or process.returncode is not None:
|
|
211
|
+
if stderr_task:
|
|
212
|
+
await stderr_task
|
|
213
|
+
return
|
|
214
|
+
if process.stdin:
|
|
215
|
+
process.stdin.close()
|
|
216
|
+
try:
|
|
217
|
+
await asyncio.wait_for(process.wait(), timeout=5)
|
|
218
|
+
except asyncio.TimeoutError:
|
|
219
|
+
process.kill()
|
|
220
|
+
await process.wait()
|
|
221
|
+
if stderr_task:
|
|
222
|
+
await stderr_task
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def _worker_for(*, user: str, session_id: str, argv: list[str], env: dict[str, str]) -> _BrowserWorker:
|
|
226
|
+
global _WORKERS_LOCK
|
|
227
|
+
_WORKERS_LOCK = _loop_lock(_WORKERS_LOCK)
|
|
228
|
+
async with _WORKERS_LOCK:
|
|
229
|
+
key = (user, session_id)
|
|
230
|
+
worker = _WORKERS.get(key)
|
|
231
|
+
if worker is None or worker.process and worker.process.returncode is not None:
|
|
232
|
+
worker = _BrowserWorker(user=user, session_id=session_id, argv=argv, env=env)
|
|
233
|
+
_WORKERS[key] = worker
|
|
234
|
+
return worker
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class BrowserRunHandler(AsyncHandler):
|
|
238
|
+
@property
|
|
239
|
+
def command_name(self) -> str:
|
|
240
|
+
return "browser_run"
|
|
241
|
+
|
|
242
|
+
async def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
243
|
+
session_id = str(message.get("session_id") or "default")
|
|
244
|
+
if not _SAFE_ID.fullmatch(session_id):
|
|
245
|
+
raise ValueError("session_id must contain only letters, numbers, dot, dash, or underscore")
|
|
246
|
+
steps = message.get("steps") or []
|
|
247
|
+
if not isinstance(steps, list) or len(steps) > 30:
|
|
248
|
+
raise ValueError("steps must be an array containing at most 30 actions")
|
|
249
|
+
encoded = json.dumps(steps)
|
|
250
|
+
if len(encoded) > 64_000:
|
|
251
|
+
raise ValueError("browser action payload is too large")
|
|
252
|
+
if sum(1 for step in steps if isinstance(step, dict) and step.get("op") == "screenshot") > 4:
|
|
253
|
+
raise ValueError("at most four explicit screenshots are allowed per run")
|
|
254
|
+
timeout_ms = min(max(int(message.get("timeout_ms") or 10_000), 250), 30_000)
|
|
255
|
+
width = min(max(int((message.get("viewport") or {}).get("width", 960)), 320), 1920)
|
|
256
|
+
height = min(max(int((message.get("viewport") or {}).get("height", 540)), 240), 1080)
|
|
257
|
+
user = get_default_runtime_user(message)
|
|
258
|
+
root = Path(get_runtime_user_home(message)) / ".local" / "share" / "portacode" / "browser"
|
|
259
|
+
profile = root / "profiles" / session_id
|
|
260
|
+
video_dir = root / "recordings" / session_id if message.get("record_video") else None
|
|
261
|
+
if not profile.exists():
|
|
262
|
+
mkdir_with_owner(profile, user)
|
|
263
|
+
if video_dir:
|
|
264
|
+
if not video_dir.exists():
|
|
265
|
+
mkdir_with_owner(video_dir, user)
|
|
266
|
+
executable = "/usr/bin/chromium-browser" if Path("/usr/bin/chromium-browser").exists() else None
|
|
267
|
+
config = {
|
|
268
|
+
"session_id": session_id, "profile_dir": str(profile), "video_dir": str(video_dir) if video_dir else None,
|
|
269
|
+
"video_size": {"width": min(width, 854), "height": min(height, 480)},
|
|
270
|
+
"viewport": {"width": width, "height": height}, "executable_path": executable,
|
|
271
|
+
"start_url": message.get("start_url"), "steps": steps, "timeout_ms": timeout_ms,
|
|
272
|
+
"deadline_ms": 45_000 if video_dir else None,
|
|
273
|
+
"image_quality": min(max(int(message.get("image_quality") or 65), 30), 85),
|
|
274
|
+
"final_screenshot": bool(message.get("final_screenshot", True)),
|
|
275
|
+
"screenshot_on_failure": bool(message.get("screenshot_on_failure", True)),
|
|
276
|
+
}
|
|
277
|
+
env = dict(os.environ)
|
|
278
|
+
env["NODE_PATH"] = await _node_path()
|
|
279
|
+
env.setdefault("PLAYWRIGHT_BROWSERS_PATH", "/opt/ms-playwright")
|
|
280
|
+
argv = wrap_argv_for_user(["node", "-e", _RUNNER], user, login=False)
|
|
281
|
+
worker = await _worker_for(
|
|
282
|
+
user=user, session_id=session_id, argv=argv, env=env,
|
|
283
|
+
)
|
|
284
|
+
run_limit = min(75 if video_dir else 300, 20 + len(steps) * timeout_ms / 1000)
|
|
285
|
+
result = await worker.run(config, timeout=run_limit)
|
|
286
|
+
result.update({"event": "browser_run_response", "success": True})
|
|
287
|
+
return result
|