painapple-code 1.0.0__py3-none-any.whl

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 (436) hide show
  1. painapple_code/__init__.py +30 -0
  2. painapple_code/__main__.py +18 -0
  3. painapple_code/_version.py +24 -0
  4. painapple_code/auth_middleware.py +930 -0
  5. painapple_code/cli/__init__.py +204 -0
  6. painapple_code/cli/deploy/__init__.py +15 -0
  7. painapple_code/cli/deploy/claude_seed.py +41 -0
  8. painapple_code/cli/deploy/config.py +260 -0
  9. painapple_code/cli/deploy/container.py +715 -0
  10. painapple_code/cli/deploy/launch.py +163 -0
  11. painapple_code/cli/deploy/runtime.py +223 -0
  12. painapple_code/cli/lifecycle_cmd.py +443 -0
  13. painapple_code/cli/list_cmd.py +572 -0
  14. painapple_code/cli/manage_cmd.py +670 -0
  15. painapple_code/cli/netinfo.py +125 -0
  16. painapple_code/cli/profiles.py +390 -0
  17. painapple_code/cli/serve_args.py +163 -0
  18. painapple_code/cli/serve_config.py +256 -0
  19. painapple_code/cli/setup_cmd.py +664 -0
  20. painapple_code/cli/ui.py +438 -0
  21. painapple_code/cost_analytics.py +879 -0
  22. painapple_code/data/models.defaults.yaml +18 -0
  23. painapple_code/data/models.yaml +18 -0
  24. painapple_code/data/presets.defaults.json +32 -0
  25. painapple_code/data/strings.yaml +2269 -0
  26. painapple_code/helpers.py +406 -0
  27. painapple_code/paths.py +1514 -0
  28. painapple_code/prompt_explorer.py +984 -0
  29. painapple_code/providers/__init__.py +224 -0
  30. painapple_code/providers/base.py +710 -0
  31. painapple_code/providers/claude/__init__.py +74 -0
  32. painapple_code/providers/claude/capabilities.py +218 -0
  33. painapple_code/providers/claude/errors.py +79 -0
  34. painapple_code/providers/claude/launch.py +50 -0
  35. painapple_code/providers/claude/summary.py +89 -0
  36. painapple_code/providers/claude/translate.py +74 -0
  37. painapple_code/providers/claude_sdk/__init__.py +139 -0
  38. painapple_code/providers/claude_sdk/driver.py +412 -0
  39. painapple_code/providers/codex/__init__.py +87 -0
  40. painapple_code/providers/codex/capabilities.py +321 -0
  41. painapple_code/providers/codex/errors.py +70 -0
  42. painapple_code/providers/codex/launch.py +142 -0
  43. painapple_code/providers/codex/summary.py +196 -0
  44. painapple_code/providers/codex/translate.py +204 -0
  45. painapple_code/providers/codex_app_server/__init__.py +100 -0
  46. painapple_code/providers/codex_app_server/capabilities.py +41 -0
  47. painapple_code/providers/codex_app_server/launch.py +202 -0
  48. painapple_code/providers/codex_app_server/summary.py +291 -0
  49. painapple_code/providers/codex_app_server/translate.py +263 -0
  50. painapple_code/providers/codex_app_server/transport.py +238 -0
  51. painapple_code/routes/__init__.py +0 -0
  52. painapple_code/routes/api_agents.py +441 -0
  53. painapple_code/routes/api_app.py +368 -0
  54. painapple_code/routes/api_app_commit_sections.py +127 -0
  55. painapple_code/routes/api_app_config.py +997 -0
  56. painapple_code/routes/api_app_session_prefs.py +217 -0
  57. painapple_code/routes/api_browser.py +690 -0
  58. painapple_code/routes/api_commands.py +374 -0
  59. painapple_code/routes/api_costs.py +128 -0
  60. painapple_code/routes/api_drafts.py +139 -0
  61. painapple_code/routes/api_exec.py +149 -0
  62. painapple_code/routes/api_files.py +542 -0
  63. painapple_code/routes/api_git.py +657 -0
  64. painapple_code/routes/api_logs.py +649 -0
  65. painapple_code/routes/api_plugins.py +275 -0
  66. painapple_code/routes/api_project_config.py +418 -0
  67. painapple_code/routes/api_prompts.py +231 -0
  68. painapple_code/routes/api_providers.py +53 -0
  69. painapple_code/routes/api_search.py +411 -0
  70. painapple_code/routes/api_session_stash.py +234 -0
  71. painapple_code/routes/api_session_welcome.py +151 -0
  72. painapple_code/routes/api_sessions.py +742 -0
  73. painapple_code/routes/api_shadow.py +323 -0
  74. painapple_code/routes/api_shadow_db.py +512 -0
  75. painapple_code/routes/api_shadow_files.py +332 -0
  76. painapple_code/routes/api_shadow_search.py +476 -0
  77. painapple_code/routes/api_skills.py +610 -0
  78. painapple_code/routes/api_tasks.py +147 -0
  79. painapple_code/routes/api_terminal.py +540 -0
  80. painapple_code/routes/api_upload.py +293 -0
  81. painapple_code/routes/api_viewer.py +229 -0
  82. painapple_code/routes/dependencies.py +109 -0
  83. painapple_code/routes/ws_chat.py +837 -0
  84. painapple_code/server.py +2208 -0
  85. painapple_code/server_logging.py +263 -0
  86. painapple_code/services/__init__.py +0 -0
  87. painapple_code/services/agent_session.py +2738 -0
  88. painapple_code/session_store.py +253 -0
  89. painapple_code/session_store_core.py +879 -0
  90. painapple_code/shadow_db.py +830 -0
  91. painapple_code/shadow_db_plans.py +369 -0
  92. painapple_code/shadow_db_queries.py +595 -0
  93. painapple_code/shadow_db_schema.py +676 -0
  94. painapple_code/shadow_git.py +1296 -0
  95. painapple_code/shadow_git_frontmatter.py +314 -0
  96. painapple_code/shadow_git_sections.py +520 -0
  97. painapple_code/shadow_git_summary.py +462 -0
  98. painapple_code/shadow_parser.py +560 -0
  99. painapple_code/static/css/00-variables.css +276 -0
  100. painapple_code/static/css/01-base.css +245 -0
  101. painapple_code/static/css/02-tooltips.css +36 -0
  102. painapple_code/static/css/09-left-rail.css +448 -0
  103. painapple_code/static/css/10-header.css +777 -0
  104. painapple_code/static/css/11-connection-bar.css +120 -0
  105. painapple_code/static/css/20-messages.css +750 -0
  106. painapple_code/static/css/21-tool-blocks.css +2340 -0
  107. painapple_code/static/css/22-edit-diff.css +619 -0
  108. painapple_code/static/css/23-thinking.css +1584 -0
  109. painapple_code/static/css/24-background-tasks.css +359 -0
  110. painapple_code/static/css/25-context-block.css +252 -0
  111. painapple_code/static/css/26-session-setup.css +144 -0
  112. painapple_code/static/css/30-markdown.css +854 -0
  113. painapple_code/static/css/40-input.css +2418 -0
  114. painapple_code/static/css/41-autocomplete.css +127 -0
  115. painapple_code/static/css/42-activity-strip.css +97 -0
  116. painapple_code/static/css/43-file-autocomplete.css +121 -0
  117. painapple_code/static/css/44-snippets-autocomplete.css +224 -0
  118. painapple_code/static/css/45-selection-bar.css +433 -0
  119. painapple_code/static/css/46-keyboard-bar.css +119 -0
  120. painapple_code/static/css/47-keyboard-longpress.css +116 -0
  121. painapple_code/static/css/47-token-profile.css +106 -0
  122. painapple_code/static/css/50-welcome.css +3095 -0
  123. painapple_code/static/css/51-modals.css +301 -0
  124. painapple_code/static/css/52-utilities.css +77 -0
  125. painapple_code/static/css/53-image-upload.css +456 -0
  126. painapple_code/static/css/54-todo-list.css +102 -0
  127. painapple_code/static/css/55-file-upload.css +113 -0
  128. painapple_code/static/css/56-chat-navigator.css +218 -0
  129. painapple_code/static/css/56-permission-card.css +332 -0
  130. painapple_code/static/css/56-plan-approval.css +204 -0
  131. painapple_code/static/css/56-question-form.css +602 -0
  132. painapple_code/static/css/57-swipe-indicator.css +95 -0
  133. painapple_code/static/css/58-session-families.css +627 -0
  134. painapple_code/static/css/59-about.css +196 -0
  135. painapple_code/static/css/61-log-explorer.css +723 -0
  136. painapple_code/static/css/62-json-tree.css +489 -0
  137. painapple_code/static/css/62-skills-widget.css +650 -0
  138. painapple_code/static/css/63-commands-widget.css +30 -0
  139. painapple_code/static/css/63-plugins-widget.css +174 -0
  140. painapple_code/static/css/63-terminal.css +516 -0
  141. painapple_code/static/css/64-search-files-widget.css +448 -0
  142. painapple_code/static/css/65-snippets-widget.css +426 -0
  143. painapple_code/static/css/66-active-sessions.css +979 -0
  144. painapple_code/static/css/66-agents-widget.css +265 -0
  145. painapple_code/static/css/66-file-explorer-widget.css +855 -0
  146. painapple_code/static/css/66-git-panel.css +791 -0
  147. painapple_code/static/css/66-history-explorer.css +1496 -0
  148. painapple_code/static/css/67-diff-viewer.css +602 -0
  149. painapple_code/static/css/67-editor-view.css +748 -0
  150. painapple_code/static/css/67-file-preview-widget.css +1873 -0
  151. painapple_code/static/css/68-browser-widget.css +337 -0
  152. painapple_code/static/css/68-compare-wizard.css +355 -0
  153. painapple_code/static/css/68-csv-preview.css +106 -0
  154. painapple_code/static/css/68-diff-preview.css +103 -0
  155. painapple_code/static/css/68-helpers-install-widget.css +491 -0
  156. painapple_code/static/css/68-image-annotate.css +346 -0
  157. painapple_code/static/css/68-terminal-view.css +98 -0
  158. painapple_code/static/css/68-uploads-widget.css +229 -0
  159. painapple_code/static/css/69-cost-analytics.css +537 -0
  160. painapple_code/static/css/70-config-panel.css +3736 -0
  161. painapple_code/static/css/70-excalidraw.css +136 -0
  162. painapple_code/static/css/70-prompt-explorer.css +890 -0
  163. painapple_code/static/css/71-chart.css +111 -0
  164. painapple_code/static/css/72-discussion-sidebar.css +678 -0
  165. painapple_code/static/css/74-debug-logs.css +656 -0
  166. painapple_code/static/css/80-open-dialog.css +146 -0
  167. painapple_code/static/css/80-quick-switcher.css +383 -0
  168. painapple_code/static/css/80-widget-system.css +1763 -0
  169. painapple_code/static/css/81-quick-actions.css +1020 -0
  170. painapple_code/static/css/82-context-popover.css +1233 -0
  171. painapple_code/static/css/83-context-menu.css +339 -0
  172. painapple_code/static/css/85-lazy-loading.css +102 -0
  173. painapple_code/static/css/86-zen-mode.css +708 -0
  174. painapple_code/static/css/87-grid-switcher.css +346 -0
  175. painapple_code/static/css/login.css +198 -0
  176. painapple_code/static/dist/app.bundle.js +7177 -0
  177. painapple_code/static/feature-triage.html +446 -0
  178. painapple_code/static/icons/apple-touch-icon.png +0 -0
  179. painapple_code/static/icons/favicon-32.png +0 -0
  180. painapple_code/static/icons/favicon.ico +0 -0
  181. painapple_code/static/icons/icon-128.png +0 -0
  182. painapple_code/static/icons/icon-144.png +0 -0
  183. painapple_code/static/icons/icon-152.png +0 -0
  184. painapple_code/static/icons/icon-180.png +0 -0
  185. painapple_code/static/icons/icon-192.png +0 -0
  186. painapple_code/static/icons/icon-384.png +0 -0
  187. painapple_code/static/icons/icon-512.png +0 -0
  188. painapple_code/static/icons/icon-72.png +0 -0
  189. painapple_code/static/icons/icon-96.png +0 -0
  190. painapple_code/static/js/action-delegate.js +187 -0
  191. painapple_code/static/js/activity-strip.js +193 -0
  192. painapple_code/static/js/app/context-menus.js +619 -0
  193. painapple_code/static/js/app/file-preview.js +163 -0
  194. painapple_code/static/js/app/init-ui.js +574 -0
  195. painapple_code/static/js/app/input-events.js +561 -0
  196. painapple_code/static/js/app/input-resize.js +313 -0
  197. painapple_code/static/js/app/message-actions.js +419 -0
  198. painapple_code/static/js/app/panels.js +210 -0
  199. painapple_code/static/js/app/rendering-delegators.js +246 -0
  200. painapple_code/static/js/app/server-loading.js +365 -0
  201. painapple_code/static/js/app/session-ops.js +287 -0
  202. painapple_code/static/js/app/session-switch.js +443 -0
  203. painapple_code/static/js/app/settings-helpers.js +280 -0
  204. painapple_code/static/js/app/startup-welcome.js +290 -0
  205. painapple_code/static/js/app-context.js +157 -0
  206. painapple_code/static/js/app.js +2134 -0
  207. painapple_code/static/js/auth-fetch.js +46 -0
  208. painapple_code/static/js/background-tasks.js +274 -0
  209. painapple_code/static/js/boot.js +73 -0
  210. painapple_code/static/js/caret-position.js +171 -0
  211. painapple_code/static/js/chat-navigator.js +710 -0
  212. painapple_code/static/js/chat-search.js +323 -0
  213. painapple_code/static/js/command-executor.js +176 -0
  214. painapple_code/static/js/command-store.js +497 -0
  215. painapple_code/static/js/components.js +1414 -0
  216. painapple_code/static/js/config.js +218 -0
  217. painapple_code/static/js/context-menu.js +740 -0
  218. painapple_code/static/js/controllers/chat-controller.js +4252 -0
  219. painapple_code/static/js/controllers/dialog-controller.js +217 -0
  220. painapple_code/static/js/controllers/tab-controller.js +1828 -0
  221. painapple_code/static/js/controllers/thinking-controller.js +915 -0
  222. painapple_code/static/js/diff-utils.js +486 -0
  223. painapple_code/static/js/drafts-pill.js +95 -0
  224. painapple_code/static/js/editor-view.js +815 -0
  225. painapple_code/static/js/effort-settings.js +430 -0
  226. painapple_code/static/js/feature-triage.js +811 -0
  227. painapple_code/static/js/file-autocomplete.js +516 -0
  228. painapple_code/static/js/file-tabs.js +123 -0
  229. painapple_code/static/js/gestures.js +448 -0
  230. painapple_code/static/js/grid-switcher.js +334 -0
  231. painapple_code/static/js/input-handler.js +1472 -0
  232. painapple_code/static/js/keyboard-bar.js +495 -0
  233. painapple_code/static/js/layout-switcher.js +109 -0
  234. painapple_code/static/js/linkify-utils.js +200 -0
  235. painapple_code/static/js/login.js +141 -0
  236. painapple_code/static/js/open-dialog.js +879 -0
  237. painapple_code/static/js/orphan-terminals.js +373 -0
  238. painapple_code/static/js/path-utils.js +179 -0
  239. painapple_code/static/js/perf-marks.js +102 -0
  240. painapple_code/static/js/permission-settings.js +475 -0
  241. painapple_code/static/js/pointer-input.js +59 -0
  242. painapple_code/static/js/preview/json-tree-menu.js +226 -0
  243. painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
  244. painapple_code/static/js/preview/json-tree.js +457 -0
  245. painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
  246. painapple_code/static/js/preview/preview-edit.js +367 -0
  247. painapple_code/static/js/preview/preview-events.js +361 -0
  248. painapple_code/static/js/preview/preview-history.js +567 -0
  249. painapple_code/static/js/preview/preview-inline-edit.js +869 -0
  250. painapple_code/static/js/preview/preview-poll.js +108 -0
  251. painapple_code/static/js/preview/preview-render.js +572 -0
  252. painapple_code/static/js/preview/preview-search.js +421 -0
  253. painapple_code/static/js/preview/preview-state.js +256 -0
  254. painapple_code/static/js/preview/preview-utils.js +251 -0
  255. painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
  256. painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
  257. painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
  258. painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
  259. painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
  260. painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
  261. painapple_code/static/js/preview-plugins/index.js +34 -0
  262. painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
  263. painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
  264. painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
  265. painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
  266. painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
  267. painapple_code/static/js/project-color-picker.js +110 -0
  268. painapple_code/static/js/project-colors.js +163 -0
  269. painapple_code/static/js/prompt-favorites.js +187 -0
  270. painapple_code/static/js/quick-actions-menu.js +1736 -0
  271. painapple_code/static/js/quick-actions-registry.js +1239 -0
  272. painapple_code/static/js/quick-switcher/controller.js +294 -0
  273. painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
  274. painapple_code/static/js/quick-switcher/index.js +30 -0
  275. painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
  276. painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
  277. painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
  278. painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
  279. painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
  280. painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
  281. painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
  282. painapple_code/static/js/quick-switcher/registry.js +69 -0
  283. painapple_code/static/js/quick-switcher/ui/item.js +54 -0
  284. painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
  285. painapple_code/static/js/recent-opens.js +40 -0
  286. painapple_code/static/js/scroll-manager.js +530 -0
  287. painapple_code/static/js/scroll-state-machine.js +359 -0
  288. painapple_code/static/js/selection/action-bar.js +793 -0
  289. painapple_code/static/js/selection/index.js +15 -0
  290. painapple_code/static/js/selection/selection-handler.js +877 -0
  291. painapple_code/static/js/selection/state.js +181 -0
  292. painapple_code/static/js/session/agent-progress.js +202 -0
  293. painapple_code/static/js/session/handle-agent-message.js +481 -0
  294. painapple_code/static/js/session/handle-message.js +550 -0
  295. painapple_code/static/js/session/interactive.js +587 -0
  296. painapple_code/static/js/session/message-store.js +271 -0
  297. painapple_code/static/js/session/messages.js +253 -0
  298. painapple_code/static/js/session/persistence.js +179 -0
  299. painapple_code/static/js/session/restore.js +136 -0
  300. painapple_code/static/js/session/sync.js +333 -0
  301. painapple_code/static/js/session-container-pool.js +371 -0
  302. painapple_code/static/js/session-setup-panel.js +274 -0
  303. painapple_code/static/js/session.js +1047 -0
  304. painapple_code/static/js/shortcut-hints.js +272 -0
  305. painapple_code/static/js/shortcuts.js +1146 -0
  306. painapple_code/static/js/skills-autocomplete.js +262 -0
  307. painapple_code/static/js/snippets-autocomplete.js +738 -0
  308. painapple_code/static/js/stash-refs-view.js +83 -0
  309. painapple_code/static/js/stash-ui.js +768 -0
  310. painapple_code/static/js/stash.js +785 -0
  311. painapple_code/static/js/status-bar.js +1184 -0
  312. painapple_code/static/js/token-profile.js +274 -0
  313. painapple_code/static/js/tool-renderer-blocks.js +1626 -0
  314. painapple_code/static/js/tool-renderer-thinking.js +813 -0
  315. painapple_code/static/js/tool-renderer.js +1500 -0
  316. painapple_code/static/js/tooltips.js +197 -0
  317. painapple_code/static/js/upload-manager.js +1019 -0
  318. painapple_code/static/js/utils.js +521 -0
  319. painapple_code/static/js/welcome/api.js +363 -0
  320. painapple_code/static/js/welcome/cards.js +626 -0
  321. painapple_code/static/js/welcome/context-menu.js +585 -0
  322. painapple_code/static/js/welcome/families.js +801 -0
  323. painapple_code/static/js/welcome/preview.js +234 -0
  324. painapple_code/static/js/welcome/state.js +129 -0
  325. painapple_code/static/js/welcome.js +1874 -0
  326. painapple_code/static/js/widget-system/base-widget.js +717 -0
  327. painapple_code/static/js/widget-system/device-manager.js +178 -0
  328. painapple_code/static/js/widget-system/event-bus.js +152 -0
  329. painapple_code/static/js/widget-system/icons.js +120 -0
  330. painapple_code/static/js/widget-system/index.js +49 -0
  331. painapple_code/static/js/widget-system/init.js +54 -0
  332. painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
  333. painapple_code/static/js/widget-system/types/floating.js +915 -0
  334. painapple_code/static/js/widget-system/types/index.js +40 -0
  335. painapple_code/static/js/widget-system/types/modal.js +147 -0
  336. painapple_code/static/js/widget-system/types/sidebar.js +158 -0
  337. painapple_code/static/js/widget-system/types/tab.js +146 -0
  338. painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
  339. painapple_code/static/js/widget-system/widget-manager.js +1187 -0
  340. painapple_code/static/js/widgets/about-widget.js +260 -0
  341. painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
  342. painapple_code/static/js/widgets/agents-widget.js +897 -0
  343. painapple_code/static/js/widgets/browser-widget.js +579 -0
  344. painapple_code/static/js/widgets/commands-widget.js +694 -0
  345. painapple_code/static/js/widgets/config/commit-sections.js +449 -0
  346. painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
  347. painapple_code/static/js/widgets/config/gestures.js +161 -0
  348. painapple_code/static/js/widgets/config/models-tab.js +988 -0
  349. painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
  350. painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
  351. painapple_code/static/js/widgets/config/state.js +517 -0
  352. painapple_code/static/js/widgets/config/system-controls.js +90 -0
  353. painapple_code/static/js/widgets/config-widget.js +1535 -0
  354. painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
  355. painapple_code/static/js/widgets/debug-widget.js +693 -0
  356. painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
  357. painapple_code/static/js/widgets/discussion-widget.js +1107 -0
  358. painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
  359. painapple_code/static/js/widgets/file-preview-widget.js +861 -0
  360. painapple_code/static/js/widgets/git-widget.js +794 -0
  361. painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
  362. painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
  363. painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
  364. painapple_code/static/js/widgets/image-preview-widget.js +692 -0
  365. painapple_code/static/js/widgets/index.js +176 -0
  366. painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
  367. painapple_code/static/js/widgets/plugins-widget.js +305 -0
  368. painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
  369. painapple_code/static/js/widgets/search-files-widget.js +529 -0
  370. painapple_code/static/js/widgets/skills-widget.js +843 -0
  371. painapple_code/static/js/widgets/snippets-widget.js +479 -0
  372. painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
  373. painapple_code/static/js/widgets/tasks-widget.js +379 -0
  374. painapple_code/static/js/widgets/terminal/connection.js +283 -0
  375. painapple_code/static/js/widgets/terminal/gestures.js +357 -0
  376. painapple_code/static/js/widgets/terminal/init.js +642 -0
  377. painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
  378. painapple_code/static/js/widgets/terminal/osc52.js +213 -0
  379. painapple_code/static/js/widgets/terminal/render.js +159 -0
  380. painapple_code/static/js/widgets/terminal/size.js +54 -0
  381. painapple_code/static/js/widgets/terminal/state.js +211 -0
  382. painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
  383. painapple_code/static/js/widgets/terminal-widget.js +793 -0
  384. painapple_code/static/js/widgets/uploads-widget.js +461 -0
  385. painapple_code/static/js/widgets/zen-widget.js +1001 -0
  386. painapple_code/static/login.html +51 -0
  387. painapple_code/static/manifest.json +73 -0
  388. painapple_code/static/sw.js +293 -0
  389. painapple_code/static/vendor/LICENSES.txt +769 -0
  390. painapple_code/static/vendor/README.md +103 -0
  391. painapple_code/static/vendor/codemirror.js +35 -0
  392. painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
  393. painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
  394. painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
  395. painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
  396. painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
  397. painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
  398. painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
  399. painapple_code/static/vendor/highlight.min.js +1213 -0
  400. painapple_code/static/vendor/marked.min.js +6 -0
  401. painapple_code/static/vendor/purify.min.js +3 -0
  402. painapple_code/static/vendor/xterm-addon-fit.js +2 -0
  403. painapple_code/static/vendor/xterm.css +209 -0
  404. painapple_code/static/vendor/xterm.js +2 -0
  405. painapple_code/static/web-client.html +635 -0
  406. painapple_code/subprocess_registry.py +535 -0
  407. painapple_code/tls_cert.py +79 -0
  408. painapple_code/tools/agents/shadow-git-helper.md +400 -0
  409. painapple_code/tools/excalidraw-to-svg.js +143 -0
  410. painapple_code/tools/install-helpers.sh +148 -0
  411. painapple_code/tools/shadow-git +320 -0
  412. painapple_code/tools/shadow-query +120 -0
  413. painapple_code/tools/vegalite-to-svg.js +79 -0
  414. painapple_code/turn_query.py +766 -0
  415. painapple_code/turn_tracker.py +221 -0
  416. painapple_code/utils/__init__.py +0 -0
  417. painapple_code/utils/agent_cli.py +306 -0
  418. painapple_code/utils/chart.py +103 -0
  419. painapple_code/utils/excalidraw.py +107 -0
  420. painapple_code/utils/file_lock.py +162 -0
  421. painapple_code/utils/file_paths.py +648 -0
  422. painapple_code/utils/generate_icons.py +251 -0
  423. painapple_code/utils/proc.py +205 -0
  424. painapple_code/utils/pty_backend.py +59 -0
  425. painapple_code/utils/pty_posix.py +160 -0
  426. painapple_code/utils/pty_windows.py +245 -0
  427. painapple_code/utils/token_profiles.py +91 -0
  428. painapple_code/viewer_templates.py +657 -0
  429. painapple_code/welcome_search.py +879 -0
  430. painapple_code-1.0.0.dist-info/METADATA +292 -0
  431. painapple_code-1.0.0.dist-info/RECORD +436 -0
  432. painapple_code-1.0.0.dist-info/WHEEL +5 -0
  433. painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
  434. painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
  435. painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
  436. painapple_code-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,443 @@
