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,369 @@
1
+ """Plan tracking for ShadowDB.
2
+
3
+ Mixed into ShadowDB. Owns plan-record creation (triggered by
4
+ EnterPlanMode / ExitPlanMode tool usage), turn-to-plan linking, and the
5
+ plan list/detail/stats queries surfaced by `/api/shadow-db/plans`.
6
+ """
7
+
8
+ import logging
9
+ import uuid
10
+ from datetime import datetime, timezone
11
+ from typing import Optional
12
+
13
+ logger = logging.getLogger("painapple-code.shadow-db")
14
+
15
+
16
+ class _PlansMixin:
17
+ """Plan CRUD, linking, and queries for ShadowDB."""
18
+
19
+ def _create_plan_from_turn(self, turn_id: str, modified_files: Optional[list[str]],
20
+ structured_data: Optional[dict] = None):
21
+ """Create a plan record when EnterPlanMode/ExitPlanMode detected."""
22
+ from pathlib import Path as _Path
23
+
24
+ # Find .claude/plans/ file
25
+ plan_file = None
26
+ plan_name = None
27
+ if modified_files:
28
+ for f in modified_files:
29
+ if '.claude/plans/' in f and f.endswith('.md'):
30
+ plan_file = f
31
+ plan_name = _Path(f).stem
32
+ break
33
+
34
+ # Extract plan title/summary from summary-fork structured data
35
+ title = None
36
+ summary = None
37
+ if structured_data:
38
+ title = structured_data.get("plan_title") or structured_data.get("session_title")
39
+ summary = structured_data.get("plan_summary") or structured_data.get("summary")
40
+
41
+ # Get turn info for session/project
42
+ turn = self._fetch_one(
43
+ "SELECT session_id, project_hash, git_repo_hash FROM turns WHERE id = ?",
44
+ [turn_id]
45
+ )
46
+ if not turn:
47
+ return
48
+
49
+ plan_id = str(uuid.uuid4())
50
+ self._execute("""
51
+ INSERT INTO plans (id, turn_id, session_id, project_hash, git_repo_hash,
52
+ plan_file, plan_name, title, summary, status)
53
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'proposed')
54
+ ON CONFLICT (turn_id) DO UPDATE SET
55
+ plan_file = COALESCE(EXCLUDED.plan_file, plans.plan_file),
56
+ plan_name = COALESCE(EXCLUDED.plan_name, plans.plan_name),
57
+ title = COALESCE(EXCLUDED.title, plans.title),
58
+ summary = COALESCE(EXCLUDED.summary, plans.summary),
59
+ updated_at = now()
60
+ """, [plan_id, turn_id, turn[0], turn[1], turn[2],
61
+ plan_file, plan_name, title, summary])
62
+
63
+ # Also link this turn to the plan as the 'planning' role
64
+ self._execute("""
65
+ INSERT INTO plan_turns (id, plan_id, turn_id, role)
66
+ VALUES (nextval('seq_plan_turns'), ?, ?, 'planning')
67
+ ON CONFLICT (plan_id, turn_id) DO NOTHING
68
+ """, [plan_id, turn_id])
69
+
70
+ def _link_turn_to_plan(self, turn_id: str):
71
+ """Link a non-plan turn to the most recent active plan in its session.
72
+
73
+ A plan is 'active' if it's proposed/approved and was the last plan in this session.
74
+ The first non-plan turn after a plan automatically marks it as approved.
75
+ """
76
+ # Get this turn's session
77
+ row = self._fetch_one(
78
+ "SELECT session_id FROM turns WHERE id = ?", [turn_id]
79
+ )
80
+ if not row:
81
+ return
82
+ session_id = row[0]
83
+
84
+ # Find the most recent plan in this session
85
+ plan = self._fetch_one("""
86
+ SELECT p.id, p.status, p.turn_id FROM plans p
87
+ WHERE p.session_id = ? AND p.status IN ('proposed', 'approved')
88
+ ORDER BY p.created_at DESC LIMIT 1
89
+ """, [session_id])
90
+ if not plan:
91
+ return
92
+
93
+ plan_id, plan_status, plan_turn_id = plan
94
+
95
+ # Don't link the planning turn itself
96
+ if turn_id == plan_turn_id:
97
+ return
98
+
99
+ # Link this turn as implementation
100
+ self._execute("""
101
+ INSERT INTO plan_turns (id, plan_id, turn_id, role)
102
+ VALUES (nextval('seq_plan_turns'), ?, ?, 'implementation')
103
+ ON CONFLICT (plan_id, turn_id) DO NOTHING
104
+ """, [plan_id, turn_id])
105
+
106
+ # First implementation turn auto-approves the plan
107
+ now = datetime.now(timezone.utc).isoformat()
108
+ if plan_status == 'proposed':
109
+ self._execute("""
110
+ UPDATE plans SET status = 'approved', approved_at = ?, updated_at = ?
111
+ WHERE id = ? AND status = 'proposed'
112
+ """, [now, now, plan_id])
113
+
114
+ # Update plan metrics
115
+ self._execute("""
116
+ UPDATE plans SET
117
+ implementation_turns = (
118
+ SELECT COUNT(*) FROM plan_turns WHERE plan_id = ? AND role = 'implementation'
119
+ ),
120
+ implementation_cost = (
121
+ SELECT COALESCE(SUM(t.cost), 0) FROM plan_turns pt
122
+ JOIN turns t ON pt.turn_id = t.id WHERE pt.plan_id = ? AND pt.role = 'implementation'
123
+ ),
124
+ files_changed = (
125
+ SELECT COUNT(DISTINCT tf.file_path) FROM plan_turns pt
126
+ JOIN turn_files tf ON pt.turn_id = tf.turn_id
127
+ WHERE pt.plan_id = ? AND pt.role = 'implementation'
128
+ ),
129
+ updated_at = ?
130
+ WHERE id = ?
131
+ """, [plan_id, plan_id, plan_id, now, plan_id])
132
+
133
+ def _backfill_plans(self, con):
134
+ """One-time backfill: create plan records from existing turn_tools data."""
135
+ try:
136
+ # Check if plans table has any rows
137
+ count = con.execute("SELECT COUNT(*) FROM plans").fetchone()[0]
138
+ if count > 0:
139
+ return # Already has data, skip backfill
140
+
141
+ from pathlib import Path as _Path
142
+
143
+ rows = con.execute("""
144
+ SELECT DISTINCT t.id, t.session_id, t.project_hash, t.git_repo_hash
145
+ FROM turns t
146
+ JOIN turn_tools tt ON t.id = tt.turn_id
147
+ WHERE tt.tool_name IN ('EnterPlanMode', 'ExitPlanMode')
148
+ """).fetchall()
149
+
150
+ for turn_id, session_id, project_hash, git_repo_hash in rows:
151
+ # Mark the turn
152
+ con.execute("UPDATE turns SET is_plan = true WHERE id = ?", [turn_id])
153
+
154
+ # Find plan file
155
+ file_row = con.execute("""
156
+ SELECT file_path FROM turn_files
157
+ WHERE turn_id = ? AND file_path LIKE '%.claude/plans/%.md'
158
+ LIMIT 1
159
+ """, [turn_id]).fetchone()
160
+ plan_file = file_row[0] if file_row else None
161
+ plan_name = _Path(plan_file).stem if plan_file else None
162
+
163
+ # Get summary
164
+ summary_row = con.execute("""
165
+ SELECT value_text FROM turn_fields
166
+ WHERE turn_id = ? AND field_key = 'summary'
167
+ """, [turn_id]).fetchone()
168
+
169
+ con.execute("""
170
+ INSERT INTO plans (id, turn_id, session_id, project_hash, git_repo_hash,
171
+ plan_file, plan_name, title, summary, status)
172
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'approved')
173
+ ON CONFLICT (turn_id) DO NOTHING
174
+ """, [str(uuid.uuid4()), turn_id, session_id, project_hash, git_repo_hash,
175
+ plan_file, plan_name, plan_name,
176
+ summary_row[0] if summary_row else None])
177
+
178
+ if rows:
179
+ logger.info(f"Backfilled {len(rows)} plan records from existing turn_tools data")
180
+ except Exception as e:
181
+ logger.warning(f"Plan backfill failed (non-fatal): {e}")
182
+
183
+ def list_plans(
184
+ self,
185
+ project_hash: Optional[str] = None,
186
+ git_repo_hash: Optional[str] = None,
187
+ session_id: Optional[str] = None,
188
+ status: Optional[str] = None,
189
+ search: Optional[str] = None,
190
+ limit: int = 50,
191
+ offset: int = 0,
192
+ ) -> dict:
193
+ """List plans with optional filters. Returns {plans, total, has_more}."""
194
+ conditions = []
195
+ params = []
196
+
197
+ if git_repo_hash:
198
+ conditions.append("p.git_repo_hash = ?")
199
+ params.append(git_repo_hash)
200
+ elif project_hash:
201
+ conditions.append("p.project_hash = ?")
202
+ params.append(project_hash)
203
+ if session_id:
204
+ conditions.append("p.session_id = ?")
205
+ params.append(session_id)
206
+ if status:
207
+ conditions.append("p.status = ?")
208
+ params.append(status)
209
+ if search:
210
+ if self._fts_ready:
211
+ self.rebuild_fts_if_needed()
212
+ # FTS on turn_fields, ILIKE on short plan-specific columns and tags
213
+ conditions.append(
214
+ "(p.title ILIKE ? OR p.summary ILIKE ? OR p.plan_name ILIKE ? "
215
+ "OR EXISTS (SELECT 1 FROM turn_fields tf WHERE tf.turn_id = p.turn_id "
216
+ "AND fts_main_turn_fields.match_bm25(tf.id, ?) IS NOT NULL) "
217
+ "OR EXISTS (SELECT 1 FROM tags tg WHERE tg.turn_id = p.turn_id "
218
+ "AND tg.tag ILIKE ?))"
219
+ )
220
+ s = f"%{search}%"
221
+ params.extend([s, s, s, search, s])
222
+ else:
223
+ conditions.append(
224
+ "(p.title ILIKE ? OR p.summary ILIKE ? OR p.plan_name ILIKE ? "
225
+ "OR EXISTS (SELECT 1 FROM turn_fields tf WHERE tf.turn_id = p.turn_id "
226
+ "AND tf.search_text ILIKE ?) "
227
+ "OR EXISTS (SELECT 1 FROM tags tg WHERE tg.turn_id = p.turn_id "
228
+ "AND tg.tag ILIKE ?))"
229
+ )
230
+ s = f"%{search}%"
231
+ params.extend([s, s, s, s, s])
232
+
233
+ where = " AND ".join(conditions) if conditions else "1=1"
234
+
235
+ total = self._fetch_one(
236
+ f"SELECT COUNT(*) FROM plans p WHERE {where}", params
237
+ )[0]
238
+
239
+ rows = self._fetch_all(
240
+ f"""SELECT p.id, p.turn_id, p.session_id, p.project_hash,
241
+ p.plan_file, p.plan_name, p.title, p.summary,
242
+ p.status, p.approved_at, p.implementation_turns,
243
+ p.implementation_cost, p.files_changed,
244
+ p.created_at, p.updated_at,
245
+ t.user_prompt, t.cost as plan_cost, t.model, t.git_branch
246
+ FROM plans p
247
+ JOIN turns t ON p.turn_id = t.id
248
+ WHERE {where}
249
+ ORDER BY p.created_at DESC
250
+ LIMIT ? OFFSET ?""",
251
+ params + [limit, offset]
252
+ )
253
+
254
+ columns = ["id", "turn_id", "session_id", "project_hash",
255
+ "plan_file", "plan_name", "title", "summary",
256
+ "status", "approved_at", "implementation_turns",
257
+ "implementation_cost", "files_changed",
258
+ "created_at", "updated_at",
259
+ "user_prompt", "plan_cost", "model", "git_branch"]
260
+
261
+ plans = []
262
+ for row in rows:
263
+ plan = dict(zip(columns, row))
264
+ # Attach tags from the planning turn
265
+ plan["tags"] = [r[0] for r in self._fetch_all(
266
+ "SELECT tag FROM tags WHERE turn_id = ?", [plan["turn_id"]]
267
+ )]
268
+ plans.append(plan)
269
+
270
+ return {"plans": plans, "total": total, "has_more": offset + limit < total}
271
+
272
+ def get_plan(self, plan_id: str) -> Optional[dict]:
273
+ """Get full plan details with implementation turns."""
274
+ columns, rows = self._fetch_columns(
275
+ """SELECT p.*, t.user_prompt, t.cost as plan_cost, t.model,
276
+ t.git_branch, t.duration_ms as plan_duration_ms
277
+ FROM plans p JOIN turns t ON p.turn_id = t.id
278
+ WHERE p.id = ?""",
279
+ [plan_id]
280
+ )
281
+ if not rows:
282
+ return None
283
+
284
+ plan = dict(zip(columns, rows[0]))
285
+
286
+ # Tags from planning turn
287
+ plan["tags"] = [r[0] for r in self._fetch_all(
288
+ "SELECT tag FROM tags WHERE turn_id = ?", [plan["turn_id"]]
289
+ )]
290
+
291
+ # Implementation turns
292
+ impl_rows = self._fetch_all("""
293
+ SELECT pt.role, t.id, t.user_prompt, t.started_at, t.cost,
294
+ t.model, t.duration_ms, tf.value_text as summary
295
+ FROM plan_turns pt
296
+ JOIN turns t ON pt.turn_id = t.id
297
+ LEFT JOIN turn_fields tf ON t.id = tf.turn_id AND tf.field_key = 'summary'
298
+ WHERE pt.plan_id = ?
299
+ ORDER BY t.started_at
300
+ """, [plan_id])
301
+
302
+ plan["turns"] = [
303
+ {"role": r[0], "turn_id": r[1], "user_prompt": r[2],
304
+ "started_at": str(r[3]) if r[3] else None, "cost": float(r[4] or 0),
305
+ "model": r[5], "duration_ms": r[6], "summary": r[7]}
306
+ for r in impl_rows
307
+ ]
308
+
309
+ return plan
310
+
311
+ def update_plan_status(self, plan_id: str, status: str):
312
+ """Manually update a plan's status (approve/reject/abandon)."""
313
+ now = datetime.now(timezone.utc).isoformat()
314
+ params = [status, now]
315
+ extra = ""
316
+ if status == "approved":
317
+ extra = ", approved_at = COALESCE(approved_at, ?)"
318
+ params.append(now)
319
+ params.append(plan_id)
320
+ self._execute(
321
+ f"UPDATE plans SET status = ?, updated_at = ?{extra} WHERE id = ?",
322
+ params
323
+ )
324
+
325
+ def update_plan_title(self, plan_id: str, title: str):
326
+ """Update a plan's title."""
327
+ self._execute(
328
+ "UPDATE plans SET title = ?, updated_at = now() WHERE id = ?",
329
+ [title, plan_id]
330
+ )
331
+
332
+ def get_plan_stats(self, project_hash: Optional[str] = None,
333
+ git_repo_hash: Optional[str] = None) -> dict:
334
+ """Get plan statistics: counts by status, avg implementation cost, etc."""
335
+ conditions = []
336
+ params = []
337
+ if git_repo_hash:
338
+ conditions.append("git_repo_hash = ?")
339
+ params.append(git_repo_hash)
340
+ elif project_hash:
341
+ conditions.append("project_hash = ?")
342
+ params.append(project_hash)
343
+ where = " AND ".join(conditions) if conditions else "1=1"
344
+
345
+ row = self._fetch_one_dict(
346
+ f"""SELECT COUNT(*) as total,
347
+ COUNT(CASE WHEN status = 'proposed' THEN 1 END) as proposed,
348
+ COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved,
349
+ COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected,
350
+ COUNT(CASE WHEN status = 'abandoned' THEN 1 END) as abandoned,
351
+ AVG(CASE WHEN status = 'approved' THEN implementation_turns END) as avg_impl_turns,
352
+ AVG(CASE WHEN status = 'approved' THEN implementation_cost END) as avg_impl_cost,
353
+ SUM(implementation_cost) as total_impl_cost,
354
+ AVG(CASE WHEN status = 'approved' THEN files_changed END) as avg_files_changed
355
+ FROM plans WHERE {where}""",
356
+ params
357
+ )
358
+
359
+ return {
360
+ "total": row["total"],
361
+ "proposed": row["proposed"],
362
+ "approved": row["approved"],
363
+ "rejected": row["rejected"],
364
+ "abandoned": row["abandoned"],
365
+ "avg_implementation_turns": round(float(row["avg_impl_turns"] or 0), 1),
366
+ "avg_implementation_cost": round(float(row["avg_impl_cost"] or 0), 4),
367
+ "total_implementation_cost": round(float(row["total_impl_cost"] or 0), 4),
368
+ "avg_files_changed": round(float(row["avg_files_changed"] or 0), 1),
369
+ }