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,441 @@
1
+ """Agents Manager API.
2
+
3
+ Endpoints for discovering, viewing, creating, editing, and deleting Claude Code
4
+ agent definitions.
5
+
6
+ Agent locations (priority order — higher wins on name collisions):
7
+ personal: ~/.claude/agents/<name>.md
8
+ project: <cwd>/.claude/agents/<name>.md (overrides personal)
9
+
10
+ Agents are flat `.md` files (one per agent), not folder-form like skills.
11
+ Frontmatter keys: `name`, `description`, optionally `tools`, `model`.
12
+
13
+ Mirrors `routes/api_skills.py` in shape so the frontend agents widget can be
14
+ a near-clone of the skills widget.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import re
20
+ from pathlib import Path
21
+ from typing import Any, Optional
22
+
23
+ import yaml
24
+ from fastapi import APIRouter, HTTPException
25
+ from pydantic import BaseModel
26
+ from painapple_code.utils.file_paths import safe_resolve
27
+
28
+ router = APIRouter(prefix="/api/agents", tags=["agents"])
29
+
30
+ NAME_RE = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")
31
+ FRONTMATTER_RE = re.compile(r"\A---\s*\n(.*?)\n---\s*\n?(.*)\Z", re.DOTALL)
32
+
33
+
34
+ # ---------- helpers ---------------------------------------------------------
35
+
36
+
37
+ def _validate_name(name: str) -> None:
38
+ if not NAME_RE.match(name):
39
+ raise HTTPException(
40
+ status_code=400,
41
+ detail=f"Invalid agent name {name!r} — must match {NAME_RE.pattern}",
42
+ )
43
+
44
+
45
+ def _scope_root(scope: str, cwd: str) -> Path:
46
+ """Return the agents directory for a given scope."""
47
+ if scope == "project":
48
+ return Path(cwd) / ".claude" / "agents"
49
+ if scope == "personal":
50
+ return Path.home() / ".claude" / "agents"
51
+ raise HTTPException(status_code=400, detail=f"Unknown scope {scope!r}")
52
+
53
+
54
+ def _safe_under(base: Path, candidate: Path) -> bool:
55
+ try:
56
+ candidate.resolve().relative_to(base.resolve())
57
+ return True
58
+ except (ValueError, OSError):
59
+ return False
60
+
61
+
62
+ def _parse_frontmatter(text: str) -> tuple[dict[str, Any], str]:
63
+ """Split agent .md contents into (frontmatter dict, body string)."""
64
+ m = FRONTMATTER_RE.match(text)
65
+ if not m:
66
+ return {}, text
67
+ try:
68
+ data = yaml.safe_load(m.group(1)) or {}
69
+ if not isinstance(data, dict):
70
+ data = {"_parse_error": "frontmatter is not a mapping"}
71
+ return data, m.group(2)
72
+ except yaml.YAMLError as e:
73
+ return {"_parse_error": str(e)}, m.group(2)
74
+
75
+
76
+ def _serialize_agent(frontmatter: dict[str, Any], body: str) -> str:
77
+ """Rebuild an agent .md-formatted string from parsed parts."""
78
+ if not frontmatter:
79
+ return body
80
+ clean = {k: v for k, v in frontmatter.items() if not k.startswith("_")}
81
+ yaml_text = yaml.safe_dump(
82
+ clean, sort_keys=False, default_flow_style=False, allow_unicode=True
83
+ ).rstrip()
84
+ body_part = body if body.startswith("\n") else "\n" + body
85
+ return f"---\n{yaml_text}\n---{body_part}"
86
+
87
+
88
+ def _body_preview(body: str, n: int = 280) -> str:
89
+ body = body.strip()
90
+ if len(body) <= n:
91
+ return body
92
+ return body[:n].rstrip() + "…"
93
+
94
+
95
+ def _iter_agents(cwd: str):
96
+ """Yield agent file descriptors across personal + project scopes."""
97
+ home = Path.home()
98
+ cwd_path = Path(cwd)
99
+
100
+ for scope, base in (("project", cwd_path), ("personal", home)):
101
+ agents_root = base / ".claude" / "agents"
102
+ if not agents_root.is_dir():
103
+ continue
104
+ for entry in sorted(agents_root.glob("*.md")):
105
+ if not entry.is_file():
106
+ continue
107
+ yield {
108
+ "scope": scope,
109
+ "name": entry.stem,
110
+ "path": entry,
111
+ }
112
+
113
+
114
+ def _resolve_path(scope: str, name: str, cwd: str) -> Path:
115
+ """Locate an agent .md file on disk."""
116
+ _validate_name(name)
117
+ if scope not in ("project", "personal"):
118
+ raise HTTPException(status_code=400, detail=f"Unknown scope {scope!r}")
119
+ base = _scope_root(scope, cwd)
120
+ agent_file = base / f"{name}.md"
121
+ if agent_file.is_file() and _safe_under(base, agent_file):
122
+ return agent_file
123
+ raise HTTPException(status_code=404, detail=f"Agent {name!r} not found in {scope}")
124
+
125
+
126
+ def _all_names_in_scope(scope: str, cwd: str) -> set[str]:
127
+ base = _scope_root(scope, cwd)
128
+ if not base.is_dir():
129
+ return set()
130
+ return {entry.stem for entry in base.glob("*.md") if entry.is_file()}
131
+
132
+
133
+ # ---------- endpoints -------------------------------------------------------
134
+
135
+
136
+ @router.get("")
137
+ async def list_agents(cwd: Optional[str] = None):
138
+ """List every agent across scopes, with rich metadata for the gallery."""
139
+ resolved_cwd = str(safe_resolve(cwd)) if cwd else str(Path.cwd())
140
+ agents = []
141
+ by_name: dict[str, list[tuple[str, str]]] = {}
142
+ counts = {"project": 0, "personal": 0}
143
+
144
+ for rec in _iter_agents(resolved_cwd):
145
+ try:
146
+ text = rec["path"].read_text(encoding="utf-8", errors="replace")
147
+ except OSError:
148
+ continue
149
+ fm, body = _parse_frontmatter(text)
150
+ agent_id = f"{rec['scope']}:{rec['name']}"
151
+ item = {
152
+ "id": agent_id,
153
+ "name": rec["name"],
154
+ "scope": rec["scope"],
155
+ "scope_label": rec["scope"].capitalize(),
156
+ "path": str(rec["path"]),
157
+ "editable": True,
158
+ "frontmatter": fm,
159
+ "description": fm.get("description") or "",
160
+ "body_preview": _body_preview(body),
161
+ "body_length": len(body),
162
+ # Back-compat: snippets-autocomplete consumer also reads
163
+ # `file` and `source` (we map source <- scope, with personal=='global').
164
+ "file": rec["path"].name,
165
+ "source": "global" if rec["scope"] == "personal" else "project",
166
+ }
167
+ agents.append(item)
168
+ counts[rec["scope"]] += 1
169
+ by_name.setdefault(rec["name"], []).append((rec["scope"], agent_id))
170
+
171
+ # Mark conflicts: project shadows personal
172
+ priority = {"personal": 0, "project": 1}
173
+ for item in agents:
174
+ peers = by_name.get(item["name"], [])
175
+ if len(peers) <= 1:
176
+ continue
177
+ winner = max(peers, key=lambda p: priority.get(p[0], -1))
178
+ if item["id"] != winner[1]:
179
+ item["shadowed_by"] = winner[1]
180
+
181
+ return {
182
+ "cwd": resolved_cwd,
183
+ "agents": agents,
184
+ "counts": counts,
185
+ "total": len(agents),
186
+ }
187
+
188
+
189
+ @router.get("/{scope}/{name}")
190
+ async def get_agent(scope: str, name: str, cwd: str):
191
+ """Return full agent metadata + body for viewing or editing."""
192
+ resolved_cwd = str(safe_resolve(cwd))
193
+ path = _resolve_path(scope, name, resolved_cwd)
194
+ text = path.read_text(encoding="utf-8", errors="replace")
195
+ fm, body = _parse_frontmatter(text)
196
+ return {
197
+ "id": f"{scope}:{name}",
198
+ "name": name,
199
+ "scope": scope,
200
+ "scope_label": scope.capitalize(),
201
+ "path": str(path),
202
+ "editable": True,
203
+ "frontmatter": fm,
204
+ "body": body,
205
+ "raw": text,
206
+ "mtime": path.stat().st_mtime,
207
+ }
208
+
209
+
210
+ class UpdateAgentBody(BaseModel):
211
+ frontmatter: Optional[dict[str, Any]] = None
212
+ body: Optional[str] = None
213
+ raw: Optional[str] = None
214
+ expected_mtime: Optional[float] = None
215
+
216
+
217
+ @router.put("/{scope}/{name}")
218
+ async def update_agent(scope: str, name: str, cwd: str, payload: UpdateAgentBody):
219
+ """Update an existing agent file."""
220
+ resolved_cwd = str(safe_resolve(cwd))
221
+ path = _resolve_path(scope, name, resolved_cwd)
222
+
223
+ if payload.expected_mtime is not None:
224
+ try:
225
+ current = path.stat().st_mtime
226
+ if abs(current - payload.expected_mtime) > 0.5:
227
+ raise HTTPException(
228
+ status_code=409,
229
+ detail="File changed on disk since you started editing",
230
+ )
231
+ except OSError:
232
+ pass
233
+
234
+ if payload.raw is not None:
235
+ new_text = payload.raw
236
+ elif payload.frontmatter is not None or payload.body is not None:
237
+ existing = path.read_text(encoding="utf-8", errors="replace")
238
+ ex_fm, ex_body = _parse_frontmatter(existing)
239
+ fm = payload.frontmatter if payload.frontmatter is not None else ex_fm
240
+ body = payload.body if payload.body is not None else ex_body
241
+ new_text = _serialize_agent(fm, body)
242
+ else:
243
+ raise HTTPException(status_code=400, detail="No content provided")
244
+
245
+ tmp = path.with_suffix(path.suffix + f".tmp.{os.getpid()}")
246
+ tmp.write_text(new_text, encoding="utf-8", newline="")
247
+ os.replace(tmp, path)
248
+
249
+ return {
250
+ "ok": True,
251
+ "path": str(path),
252
+ "mtime": path.stat().st_mtime,
253
+ "bytes": len(new_text.encode("utf-8")),
254
+ }
255
+
256
+
257
+ # ---------- create / delete / duplicate -------------------------------------
258
+
259
+
260
+ TEMPLATES: dict[str, dict[str, Any]] = {
261
+ "blank": {
262
+ "frontmatter": {},
263
+ "body": (
264
+ "\n"
265
+ "Describe what this agent does, when to use it, and what it should "
266
+ "return.\n"
267
+ ),
268
+ },
269
+ "researcher": {
270
+ "frontmatter": {
271
+ "tools": "Read, Grep, Glob, Bash",
272
+ },
273
+ "body": (
274
+ "\n"
275
+ "You are a research agent. Investigate the user's question and report "
276
+ "findings concisely.\n\n"
277
+ "Steps:\n"
278
+ "1. Identify the relevant files and code paths\n"
279
+ "2. Read enough context to answer the question\n"
280
+ "3. Return a concise summary with file:line references\n"
281
+ ),
282
+ },
283
+ "reviewer": {
284
+ "frontmatter": {
285
+ "tools": "Read, Grep, Glob, Bash",
286
+ },
287
+ "body": (
288
+ "\n"
289
+ "You are a code-reviewer agent. Review the diff or changes the user "
290
+ "describes and report issues you find.\n\n"
291
+ "Focus on:\n"
292
+ "- Correctness bugs\n"
293
+ "- Security issues\n"
294
+ "- Performance pitfalls\n"
295
+ "- Style/readability — only when impactful\n\n"
296
+ "Return findings grouped by severity.\n"
297
+ ),
298
+ },
299
+ "implementer": {
300
+ "frontmatter": {
301
+ "tools": "Read, Edit, Write, Grep, Glob, Bash",
302
+ },
303
+ "body": (
304
+ "\n"
305
+ "You are an implementation agent. Make the requested code change "
306
+ "carefully and verify it.\n\n"
307
+ "Steps:\n"
308
+ "1. Read the relevant files first\n"
309
+ "2. Make the minimal change required\n"
310
+ "3. Run tests / type checks where appropriate\n"
311
+ "4. Report what you changed with diffs\n"
312
+ ),
313
+ },
314
+ }
315
+
316
+
317
+ def _next_duplicate_name(existing_names: set[str], base: str) -> str:
318
+ candidate = f"duplicate-of-{base}"[:64]
319
+ if candidate not in existing_names:
320
+ return candidate
321
+ for i in range(2, 100):
322
+ cand = f"{candidate}-{i}"[:64]
323
+ if cand not in existing_names:
324
+ return cand
325
+ raise HTTPException(status_code=409, detail="Too many duplicates")
326
+
327
+
328
+ class CreateAgentBody(BaseModel):
329
+ description: Optional[str] = ""
330
+ template: Optional[str] = "blank"
331
+ frontmatter: Optional[dict[str, Any]] = None
332
+ body: Optional[str] = None
333
+
334
+
335
+ @router.post("/{scope}/{name}")
336
+ async def create_agent(scope: str, name: str, cwd: str, payload: CreateAgentBody):
337
+ """Create a new agent .md from a template."""
338
+ _validate_name(name)
339
+ resolved_cwd = str(safe_resolve(cwd))
340
+
341
+ base = _scope_root(scope, resolved_cwd)
342
+ target = base / f"{name}.md"
343
+
344
+ existing = _all_names_in_scope(scope, resolved_cwd)
345
+ if name in existing:
346
+ raise HTTPException(status_code=409, detail=f"Agent {name!r} already exists in {scope}")
347
+
348
+ template_key = payload.template or "blank"
349
+ template = TEMPLATES.get(template_key, TEMPLATES["blank"])
350
+ fm: dict[str, Any] = {"name": name}
351
+ if payload.description:
352
+ fm["description"] = payload.description
353
+ fm.update(template.get("frontmatter") or {})
354
+ if payload.frontmatter:
355
+ fm.update(payload.frontmatter)
356
+ body = payload.body if payload.body is not None else (template.get("body") or "")
357
+
358
+ base.mkdir(parents=True, exist_ok=True)
359
+ text = _serialize_agent(fm, body)
360
+ tmp = target.with_suffix(target.suffix + f".tmp.{os.getpid()}")
361
+ tmp.write_text(text, encoding="utf-8", newline="")
362
+ os.replace(tmp, target)
363
+
364
+ return {
365
+ "ok": True,
366
+ "id": f"{scope}:{name}",
367
+ "scope": scope,
368
+ "name": name,
369
+ "path": str(target),
370
+ "mtime": target.stat().st_mtime,
371
+ }
372
+
373
+
374
+ @router.delete("/{scope}/{name}")
375
+ async def delete_agent(scope: str, name: str, cwd: str):
376
+ """Delete an agent .md file."""
377
+ resolved_cwd = str(safe_resolve(cwd))
378
+ path = _resolve_path(scope, name, resolved_cwd)
379
+
380
+ parts = path.resolve().parts
381
+ if ".claude" not in parts:
382
+ raise HTTPException(status_code=400, detail="Refusing to delete outside .claude/")
383
+ if path.parent.name != "agents":
384
+ raise HTTPException(status_code=400, detail="Unexpected agent file layout")
385
+
386
+ try:
387
+ path.unlink()
388
+ except OSError as e:
389
+ raise HTTPException(status_code=500, detail=str(e))
390
+ return {"ok": True, "removed": str(path), "kind": "file"}
391
+
392
+
393
+ class DuplicateBody(BaseModel):
394
+ target_scope: Optional[str] = None
395
+ target_name: Optional[str] = None
396
+
397
+
398
+ @router.post("/{scope}/{name}/duplicate")
399
+ async def duplicate_agent(scope: str, name: str, cwd: str, payload: DuplicateBody):
400
+ """Copy an agent into a new name/scope."""
401
+ resolved_cwd = str(safe_resolve(cwd))
402
+ src = _resolve_path(scope, name, resolved_cwd)
403
+
404
+ target_scope = payload.target_scope or scope
405
+ if target_scope not in ("project", "personal"):
406
+ raise HTTPException(status_code=400, detail=f"Cannot duplicate to scope {target_scope!r}")
407
+
408
+ existing = _all_names_in_scope(target_scope, resolved_cwd)
409
+ new_name = payload.target_name or _next_duplicate_name(existing, name)
410
+ _validate_name(new_name)
411
+ if new_name in existing:
412
+ raise HTTPException(status_code=409, detail=f"Agent {new_name!r} already exists in {target_scope}")
413
+
414
+ target_base = _scope_root(target_scope, resolved_cwd)
415
+ target = target_base / f"{new_name}.md"
416
+
417
+ src_text = src.read_text(encoding="utf-8", errors="replace")
418
+ fm, body = _parse_frontmatter(src_text)
419
+
420
+ fm = dict(fm or {})
421
+ fm = {k: v for k, v in fm.items() if not k.startswith("_")}
422
+ fm["name"] = new_name
423
+ original_desc = fm.get("description") or ""
424
+ if original_desc:
425
+ fm["description"] = f"Duplicate of {name}: {original_desc}"
426
+ else:
427
+ fm["description"] = f"Duplicate of {name}"
428
+
429
+ target_base.mkdir(parents=True, exist_ok=True)
430
+ new_text = _serialize_agent(fm, body)
431
+ tmp = target.with_suffix(target.suffix + f".tmp.{os.getpid()}")
432
+ tmp.write_text(new_text, encoding="utf-8", newline="")
433
+ os.replace(tmp, target)
434
+
435
+ return {
436
+ "ok": True,
437
+ "id": f"{target_scope}:{new_name}",
438
+ "scope": target_scope,
439
+ "name": new_name,
440
+ "path": str(target),
441
+ }