1
+ """``painapple start/stop/restart`` — background lifecycle for local
2
+ host-mode instances (the action verbs next to ``painapple list``).
3
+
4
+ There is no daemon or PID registry — a painapple server process IS the
5
+ instance — so these commands reuse ``list_cmd``'s process-table
6
+ discovery to find the target, and plain signals to stop it:
7
+
8
+ painapple start [NAME] spawn detached (logs to <home>/logs/),
9
+ wait until the port accepts, print the
10
+ login URL
11
+ painapple stop [NAME] SIGTERM → poll → SIGKILL
12
+ painapple restart [NAME] stop (if running), then start
13
+
14
+ ``NAME`` resolves in order: a saved serve profile (``painapple setup
15
+ NAME``) → a running instance's label (exact, then case-insensitive) →
16
+ its PID → its port — so anything `painapple list` prints is a valid
17
+ target. Omitted (or ``default``) targets the flag-less root deployment.
18
+
19
+ Profile targets restart from their saved serve.yaml. A label/pid/port
20
+ target has no saved config — it was launched ad hoc with flags — so
21
+ ``restart`` recaptures the live process's own command line, working
22
+ directory, and environment (via psutil, so on all three platforms) and
23
+ respawns it verbatim.
24
+
25
+ Extra serve flags after the name are forwarded to the spawned server
26
+ (``painapple start work --port 9001``; on an ad-hoc restart they're
27
+ appended, and argparse last-wins makes them override) — they apply to
28
+ THIS start only, they are not saved.
29
+
30
+ Matching a profile to a running process mirrors ``list_cmd.serve_profiles``:
31
+ exact data-home match (psutil ``environ()``) first, port match as the
32
+ fallback for processes whose environment isn't readable.
33
+ """
34
+
35
+ import os
36
+ import re
37
+ import socket
38
+ import subprocess
39
+ import sys
40
+ import time
41
+ from pathlib import Path
42
+
43
+ from painapple_code.cli import profiles, serve_config
44
+ from painapple_code.cli.ui import BOLD, DIM, GREEN, RESET, err, info, ok, say, warn
45
+ from painapple_code.utils.proc import pid_alive
46
+
47
+ START_TIMEOUT = 20 # seconds for the port to start accepting
48
+ STOP_TIMEOUT = 10 # seconds after SIGTERM before escalating to SIGKILL
49
+
50
+
51
+ # ──── Target resolution ──────────────────────────────────────────────────
52
+
53
+ def _resolve(prof):
54
+ """{profile, label, home, host, port} for a SAVED HOST target: a
55
+ profile name that exists on disk, or None = the root deployment."""
56
+ if prof:
57
+ home = profiles.profile_home(prof)
58
+ loaded = profiles.load(prof)
59
+ vals = loaded.host_values() if loaded else {}
60
+ else:
61
+ home = serve_config.home_root()
62
+ vals, _ = serve_config.load(home / "serve.yaml")
63
+ return {
64
+ "profile": prof,
65
+ "label": prof or "default",
66
+ "home": home,
67
+ "host": vals.get("host", "127.0.0.1"),
68
+ "port": int(vals.get("port", 8765)),
69
+ }
70
+
71
+
72
+ def _match(running, target):
73
+ """PID of the running server for this saved target, or None — the
74
+ same home/port matching `painapple list` uses to fill its rows, so
75
+ what the fleet view calls running is exactly what stop targets."""
76
+ from painapple_code.cli.list_cmd import match_process
77
+ return match_process(running, target["home"], target["port"])
78
+
79
+
80
+ def _match_running(token):
81
+ """(row, problem) — the running local server `token` names, by
82
+ instance label (exact, then case-insensitive), PID, or port.
83
+ (None, None) when nothing matches; (None, message) when the token is
84
+ ambiguous."""
85
+ from painapple_code.cli.list_cmd import local_servers
86
+ rows = local_servers()
87
+ matches = [r for r in rows if r["name"] and r["name"] == token]
88
+ if not matches:
89
+ matches = [r for r in rows
90
+ if r["name"] and r["name"].lower() == token.lower()]
91
+ if not matches and token.isdigit():
92
+ matches = ([r for r in rows if str(r["pid"]) == token]
93
+ or [r for r in rows if r["port"] == token])
94
+ if len(matches) > 1:
95
+ listing = ", ".join(f"{r['name'] or '—'} (pid {r['pid']}, "
96
+ f"port {r['port']})" for r in matches)
97
+ return None, f"{token!r} is ambiguous — matches {listing}. Use the pid."
98
+ return (matches[0] if matches else None), None
99
+
100
+
101
+ def _row_label(row):
102
+ return row["name"] or f"pid {row['pid']}"
103
+
104
+
105
+ # ──── stop ───────────────────────────────────────────────────────────────
106
+
107
+ def _wait_gone(pid, timeout):
108
+ # pid_alive is psutil, not os.kill(pid, 0): the POSIX idiom lies on
109
+ # Windows (sig 0 is CTRL_C_EVENT and succeeds even for dead pids).
110
+ # Same contract: exists → True, including other users' processes.
111
+ deadline = time.monotonic() + timeout
112
+ while time.monotonic() < deadline:
113
+ if not pid_alive(pid):
114
+ return True
115
+ time.sleep(0.2)
116
+ return not pid_alive(pid)
117
+
118
+
119
+ def _kill(label, pid):
120
+ # psutil, not raw signals: SIGKILL doesn't exist on win32 and os.kill
121
+ # semantics differ there; terminate() is SIGTERM on POSIX and
122
+ # TerminateProcess on Windows.
123
+ import psutil
124
+ from painapple_code.utils.proc import kill_pid
125
+ try:
126
+ psutil.Process(pid).terminate()
127
+ except psutil.NoSuchProcess:
128
+ say(f"{DIM}{label}: not running{RESET}")
129
+ return 0
130
+ except psutil.AccessDenied:
131
+ err(f"{label}: pid {pid} belongs to another user — can't stop it")
132
+ return 1
133
+ if not _wait_gone(pid, STOP_TIMEOUT):
134
+ warn(f"{label}: pid {pid} ignored SIGTERM for "
135
+ f"{STOP_TIMEOUT}s — sending SIGKILL")
136
+ kill_pid(pid)
137
+ if not _wait_gone(pid, 3):
138
+ err(f"{label}: pid {pid} would not die")
139
+ return 1
140
+ ok(f"Stopped {label} (pid {pid})")
141
+ return 0
142
+
143
+
144
+ def _stop(target):
145
+ from painapple_code.cli.list_cmd import local_servers
146
+ pid = _match(local_servers(), target)
147
+ if pid is None:
148
+ say(f"{DIM}{target['label']}: not running{RESET}")
149
+ return 0
150
+ return _kill(target["label"], pid)
151
+
152
+
153
+ # ──── start ──────────────────────────────────────────────────────────────
154
+
155
+ def _spawn_env(target):
156
+ """Child environment: the profile's data home exported explicitly
157
+ (so `painapple list` can home-match it via /proc/<pid>/environ) and
158
+ PAINAPPLE_PROFILE stamped for the instance-name default. The child's
159
+ activate() recognizes an already-profile-shaped home and doesn't
160
+ nest it (see profiles.activate)."""
161
+ env = {k: v for k, v in os.environ.items() if k != "PAINAPPLE_PROFILE"}
162
+ if target["profile"]:
163
+ env["PAINAPPLE_CODE_HOME"] = str(target["home"])
164
+ env["PAINAPPLE_PROFILE"] = target["profile"]
165
+ return env
166
+
167
+
168
+ def _print_login_url(*logs):
169
+ """Echo the login URL so a background start is as usable as a
170
+ foreground one.
171
+
172
+ Scans each log in turn: the URL lives in the startup box on stdout
173
+ (console.log), with server.log kept as a fallback for older builds
174
+ that logged it as an INFO line instead. Non-loopback binds hide the
175
+ ?tkn= URL from their own stdout by default, so when only the bare
176
+ box URL is found we rebuild the full login URL from the auth config
177
+ — `painapple start` runs as the operator on this box, who can read
178
+ that file anyway (it's exactly what `painapple password` prints)."""
179
+ bare = None
180
+ for log in logs:
181
+ try:
182
+ text = log.read_text(encoding="utf-8", errors="replace")[-8000:]
183
+ except OSError:
184
+ continue
185
+ for line in reversed(text.splitlines()):
186
+ m = re.search(r"https?://\S+\?tkn=\S+", line)
187
+ if m:
188
+ say(f" {BOLD}Log in:{RESET} {BOLD}{GREEN}{m.group(0)}{RESET}")
189
+ return
190
+ if bare is None and "pAInapple Code:" in line:
191
+ b = re.search(r"https?://\S+", line)
192
+ if b:
193
+ bare = b.group(0).rstrip("/")
194
+ if bare:
195
+ from painapple_code.cli.manage_cmd import _host_token_value
196
+ token = _host_token_value()
197
+ url = f"{bare}/?tkn={token}" if token else f"{bare}/"
198
+ say(f" {BOLD}Log in:{RESET} {BOLD}{GREEN}{url}{RESET}")
199
+
200
+
201
+ def _tail(path, n=15):
202
+ try:
203
+ lines = path.read_text(encoding="utf-8", errors="replace").splitlines()[-n:]
204
+ except OSError:
205
+ return
206
+ for line in lines:
207
+ say(f" {DIM}{line}{RESET}")
208
+
209
+
210
+ def _spawn_and_wait(label, argv, *, env, cwd, home, host, port):
211
+ """Detached spawn + port-readiness wait + login-URL echo — shared by
212
+ profile starts and ad-hoc restarts. env/cwd None = inherit ours."""
213
+ logs = Path(home).expanduser() / "logs"
214
+ logs.mkdir(parents=True, exist_ok=True)
215
+ console = logs / "console.log"
216
+ with open(console, "ab") as out:
217
+ out.write(f"\n--- painapple start {label} ---\n".encode())
218
+ out.flush()
219
+ from painapple_code.utils.proc import popen_kwargs_detached
220
+ proc = subprocess.Popen(
221
+ argv, stdout=out, stderr=subprocess.STDOUT,
222
+ stdin=subprocess.DEVNULL, env=env, cwd=cwd,
223
+ **popen_kwargs_detached(fully_detached=True))
224
+
225
+ info(f"Starting {label} (pid {proc.pid}) on {host}:{port} …")
226
+ probe_host = "127.0.0.1" if host in ("0.0.0.0", "::") else host
227
+ deadline = time.monotonic() + START_TIMEOUT
228
+ while time.monotonic() < deadline:
229
+ if proc.poll() is not None:
230
+ err(f"{label} exited during startup "
231
+ f"(code {proc.returncode}) — last output:")
232
+ _tail(console)
233
+ return 1
234
+ try:
235
+ with socket.create_connection((probe_host, port), timeout=1):
236
+ pass
237
+ except OSError:
238
+ time.sleep(0.3)
239
+ continue
240
+ # Something answers — but make sure it's OUR child and not a
241
+ # foreign listener that was already there (ours would have died
242
+ # on its own bind pre-flight, a moment after this connect).
243
+ time.sleep(0.3)
244
+ if proc.poll() is None:
245
+ break
246
+ err(f"{label} exited during startup (code {proc.returncode}) — "
247
+ f"something else is answering on {probe_host}:{port}. "
248
+ f"Last output:")
249
+ _tail(console)
250
+ return 1
251
+ else:
252
+ warn(f"{label}: pid {proc.pid} is up but {probe_host}:{port} "
253
+ f"didn't accept within {START_TIMEOUT}s — check {console}")
254
+ return 1
255
+
256
+ ok(f"{label} running — pid {proc.pid} on {host}:{port}")
257
+ _print_login_url(console, logs / "server.log")
258
+ return 0
259
+
260
+
261
+ def _start(target, extra):
262
+ from painapple_code.cli.list_cmd import local_servers
263
+ running = local_servers()
264
+ pid = _match(running, target)
265
+ if pid is not None:
266
+ warn(f"{target['label']} is already running (pid {pid}) — "
267
+ f"use: painapple restart {target['profile'] or ''}".rstrip())
268
+ return 1
269
+
270
+ # Validate forwarded serve flags HERE (same import-light parser the
271
+ # serve gate uses) so a typo fails loudly instead of inside a
272
+ # detached child. parse_args exits 2 with usage on a bad flag.
273
+ if extra:
274
+ from painapple_code.cli.serve_args import build_parser
275
+ build_parser().parse_args(extra)
276
+
277
+ host = _cli_flag(extra, "--host") or target["host"]
278
+ port = int(_cli_flag(extra, "--port") or target["port"])
279
+
280
+ # Bind test, not a scan of our own process table — a port held by
281
+ # ANYTHING (another painapple, a stray dev server) must fail here,
282
+ # loudly, rather than inside a detached child nobody is watching.
283
+ from painapple_code.cli.netinfo import port_holder, port_taken
284
+ reason = port_taken(host, port)
285
+ if reason:
286
+ holder = port_holder(port)
287
+ err(f"Port {port} on {host} is already in use — {reason}"
288
+ + (f" (held by {holder})" if holder else ""))
289
+ return 1
290
+
291
+ return _spawn_and_wait(
292
+ target["label"], [sys.executable, "-m", "painapple_code", *extra],
293
+ env=_spawn_env(target), cwd=None, home=target["home"],
294
+ host=host, port=port)
295
+
296
+
297
+ def _cli_flag(args, name):
298
+ for i, tok in enumerate(args):
299
+ if tok == name and i + 1 < len(args):
300
+ return args[i + 1]
301
+ if tok.startswith(name + "="):
302
+ return tok.split("=", 1)[1]
303
+ return None
304
+
305
+
306
+ # ──── Ad-hoc targets (label / pid / port of a flag-launched server) ──────
307
+
308
+ def _respawn_spec(row):
309
+ """(argv, cwd, env) to faithfully relaunch this running server, or None.
310
+
311
+ psutil for all three fields — the same wrappers `painapple list`
312
+ already uses. The hand-rolled /proc cmdline/cwd/environ reader this
313
+ replaces was the PREFERRED branch and Linux-only, so the platforms
314
+ this branch adds fell through to `return list(argv), None, None` and
315
+ respawned WITHOUT the process's working directory or environment:
316
+ lossy on exactly the hosts nobody exercises locally.
317
+
318
+ Each field is best-effort on its own — AccessDenied reading environ()
319
+ must not cost us the argv — and the process-scan argv stays as the
320
+ fallback for cmdline().
321
+ """
322
+ import psutil
323
+
324
+ from painapple_code.cli.list_cmd import _proc_environ
325
+
326
+ pid = row["pid"]
327
+ argv = list(row.get("argv") or [])
328
+ cwd = None
329
+ env = _proc_environ(pid) # None = unreadable → inherit ours
330
+ try:
331
+ proc = psutil.Process(pid)
332
+ argv = list(proc.cmdline()) or argv
333
+ cwd = proc.cwd()
334
+ except Exception:
335
+ pass # other-user/vanished process — the kill will refuse anyway
336
+ return (argv, cwd, env) if argv else None
337
+
338
+
339
+ def _restart_adhoc(row, extra):
340
+ """Restart a running instance that has NO saved profile: recapture
341
+ its command line BEFORE killing it, then respawn verbatim (+ any
342
+ forwarded flags, which argparse last-wins into overrides)."""
343
+ label = _row_label(row)
344
+ spec = _respawn_spec(row)
345
+ if spec is None:
346
+ err(f"{label}: can't recover the process command line to respawn it")
347
+ return 1
348
+ if extra:
349
+ from painapple_code.cli.serve_args import build_parser
350
+ build_parser().parse_args(extra)
351
+ argv, cwd, env = spec
352
+ rc = _kill(label, row["pid"])
353
+ if rc:
354
+ return rc
355
+ host = _cli_flag(extra, "--host") or row["host"]
356
+ port = int(_cli_flag(extra, "--port") or row["port"])
357
+ home = row.get("home") or os.environ.get("PAINAPPLE_CODE_HOME",
358
+ "~/.painapple-code")
359
+ return _spawn_and_wait(label, argv + list(extra), env=env, cwd=cwd,
360
+ home=home, host=host, port=port)
361
+
362
+
363
+ # ──── Command ────────────────────────────────────────────────────────────
364
+
365
+ def main(cmd, argv):
366
+ """Dispatch target for start/stop/restart. The target is a leading
367
+ positional (``painapple restart work`` / ``STAGING`` / a pid / a
368
+ port), ``--profile NAME``, or $PAINAPPLE_PROFILE; nothing targets
369
+ the root deployment."""
370
+ try:
371
+ prof, rest = serve_config.extract_profile(list(argv or []))
372
+ except ValueError as e:
373
+ err(str(e))
374
+ return 2
375
+ if rest and not rest[0].startswith("-"):
376
+ if prof and prof != rest[0]:
377
+ err(f"Profile given twice: {rest[0]!r} and --profile {prof!r}")
378
+ return 2
379
+ prof, rest = rest[0], rest[1:]
380
+ if prof in ("", "default"):
381
+ prof = None
382
+ if cmd == "stop" and rest:
383
+ err(f"stop takes no flags (got: {' '.join(rest)})")
384
+ return 2
385
+
386
+ profiles.ensure_migrated()
387
+
388
+ # A saved DOCKER profile: the container runtime already is the
389
+ # background lifecycle — start = run -d, stop = container stop.
390
+ if prof and profiles.exists(prof):
391
+ loaded = profiles.load(prof)
392
+ if loaded is not None and loaded.is_docker:
393
+ return _docker_lifecycle(cmd, loaded, rest)
394
+
395
+ # Not a saved profile → try what `painapple list` shows: a running
396
+ # instance's label, pid, or port.
397
+ if prof and not profiles.exists(prof):
398
+ row, problem = _match_running(prof)
399
+ if problem:
400
+ err(problem)
401
+ return 1
402
+ if row is not None:
403
+ if cmd == "stop":
404
+ return _kill(_row_label(row), row["pid"])
405
+ if cmd == "restart":
406
+ return _restart_adhoc(row, rest)
407
+ warn(f"{_row_label(row)} is already running (pid {row['pid']}) — "
408
+ f"use: painapple restart {prof}")
409
+ return 1
410
+ err(f"{prof!r} matches no saved profile and no running instance "
411
+ f"(label, pid, or port).")
412
+ if profiles.valid_name(prof):
413
+ say(f" {DIM}Create a profile with:{RESET} painapple setup {prof}")
414
+ return 1
415
+
416
+ target = _resolve(prof)
417
+ if cmd == "stop":
418
+ return _stop(target)
419
+ if cmd == "restart":
420
+ rc = _stop(target)
421
+ if rc:
422
+ return rc
423
+ return _start(target, rest)
424
+
425
+
426
+ def _docker_lifecycle(cmd, loaded, rest):
427
+ """start/stop/restart for a docker-mode profile — delegates to the
428
+ container runtime (detached runs are durable: --restart
429
+ unless-stopped)."""
430
+ from painapple_code.cli.deploy.container import (
431
+ remove_container, stop_container)
432
+ from painapple_code.cli.deploy.launch import profile_settings
433
+ try:
434
+ cfg = profile_settings(loaded, rest)
435
+ if cmd == "stop":
436
+ return stop_container(cfg, profile=loaded.name)
437
+ if cmd == "restart":
438
+ remove_container(cfg)
439
+ from painapple_code.cli.deploy.container import run_container
440
+ return run_container(cfg, detach=True, profile=loaded.name)
441
+ except KeyboardInterrupt:
442
+ say()
443
+ return 130