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.
Files changed (511) hide show
  1. {painapple_code-1.0.2/src/painapple_code.egg-info → painapple_code-1.0.4}/PKG-INFO +1 -1
  2. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-docker.md +22 -2
  3. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/_version.py +3 -3
  4. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/container.py +121 -2
  5. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/launch.py +31 -8
  6. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/lifecycle_cmd.py +14 -1
  7. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/netinfo.py +44 -3
  8. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/serve_args.py +6 -0
  9. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/setup_cmd.py +23 -3
  10. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/paths.py +24 -0
  11. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_session_welcome.py +33 -1
  12. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/server.py +44 -7
  13. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-file-preview-widget.css +17 -0
  14. {painapple_code-1.0.2 → painapple_code-1.0.4/src/painapple_code.egg-info}/PKG-INFO +1 -1
  15. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/scm_file_list.json +2 -0
  16. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/scm_version.json +2 -2
  17. {painapple_code-1.0.2 → painapple_code-1.0.4}/.devcontainer/devcontainer.json +0 -0
  18. {painapple_code-1.0.2 → painapple_code-1.0.4}/.dockerignore +0 -0
  19. {painapple_code-1.0.2 → painapple_code-1.0.4}/.gitignore +0 -0
  20. {painapple_code-1.0.2 → painapple_code-1.0.4}/CHANGELOG.md +0 -0
  21. {painapple_code-1.0.2 → painapple_code-1.0.4}/CONTRIBUTING.md +0 -0
  22. {painapple_code-1.0.2 → painapple_code-1.0.4}/Dockerfile +0 -0
  23. {painapple_code-1.0.2 → painapple_code-1.0.4}/LICENSE +0 -0
  24. {painapple_code-1.0.2 → painapple_code-1.0.4}/MANIFEST.in +0 -0
  25. {painapple_code-1.0.2 → painapple_code-1.0.4}/README.dockerhub.md +0 -0
  26. {painapple_code-1.0.2 → painapple_code-1.0.4}/README.md +0 -0
  27. {painapple_code-1.0.2 → painapple_code-1.0.4}/SECURITY.md +0 -0
  28. {painapple_code-1.0.2 → painapple_code-1.0.4}/THIRD_PARTY_NOTICES.md +0 -0
  29. {painapple_code-1.0.2 → painapple_code-1.0.4}/build-frontend.sh +0 -0
  30. {painapple_code-1.0.2 → painapple_code-1.0.4}/docker-compose.yml +0 -0
  31. {painapple_code-1.0.2 → painapple_code-1.0.4}/docker-entrypoint.sh +0 -0
  32. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/annotate-editor.png +0 -0
  33. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/browser-widget.png +0 -0
  34. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/comments-stash.gif +0 -0
  35. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/cost-analytics.png +0 -0
  36. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/favicon.png +0 -0
  37. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/input-autocomplete.gif +0 -0
  38. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/logo.png +0 -0
  39. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/markdown-quick-edit.gif +0 -0
  40. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/overview.png +0 -0
  41. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/prompt-history.png +0 -0
  42. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/quick-switcher.gif +0 -0
  43. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/quick-switcher.png +0 -0
  44. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/settings-appearance.png +0 -0
  45. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/settings-claude.png +0 -0
  46. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/shadow-journal.png +0 -0
  47. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/snippets-picker.png +0 -0
  48. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/terminal.gif +0 -0
  49. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/turn-summary-bar-expanded.png +0 -0
  50. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/turn-summary-bar.png +0 -0
  51. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/welcome-screen.png +0 -0
  52. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/assets/zen-mode.png +0 -0
  53. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/features.md +0 -0
  54. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/first-run.md +0 -0
  55. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-devcontainer.md +0 -0
  56. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/install-pip.md +0 -0
  57. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/requirements.md +0 -0
  58. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/getting-started/security.md +0 -0
  59. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/analytics-and-history.md +0 -0
  60. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/claude-artifacts.md +0 -0
  61. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/customization.md +0 -0
  62. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/files.md +0 -0
  63. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/git-and-diffs.md +0 -0
  64. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/images-and-annotation.md +0 -0
  65. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/input-and-commands.md +0 -0
  66. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/ipad-and-mobile.md +0 -0
  67. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/permissions-and-thinking.md +0 -0
  68. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/providers.md +0 -0
  69. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/sessions.md +0 -0
  70. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/shadow-git.md +0 -0
  71. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/stash-and-discussions.md +0 -0
  72. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/terminal.md +0 -0
  73. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/welcome-screen.md +0 -0
  74. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/guides/zen-mode.md +0 -0
  75. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/index.md +0 -0
  76. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/api.md +0 -0
  77. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/commands.md +0 -0
  78. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/data-storage.md +0 -0
  79. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/keyboard-shortcuts.md +0 -0
  80. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/optional-helpers.md +0 -0
  81. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/profiles.md +0 -0
  82. {painapple_code-1.0.2 → painapple_code-1.0.4}/docs-site/reference/server-cli.md +0 -0
  83. {painapple_code-1.0.2 → painapple_code-1.0.4}/mkdocs.yml +0 -0
  84. {painapple_code-1.0.2 → painapple_code-1.0.4}/painapple-docker.sh +0 -0
  85. {painapple_code-1.0.2 → painapple_code-1.0.4}/pyproject.toml +0 -0
  86. {painapple_code-1.0.2 → painapple_code-1.0.4}/requirements-docs.txt +0 -0
  87. {painapple_code-1.0.2 → painapple_code-1.0.4}/requirements.txt +0 -0
  88. {painapple_code-1.0.2 → painapple_code-1.0.4}/setup.cfg +0 -0
  89. {painapple_code-1.0.2 → painapple_code-1.0.4}/setup.py +0 -0
  90. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/__init__.py +0 -0
  91. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/__main__.py +0 -0
  92. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/auth_middleware.py +0 -0
  93. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/__init__.py +0 -0
  94. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/__init__.py +0 -0
  95. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/claude_seed.py +0 -0
  96. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/config.py +0 -0
  97. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/deploy/runtime.py +0 -0
  98. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/list_cmd.py +0 -0
  99. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/manage_cmd.py +0 -0
  100. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/profiles.py +0 -0
  101. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/serve_config.py +0 -0
  102. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cli/ui.py +0 -0
  103. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/cost_analytics.py +0 -0
  104. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/models.defaults.yaml +0 -0
  105. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/models.yaml +0 -0
  106. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/presets.defaults.json +0 -0
  107. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/data/strings.yaml +0 -0
  108. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/helpers.py +0 -0
  109. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/prompt_explorer.py +0 -0
  110. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/__init__.py +0 -0
  111. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/base.py +0 -0
  112. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/__init__.py +0 -0
  113. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/capabilities.py +0 -0
  114. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/errors.py +0 -0
  115. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/launch.py +0 -0
  116. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/summary.py +0 -0
  117. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude/translate.py +0 -0
  118. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude_sdk/__init__.py +0 -0
  119. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/claude_sdk/driver.py +0 -0
  120. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/__init__.py +0 -0
  121. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/capabilities.py +0 -0
  122. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/errors.py +0 -0
  123. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex/translate.py +0 -0
  124. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/__init__.py +0 -0
  125. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/capabilities.py +0 -0
  126. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/launch.py +0 -0
  127. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/summary.py +0 -0
  128. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/translate.py +0 -0
  129. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/providers/codex_app_server/transport.py +0 -0
  130. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/__init__.py +0 -0
  131. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_agents.py +0 -0
  132. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app.py +0 -0
  133. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_commit_sections.py +0 -0
  134. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_config.py +0 -0
  135. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_app_session_prefs.py +0 -0
  136. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_browser.py +0 -0
  137. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_commands.py +0 -0
  138. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_costs.py +0 -0
  139. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_drafts.py +0 -0
  140. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_exec.py +0 -0
  141. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_files.py +0 -0
  142. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_git.py +0 -0
  143. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_logs.py +0 -0
  144. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_plugins.py +0 -0
  145. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_project_config.py +0 -0
  146. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_prompts.py +0 -0
  147. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_providers.py +0 -0
  148. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_search.py +0 -0
  149. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_session_stash.py +0 -0
  150. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_sessions.py +0 -0
  151. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow.py +0 -0
  152. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_db.py +0 -0
  153. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_files.py +0 -0
  154. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_shadow_search.py +0 -0
  155. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_skills.py +0 -0
  156. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_tasks.py +0 -0
  157. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_terminal.py +0 -0
  158. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_upload.py +0 -0
  159. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/api_viewer.py +0 -0
  160. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/dependencies.py +0 -0
  161. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/routes/ws_chat.py +0 -0
  162. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/server_logging.py +0 -0
  163. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/services/__init__.py +0 -0
  164. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/services/agent_session.py +0 -0
  165. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/session_store.py +0 -0
  166. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/session_store_core.py +0 -0
  167. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db.py +0 -0
  168. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_plans.py +0 -0
  169. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_queries.py +0 -0
  170. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_db_schema.py +0 -0
  171. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git.py +0 -0
  172. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_frontmatter.py +0 -0
  173. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_sections.py +0 -0
  174. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_git_summary.py +0 -0
  175. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/shadow_parser.py +0 -0
  176. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/00-variables.css +0 -0
  177. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/01-base.css +0 -0
  178. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/02-tooltips.css +0 -0
  179. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/09-left-rail.css +0 -0
  180. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/10-header.css +0 -0
  181. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/11-connection-bar.css +0 -0
  182. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/20-messages.css +0 -0
  183. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/21-tool-blocks.css +0 -0
  184. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/22-edit-diff.css +0 -0
  185. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/23-thinking.css +0 -0
  186. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/24-background-tasks.css +0 -0
  187. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/25-context-block.css +0 -0
  188. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/26-session-setup.css +0 -0
  189. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/30-markdown.css +0 -0
  190. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/40-input.css +0 -0
  191. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/41-autocomplete.css +0 -0
  192. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/42-activity-strip.css +0 -0
  193. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/43-file-autocomplete.css +0 -0
  194. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/44-snippets-autocomplete.css +0 -0
  195. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/45-selection-bar.css +0 -0
  196. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/46-keyboard-bar.css +0 -0
  197. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/47-keyboard-longpress.css +0 -0
  198. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/47-token-profile.css +0 -0
  199. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/50-welcome.css +0 -0
  200. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/51-modals.css +0 -0
  201. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/52-utilities.css +0 -0
  202. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/53-image-upload.css +0 -0
  203. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/54-todo-list.css +0 -0
  204. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/55-file-upload.css +0 -0
  205. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-chat-navigator.css +0 -0
  206. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-permission-card.css +0 -0
  207. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-plan-approval.css +0 -0
  208. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/56-question-form.css +0 -0
  209. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/57-swipe-indicator.css +0 -0
  210. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/58-session-families.css +0 -0
  211. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/59-about.css +0 -0
  212. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/61-log-explorer.css +0 -0
  213. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/62-json-tree.css +0 -0
  214. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/62-skills-widget.css +0 -0
  215. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-commands-widget.css +0 -0
  216. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-plugins-widget.css +0 -0
  217. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/63-terminal.css +0 -0
  218. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/64-search-files-widget.css +0 -0
  219. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/65-snippets-widget.css +0 -0
  220. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-active-sessions.css +0 -0
  221. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-agents-widget.css +0 -0
  222. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-file-explorer-widget.css +0 -0
  223. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-git-panel.css +0 -0
  224. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/66-history-explorer.css +0 -0
  225. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-diff-viewer.css +0 -0
  226. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/67-editor-view.css +0 -0
  227. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-browser-widget.css +0 -0
  228. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-compare-wizard.css +0 -0
  229. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-csv-preview.css +0 -0
  230. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-diff-preview.css +0 -0
  231. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-helpers-install-widget.css +0 -0
  232. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-image-annotate.css +0 -0
  233. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-terminal-view.css +0 -0
  234. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/68-uploads-widget.css +0 -0
  235. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/69-cost-analytics.css +0 -0
  236. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-config-panel.css +0 -0
  237. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-excalidraw.css +0 -0
  238. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/70-prompt-explorer.css +0 -0
  239. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/71-chart.css +0 -0
  240. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/72-discussion-sidebar.css +0 -0
  241. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/74-debug-logs.css +0 -0
  242. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-open-dialog.css +0 -0
  243. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-quick-switcher.css +0 -0
  244. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/80-widget-system.css +0 -0
  245. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/81-quick-actions.css +0 -0
  246. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/82-context-popover.css +0 -0
  247. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/83-context-menu.css +0 -0
  248. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/85-lazy-loading.css +0 -0
  249. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/86-zen-mode.css +0 -0
  250. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/87-grid-switcher.css +0 -0
  251. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/css/login.css +0 -0
  252. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/dist/app.bundle.js +0 -0
  253. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/feature-triage.html +0 -0
  254. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/apple-touch-icon.png +0 -0
  255. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/favicon-32.png +0 -0
  256. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/favicon.ico +0 -0
  257. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-128.png +0 -0
  258. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-144.png +0 -0
  259. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-152.png +0 -0
  260. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-180.png +0 -0
  261. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-192.png +0 -0
  262. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-384.png +0 -0
  263. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-512.png +0 -0
  264. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-72.png +0 -0
  265. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/icons/icon-96.png +0 -0
  266. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/action-delegate.js +0 -0
  267. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/activity-strip.js +0 -0
  268. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/context-menus.js +0 -0
  269. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/file-preview.js +0 -0
  270. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/init-ui.js +0 -0
  271. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/input-events.js +0 -0
  272. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/input-resize.js +0 -0
  273. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/message-actions.js +0 -0
  274. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/panels.js +0 -0
  275. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/rendering-delegators.js +0 -0
  276. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/server-loading.js +0 -0
  277. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/session-ops.js +0 -0
  278. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/session-switch.js +0 -0
  279. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/settings-helpers.js +0 -0
  280. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app/startup-welcome.js +0 -0
  281. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app-context.js +0 -0
  282. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/app.js +0 -0
  283. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/auth-fetch.js +0 -0
  284. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/background-tasks.js +0 -0
  285. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/boot.js +0 -0
  286. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/caret-position.js +0 -0
  287. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/chat-navigator.js +0 -0
  288. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/chat-search.js +0 -0
  289. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/command-executor.js +0 -0
  290. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/command-store.js +0 -0
  291. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/components.js +0 -0
  292. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/config.js +0 -0
  293. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/context-menu.js +0 -0
  294. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/chat-controller.js +0 -0
  295. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/dialog-controller.js +0 -0
  296. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/tab-controller.js +0 -0
  297. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/controllers/thinking-controller.js +0 -0
  298. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/diff-utils.js +0 -0
  299. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/drafts-pill.js +0 -0
  300. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/editor-view.js +0 -0
  301. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/effort-settings.js +0 -0
  302. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/feature-triage.js +0 -0
  303. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/file-autocomplete.js +0 -0
  304. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/file-tabs.js +0 -0
  305. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/gestures.js +0 -0
  306. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/grid-switcher.js +0 -0
  307. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/input-handler.js +0 -0
  308. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/keyboard-bar.js +0 -0
  309. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/layout-switcher.js +0 -0
  310. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/linkify-utils.js +0 -0
  311. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/login.js +0 -0
  312. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/open-dialog.js +0 -0
  313. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/orphan-terminals.js +0 -0
  314. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/path-utils.js +0 -0
  315. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/perf-marks.js +0 -0
  316. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/permission-settings.js +0 -0
  317. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/pointer-input.js +0 -0
  318. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree-menu.js +0 -0
  319. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree-toolbar.js +0 -0
  320. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/json-tree.js +0 -0
  321. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-breadcrumb.js +0 -0
  322. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-edit.js +0 -0
  323. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-events.js +0 -0
  324. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-history.js +0 -0
  325. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-inline-edit.js +0 -0
  326. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-poll.js +0 -0
  327. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-render.js +0 -0
  328. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-search.js +0 -0
  329. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-state.js +0 -0
  330. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview/preview-utils.js +0 -0
  331. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/chart-plugin.js +0 -0
  332. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/csv-plugin.js +0 -0
  333. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/diff-plugin.js +0 -0
  334. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/excalidraw-plugin.js +0 -0
  335. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/html-plugin.js +0 -0
  336. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/image-plugin.js +0 -0
  337. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/index.js +0 -0
  338. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/json-plugin.js +0 -0
  339. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/jsonl-plugin.js +0 -0
  340. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/markdown-plugin.js +0 -0
  341. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/panzoom-plugin.js +0 -0
  342. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/preview-plugins/plugin-helpers.js +0 -0
  343. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/project-color-picker.js +0 -0
  344. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/project-colors.js +0 -0
  345. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/prompt-favorites.js +0 -0
  346. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-actions-menu.js +0 -0
  347. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-actions-registry.js +0 -0
  348. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/controller.js +0 -0
  349. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/fuzzy-scorer.js +0 -0
  350. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/index.js +0 -0
  351. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/base-provider.js +0 -0
  352. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/command-provider.js +0 -0
  353. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/file-provider.js +0 -0
  354. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/panel-provider.js +0 -0
  355. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/project-provider.js +0 -0
  356. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/read-files-provider.js +0 -0
  357. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/providers/skills-provider.js +0 -0
  358. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/registry.js +0 -0
  359. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/ui/item.js +0 -0
  360. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/quick-switcher/ui/picker.js +0 -0
  361. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/recent-opens.js +0 -0
  362. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/scroll-manager.js +0 -0
  363. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/scroll-state-machine.js +0 -0
  364. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/action-bar.js +0 -0
  365. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/index.js +0 -0
  366. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/selection-handler.js +0 -0
  367. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/selection/state.js +0 -0
  368. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/agent-progress.js +0 -0
  369. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/handle-agent-message.js +0 -0
  370. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/handle-message.js +0 -0
  371. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/interactive.js +0 -0
  372. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/message-store.js +0 -0
  373. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/messages.js +0 -0
  374. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/persistence.js +0 -0
  375. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/restore.js +0 -0
  376. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session/sync.js +0 -0
  377. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session-container-pool.js +0 -0
  378. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session-setup-panel.js +0 -0
  379. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/session.js +0 -0
  380. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/shortcut-hints.js +0 -0
  381. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/shortcuts.js +0 -0
  382. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/skills-autocomplete.js +0 -0
  383. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/snippets-autocomplete.js +0 -0
  384. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash-refs-view.js +0 -0
  385. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash-ui.js +0 -0
  386. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/stash.js +0 -0
  387. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/status-bar.js +0 -0
  388. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/token-profile.js +0 -0
  389. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer-blocks.js +0 -0
  390. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer-thinking.js +0 -0
  391. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tool-renderer.js +0 -0
  392. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/tooltips.js +0 -0
  393. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/upload-manager.js +0 -0
  394. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/utils.js +0 -0
  395. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/api.js +0 -0
  396. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/cards.js +0 -0
  397. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/context-menu.js +0 -0
  398. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/families.js +0 -0
  399. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/preview.js +0 -0
  400. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome/state.js +0 -0
  401. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/welcome.js +0 -0
  402. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/base-widget.js +0 -0
  403. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/device-manager.js +0 -0
  404. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/event-bus.js +0 -0
  405. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/icons.js +0 -0
  406. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/index.js +0 -0
  407. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/init.js +0 -0
  408. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/bottom-sheet.js +0 -0
  409. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/floating.js +0 -0
  410. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/index.js +0 -0
  411. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/modal.js +0 -0
  412. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/sidebar.js +0 -0
  413. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/tab.js +0 -0
  414. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/types/top-sheet.js +0 -0
  415. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widget-system/widget-manager.js +0 -0
  416. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/about-widget.js +0 -0
  417. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/active-sessions-widget.js +0 -0
  418. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/agents-widget.js +0 -0
  419. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/browser-widget.js +0 -0
  420. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/commands-widget.js +0 -0
  421. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/commit-sections.js +0 -0
  422. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/dir-autocomplete.js +0 -0
  423. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/gestures.js +0 -0
  424. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/models-tab.js +0 -0
  425. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/quick-actions-tab.js +0 -0
  426. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/shortcut-editor.js +0 -0
  427. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/state.js +0 -0
  428. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config/system-controls.js +0 -0
  429. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/config-widget.js +0 -0
  430. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/cost-analytics-widget.js +0 -0
  431. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/debug-widget.js +0 -0
  432. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/diff-viewer-widget.js +0 -0
  433. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/discussion-widget.js +0 -0
  434. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/file-explorer-widget.js +0 -0
  435. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/file-preview-widget.js +0 -0
  436. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/git-widget.js +0 -0
  437. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/helpers-install-widget.js +0 -0
  438. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/history-explorer-widget.js +0 -0
  439. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/image-annotate-widget.js +0 -0
  440. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/image-preview-widget.js +0 -0
  441. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/index.js +0 -0
  442. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/log-explorer-widget.js +0 -0
  443. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/plugins-widget.js +0 -0
  444. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/prompt-explorer-widget.js +0 -0
  445. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/search-files-widget.js +0 -0
  446. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/skills-widget.js +0 -0
  447. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/snippets-widget.js +0 -0
  448. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/sub-agents-widget.js +0 -0
  449. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/tasks-widget.js +0 -0
  450. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/connection.js +0 -0
  451. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/gestures.js +0 -0
  452. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/init.js +0 -0
  453. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/link-providers.js +0 -0
  454. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/osc52.js +0 -0
  455. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/render.js +0 -0
  456. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/size.js +0 -0
  457. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/state.js +0 -0
  458. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal/wrap-utils.js +0 -0
  459. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/terminal-widget.js +0 -0
  460. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/uploads-widget.js +0 -0
  461. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/js/widgets/zen-widget.js +0 -0
  462. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/login.html +0 -0
  463. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/manifest.json +0 -0
  464. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/sw.js +0 -0
  465. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/LICENSES.txt +0 -0
  466. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/README.md +0 -0
  467. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/codemirror.js +0 -0
  468. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/codemirror.js.LEGAL.txt +0 -0
  469. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/github-markdown-dark.min.css +0 -0
  470. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-github-dark.min.css +0 -0
  471. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-dockerfile.min.js +0 -0
  472. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-nginx.min.js +0 -0
  473. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-properties.min.js +0 -0
  474. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight-lang-scala.min.js +0 -0
  475. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/highlight.min.js +0 -0
  476. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/marked.min.js +0 -0
  477. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/purify.min.js +0 -0
  478. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm-addon-fit.js +0 -0
  479. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm.css +0 -0
  480. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/vendor/xterm.js +0 -0
  481. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/static/web-client.html +0 -0
  482. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/subprocess_registry.py +0 -0
  483. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tls_cert.py +0 -0
  484. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/agents/shadow-git-helper.md +0 -0
  485. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/excalidraw-to-svg.js +0 -0
  486. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/install-helpers.sh +0 -0
  487. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/shadow-git +0 -0
  488. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/shadow-query +0 -0
  489. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/tools/vegalite-to-svg.js +0 -0
  490. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/turn_query.py +0 -0
  491. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/turn_tracker.py +0 -0
  492. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/__init__.py +0 -0
  493. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/agent_cli.py +0 -0
  494. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/chart.py +0 -0
  495. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/excalidraw.py +0 -0
  496. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/file_lock.py +0 -0
  497. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/file_paths.py +0 -0
  498. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/generate_icons.py +0 -0
  499. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/proc.py +0 -0
  500. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_backend.py +0 -0
  501. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_posix.py +0 -0
  502. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/pty_windows.py +0 -0
  503. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/utils/token_profiles.py +0 -0
  504. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/viewer_templates.py +0 -0
  505. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code/welcome_search.py +0 -0
  506. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/SOURCES.txt +0 -0
  507. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/dependency_links.txt +0 -0
  508. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/entry_points.txt +0 -0
  509. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/requires.txt +0 -0
  510. {painapple_code-1.0.2 → painapple_code-1.0.4}/src/painapple_code.egg-info/top_level.txt +0 -0
  511. {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.2
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 "http://localhost:8765/?tkn=$(docker exec painapple-code \
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 `http://localhost:8765/` in a browser. 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).
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.2'
22
- __version_tuple__ = version_tuple = (1, 0, 2)
21
+ __version__ = version = '1.0.4'
22
+ __version_tuple__ = version_tuple = (1, 0, 4)
23
23
 
24
- __commit_id__ = commit_id = 'ge157c77'
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
- say(f" {DIM}Pick another port: {hint('setup', profile)}{RESET}")
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 _auto_workspace_mode(cfg):
62
- """Zero-config mode pick: a git checkout → 'project', anything else
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
- mode = "project" if (ws / ".git").exists() else "parent"
67
- reason = "found .git" if mode == "project" else "no .git"
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. Unresolvable host counts as taken (with the reason)."""
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
- return e.strerror or str(e)
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
- port = ui.int_input("Port to bind", default=cfg.get("port") or 8765,
78
- lo=1, hi=65535)
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 with other profiles (advisory only).
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
@@ -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 IS the root: the dir that holds the project subdirs, not
2043
- # one project. Sessions pick their own cwd from the welcome screen and
2044
- # files/git/terminal follow THAT, so this only anchors discovery (the
2045
- # welcome "Unvisited" chips, the file-explorer browse root) and supplies
2046
- # a fallback cwd for sessions created without one. Stored as absolute
2047
- # string (or None) for route modules to read.
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);