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,476 @@
1
+ """
2
+ Shadow Git Search & Analysis API Routes
3
+
4
+ Endpoints for searching, parsing, and extracting insights from shadow git commits:
5
+ - Commit search with filtering
6
+ - Parsed commit details and diffs
7
+ - Extracted decisions, problems, learnings
8
+ - Tag listing and tag-filtered commits
9
+ """
10
+
11
+ import logging
12
+ import re
13
+ from urllib.parse import unquote
14
+
15
+ from fastapi import APIRouter, HTTPException
16
+
17
+ from painapple_code.shadow_git import get_shadow_git
18
+ from painapple_code.shadow_parser import (
19
+ parse_commit_message,
20
+ parse_diff_output,
21
+ search_commits,
22
+ extract_decisions,
23
+ extract_problems,
24
+ extract_learnings,
25
+ )
26
+ from painapple_code.utils.file_paths import resolve_work_dir
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ router = APIRouter(prefix="/api/shadow", tags=["shadow"])
31
+
32
+
33
+ # ═══════════════════════════════════════════════════════════════════════════════
34
+ # Search
35
+ # ═══════════════════════════════════════════════════════════════════════════════
36
+
37
+ @router.get("/search")
38
+ async def shadow_search(
39
+ cwd: str = None,
40
+ q: str = "",
41
+ tags: str = "",
42
+ files: str = "",
43
+ session_id: str = None,
44
+ since: str = None,
45
+ until: str = None,
46
+ limit: int = 100,
47
+ ):
48
+ """
49
+ Search shadow git commits with filtering.
50
+
51
+ Args:
52
+ cwd: Project directory
53
+ q: Text search query
54
+ tags: Comma-separated tags (e.g., "#feature,#bugfix")
55
+ files: Comma-separated file patterns
56
+ session_id: Filter by session
57
+ since: ISO date filter (inclusive)
58
+ until: ISO date filter (inclusive)
59
+ limit: Max results
60
+ """
61
+ try:
62
+ work_dir = resolve_work_dir(cwd)
63
+
64
+ shadow = get_shadow_git(str(work_dir))
65
+
66
+ if not shadow.git_dir.exists():
67
+ return {"commits": [], "total": 0, "message": "Shadow git not initialized"}
68
+
69
+ # Get raw commits
70
+ raw_commits = await shadow.get_full_log(limit * 2) # Get extra for filtering
71
+
72
+ # Parse each commit
73
+ parsed_commits = []
74
+ for raw in raw_commits:
75
+ # Get full commit message
76
+ msg_out, _, _ = await shadow._run([
77
+ "log", "-1", "--format=%B", raw['hash']
78
+ ], check=False)
79
+
80
+ # Parse timestamp
81
+ ts_str = raw.get('timestamp', '')
82
+ ts_int = 0
83
+ if ts_str:
84
+ try:
85
+ # Format: "2025-12-30 09:06:05 +0100"
86
+ from datetime import datetime
87
+ dt = datetime.strptime(ts_str[:19], "%Y-%m-%d %H:%M:%S")
88
+ ts_int = int(dt.timestamp())
89
+ except (ValueError, TypeError):
90
+ pass
91
+
92
+ parsed = parse_commit_message(
93
+ msg_out,
94
+ hash_full=raw['hash'],
95
+ hash_short=raw['hash'][:8],
96
+ timestamp=ts_int,
97
+ )
98
+ parsed_commits.append(parsed.to_dict())
99
+
100
+ # Apply filters
101
+ tag_list = [t.strip() for t in tags.split(',') if t.strip()] if tags else None
102
+ file_list = [f.strip() for f in files.split(',') if f.strip()] if files else None
103
+
104
+ filtered = search_commits(
105
+ parsed_commits,
106
+ query=q,
107
+ tags=tag_list,
108
+ files=file_list,
109
+ session_id=session_id,
110
+ )
111
+
112
+ # Date filtering
113
+ if since or until:
114
+ from datetime import datetime
115
+ since_dt = datetime.fromisoformat(since) if since else None
116
+ until_dt = datetime.fromisoformat(until) if until else None
117
+
118
+ def in_range(commit):
119
+ ts = commit.get('timestamp')
120
+ if not ts:
121
+ return True
122
+ try:
123
+ commit_dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
124
+ if since_dt and commit_dt < since_dt:
125
+ return False
126
+ if until_dt and commit_dt > until_dt:
127
+ return False
128
+ return True
129
+ except (ValueError, TypeError):
130
+ return True
131
+
132
+ filtered = [c for c in filtered if in_range(c)]
133
+
134
+ # Limit results
135
+ filtered = filtered[:limit]
136
+
137
+ # Build facets for filtering UI
138
+ all_tags = set()
139
+ all_files = set()
140
+ for c in parsed_commits:
141
+ all_tags.update(c.get('tags', []))
142
+ all_files.update(c.get('files', []))
143
+
144
+ return {
145
+ "commits": filtered,
146
+ "total": len(filtered),
147
+ "facets": {
148
+ "tags": sorted(all_tags),
149
+ "files": sorted(all_files)[:50], # Limit file list
150
+ }
151
+ }
152
+
153
+ except HTTPException:
154
+ raise
155
+ except Exception as e:
156
+ logger.exception(f"Shadow search error: {e}")
157
+ raise HTTPException(status_code=500, detail=str(e))
158
+
159
+
160
+ # ═══════════════════════════════════════════════════════════════════════════════
161
+ # Parsed Commit Details
162
+ # ═══════════════════════════════════════════════════════════════════════════════
163
+
164
+ @router.get("/commits/{commit_hash}/parsed")
165
+ async def get_shadow_commit_parsed(commit_hash: str, cwd: str = None):
166
+ """
167
+ Get fully parsed commit with YAML frontmatter and sections.
168
+ """
169
+ try:
170
+ work_dir = resolve_work_dir(cwd)
171
+
172
+ if not re.match(r'^[a-fA-F0-9]{7,40}$', commit_hash):
173
+ raise HTTPException(status_code=400, detail="Invalid commit hash")
174
+
175
+ shadow = get_shadow_git(str(work_dir))
176
+
177
+ if not shadow.git_dir.exists():
178
+ raise HTTPException(status_code=404, detail="Shadow git not initialized")
179
+
180
+ # Get full commit message
181
+ msg_out, stderr, code = await shadow._run([
182
+ "log", "-1", "--format=%H%n%at%n%B", commit_hash
183
+ ], check=False)
184
+
185
+ if code != 0:
186
+ raise HTTPException(status_code=404, detail="Commit not found")
187
+
188
+ lines = msg_out.split('\n')
189
+ hash_full = lines[0] if lines else commit_hash
190
+ timestamp = int(lines[1]) if len(lines) > 1 and lines[1].isdigit() else 0
191
+ full_message = '\n'.join(lines[2:]) if len(lines) > 2 else ''
192
+
193
+ parsed = parse_commit_message(
194
+ full_message,
195
+ hash_full=hash_full,
196
+ hash_short=hash_full[:8],
197
+ timestamp=timestamp,
198
+ )
199
+
200
+ return parsed.to_dict()
201
+
202
+ except HTTPException:
203
+ raise
204
+ except Exception as e:
205
+ logger.exception(f"Shadow commit parsed error: {e}")
206
+ raise HTTPException(status_code=500, detail=str(e))
207
+
208
+
209
+ @router.get("/commits/{commit_hash}/diff")
210
+ async def get_shadow_commit_diff(commit_hash: str, cwd: str = None):
211
+ """
212
+ Get structured diff for a commit.
213
+ """
214
+ try:
215
+ work_dir = resolve_work_dir(cwd)
216
+
217
+ if not re.match(r'^[a-fA-F0-9]{7,40}$', commit_hash):
218
+ raise HTTPException(status_code=400, detail="Invalid commit hash")
219
+
220
+ shadow = get_shadow_git(str(work_dir))
221
+
222
+ if not shadow.git_dir.exists():
223
+ raise HTTPException(status_code=404, detail="Shadow git not initialized")
224
+
225
+ # Get diff
226
+ diff_out, stderr, code = await shadow._run([
227
+ "show", "--no-color", "--format=", commit_hash
228
+ ], check=False)
229
+
230
+ if code != 0:
231
+ raise HTTPException(status_code=404, detail="Commit not found")
232
+
233
+ parsed_diff = parse_diff_output(diff_out)
234
+
235
+ return {
236
+ "commitHash": commit_hash,
237
+ **parsed_diff,
238
+ }
239
+
240
+ except HTTPException:
241
+ raise
242
+ except Exception as e:
243
+ logger.exception(f"Shadow diff error: {e}")
244
+ raise HTTPException(status_code=500, detail=str(e))
245
+
246
+
247
+ # ═══════════════════════════════════════════════════════════════════════════════
248
+ # Extracted Information (Decisions, Problems, Learnings)
249
+ # ═══════════════════════════════════════════════════════════════════════════════
250
+
251
+ async def _get_parsed_commits(shadow, limit: int):
252
+ """Helper to get and parse commits."""
253
+ raw_commits = await shadow.get_full_log(limit * 2)
254
+ parsed_commits = []
255
+
256
+ for raw in raw_commits[:limit]:
257
+ msg_out, _, _ = await shadow._run([
258
+ "log", "-1", "--format=%at%n%B", raw['hash']
259
+ ], check=False)
260
+
261
+ lines = msg_out.split('\n')
262
+ ts = int(lines[0]) if lines and lines[0].isdigit() else 0
263
+ msg = '\n'.join(lines[1:])
264
+
265
+ parsed = parse_commit_message(msg, raw['hash'], raw['hash'][:8], ts)
266
+ parsed_commits.append(parsed.to_dict())
267
+
268
+ return parsed_commits
269
+
270
+
271
+ @router.get("/decisions")
272
+ async def get_shadow_decisions(cwd: str = None, limit: int = 100):
273
+ """
274
+ Extract all Decisions sections from commits.
275
+ """
276
+ try:
277
+ work_dir = resolve_work_dir(cwd)
278
+
279
+ shadow = get_shadow_git(str(work_dir))
280
+
281
+ if not shadow.git_dir.exists():
282
+ return {"decisions": [], "total": 0}
283
+
284
+ parsed_commits = await _get_parsed_commits(shadow, limit)
285
+ decisions = extract_decisions(parsed_commits)
286
+
287
+ return {
288
+ "decisions": decisions,
289
+ "total": len(decisions),
290
+ }
291
+
292
+ except Exception as e:
293
+ logger.exception(f"Shadow decisions error: {e}")
294
+ raise HTTPException(status_code=500, detail=str(e))
295
+
296
+
297
+ @router.get("/problems")
298
+ async def get_shadow_problems(cwd: str = None, limit: int = 100):
299
+ """
300
+ Extract all Problems Solved sections from commits.
301
+ """
302
+ try:
303
+ work_dir = resolve_work_dir(cwd)
304
+
305
+ shadow = get_shadow_git(str(work_dir))
306
+
307
+ if not shadow.git_dir.exists():
308
+ return {"problems": [], "total": 0}
309
+
310
+ parsed_commits = await _get_parsed_commits(shadow, limit)
311
+ problems = extract_problems(parsed_commits)
312
+
313
+ return {
314
+ "problems": problems,
315
+ "total": len(problems),
316
+ }
317
+
318
+ except Exception as e:
319
+ logger.exception(f"Shadow problems error: {e}")
320
+ raise HTTPException(status_code=500, detail=str(e))
321
+
322
+
323
+ @router.get("/learnings")
324
+ async def get_shadow_learnings(cwd: str = None, limit: int = 100):
325
+ """
326
+ Extract all Learnings sections from commits.
327
+ """
328
+ try:
329
+ work_dir = resolve_work_dir(cwd)
330
+
331
+ shadow = get_shadow_git(str(work_dir))
332
+
333
+ if not shadow.git_dir.exists():
334
+ return {"learnings": [], "total": 0}
335
+
336
+ parsed_commits = await _get_parsed_commits(shadow, limit)
337
+ learnings = extract_learnings(parsed_commits)
338
+
339
+ return {
340
+ "learnings": learnings,
341
+ "total": len(learnings),
342
+ }
343
+
344
+ except Exception as e:
345
+ logger.exception(f"Shadow learnings error: {e}")
346
+ raise HTTPException(status_code=500, detail=str(e))
347
+
348
+
349
+ # ═══════════════════════════════════════════════════════════════════════════════
350
+ # Tags
351
+ # ═══════════════════════════════════════════════════════════════════════════════
352
+
353
+ @router.get("/tags")
354
+ async def get_shadow_tags(cwd: str = None, limit: int = 100, recent: int = 20):
355
+ """
356
+ Get all unique tags from commits with counts and metadata.
357
+ """
358
+ try:
359
+ work_dir = resolve_work_dir(cwd)
360
+
361
+ shadow = get_shadow_git(str(work_dir))
362
+
363
+ if not shadow.git_dir.exists():
364
+ return {"tags": [], "total": 0, "recentTags": []}
365
+
366
+ raw_commits = await shadow.get_full_log(limit)
367
+
368
+ # Collect tag data
369
+ tag_data = {} # tag -> {count, firstSeen, lastSeen, recentCount, commits}
370
+
371
+ for idx, raw in enumerate(raw_commits):
372
+ msg_out, _, _ = await shadow._run([
373
+ "log", "-1", "--format=%at%n%B", raw['hash']
374
+ ], check=False)
375
+
376
+ lines = msg_out.split('\n')
377
+ ts = int(lines[0]) if lines and lines[0].isdigit() else 0
378
+ msg = '\n'.join(lines[1:])
379
+
380
+ parsed = parse_commit_message(msg, raw['hash'], raw['hash'][:8], ts)
381
+
382
+ is_recent = idx < recent
383
+
384
+ for tag in parsed.tags:
385
+ if tag not in tag_data:
386
+ tag_data[tag] = {
387
+ 'name': tag,
388
+ 'count': 0,
389
+ 'firstSeen': ts,
390
+ 'lastSeen': ts,
391
+ 'recentCount': 0,
392
+ 'commits': []
393
+ }
394
+
395
+ td = tag_data[tag]
396
+ td['count'] += 1
397
+ td['commits'].append(raw['hash'][:8])
398
+
399
+ # Update timestamps (firstSeen = oldest, lastSeen = newest)
400
+ if ts < td['firstSeen']:
401
+ td['firstSeen'] = ts
402
+ if ts > td['lastSeen']:
403
+ td['lastSeen'] = ts
404
+
405
+ if is_recent:
406
+ td['recentCount'] += 1
407
+
408
+ # Convert to list and sort by count (most used first)
409
+ tags_list = sorted(tag_data.values(), key=lambda t: (-t['count'], t['name']))
410
+
411
+ # Limit commits array to 10 most recent per tag
412
+ for t in tags_list:
413
+ t['commits'] = t['commits'][:10]
414
+
415
+ # Get recent tags (tags appearing in recent commits, sorted by recent count)
416
+ recent_tags = sorted(
417
+ [t for t in tags_list if t['recentCount'] > 0],
418
+ key=lambda t: (-t['recentCount'], -t['count'])
419
+ )[:10]
420
+
421
+ return {
422
+ "tags": tags_list,
423
+ "total": len(tags_list),
424
+ "recentTags": [t['name'] for t in recent_tags]
425
+ }
426
+
427
+ except Exception as e:
428
+ logger.exception(f"Shadow tags error: {e}")
429
+ raise HTTPException(status_code=500, detail=str(e))
430
+
431
+
432
+ @router.get("/tags/{tag_name}/commits")
433
+ async def get_shadow_tag_commits(tag_name: str, cwd: str = None, limit: int = 50):
434
+ """
435
+ Get all commits with a specific tag.
436
+ """
437
+ try:
438
+ work_dir = resolve_work_dir(cwd)
439
+
440
+ shadow = get_shadow_git(str(work_dir))
441
+
442
+ if not shadow.git_dir.exists():
443
+ return {"commits": [], "total": 0, "tag": tag_name}
444
+
445
+ # URL decode the tag name (e.g., %23feature -> #feature)
446
+ decoded_tag = unquote(tag_name)
447
+
448
+ raw_commits = await shadow.get_full_log(limit * 3) # Fetch more to filter
449
+ matching_commits = []
450
+
451
+ for raw in raw_commits:
452
+ if len(matching_commits) >= limit:
453
+ break
454
+
455
+ msg_out, _, _ = await shadow._run([
456
+ "log", "-1", "--format=%at%n%B", raw['hash']
457
+ ], check=False)
458
+
459
+ lines = msg_out.split('\n')
460
+ ts = int(lines[0]) if lines and lines[0].isdigit() else 0
461
+ msg = '\n'.join(lines[1:])
462
+
463
+ parsed = parse_commit_message(msg, raw['hash'], raw['hash'][:8], ts)
464
+
465
+ if decoded_tag in parsed.tags:
466
+ matching_commits.append(parsed.to_dict())
467
+
468
+ return {
469
+ "commits": matching_commits,
470
+ "total": len(matching_commits),
471
+ "tag": decoded_tag
472
+ }
473
+
474
+ except Exception as e:
475
+ logger.exception(f"Shadow tag commits error: {e}")
476
+ raise HTTPException(status_code=500, detail=str(e))