painapple-code 1.0.2__tar.gz → 1.0.4__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.
- {painapple_code-1.0.2/src/painapple_code.egg-info → painapple_code-1.0.4}/PKG-INFO +1 -1
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-docker.md +22 -2
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/_version.py +3 -3
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/container.py +121 -2
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/launch.py +31 -8
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/lifecycle_cmd.py +14 -1
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/netinfo.py +44 -3
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/serve_args.py +6 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/setup_cmd.py +23 -3
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/paths.py +24 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_session_welcome.py +33 -1
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/server.py +44 -7
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-file-preview-widget.css +17 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4/src/painapple_code.egg-info}/PKG-INFO +1 -1
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/scm_file_list.json +2 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/scm_version.json +2 -2
- {painapple_code-1.0.2 → painapple_code-1.0.4}/.devcontainer/devcontainer.json +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/.dockerignore +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/.gitignore +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/CHANGELOG.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/CONTRIBUTING.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/Dockerfile +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/LICENSE +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/MANIFEST.in +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/README.dockerhub.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/README.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/SECURITY.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/THIRD_PARTY_NOTICES.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/build-frontend.sh +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docker-compose.yml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docker-entrypoint.sh +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/annotate-editor.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/browser-widget.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/comments-stash.gif +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/cost-analytics.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/favicon.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/input-autocomplete.gif +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/logo.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/markdown-quick-edit.gif +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/overview.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/prompt-history.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/quick-switcher.gif +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/quick-switcher.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/settings-appearance.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/settings-claude.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/shadow-journal.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/snippets-picker.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/terminal.gif +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/turn-summary-bar-expanded.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/turn-summary-bar.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/welcome-screen.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/zen-mode.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/features.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/first-run.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-devcontainer.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-pip.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/requirements.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/security.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/analytics-and-history.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/claude-artifacts.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/customization.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/files.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/git-and-diffs.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/images-and-annotation.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/input-and-commands.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/ipad-and-mobile.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/permissions-and-thinking.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/providers.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/sessions.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/shadow-git.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/stash-and-discussions.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/terminal.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/welcome-screen.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/zen-mode.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/index.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/api.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/commands.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/data-storage.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/keyboard-shortcuts.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/optional-helpers.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/profiles.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/server-cli.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/mkdocs.yml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/painapple-docker.sh +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/pyproject.toml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/requirements-docs.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/requirements.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/setup.cfg +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/setup.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/__main__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/auth_middleware.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/claude_seed.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/config.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/runtime.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/list_cmd.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/manage_cmd.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/profiles.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/serve_config.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/ui.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cost_analytics.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/models.defaults.yaml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/models.yaml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/presets.defaults.json +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/strings.yaml +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/helpers.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/prompt_explorer.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/base.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/capabilities.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/errors.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/launch.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/summary.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/translate.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude_sdk/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude_sdk/driver.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/capabilities.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/errors.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/translate.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/capabilities.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/launch.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/summary.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/translate.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/transport.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_agents.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_commit_sections.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_config.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_session_prefs.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_browser.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_commands.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_costs.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_drafts.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_exec.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_files.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_git.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_logs.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_plugins.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_project_config.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_prompts.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_providers.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_search.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_session_stash.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_sessions.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_db.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_files.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_search.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_skills.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_tasks.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_terminal.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_upload.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_viewer.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/dependencies.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/ws_chat.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/server_logging.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/services/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/services/agent_session.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/session_store.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/session_store_core.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_plans.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_queries.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_schema.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_frontmatter.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_sections.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_summary.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_parser.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/00-variables.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/01-base.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/02-tooltips.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/09-left-rail.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/10-header.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/11-connection-bar.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/20-messages.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/21-tool-blocks.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/22-edit-diff.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/23-thinking.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/24-background-tasks.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/25-context-block.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/26-session-setup.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/30-markdown.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/40-input.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/41-autocomplete.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/42-activity-strip.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/43-file-autocomplete.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/44-snippets-autocomplete.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/45-selection-bar.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/46-keyboard-bar.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/47-keyboard-longpress.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/47-token-profile.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/50-welcome.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/51-modals.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/52-utilities.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/53-image-upload.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/54-todo-list.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/55-file-upload.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-chat-navigator.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-permission-card.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-plan-approval.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-question-form.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/57-swipe-indicator.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/58-session-families.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/59-about.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/61-log-explorer.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/62-json-tree.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/62-skills-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-commands-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-plugins-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-terminal.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/64-search-files-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/65-snippets-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-active-sessions.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-agents-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-file-explorer-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-git-panel.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-history-explorer.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-diff-viewer.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-editor-view.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-browser-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-compare-wizard.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-csv-preview.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-diff-preview.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-helpers-install-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-image-annotate.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-terminal-view.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-uploads-widget.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/69-cost-analytics.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-config-panel.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-excalidraw.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-prompt-explorer.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/71-chart.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/72-discussion-sidebar.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/74-debug-logs.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-open-dialog.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-quick-switcher.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-widget-system.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/81-quick-actions.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/82-context-popover.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/83-context-menu.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/85-lazy-loading.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/86-zen-mode.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/87-grid-switcher.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/login.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/dist/app.bundle.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/feature-triage.html +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/apple-touch-icon.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/favicon-32.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/favicon.ico +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-128.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-144.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-152.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-180.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-192.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-384.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-512.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-72.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-96.png +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/action-delegate.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/activity-strip.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/context-menus.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/file-preview.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/init-ui.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/input-events.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/input-resize.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/message-actions.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/panels.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/rendering-delegators.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/server-loading.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/session-ops.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/session-switch.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/settings-helpers.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/startup-welcome.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app-context.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/auth-fetch.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/background-tasks.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/boot.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/caret-position.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/chat-navigator.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/chat-search.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/command-executor.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/command-store.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/components.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/config.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/context-menu.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/chat-controller.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/dialog-controller.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/tab-controller.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/thinking-controller.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/diff-utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/drafts-pill.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/editor-view.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/effort-settings.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/feature-triage.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/file-autocomplete.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/file-tabs.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/gestures.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/grid-switcher.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/input-handler.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/keyboard-bar.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/layout-switcher.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/linkify-utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/login.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/open-dialog.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/orphan-terminals.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/path-utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/perf-marks.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/permission-settings.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/pointer-input.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree-menu.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree-toolbar.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-breadcrumb.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-edit.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-events.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-history.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-inline-edit.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-poll.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-render.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-search.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-state.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/chart-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/csv-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/diff-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/excalidraw-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/html-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/image-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/json-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/jsonl-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/markdown-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/panzoom-plugin.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/plugin-helpers.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/project-color-picker.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/project-colors.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/prompt-favorites.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-actions-menu.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-actions-registry.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/controller.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/fuzzy-scorer.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/base-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/command-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/file-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/panel-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/project-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/read-files-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/skills-provider.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/registry.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/ui/item.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/ui/picker.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/recent-opens.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/scroll-manager.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/scroll-state-machine.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/action-bar.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/selection-handler.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/state.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/agent-progress.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/handle-agent-message.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/handle-message.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/interactive.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/message-store.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/messages.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/persistence.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/restore.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/sync.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session-container-pool.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session-setup-panel.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/shortcut-hints.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/shortcuts.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/skills-autocomplete.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/snippets-autocomplete.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash-refs-view.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash-ui.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/status-bar.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/token-profile.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer-blocks.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer-thinking.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tooltips.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/upload-manager.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/api.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/cards.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/context-menu.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/families.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/preview.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/state.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/base-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/device-manager.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/event-bus.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/icons.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/init.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/bottom-sheet.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/floating.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/modal.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/sidebar.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/tab.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/top-sheet.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/widget-manager.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/about-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/active-sessions-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/agents-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/browser-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/commands-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/commit-sections.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/dir-autocomplete.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/gestures.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/models-tab.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/quick-actions-tab.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/shortcut-editor.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/state.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/system-controls.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/cost-analytics-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/debug-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/diff-viewer-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/discussion-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/file-explorer-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/file-preview-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/git-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/helpers-install-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/history-explorer-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/image-annotate-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/image-preview-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/index.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/log-explorer-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/plugins-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/prompt-explorer-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/search-files-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/skills-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/snippets-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/sub-agents-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/tasks-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/connection.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/gestures.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/init.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/link-providers.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/osc52.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/render.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/size.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/state.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/wrap-utils.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/uploads-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/zen-widget.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/login.html +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/manifest.json +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/sw.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/LICENSES.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/README.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/codemirror.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/codemirror.js.LEGAL.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/github-markdown-dark.min.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-github-dark.min.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-dockerfile.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-nginx.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-properties.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-scala.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/marked.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/purify.min.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm-addon-fit.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm.css +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/web-client.html +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/subprocess_registry.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tls_cert.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/agents/shadow-git-helper.md +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/excalidraw-to-svg.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/install-helpers.sh +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/shadow-git +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/shadow-query +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/vegalite-to-svg.js +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/turn_query.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/turn_tracker.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/__init__.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/agent_cli.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/chart.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/excalidraw.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/file_lock.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/file_paths.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/generate_icons.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/proc.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_backend.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_posix.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_windows.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/token_profiles.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/viewer_templates.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/welcome_search.py +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/SOURCES.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/dependency_links.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/entry_points.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/requires.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/top_level.txt +0 -0
- {painapple_code-1.0.2 → painapple_code-1.0.4}/start.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: painapple-code
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Self-hosted remote UI for Claude Code that journals every turn - background AI summaries become shadow-git commits and a queryable DuckDB history
|
|
5
5
|
Author-email: Michał Booth-Wrotkowski <michal@boothw.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -35,6 +35,9 @@ Docker and Podman are auto-detected (pick one — or a custom binary path — in
|
|
|
35
35
|
## Option B — raw `docker run` (one-liner, no clone needed)
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
+
# Podman won't create a missing bind-mount source (Docker will) — make it first:
|
|
39
|
+
mkdir -p "$HOME/.painapple-code/.claude"
|
|
40
|
+
|
|
38
41
|
docker run -d --name painapple-code \
|
|
39
42
|
-p 127.0.0.1:8765:8765 \
|
|
40
43
|
-v "$PWD:/workspace" \
|
|
@@ -44,10 +47,27 @@ docker run -d --name painapple-code \
|
|
|
44
47
|
# Bootstrap URL — the container hides credentials from its own logs, so
|
|
45
48
|
# read the derived API token from its config instead (?tkn= links never
|
|
46
49
|
# carry the password):
|
|
47
|
-
echo "
|
|
50
|
+
echo "https://localhost:8765/?tkn=$(docker exec painapple-code \
|
|
48
51
|
awk '/^api_token:/ {print $2}' /home/app/.config/painapple-code/config.yaml)"
|
|
49
52
|
```
|
|
50
53
|
|
|
54
|
+
!!! note "Why `https://` here, but `http://` with `painapple --in-docker`"
|
|
55
|
+
|
|
56
|
+
The server inside the container always binds `0.0.0.0` — it has to, or
|
|
57
|
+
the published port would have nothing to forward to. So it cannot tell
|
|
58
|
+
whether you published on loopback or on the LAN, and `--tls auto`
|
|
59
|
+
resolves to **on**: you get HTTPS with a self-signed certificate and a
|
|
60
|
+
one-time browser warning. That is the safe default for a command whose
|
|
61
|
+
reachability is unknown.
|
|
62
|
+
|
|
63
|
+
`painapple --in-docker` knows the publish interface, so it resolves TLS
|
|
64
|
+
on the host side and turns it off for a loopback-only run.
|
|
65
|
+
|
|
66
|
+
You *can* append `--tls off` to the `docker run` command for plain HTTP
|
|
67
|
+
on a loopback publish — but then remember to remove it if you ever widen
|
|
68
|
+
`-p 127.0.0.1:…` to another interface, or the token and chat traffic go
|
|
69
|
+
out unencrypted.
|
|
70
|
+
|
|
51
71
|
**Image tags:**
|
|
52
72
|
|
|
53
73
|
| Tag | Meaning |
|
|
@@ -84,7 +104,7 @@ To layer your own tooling on top of the base image:
|
|
|
84
104
|
./painapple-docker.sh build --dockerfile ~/my-project/Dockerfile
|
|
85
105
|
```
|
|
86
106
|
|
|
87
|
-
Open `
|
|
107
|
+
Open `https://localhost:8765/` in a browser (self-signed certificate — accept the one-time warning). The first run generates an auth password — the container keeps it out of `docker logs` (they persist), so reveal the bootstrap URL with `painapple password` (pip CLI) or the `docker exec … awk` one-liner above, and open it once; the cookie keeps you logged in. See [First run & login](first-run.md).
|
|
88
108
|
|
|
89
109
|
## Manual Compose / Podman (no wrapper)
|
|
90
110
|
|
|
@@ -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.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g682c211'
|
|
@@ -14,6 +14,7 @@ import threading
|
|
|
14
14
|
import time
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
+
from painapple_code import __version__
|
|
17
18
|
from painapple_code.cli.netinfo import detect_local_ips
|
|
18
19
|
from painapple_code.cli.ui import (
|
|
19
20
|
BOLD, DIM, GREEN, RESET, die, err, info, ok, print_credentials, say, warn,
|
|
@@ -136,6 +137,70 @@ def print_bootstrap_url(cfg, pw, profile=None, raw_tty=False, token=None):
|
|
|
136
137
|
sys.stdout.flush()
|
|
137
138
|
|
|
138
139
|
|
|
140
|
+
def suggest_profile_name(workspace):
|
|
141
|
+
"""A profile name suggestion derived from the workspace dir, or ''.
|
|
142
|
+
|
|
143
|
+
Sanitized to profiles.valid_name()'s alphabet so the printed command
|
|
144
|
+
actually runs, and skipped when the name is already taken (a hint
|
|
145
|
+
that would reconfigure someone's existing deployment is worse than
|
|
146
|
+
no hint) or reserved ('default').
|
|
147
|
+
"""
|
|
148
|
+
import re
|
|
149
|
+
from painapple_code.cli import profiles
|
|
150
|
+
|
|
151
|
+
base = Path(workspace or "").name.lower()
|
|
152
|
+
# Strip separators from BOTH ends, and again after truncation — a cut
|
|
153
|
+
# at 32 chars can leave a trailing dash of its own.
|
|
154
|
+
name = re.sub(r"[^a-z0-9._-]", "-", base).strip("-._")[:32].strip("-._")
|
|
155
|
+
if not name or not profiles.valid_name(name):
|
|
156
|
+
return ""
|
|
157
|
+
return "" if name in profiles.list_profiles() else name
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ──── image version ──────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
def _release_tuple(version):
|
|
163
|
+
"""(major, minor, patch) from a version string, pre/dev/local suffixes
|
|
164
|
+
dropped — "1.0.0rc1" → (1, 0, 0). None when it can't be parsed."""
|
|
165
|
+
if not version:
|
|
166
|
+
return None
|
|
167
|
+
parts = []
|
|
168
|
+
for chunk in version.lstrip("vV").split("+")[0].split(".")[:3]:
|
|
169
|
+
digits = ""
|
|
170
|
+
for ch in chunk:
|
|
171
|
+
if not ch.isdigit():
|
|
172
|
+
break
|
|
173
|
+
digits += ch
|
|
174
|
+
if not digits:
|
|
175
|
+
break
|
|
176
|
+
parts.append(int(digits))
|
|
177
|
+
return tuple(parts) if len(parts) == 3 else None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _is_prerelease(version):
|
|
181
|
+
core = (version or "").lstrip("vV").split("+")[0]
|
|
182
|
+
return any(marker in core for marker in ("rc", "a", "b", "dev"))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _image_is_stale(img_version, cli=None):
|
|
186
|
+
"""Is the local image an older build than this CLI?
|
|
187
|
+
|
|
188
|
+
Only a CONFIDENT comparison warns — an unparseable or unlabeled
|
|
189
|
+
version stays quiet rather than crying wolf on every start. Equal
|
|
190
|
+
releases still count as stale when the image is a pre-release and
|
|
191
|
+
the CLI is not (an rc image under a released CLI). ``cli`` overrides
|
|
192
|
+
the running version (tests).
|
|
193
|
+
"""
|
|
194
|
+
cli_version = cli or __version__
|
|
195
|
+
img, cli_rel = _release_tuple(img_version), _release_tuple(cli_version)
|
|
196
|
+
if img is None or cli_rel is None:
|
|
197
|
+
return False
|
|
198
|
+
if img < cli_rel:
|
|
199
|
+
return True
|
|
200
|
+
return (img == cli_rel and _is_prerelease(img_version)
|
|
201
|
+
and not _is_prerelease(cli_version))
|
|
202
|
+
|
|
203
|
+
|
|
139
204
|
# ──── pull ────────────────────────────────────────────────────────────────
|
|
140
205
|
|
|
141
206
|
def pull_image(cfg, rt, tag="latest", source=None):
|
|
@@ -400,6 +465,13 @@ def build_run_argv(cfg, rt, detach, profile=None, ident=None):
|
|
|
400
465
|
argv += [cfg.image, "python", "-m", "painapple_code",
|
|
401
466
|
"--host", "0.0.0.0", "--port", "8765",
|
|
402
467
|
"--workspace", "/workspace"]
|
|
468
|
+
# Parent mode mounts the dir AT /workspace. If it's a git checkout
|
|
469
|
+
# (forced --no-project, or a hand-edited profile), the container's own
|
|
470
|
+
# serve auto-detect would see /workspace/.git and flip to single-project
|
|
471
|
+
# mode — pin it to wrapper. Only appended when needed: older images
|
|
472
|
+
# don't know the flag and would crash-loop on it.
|
|
473
|
+
if cfg.workspace_mode == "parent" and (Path(cfg.workspace) / ".git").exists():
|
|
474
|
+
argv += ["--no-project"]
|
|
403
475
|
if cfg.instance_name:
|
|
404
476
|
argv += ["--instance-name", cfg.instance_name]
|
|
405
477
|
if cfg.accent:
|
|
@@ -530,13 +602,46 @@ def run_container(cfg, detach, profile=None):
|
|
|
530
602
|
# inside the runtime with a message that never mentions painapple
|
|
531
603
|
# ("pasta failed with exit code 1…") — and, being on stderr, it lands
|
|
532
604
|
# out of order among our own lines. Check it ourselves, first.
|
|
533
|
-
from painapple_code.cli.netinfo import port_holder, port_taken
|
|
605
|
+
from painapple_code.cli.netinfo import first_free_port, port_holder, port_taken
|
|
534
606
|
reason = port_taken(cfg.listen_host, cfg.port)
|
|
535
607
|
if reason:
|
|
608
|
+
if getattr(reason, "bad_host", False):
|
|
609
|
+
err(f"Cannot bind {cfg.listen_host}:{cfg.port} — {reason}")
|
|
610
|
+
say(f" {DIM}Fix the bind host: {hint('setup', profile)}{RESET}")
|
|
611
|
+
return 1
|
|
536
612
|
holder = port_holder(cfg.port)
|
|
537
613
|
err(f"Port {cfg.port} on {cfg.listen_host} is already in use — {reason}"
|
|
538
614
|
+ (f" (held by {holder})" if holder else ""))
|
|
539
|
-
|
|
615
|
+
# Lead with the one-off retry, not the wizard: the user wants THIS
|
|
616
|
+
# launch to go, and `setup` rewrites global/profile defaults for
|
|
617
|
+
# every future start. A concrete free port beats "pick another".
|
|
618
|
+
#
|
|
619
|
+
# A named profile gets the whole command (self-contained). The
|
|
620
|
+
# ad-hoc sandbox gets the FLAG to append instead — printing
|
|
621
|
+
# `painapple --in-docker --port N` would quietly drop the rest of
|
|
622
|
+
# what the user typed (--project, --workspace, --accent …), and a
|
|
623
|
+
# copied hint that changes behavior is worse than no hint.
|
|
624
|
+
free = first_free_port(cfg.listen_host, cfg.port + 1)
|
|
625
|
+
port_arg = f"--port {free}" if free else "--port N"
|
|
626
|
+
if profile:
|
|
627
|
+
say(f" {DIM}Retry on another port: "
|
|
628
|
+
f"{hint('start', profile)} {port_arg}{RESET}")
|
|
629
|
+
say(f" {DIM}Or change the saved default: "
|
|
630
|
+
f"{hint('setup', profile)}{RESET}")
|
|
631
|
+
else:
|
|
632
|
+
say(f" {DIM}Retry on another port: add {port_arg} to your "
|
|
633
|
+
f"command{RESET}")
|
|
634
|
+
# A named profile is the right PERMANENT answer for a
|
|
635
|
+
# repeat launch that collides: it gets its own port,
|
|
636
|
+
# container, volumes and config. Unnamed `setup` would
|
|
637
|
+
# instead move the GLOBAL default under every future
|
|
638
|
+
# launch — rarely what someone juggling two instances wants,
|
|
639
|
+
# so it goes last.
|
|
640
|
+
suggested = suggest_profile_name(cfg.workspace)
|
|
641
|
+
say(f" {DIM}Or save it as its own deployment: "
|
|
642
|
+
f"painapple setup {suggested or 'NAME'}{RESET}")
|
|
643
|
+
say(f" {DIM}Or change the global default: "
|
|
644
|
+
f"{hint('setup', profile)}{RESET}")
|
|
540
645
|
return 1
|
|
541
646
|
|
|
542
647
|
# Bind-mount dirs must exist so docker doesn't create them as root.
|
|
@@ -563,6 +668,20 @@ def run_container(cfg, detach, profile=None):
|
|
|
563
668
|
say(f" Config : {DIM}{cfg.config_volume}{RESET} ({'bind' if cfg.config_is_bind() else 'volume'}) → /home/app/.config/painapple-code")
|
|
564
669
|
say(f" Listen on : {DIM}{cfg.listen_host}:{cfg.port}{RESET} → container :8765 "
|
|
565
670
|
f"{DIM}(host bind — {listen_scope(cfg)}){RESET}")
|
|
671
|
+
# Name the image's version, always — the same reason `pull` does it.
|
|
672
|
+
# `run` uses whatever image is already local and never re-pulls, so a
|
|
673
|
+
# box that pulled once during the rc series keeps booting that build
|
|
674
|
+
# forever while the host CLI reports a current version. Unlabeled
|
|
675
|
+
# (pre-label) images say so rather than printing nothing.
|
|
676
|
+
img_version = rt.label(cfg.image, "org.opencontainers.image.version")
|
|
677
|
+
say(f" Image : {DIM}{cfg.image}{RESET}"
|
|
678
|
+
+ (f" {DIM}(version {img_version}){RESET}" if img_version
|
|
679
|
+
else f" {DIM}(version unknown — predates the label){RESET}"))
|
|
680
|
+
if _image_is_stale(img_version):
|
|
681
|
+
warn(f"The local image is {img_version or 'older than this CLI'} but "
|
|
682
|
+
f"this CLI is {__version__} — flags and endpoints added since "
|
|
683
|
+
f"then will be missing inside the container. Refresh it with: "
|
|
684
|
+
f"{hint('pull', profile)}")
|
|
566
685
|
if cfg.instance_name:
|
|
567
686
|
say(f" Instance : {DIM}{cfg.instance_name}{RESET}")
|
|
568
687
|
if cfg.accent:
|
|
@@ -58,13 +58,36 @@ def _apply_serve_flags(cfg, argv):
|
|
|
58
58
|
return False
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
"""
|
|
61
|
+
def _forced_project_flag(argv):
|
|
62
|
+
"""--project / --no-project from raw argv: True / False / None (auto).
|
|
63
|
+
Last occurrence wins, matching argparse BooleanOptionalAction."""
|
|
64
|
+
forced = None
|
|
65
|
+
for tok in argv:
|
|
66
|
+
if tok == "--project":
|
|
67
|
+
forced = True
|
|
68
|
+
elif tok == "--no-project":
|
|
69
|
+
forced = False
|
|
70
|
+
return forced
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _auto_workspace_mode(cfg, argv=()):
|
|
74
|
+
"""Mode pick for the mount layer. --project/--no-project force it;
|
|
75
|
+
otherwise zero-config: a git checkout → 'project', anything else
|
|
63
76
|
→ 'parent' — matching "I'm in a repo" vs "I opened a folder of
|
|
64
|
-
repos" without asking. (.git can be a dir or a worktree file.)
|
|
77
|
+
repos" without asking. (.git can be a dir or a worktree file.)
|
|
78
|
+
The serve flag itself is NOT forwarded into the container — project
|
|
79
|
+
mode is realized by the one-level-down mount (container_mount());
|
|
80
|
+
build_run_argv adds --no-project only where the container's own
|
|
81
|
+
auto-detect would wrongly flip a parent-mode git mount."""
|
|
65
82
|
ws = Path(cfg.workspace or ".")
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
forced = _forced_project_flag(argv)
|
|
84
|
+
if forced is True:
|
|
85
|
+
mode, reason = "project", "forced by --project"
|
|
86
|
+
elif forced is False:
|
|
87
|
+
mode, reason = "parent", "forced by --no-project"
|
|
88
|
+
else:
|
|
89
|
+
mode = "project" if (ws / ".git").exists() else "parent"
|
|
90
|
+
reason = "found .git" if mode == "project" else "no .git"
|
|
68
91
|
cfg.workspace_mode = mode
|
|
69
92
|
info(f"{ws} → {mode} mode {DIM}({reason}){RESET}")
|
|
70
93
|
|
|
@@ -100,7 +123,7 @@ def adhoc_settings(argv):
|
|
|
100
123
|
explicit_ws = _apply_serve_flags(cfg, argv)
|
|
101
124
|
if not explicit_ws:
|
|
102
125
|
cfg.workspace = str(Path.cwd())
|
|
103
|
-
_auto_workspace_mode(cfg)
|
|
126
|
+
_auto_workspace_mode(cfg, argv)
|
|
104
127
|
return cfg
|
|
105
128
|
|
|
106
129
|
|
|
@@ -146,9 +169,9 @@ def profile_settings(prof, argv=()):
|
|
|
146
169
|
explicit_ws = _apply_serve_flags(cfg, list(argv))
|
|
147
170
|
if not cfg.workspace and cfg.workspace_mode != "multi":
|
|
148
171
|
cfg.workspace = str(Path.cwd())
|
|
149
|
-
_auto_workspace_mode(cfg)
|
|
172
|
+
_auto_workspace_mode(cfg, argv)
|
|
150
173
|
elif explicit_ws and prof.is_docker is False:
|
|
151
|
-
_auto_workspace_mode(cfg)
|
|
174
|
+
_auto_workspace_mode(cfg, argv)
|
|
152
175
|
return cfg
|
|
153
176
|
|
|
154
177
|
|
|
@@ -280,12 +280,25 @@ def _start(target, extra):
|
|
|
280
280
|
# Bind test, not a scan of our own process table — a port held by
|
|
281
281
|
# ANYTHING (another painapple, a stray dev server) must fail here,
|
|
282
282
|
# loudly, rather than inside a detached child nobody is watching.
|
|
283
|
-
from painapple_code.cli.netinfo import port_holder, port_taken
|
|
283
|
+
from painapple_code.cli.netinfo import first_free_port, port_holder, port_taken
|
|
284
284
|
reason = port_taken(host, port)
|
|
285
285
|
if reason:
|
|
286
|
+
if getattr(reason, "bad_host", False):
|
|
287
|
+
err(f"Cannot bind {host}:{port} — {reason}. Check the host address.")
|
|
288
|
+
return 1
|
|
286
289
|
holder = port_holder(port)
|
|
287
290
|
err(f"Port {port} on {host} is already in use — {reason}"
|
|
288
291
|
+ (f" (held by {holder})" if holder else ""))
|
|
292
|
+
# Same actionable exit as the container path: a concrete free port
|
|
293
|
+
# to retry on, then the wizard for a permanent change.
|
|
294
|
+
free = first_free_port(host, port + 1)
|
|
295
|
+
port_arg = f"--port {free}" if free else "--port N"
|
|
296
|
+
label = target.get("label") or ""
|
|
297
|
+
name = label if label and label != "default" else ""
|
|
298
|
+
say(f" {DIM}Retry on another port: "
|
|
299
|
+
f"painapple start{f' {name}' if name else ''} {port_arg}{RESET}")
|
|
300
|
+
say(f" {DIM}Or change the saved default: "
|
|
301
|
+
f"painapple setup{f' {name}' if name else ''}{RESET}")
|
|
289
302
|
return 1
|
|
290
303
|
|
|
291
304
|
return _spawn_and_wait(
|
|
@@ -37,18 +37,36 @@ def detect_local_ips():
|
|
|
37
37
|
return results
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class BindFailure(str):
|
|
41
|
+
"""Reason string from port_taken(). `bad_host` is True when the HOST is
|
|
42
|
+
the problem (unresolvable, or not an address on this machine —
|
|
43
|
+
EADDRNOTAVAIL) rather than the port being held by another process, so
|
|
44
|
+
callers can skip the "already in use" framing and the holder lookup."""
|
|
45
|
+
bad_host = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _bind_failure(reason, bad_host=False):
|
|
49
|
+
out = BindFailure(reason)
|
|
50
|
+
out.bad_host = bad_host
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
40
54
|
def port_taken(host, port):
|
|
41
55
|
"""Reason string when `host:port` can't be bound, '' when it's free.
|
|
42
56
|
|
|
43
57
|
A plain bind test — catches ANY holder, unlike a scan of painapple's
|
|
44
58
|
own process table. Used by the server's own pre-flight and by
|
|
45
59
|
`painapple start` so the failure is reported before a detached child
|
|
46
|
-
inherits it.
|
|
60
|
+
inherits it. Non-empty returns are BindFailure: `bad_host` marks a
|
|
61
|
+
wrong bind address (unresolvable / not local) as opposed to a port
|
|
62
|
+
genuinely held by another process."""
|
|
63
|
+
import errno
|
|
47
64
|
import socket
|
|
48
65
|
try:
|
|
49
66
|
infos = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)
|
|
50
67
|
except socket.gaierror as e:
|
|
51
|
-
return f"cannot resolve host {host!r} ({e})"
|
|
68
|
+
return _bind_failure(f"cannot resolve host {host!r} ({e})",
|
|
69
|
+
bad_host=True)
|
|
52
70
|
family, socktype, proto, _canon, sockaddr = infos[0]
|
|
53
71
|
with socket.socket(family, socktype, proto) as sock:
|
|
54
72
|
# SO_REUSEADDR means opposite things on the two platforms. On POSIX
|
|
@@ -65,10 +83,33 @@ def port_taken(host, port):
|
|
|
65
83
|
try:
|
|
66
84
|
sock.bind(sockaddr)
|
|
67
85
|
except OSError as e:
|
|
68
|
-
|
|
86
|
+
msg = e.strerror or str(e)
|
|
87
|
+
# EADDRNOTAVAIL ("Cannot assign requested address") is not a
|
|
88
|
+
# port conflict — the IP isn't assigned to any interface here.
|
|
89
|
+
# Framing it as "already in use" sent users hunting for a
|
|
90
|
+
# holder that doesn't exist (winsock code on Windows differs).
|
|
91
|
+
if e.errno in (errno.EADDRNOTAVAIL,
|
|
92
|
+
getattr(errno, "WSAEADDRNOTAVAIL", None)):
|
|
93
|
+
return _bind_failure(
|
|
94
|
+
f"{msg} — {host!r} is not an address on this machine",
|
|
95
|
+
bad_host=True)
|
|
96
|
+
return _bind_failure(msg)
|
|
69
97
|
return ""
|
|
70
98
|
|
|
71
99
|
|
|
100
|
+
def first_free_port(host, start, span=40):
|
|
101
|
+
"""First bindable port at or above ``start``, or None if the whole
|
|
102
|
+
span is taken. Used to turn "port busy" into an actionable
|
|
103
|
+
"--port N" suggestion instead of sending the user to a wizard."""
|
|
104
|
+
for candidate in range(start, min(start + span, 65536)):
|
|
105
|
+
reason = port_taken(host, candidate)
|
|
106
|
+
if getattr(reason, "bad_host", False):
|
|
107
|
+
return None # host is the problem — no port helps
|
|
108
|
+
if not reason:
|
|
109
|
+
return candidate
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
72
113
|
def _listener_pid(port):
|
|
73
114
|
"""PID of whatever holds the listening socket on `port`, or None.
|
|
74
115
|
Never raises — every caller is decoration."""
|
|
@@ -85,6 +85,12 @@ def build_parser():
|
|
|
85
85
|
"its own directory. This anchors the file explorer and "
|
|
86
86
|
"the welcome screen's project suggestions. Mapped to "
|
|
87
87
|
"/workspace inside Docker. (--cwd is an alias)")
|
|
88
|
+
core.add_argument("--project", dest="project",
|
|
89
|
+
action=argparse.BooleanOptionalAction, default=None,
|
|
90
|
+
help="Treat the workspace as ONE project (the welcome screen "
|
|
91
|
+
"offers it as a single entry) instead of a folder of "
|
|
92
|
+
"projects. Default: auto-detect by a top-level .git; "
|
|
93
|
+
"--no-project disables the auto-detect.")
|
|
88
94
|
core.add_argument("--instance-name", default=None,
|
|
89
95
|
help="Instance label for PWA icon and UI (e.g., DEV, STABLE)")
|
|
90
96
|
core.add_argument("--accent", default=None,
|
|
@@ -74,8 +74,16 @@ def step_network(cfg, back, n=1):
|
|
|
74
74
|
warn(f"Server will be reachable on {cfg['host']} — anyone with the "
|
|
75
75
|
"password can connect.")
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
# A profile the user has already configured keeps its own port. A NEW
|
|
78
|
+
# one defaults to the first FREE port at/above 8765 rather than 8765
|
|
79
|
+
# flat: the common reason to create a second deployment is that
|
|
80
|
+
# something already holds the default, and offering the taken port
|
|
81
|
+
# walks straight back into the collision that sent them here.
|
|
82
|
+
default_port = cfg.get("port")
|
|
83
|
+
if not default_port:
|
|
84
|
+
from painapple_code.cli.netinfo import first_free_port
|
|
85
|
+
default_port = first_free_port(cfg.get("host") or "127.0.0.1", 8765) or 8765
|
|
86
|
+
port = ui.int_input("Port to bind", default=default_port, lo=1, hi=65535)
|
|
79
87
|
if port is BACK:
|
|
80
88
|
return step_network(cfg, back, n)
|
|
81
89
|
cfg["port"] = port
|
|
@@ -568,7 +576,11 @@ def _profile_setup(name):
|
|
|
568
576
|
docker_only(lambda back: step_storage(cfg, back)),
|
|
569
577
|
]
|
|
570
578
|
|
|
571
|
-
# Warn about port collisions
|
|
579
|
+
# Warn about port collisions (advisory only) — against other
|
|
580
|
+
# profiles AND against whatever is listening right now. The
|
|
581
|
+
# profile-only check missed the case that actually bites: the root
|
|
582
|
+
# deployment (or any unmanaged process) already holding the port,
|
|
583
|
+
# which is configured nowhere this loop can see.
|
|
572
584
|
def _port_clash():
|
|
573
585
|
for other in profiles.list_profiles():
|
|
574
586
|
if other == name:
|
|
@@ -578,6 +590,14 @@ def _profile_setup(name):
|
|
|
578
590
|
warn(f"Port {cfg.get('port')} is also configured on profile "
|
|
579
591
|
f"'{other}' — they can't run at the same time.")
|
|
580
592
|
return
|
|
593
|
+
from painapple_code.cli.netinfo import port_holder, port_taken
|
|
594
|
+
host = cfg.get("host") or "127.0.0.1"
|
|
595
|
+
reason = port_taken(host, cfg.get("port"))
|
|
596
|
+
if reason and not getattr(reason, "bad_host", False):
|
|
597
|
+
holder = port_holder(cfg.get("port"))
|
|
598
|
+
warn(f"Port {cfg.get('port')} on {host} is in use right now"
|
|
599
|
+
+ (f" (held by {holder})" if holder else "")
|
|
600
|
+
+ " — this profile won't start until that frees up.")
|
|
581
601
|
|
|
582
602
|
try:
|
|
583
603
|
i = 0
|
|
@@ -494,6 +494,30 @@ def _looks_like_project(child: Path) -> bool:
|
|
|
494
494
|
return False
|
|
495
495
|
|
|
496
496
|
|
|
497
|
+
def resolve_workspace_is_project(workspace: str, flag: Optional[bool]) -> tuple[bool, str]:
|
|
498
|
+
"""Decide whether the workspace IS one project (vs a wrapper dir of projects).
|
|
499
|
+
|
|
500
|
+
flag: True/False = forced by --project/--no-project, None = auto-detect.
|
|
501
|
+
Auto-detect keys on a top-level ``.git`` ONLY (dir for a normal repo,
|
|
502
|
+
file for a git worktree) — the same zero-config pick the Docker launch
|
|
503
|
+
path uses (cli/deploy/launch.py), deliberately NOT the broader
|
|
504
|
+
``_looks_like_project`` manifest list: a wrapper dir with a stray
|
|
505
|
+
requirements.txt must not silently flip modes.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
(is_project, reason) — reason is human-readable for the startup log.
|
|
509
|
+
"""
|
|
510
|
+
if flag is True:
|
|
511
|
+
return True, "forced by --project"
|
|
512
|
+
if flag is False:
|
|
513
|
+
return False, "forced by --no-project"
|
|
514
|
+
try:
|
|
515
|
+
has_git = (Path(workspace).expanduser() / ".git").exists()
|
|
516
|
+
except OSError:
|
|
517
|
+
has_git = False
|
|
518
|
+
return (True, "found .git") if has_git else (False, "no .git")
|
|
519
|
+
|
|
520
|
+
|
|
497
521
|
def list_workspace_dirs(root: str, exclude_paths: Optional[list[str]] = None,
|
|
498
522
|
limit: int = 100) -> list[dict]:
|
|
499
523
|
"""List immediate sub-directories of a workspace root, excluding ones
|
{painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_session_welcome.py
RENAMED
|
@@ -93,18 +93,50 @@ async def get_welcome_projects(request: Request):
|
|
|
93
93
|
enriched.sort(key=lambda x: x["name"].lower())
|
|
94
94
|
|
|
95
95
|
workspace_root = getattr(request.app.state, "workspace_root", None)
|
|
96
|
+
is_project = bool(getattr(request.app.state, "workspace_is_project", False))
|
|
96
97
|
workspace_dirs: list[dict] = []
|
|
97
|
-
if workspace_root:
|
|
98
|
+
if workspace_root and is_project:
|
|
99
|
+
# The workspace IS one project — never list its internals
|
|
100
|
+
# (src/, node_modules/, …). Offer the root itself as the single
|
|
101
|
+
# "unvisited" chip until the first session tracks it as a project.
|
|
102
|
+
workspace_dirs = _root_as_workspace_entry(workspace_root, projects)
|
|
103
|
+
elif workspace_root:
|
|
98
104
|
exclude = [p["path"] for p in projects]
|
|
99
105
|
workspace_dirs = list_workspace_dirs(workspace_root, exclude_paths=exclude)
|
|
100
106
|
|
|
101
107
|
return {
|
|
102
108
|
"projects": enriched,
|
|
103
109
|
"workspace_root": workspace_root,
|
|
110
|
+
"workspace_is_project": is_project,
|
|
104
111
|
"workspace_dirs": workspace_dirs,
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
|
|
115
|
+
def _root_as_workspace_entry(workspace_root: str, projects: list[dict]) -> list[dict]:
|
|
116
|
+
"""Project-mode chip list: the workspace root itself, in the same shape
|
|
117
|
+
`list_workspace_dirs` emits — or [] once it's already a tracked project
|
|
118
|
+
(it then appears in `projects` instead)."""
|
|
119
|
+
root = Path(workspace_root)
|
|
120
|
+
tracked = set()
|
|
121
|
+
for p in projects:
|
|
122
|
+
try:
|
|
123
|
+
tracked.add(str(Path(p["path"]).expanduser().resolve()))
|
|
124
|
+
except (OSError, RuntimeError):
|
|
125
|
+
continue
|
|
126
|
+
if str(root) in tracked:
|
|
127
|
+
return []
|
|
128
|
+
try:
|
|
129
|
+
mtime = root.stat().st_mtime
|
|
130
|
+
except OSError:
|
|
131
|
+
return []
|
|
132
|
+
return [{
|
|
133
|
+
"path": str(root),
|
|
134
|
+
"name": root.name,
|
|
135
|
+
"mtime": mtime,
|
|
136
|
+
"looks_like_project": True,
|
|
137
|
+
}]
|
|
138
|
+
|
|
139
|
+
|
|
108
140
|
@router.get("/api/welcome/projects/sessions")
|
|
109
141
|
async def get_project_sessions(path: str, limit: int = 15):
|
|
110
142
|
"""Recent sessions for a specific project — drives the quick-switcher drill-in.
|
|
@@ -1794,6 +1794,24 @@ def _preflight_port(host: str, port: int) -> None:
|
|
|
1794
1794
|
if not reason:
|
|
1795
1795
|
return
|
|
1796
1796
|
|
|
1797
|
+
if getattr(reason, "bad_host", False):
|
|
1798
|
+
# The HOST is wrong (unresolvable / not a local address), not the
|
|
1799
|
+
# port — the "already in use" box below would send the user cycling
|
|
1800
|
+
# through ports that were never the problem.
|
|
1801
|
+
print(f"""
|
|
1802
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
1803
|
+
║ pAInapple Code Server — cannot start
|
|
1804
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
1805
|
+
║ Cannot bind {host}:{port} — the bind host is wrong.
|
|
1806
|
+
║ {reason}
|
|
1807
|
+
║
|
|
1808
|
+
║ Try: {'painapple --host 0.0.0.0':<27} (listen on all interfaces)
|
|
1809
|
+
║ {'painapple --host 127.0.0.1':<27} (this machine only)
|
|
1810
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
1811
|
+
""", file=sys.__stderr__ or sys.stdout)
|
|
1812
|
+
logger.error(f"Cannot bind {host}:{port} — {reason}; not starting")
|
|
1813
|
+
sys.exit(1)
|
|
1814
|
+
|
|
1797
1815
|
holder = port_holder(port)
|
|
1798
1816
|
port_flag = f"painapple --port {port + 1}"
|
|
1799
1817
|
print(f"""
|
|
@@ -2039,16 +2057,23 @@ def main(argv=None):
|
|
|
2039
2057
|
app.state.agents = agents
|
|
2040
2058
|
app.state.workspace = args.workspace
|
|
2041
2059
|
|
|
2042
|
-
# --workspace
|
|
2043
|
-
#
|
|
2044
|
-
#
|
|
2045
|
-
#
|
|
2046
|
-
#
|
|
2047
|
-
#
|
|
2060
|
+
# --workspace anchors discovery (the welcome "Unvisited" chips, the
|
|
2061
|
+
# file-explorer browse root) and supplies a fallback cwd for sessions
|
|
2062
|
+
# created without one — sessions pick their own cwd from the welcome
|
|
2063
|
+
# screen and files/git/terminal follow THAT. Two modes: a WRAPPER dir
|
|
2064
|
+
# holding project subdirs (welcome lists them), or ONE project
|
|
2065
|
+
# (welcome offers just the workspace itself — never its internals like
|
|
2066
|
+
# src/ or node_modules/). Mode from --project/--no-project, else
|
|
2067
|
+
# auto-detected by a top-level .git (same pick as Docker's launch
|
|
2068
|
+
# path, which sidesteps this via the one-level-down mount). Stored as
|
|
2069
|
+
# absolute string (or None) for route modules to read.
|
|
2048
2070
|
try:
|
|
2049
2071
|
app.state.workspace_root = str(Path(args.workspace).expanduser().resolve())
|
|
2050
2072
|
except (OSError, RuntimeError):
|
|
2051
2073
|
app.state.workspace_root = None
|
|
2074
|
+
is_project, project_reason = paths.resolve_workspace_is_project(
|
|
2075
|
+
args.workspace, args.project)
|
|
2076
|
+
app.state.workspace_is_project = is_project
|
|
2052
2077
|
|
|
2053
2078
|
# Load config file and populate auth state (fails open if the directory
|
|
2054
2079
|
# can't be written — intentional: we want a noisy permissions error).
|
|
@@ -2116,6 +2141,13 @@ def main(argv=None):
|
|
|
2116
2141
|
"Use --tls=auto (default) unless you have a reason."
|
|
2117
2142
|
)
|
|
2118
2143
|
|
|
2144
|
+
# Project-mode annotation on the Workspace banner line; a fuller notice
|
|
2145
|
+
# prints below the box ONLY when the mode was auto-detected (explicit
|
|
2146
|
+
# --project/--no-project users already know what they asked for).
|
|
2147
|
+
workspace_mode = ""
|
|
2148
|
+
if app.state.workspace_is_project:
|
|
2149
|
+
workspace_mode = f" (single project: {project_reason})"
|
|
2150
|
+
|
|
2119
2151
|
instance_line = ""
|
|
2120
2152
|
if instance_config.get("name"):
|
|
2121
2153
|
instance_line = f"\n║ Instance: {instance_config['name']} (accent: {instance_config.get('accent', 'default')})"
|
|
@@ -2164,11 +2196,16 @@ def main(argv=None):
|
|
|
2164
2196
|
╠══════════════════════════════════════════════════════════════╣
|
|
2165
2197
|
║ WebSocket: {ws_scheme}://{args.host}:{args.port}/chat
|
|
2166
2198
|
║ Files API: {scheme}://{args.host}:{args.port}/api/files
|
|
2167
|
-
║ Workspace: {args.workspace}{instance_line}{shadow_db_line}{log_dir_line}{state_suffix_line}
|
|
2199
|
+
║ Workspace: {args.workspace}{workspace_mode}{instance_line}{shadow_db_line}{log_dir_line}{state_suffix_line}
|
|
2168
2200
|
║ Auth File: {app.state.auth_config_file}{tls_line}{auth_lines}
|
|
2169
2201
|
╚══════════════════════════════════════════════════════════════╝
|
|
2170
2202
|
""")
|
|
2171
2203
|
|
|
2204
|
+
if args.project is None and app.state.workspace_is_project:
|
|
2205
|
+
print(f"{DIM}Workspace looks like a project (found .git) — the welcome "
|
|
2206
|
+
f"screen shows it as one project, not its subfolders. Pass "
|
|
2207
|
+
f"--no-project to treat it as a folder of projects.{_ANSI_RESET}")
|
|
2208
|
+
|
|
2172
2209
|
# Trust X-Forwarded-* only from loopback by default (a reverse proxy on
|
|
2173
2210
|
# the same host). A remote proxy sets FORWARDED_ALLOW_IPS explicitly. The
|
|
2174
2211
|
# old "*" let ANY direct client spoof X-Forwarded-Proto and flip the
|
|
@@ -1031,6 +1031,23 @@
|
|
|
1031
1031
|
transition: all 0.15s;
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
|
+
/* Icon buttons in the panzoom toolbar (e.g. annotate): the inline SVG ships
|
|
1035
|
+
only a viewBox, so without explicit sizing it renders at the 300×150
|
|
1036
|
+
replaced-element default and the icon clips out of the button entirely. */
|
|
1037
|
+
.panzoom-annotate-btn {
|
|
1038
|
+
display: inline-flex;
|
|
1039
|
+
align-items: center;
|
|
1040
|
+
justify-content: center;
|
|
1041
|
+
width: var(--layout-btn-sm);
|
|
1042
|
+
padding: 0;
|
|
1043
|
+
flex-shrink: 0;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.panzoom-annotate-btn svg {
|
|
1047
|
+
width: 14px;
|
|
1048
|
+
height: 14px;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1034
1051
|
.panzoom-ctrl-btn:hover, .panzoom-ctrl-btn:active {
|
|
1035
1052
|
background: var(--surface-hover, #30363d);
|
|
1036
1053
|
color: var(--text-primary, #e6edf3);
|