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,462 @@
1
+ """
2
+ Shadow Git - summary fork for rich commit messages.
3
+
4
+ `ShadowGit.commit_turn` delegates to the methods in `_SummaryMixin` when the
5
+ project has rich commits enabled:
6
+
7
+ - `_build_journey_context` reads previous commits' YAML frontmatter and
8
+ filters by branch to assemble a per-session journey timeline.
9
+ - `_generate_rich_commit_message` is the heavy lifter: it asks the session's
10
+ provider how to fork itself (`build_summary_fork`), spawns that subprocess,
11
+ registers it for visibility in the Active Sessions widget, parses the
12
+ structured JSON response, and renders a markdown commit body.
13
+ - `_extract_title` mines a one-liner title out of the rendered body.
14
+ - `_update_session_meta` rolls per-turn cost/file/tool counters back into
15
+ the session's `meta.json` (atomic write).
16
+
17
+ The mixin holds no state of its own — every method uses attributes set by
18
+ `ShadowGit.__init__` (`self.config`, `self.project_path`) and helpers from
19
+ the core class (`self._run`).
20
+ """
21
+
22
+ import asyncio
23
+ import json
24
+ import logging
25
+ import os
26
+ import tempfile
27
+ import time
28
+ from typing import Optional
29
+
30
+ from painapple_code.paths import get_sessions_dir
31
+ from painapple_code.subprocess_registry import agent_subprocesses, SubprocessType, SubprocessStatus
32
+ from painapple_code.utils.proc import popen_kwargs_detached, resolve_binary
33
+ from painapple_code.turn_tracker import TurnTracker
34
+
35
+ from painapple_code.shadow_git_frontmatter import (
36
+ SummaryCost,
37
+ MAX_JOURNEY_TURNS,
38
+ build_journey_section,
39
+ parse_yaml_frontmatter,
40
+ )
41
+ from painapple_code.shadow_git_sections import (
42
+ build_commit_prompt_for_json,
43
+ build_commit_schema,
44
+ structured_to_markdown,
45
+ )
46
+
47
+ logger = logging.getLogger("painapple-code.shadow-git")
48
+
49
+
50
+ class _SummaryMixin:
51
+ """Rich-commit methods mixed into ShadowGit. Not instantiated directly."""
52
+
53
+ def _extract_title(self, message_body: str) -> str:
54
+ """Extract title from rich commit message (first ## Summary line)."""
55
+ for line in message_body.split("\n"):
56
+ line = line.strip()
57
+ if line.startswith("## Summary"):
58
+ continue
59
+ if line and not line.startswith("#"):
60
+ return line[:60]
61
+ return "Update"
62
+
63
+ async def _build_journey_context(
64
+ self,
65
+ session_id: str,
66
+ current_branch: Optional[str] = None
67
+ ) -> list[dict]:
68
+ """
69
+ Build journey context from previous commits for this session.
70
+
71
+ Fetches previous commits and extracts their summaries from YAML frontmatter.
72
+ When current_branch is provided, only includes entries from the same branch
73
+ to prevent context bleeding across branches.
74
+
75
+ Args:
76
+ session_id: Session to get journey for
77
+ current_branch: Current project branch (for filtering)
78
+
79
+ Returns:
80
+ List of journey entries: [{turn: 1, summary: "..."}, ...]
81
+ """
82
+ journey = []
83
+
84
+ try:
85
+ # Get previous commits for this session (most recent first)
86
+ # Fetch extra commits to allow for branch filtering
87
+ stdout, _, exit_code = await self._run([
88
+ "log",
89
+ f"--grep=\\[{session_id[:8]}",
90
+ f"-n{MAX_JOURNEY_TURNS * 2 + 1}", # Get extra for branch filtering
91
+ "--format=%H"
92
+ ], check=False)
93
+
94
+ if exit_code != 0 or not stdout.strip():
95
+ return []
96
+
97
+ commit_hashes = stdout.strip().split("\n")
98
+
99
+ # Fetch full commit message for each and extract summary
100
+ # Get extra commits to allow for branch filtering
101
+ for commit_hash in commit_hashes[:MAX_JOURNEY_TURNS * 2]:
102
+ if not commit_hash:
103
+ continue
104
+ if len(journey) >= MAX_JOURNEY_TURNS:
105
+ break
106
+
107
+ try:
108
+ msg_stdout, _, _ = await self._run([
109
+ "log", "-1", "--format=%B", commit_hash
110
+ ])
111
+
112
+ # Parse YAML frontmatter
113
+ yaml_data = parse_yaml_frontmatter(msg_stdout)
114
+
115
+ if yaml_data:
116
+ turn = yaml_data.get('turn', 0)
117
+ summary = yaml_data.get('summary', '')
118
+ commit_branch = yaml_data.get('project_branch')
119
+
120
+ # Filter by branch: skip entries from different branches
121
+ # Include if: no current_branch, no commit_branch (old format), or branches match
122
+ if current_branch and commit_branch and commit_branch != current_branch:
123
+ logger.debug(
124
+ f"Skipping journey entry from different branch: "
125
+ f"{commit_branch} != {current_branch}"
126
+ )
127
+ continue
128
+
129
+ if turn and summary:
130
+ journey.append({
131
+ 'turn': turn,
132
+ 'summary': summary
133
+ })
134
+ except Exception as e:
135
+ logger.debug(f"Failed to parse commit {commit_hash[:8]}: {e}")
136
+ continue
137
+
138
+ # Sort by turn number (oldest first for chronological order)
139
+ journey.sort(key=lambda x: x.get('turn', 0))
140
+
141
+ except Exception as e:
142
+ logger.warning(f"Failed to build journey context: {e}")
143
+
144
+ return journey
145
+
146
+ async def _generate_rich_commit_message(
147
+ self,
148
+ provider,
149
+ provider_session_id: str,
150
+ prompt: str,
151
+ tracker: TurnTracker,
152
+ journey: list[dict] = None,
153
+ app_session_id: Optional[str] = None,
154
+ token_profile: Optional[str] = None,
155
+ session_model: Optional[str] = None,
156
+ ) -> tuple[Optional[str], Optional[SummaryCost], Optional[dict]]:
157
+ """
158
+ Fork the session's provider to generate a structured commit message.
159
+
160
+ The provider owns the fork mechanism and result parsing
161
+ (`build_summary_fork` / `parse_summary_fork`): Claude branches with
162
+ `--resume --fork-session`, Codex copies its rollout under a fresh id and
163
+ resumes the copy. This method owns the provider-agnostic plumbing —
164
+ prompt + schema assembly, the subprocess registry entry, the 300s
165
+ timeout, cancellation, temp-file cleanup, and rendering the structured
166
+ result to markdown.
167
+
168
+ Args:
169
+ provider: The session's Provider (supplies the fork argv + parser).
170
+ provider_session_id: Provider session/thread id the fork resumes.
171
+ prompt: User's prompt for this turn
172
+ tracker: TurnTracker with files and tools
173
+ journey: Previous turns' summaries for context
174
+ app_session_id: painapple-code session ID (for subprocess tracking)
175
+ token_profile: OAuth token profile for the fork (Claude only)
176
+ session_model: Parent session's model ID (to inherit context tier)
177
+
178
+ Returns:
179
+ Tuple of (message_body, summary_cost, structured_data), or
180
+ (None, None, None) when the provider can't fork or the fork fails.
181
+ """
182
+ if not provider_session_id:
183
+ return None, None, None
184
+
185
+ # Build journey section for the prompt
186
+ journey_section = build_journey_section(journey or [])
187
+
188
+ # Build prompt and schema for structured JSON output
189
+ prompt_type = "file_changes" if tracker.has_file_changes else "tool_only"
190
+ fork_prompt = build_commit_prompt_for_json(
191
+ project_config=self.config,
192
+ prompt_type=prompt_type,
193
+ journey_section=journey_section,
194
+ user_prompt=prompt[:500],
195
+ files=", ".join(sorted(tracker.modified_files)) if tracker.has_file_changes else "",
196
+ tools=tracker.format_for_prompt()
197
+ )
198
+ schema_str = json.dumps(build_commit_schema(self.config, prompt_type))
199
+
200
+ # Ask the provider how to fork itself for this turn. None → the provider
201
+ # can't fork (unsupported, or its rollout is missing); the basic shadow
202
+ # commit still happens upstream, just without AI sections.
203
+ plan = provider.build_summary_fork(
204
+ session_id=provider_session_id,
205
+ fork_prompt=fork_prompt,
206
+ schema_json=schema_str,
207
+ cwd=str(self.project_path),
208
+ token_profile=token_profile,
209
+ session_model=session_model,
210
+ )
211
+ if plan is None:
212
+ return None, None, None
213
+
214
+ proc = None
215
+ # A provider whose wire isn't the default newline-JSON ("lines") conducts
216
+ # the summary over the live process itself (drive_summary_fork) instead of
217
+ # a one-shot communicate() + parse_summary_fork — e.g. the app-server's
218
+ # JSON-RPC thread/fork + turn/start. We still own spawn/registry/timeout.
219
+ transport_driven = getattr(provider.capabilities, "transport", "lines") != "lines"
220
+ # A provider may hand us the prompt to write over stdin (keeps a large
221
+ # prompt out of argv/ps and clear of ARG_MAX) — that also needs a PIPE.
222
+ stdin_input = getattr(plan, "stdin_input", None)
223
+ try:
224
+ start_time = time.time()
225
+ stdout, stderr = b"", b""
226
+
227
+ proc = await asyncio.create_subprocess_exec(
228
+ resolve_binary(plan.argv[0]), *plan.argv[1:],
229
+ stdin=asyncio.subprocess.PIPE if (transport_driven or stdin_input is not None) else None,
230
+ stdout=asyncio.subprocess.PIPE,
231
+ stderr=asyncio.subprocess.PIPE,
232
+ cwd=str(self.project_path),
233
+ env=plan.env,
234
+ **popen_kwargs_detached(), # isolate from server's process group
235
+ )
236
+
237
+ # Register subprocess for visibility in Active Sessions widget
238
+ agent_subprocesses.register(
239
+ pid=proc.pid,
240
+ subprocess_type=SubprocessType.SUMMARY_FORK,
241
+ parent_session_id=app_session_id,
242
+ model=plan.model,
243
+ purpose="Rich commit message",
244
+ cwd=str(self.project_path),
245
+ )
246
+
247
+ if transport_driven:
248
+ structured_data, cost = await asyncio.wait_for(
249
+ provider.drive_summary_fork(proc, plan),
250
+ timeout=300, # summarizer may need compaction
251
+ )
252
+ else:
253
+ stdout, stderr = await asyncio.wait_for(
254
+ proc.communicate(
255
+ input=stdin_input.encode() if stdin_input is not None else None
256
+ ),
257
+ timeout=300 # summarizer may need compaction when context exceeds the window
258
+ )
259
+ # Provider parses its own output → (structured_data, cost).
260
+ structured_data, cost = provider.parse_summary_fork(
261
+ plan=plan,
262
+ returncode=proc.returncode or 0,
263
+ stdout=stdout or b"",
264
+ stderr=stderr or b"",
265
+ )
266
+
267
+ duration = time.time() - start_time
268
+
269
+ if not structured_data:
270
+ err = stderr.decode(errors="replace").strip() if stderr else ""
271
+ if not err and stdout:
272
+ err = stdout.decode(errors="replace").strip()[:300]
273
+ logger.warning(
274
+ f"Rich-commit fork failed (rc={proc.returncode}): "
275
+ f"{err[:300] or 'no structured output'}"
276
+ )
277
+ agent_subprocesses.unregister(
278
+ proc.pid,
279
+ status=SubprocessStatus.ERROR,
280
+ error_message=err[:200] or "no structured output",
281
+ )
282
+ return None, None, None
283
+
284
+ summary_cost = SummaryCost(
285
+ cost=(cost or {}).get("cost", 0.0),
286
+ input_tokens=(cost or {}).get("input_tokens", 0),
287
+ output_tokens=(cost or {}).get("output_tokens", 0),
288
+ duration=duration,
289
+ )
290
+ message = structured_to_markdown(structured_data, self.config, prompt_type)
291
+
292
+ logger.info(
293
+ f"Rich-commit fork: ${summary_cost.cost:.4f} "
294
+ f"({summary_cost.input_tokens}↓ {summary_cost.output_tokens}↑) "
295
+ f"in {duration:.1f}s"
296
+ )
297
+
298
+ # Unregister with success status and full details
299
+ agent_subprocesses.unregister(
300
+ proc.pid,
301
+ status=SubprocessStatus.SUCCESS,
302
+ result_preview=message[:100] if message else None,
303
+ cost=summary_cost.cost,
304
+ input_tokens=summary_cost.input_tokens,
305
+ output_tokens=summary_cost.output_tokens,
306
+ )
307
+
308
+ return message, summary_cost, structured_data
309
+
310
+ except asyncio.TimeoutError:
311
+ logger.warning("Rich-commit fork timed out after 300s")
312
+ # Unregister with timeout status
313
+ if proc:
314
+ agent_subprocesses.unregister(
315
+ proc.pid,
316
+ status=SubprocessStatus.TIMEOUT,
317
+ error_message="Process timed out after 300s",
318
+ )
319
+ # Kill the process if still running
320
+ if proc.returncode is None:
321
+ try:
322
+ proc.kill()
323
+ await proc.wait()
324
+ except Exception:
325
+ pass
326
+ return None, None, None
327
+ except asyncio.CancelledError:
328
+ logger.debug("Rich-commit fork cancelled (user sent new prompt)")
329
+ # Unregister with killed status
330
+ if proc:
331
+ agent_subprocesses.unregister(
332
+ proc.pid,
333
+ status=SubprocessStatus.KILLED,
334
+ error_message="Cancelled by new user prompt",
335
+ )
336
+ # Kill the process if still running
337
+ if proc.returncode is None:
338
+ try:
339
+ proc.kill()
340
+ await proc.wait()
341
+ except Exception:
342
+ pass
343
+ raise # Re-raise to propagate cancellation
344
+ except Exception as e:
345
+ logger.exception(f"Rich-commit fork error: {e}")
346
+ # Unregister with error status
347
+ if proc:
348
+ agent_subprocesses.unregister(
349
+ proc.pid,
350
+ status=SubprocessStatus.ERROR,
351
+ error_message=str(e)[:200],
352
+ )
353
+ return None, None, None
354
+ finally:
355
+ # Reap a still-running process. The exec path exits via communicate()
356
+ # and the timeout/cancel branches kill explicitly; this backstops the
357
+ # transport-driven success path and a generic error before any kill.
358
+ if proc is not None and proc.returncode is None:
359
+ try:
360
+ proc.kill()
361
+ await proc.wait()
362
+ except Exception:
363
+ pass
364
+ # Remove any temp copies/files the provider created for this fork
365
+ # (Codex exec's rollout copy + schema/output temp files; none for
366
+ # Claude or the app-server's native fork).
367
+ for path in plan.cleanup_paths:
368
+ try:
369
+ os.unlink(path)
370
+ except OSError:
371
+ pass
372
+
373
+ async def _update_session_meta(
374
+ self,
375
+ session_id: str,
376
+ commit_hash: str,
377
+ tracker: TurnTracker,
378
+ cost_info,
379
+ summary_cost: Optional[SummaryCost] = None,
380
+ session_title: Optional[str] = None
381
+ ):
382
+ """Update session's meta.json with shadow git info and optional title."""
383
+ sessions_dir = get_sessions_dir(str(self.project_path))
384
+ meta_file = sessions_dir / session_id / "meta.json"
385
+
386
+ if not meta_file.exists():
387
+ logger.warning(f"Session meta not found: {meta_file}")
388
+ return
389
+
390
+ try:
391
+ meta = json.loads(meta_file.read_text(encoding="utf-8"))
392
+
393
+ # Update session title if provided (from the summary fork)
394
+ # Skip if user manually renamed the session (manual_name flag)
395
+ if session_title and not meta.get("manual_name"):
396
+ meta["name"] = session_title
397
+ logger.debug(f"Updated session title: {session_title}")
398
+
399
+ if "shadow" not in meta:
400
+ meta["shadow"] = {
401
+ "turn_count": 0,
402
+ "file_commits": 0,
403
+ "tool_commits": 0,
404
+ "first_commit": commit_hash,
405
+ "files_touched": [],
406
+ "tools_summary": {},
407
+ "total_cost": 0,
408
+ "total_input_tokens": 0,
409
+ "total_output_tokens": 0,
410
+ "summary_cost": 0,
411
+ "summary_calls": 0,
412
+ "archived": False,
413
+ "archive_tag": None
414
+ }
415
+
416
+ shadow = meta["shadow"]
417
+ shadow["turn_count"] += 1
418
+ shadow["last_commit"] = commit_hash
419
+
420
+ if tracker.has_file_changes:
421
+ shadow["file_commits"] += 1
422
+ shadow["files_touched"] = list(set(
423
+ shadow["files_touched"] + list(tracker.modified_files)
424
+ ))
425
+ else:
426
+ shadow["tool_commits"] += 1
427
+
428
+ # Merge tool counts
429
+ for name, count in tracker.get_tools_summary().items():
430
+ shadow["tools_summary"][name] = shadow["tools_summary"].get(name, 0) + count
431
+
432
+ shadow["total_cost"] += cost_info.cost
433
+ shadow["total_input_tokens"] += cost_info.input_tokens
434
+ shadow["total_output_tokens"] += cost_info.output_tokens
435
+
436
+ # Track summary-fork costs for rich commits.
437
+ # Back-compat: migrate the pre-rename haiku_* keys on first write.
438
+ if summary_cost:
439
+ shadow["summary_cost"] = shadow.get("summary_cost", shadow.pop("haiku_cost", 0)) + summary_cost.cost
440
+ shadow["summary_calls"] = shadow.get("summary_calls", shadow.pop("haiku_calls", 0)) + 1
441
+
442
+ # Atomic write to avoid corruption on disk-full
443
+ tmp_path = None
444
+ data = json.dumps(meta, indent=2).encode()
445
+ fd, tmp_path = tempfile.mkstemp(
446
+ dir=meta_file.parent, prefix=".meta_", suffix=".tmp"
447
+ )
448
+ try:
449
+ os.write(fd, data)
450
+ os.fsync(fd)
451
+ finally:
452
+ os.close(fd)
453
+ os.replace(tmp_path, meta_file)
454
+
455
+ except Exception as e:
456
+ logger.exception(f"Failed to update session meta: {e}")
457
+ # Clean up temp file if atomic write failed partway
458
+ if tmp_path:
459
+ try:
460
+ os.unlink(tmp_path)
461
+ except OSError:
462
+ pass