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,438 @@
1
+ """Terminal UI helpers shared by CLI subcommands.
2
+
3
+ Two layers:
4
+
5
+ 1. Plain ANSI output helpers (``say``/``info``/``ok``/``warn``/``err``) —
6
+ zero dependencies, safe to import in non-TTY contexts. Mirrors the
7
+ look of painapple-docker.sh so the two tools feel like one.
8
+
9
+ 2. Interactive prompt widgets built on questionary/prompt_toolkit —
10
+ imported lazily so `docker up`/`logs`/etc. never pay the import and
11
+ never break in a pipe. Every widget understands "back": selects get
12
+ a dedicated ``← Back`` entry, text/path prompts bind Esc. A widget
13
+ returns the sentinel :data:`BACK` when the user backs out, so wizard
14
+ step machines can just compare against it.
15
+ """
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+
21
+
22
+ # Unique sentinel: "user asked to go back one step".
23
+ class _Back:
24
+ def __repr__(self):
25
+ return "<BACK>"
26
+
27
+
28
+ BACK = _Back()
29
+
30
+ # Legacy Windows conhost ignores ANSI escapes until VT processing is
31
+ # switched on; the empty os.system() call does exactly that (Windows
32
+ # Terminal and every unix terminal don't need it, and it's a no-op there).
33
+ if os.name == "nt" and sys.stdout.isatty():
34
+ os.system("")
35
+
36
+ _COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
37
+
38
+ BOLD = "\033[1m" if _COLOR else ""
39
+ DIM = "\033[2m" if _COLOR else ""
40
+ GREEN = "\033[32m" if _COLOR else ""
41
+ YELLOW = "\033[33m" if _COLOR else ""
42
+ RED = "\033[31m" if _COLOR else ""
43
+ BLUE = "\033[34m" if _COLOR else ""
44
+ CYAN = "\033[36m" if _COLOR else ""
45
+ RESET = "\033[0m" if _COLOR else ""
46
+
47
+
48
+ def say(msg=""):
49
+ print(msg)
50
+
51
+
52
+ def info(msg):
53
+ print(f"{BLUE}→{RESET} {msg}")
54
+
55
+
56
+ def ok(msg):
57
+ print(f"{GREEN}✓{RESET} {msg}")
58
+
59
+
60
+ def warn(msg):
61
+ print(f"{YELLOW}⚠{RESET} {msg}", file=sys.stderr)
62
+
63
+
64
+ def err(msg):
65
+ print(f"{RED}✗{RESET} {msg}", file=sys.stderr)
66
+
67
+
68
+ def die(msg, hint=None):
69
+ err(msg)
70
+ if hint:
71
+ say(hint)
72
+ raise SystemExit(1)
73
+
74
+
75
+ def sanitize(value):
76
+ """Strip control bytes some terminals smuggle into typed input."""
77
+ return re.sub(r"[\x00-\x1f\x7f]", "", value)
78
+
79
+
80
+ def print_credentials(urls, pw, token):
81
+ """The credential block behind `painapple password` (host AND docker).
82
+
83
+ One shared body so the two paths can't drift. Labels carry each
84
+ credential's purpose — users copy the line they need, not the footer
85
+ hints. ``token`` is "" on a config written by a pre-WP-02 build; the
86
+ URL is then a bare address (manual login form) and the API-token
87
+ row/hint are dropped.
88
+ """
89
+ width = len("Password (login form):") + 2
90
+
91
+ def _row(label, value):
92
+ say(f"{BOLD}{label}{RESET}{' ' * (width - len(label))}{value}")
93
+
94
+ url_label = "Auto-Login URL:" if token else "URL:"
95
+ for url in urls:
96
+ _row(url_label, url)
97
+ url_label = ""
98
+ _row("Password (login form):", pw)
99
+ if token:
100
+ _row("API token (scripts):", token)
101
+ say(f"{DIM} Open the URL once; the cookie keeps you logged in after that.{RESET}")
102
+ say(f"{DIM} Scripts use the API token (Bearer / ?tkn=), never the password.{RESET}")
103
+ else:
104
+ say(f"{DIM} Open the URL and log in with the password; the cookie keeps you logged in.{RESET}")
105
+
106
+
107
+ def require_tty(what="interactive setup", alternative=None):
108
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
109
+ die(f"{what} needs a terminal.",
110
+ alternative or
111
+ f"{DIM}Non-interactive alternative: painapple profile set "
112
+ f"NAME KEY=VALUE …{RESET}")
113
+
114
+
115
+ # ──── Interactive widgets (lazy questionary import) ─────────────────────────
116
+
117
+ _BACK_TITLE = "← Back"
118
+
119
+
120
+ def _questionary():
121
+ try:
122
+ import questionary
123
+ return questionary
124
+ except ImportError:
125
+ die("The interactive setup needs the 'questionary' package.",
126
+ f"{DIM}Install it with: pip install questionary{RESET}")
127
+
128
+
129
+ def _style():
130
+ from prompt_toolkit.styles import Style
131
+ return Style([
132
+ ("qmark", "fg:ansicyan bold"),
133
+ ("question", "bold"),
134
+ ("answer", "fg:ansicyan"),
135
+ ("pointer", "fg:ansicyan bold"),
136
+ ("highlighted", "fg:ansicyan bold"),
137
+ ("instruction", "fg:ansibrightblack"),
138
+ ("text", ""),
139
+ ("dim", "fg:ansibrightblack"),
140
+ ])
141
+
142
+
143
+ class Choice:
144
+ """One selectable option: a value, a short title, an optional dim hint."""
145
+
146
+ def __init__(self, value, title, hint=None):
147
+ self.value = value
148
+ self.title = title
149
+ self.hint = hint
150
+
151
+
152
+ def select(message, choices, default=None, back=False, instruction=None,
153
+ use_search=False, on_left=None, right_pick=None):
154
+ """Arrow-key menu. Returns the picked Choice's value, or BACK.
155
+
156
+ ``choices`` is a list of :class:`Choice` (or plain strings, used as
157
+ both value and title). ``default`` pre-highlights the choice with
158
+ that value. ``back=True`` appends a dim ``← Back`` entry — and makes
159
+ the ← key return BACK too (previous step), unless ``on_left``
160
+ overrides it with a different value (the directory browser maps ← to
161
+ "go to parent"). ``right_pick`` is a predicate on choice values: when
162
+ it accepts the highlighted value, the → key picks it (browser: enter
163
+ the highlighted folder). ``use_search=True`` enables type-to-filter.
164
+ """
165
+ questionary = _questionary()
166
+
167
+ if on_left is None and back:
168
+ on_left = BACK
169
+
170
+ normalized = [c if isinstance(c, Choice) else Choice(c, str(c)) for c in choices]
171
+ q_choices = []
172
+ default_choice = None
173
+ for c in normalized:
174
+ # questionary's search filter calls .lower() on the title, so
175
+ # searchable selects need plain-string titles (hint folded in,
176
+ # un-dimmed — and conveniently filterable too).
177
+ if use_search:
178
+ title = f"{c.title} {c.hint}" if c.hint else c.title
179
+ else:
180
+ title = [("class:text", c.title)]
181
+ if c.hint:
182
+ title.append(("class:dim", f" {c.hint}"))
183
+ qc = questionary.Choice(title=title, value=c.value)
184
+ q_choices.append(qc)
185
+ if default is not None and c.value == default:
186
+ default_choice = qc
187
+ if back:
188
+ q_choices.append(questionary.Choice(
189
+ title=_BACK_TITLE if use_search else [("class:dim", _BACK_TITLE)],
190
+ value=BACK))
191
+
192
+ if instruction is None:
193
+ base = "type to filter, ↑↓ + Enter" if use_search else "↑↓ + Enter"
194
+ instruction = f"({base}, ← back)" if on_left is BACK else f"({base})"
195
+
196
+ question = questionary.select(
197
+ message,
198
+ choices=q_choices,
199
+ default=default_choice,
200
+ style=_style(),
201
+ instruction=instruction,
202
+ use_shortcuts=False,
203
+ use_search_filter=use_search,
204
+ use_jk_keys=False,
205
+ )
206
+
207
+ # ←/→ bindings ride on top of questionary's own. The application
208
+ # consults key_bindings dynamically, so merging before run() works.
209
+ if on_left is not None or right_pick is not None:
210
+ from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
211
+ extra = KeyBindings()
212
+ app = question.application
213
+ if on_left is not None:
214
+ @extra.add("left")
215
+ def _(event):
216
+ event.app.exit(result=on_left)
217
+ if right_pick is not None:
218
+ ic = _find_inquirer_control(app)
219
+ if ic is not None:
220
+ @extra.add("right")
221
+ def _(event):
222
+ pointed = ic.get_pointed_at()
223
+ if pointed is not None and right_pick(pointed.value):
224
+ event.app.exit(result=pointed.value)
225
+ app.key_bindings = (merge_key_bindings([app.key_bindings, extra])
226
+ if app.key_bindings else extra)
227
+
228
+ return question.unsafe_ask()
229
+
230
+
231
+ def _find_inquirer_control(app):
232
+ """The list control inside a questionary select — needed to read the
233
+ highlighted choice from a custom key binding."""
234
+ from questionary.prompts.common import InquirerControl
235
+ for control in app.layout.find_all_controls():
236
+ if isinstance(control, InquirerControl):
237
+ return control
238
+ return None
239
+
240
+
241
+ def _prompt_session(completer=None):
242
+ from prompt_toolkit import PromptSession
243
+ from prompt_toolkit.key_binding import KeyBindings
244
+
245
+ bindings = KeyBindings()
246
+
247
+ class _BackRequested(Exception):
248
+ pass
249
+
250
+ @bindings.add("escape", eager=True)
251
+ def _(event):
252
+ event.app.exit(exception=_BackRequested())
253
+
254
+ return PromptSession(key_bindings=bindings, completer=completer), _BackRequested
255
+
256
+
257
+ def text(message, default="", validate=None, back=True, completer=None,
258
+ hint=None):
259
+ """One-line input with the default pre-filled and editable.
260
+
261
+ The user edits the default in place (arrows/Home/End work) instead
262
+ of retyping it. Esc returns BACK (when ``back``). ``validate`` is a
263
+ callable returning an error string (re-prompt) or None (accept).
264
+ """
265
+ from prompt_toolkit.formatted_text import FormattedText
266
+
267
+ session, back_exc = _prompt_session(completer)
268
+ esc_part = "Esc = back, " if back else ""
269
+ suffix = hint or ""
270
+ prompt_msg = FormattedText([
271
+ ("fg:ansicyan bold", "? "),
272
+ ("bold", message),
273
+ ("fg:ansibrightblack", f" ({esc_part}Enter = accept){suffix}"),
274
+ ("", "\n "),
275
+ ])
276
+ while True:
277
+ try:
278
+ reply = session.prompt(prompt_msg, default=default)
279
+ except back_exc:
280
+ if back:
281
+ return BACK
282
+ continue
283
+ reply = sanitize(reply).strip()
284
+ if validate:
285
+ problem = validate(reply)
286
+ if problem:
287
+ warn(problem)
288
+ default = reply # keep what they typed for correction
289
+ continue
290
+ return reply
291
+
292
+
293
+ def path_input(message, default="", must_exist=False, back=True,
294
+ offer_create=False):
295
+ """Path input: pre-filled editable default + filesystem tab-completion.
296
+
297
+ ``must_exist`` re-prompts (keeping the typed value) until the
298
+ directory exists; with ``offer_create`` the user is asked whether to
299
+ mkdir -p it instead.
300
+ """
301
+ from prompt_toolkit.completion import PathCompleter
302
+ from pathlib import Path
303
+
304
+ completer = PathCompleter(expanduser=True)
305
+ current_default = default
306
+ while True:
307
+ reply = text(message, default=current_default, back=back,
308
+ completer=completer, hint=" Tab = complete")
309
+ if reply is BACK:
310
+ return BACK
311
+ if not reply:
312
+ warn("Path can't be empty.")
313
+ continue
314
+ p = Path(reply).expanduser()
315
+ if must_exist and not p.is_dir():
316
+ if offer_create and confirm(f"Directory does not exist: {p} — create it?",
317
+ default=False):
318
+ p.mkdir(parents=True, exist_ok=True)
319
+ else:
320
+ warn(f"Directory does not exist: {p}")
321
+ current_default = reply
322
+ continue
323
+ return str(p.resolve() if p.exists() else p)
324
+
325
+
326
+ def int_input(message, default, lo=None, hi=None, back=True):
327
+ def _validate(v):
328
+ if not v.isdigit():
329
+ return f"Need a number{f' between {lo} and {hi}' if lo else ''}."
330
+ n = int(v)
331
+ if lo is not None and n < lo or hi is not None and n > hi:
332
+ return f"Must be between {lo} and {hi}."
333
+ return None
334
+
335
+ reply = text(message, default=str(default), validate=_validate, back=back)
336
+ return reply if reply is BACK else int(reply)
337
+
338
+
339
+ def confirm(message, default=True):
340
+ questionary = _questionary()
341
+ return questionary.confirm(message, default=default, style=_style()).unsafe_ask()
342
+
343
+
344
+ def browse_dir(message, start=None, allow_create=False, back=True,
345
+ suggest=None):
346
+ """Interactive directory picker (mc/ncdu-style): descend into
347
+ subdirectories, go up with `..`, type letters to filter the list.
348
+
349
+ Escape hatches: "Type a path instead…" drops to the completing text
350
+ prompt; ``allow_create`` adds a "New folder here…" entry. ``suggest``
351
+ keeps a not-yet-existing default one Enter away (shown as its own
352
+ entry, created later by the caller). Returns the chosen absolute
353
+ path as str, or BACK.
354
+ """
355
+ from pathlib import Path
356
+
357
+ suggest_path = None
358
+ if suggest:
359
+ suggest_path = Path(suggest).expanduser()
360
+ if suggest_path.is_dir():
361
+ suggest_path = None # it exists — the browser just starts there
362
+
363
+ current = Path(start).expanduser() if start else Path.cwd()
364
+ try:
365
+ current = current.resolve()
366
+ except OSError:
367
+ current = Path.home()
368
+ # A saved-but-vanished default shouldn't strand the browser — walk up
369
+ # to the nearest ancestor that still exists.
370
+ while not current.is_dir() and current.parent != current:
371
+ current = current.parent
372
+ if not current.is_dir():
373
+ current = Path.home()
374
+
375
+ show_hidden = False
376
+ while True:
377
+ try:
378
+ subdirs = sorted(
379
+ (p for p in current.iterdir()
380
+ if p.is_dir() and (show_hidden or not p.name.startswith("."))),
381
+ key=lambda p: p.name.lower())
382
+ except OSError as e:
383
+ warn(f"Can't list {current}: {e}")
384
+ subdirs = []
385
+
386
+ choices = []
387
+ if suggest_path is not None:
388
+ choices.append(Choice("suggest", f"✓ Use {suggest_path}",
389
+ "default — will be created"))
390
+ choices += [Choice("use", "✓ Use this directory", str(current)),
391
+ Choice("type", "✎ Type a path instead…")]
392
+ if current.parent != current:
393
+ choices.append(Choice("up", "↑ ..", "up one level"))
394
+ choices.extend(Choice(("cd", p), f"{p.name}/") for p in subdirs)
395
+ if allow_create:
396
+ choices.append(Choice("mkdir", "+ New folder here…"))
397
+ choices.append(Choice("hidden", "· Hide hidden folders" if show_hidden
398
+ else "· Show hidden folders"))
399
+
400
+ # Plain string — questionary messages go through prompt_toolkit,
401
+ # which renders raw ANSI escapes as literal text.
402
+ # ← climbs to the parent (not "previous step" like elsewhere) and
403
+ # → enters the highlighted folder — mc-style navigation.
404
+ picked = select(
405
+ f"{message} [{current}]", choices, back=back, use_search=True,
406
+ on_left="up",
407
+ right_pick=lambda v: v == "up" or (isinstance(v, tuple) and v[0] == "cd"),
408
+ instruction="(type to filter, ↑↓ + Enter, ←/→ = parent/enter dir)")
409
+ if picked is BACK:
410
+ return BACK
411
+ if picked == "suggest":
412
+ return str(suggest_path)
413
+ if picked == "use":
414
+ return str(current)
415
+ if picked == "up":
416
+ current = current.parent
417
+ elif picked == "hidden":
418
+ show_hidden = not show_hidden
419
+ elif picked == "type":
420
+ typed = path_input(message, default=str(current),
421
+ must_exist=not allow_create,
422
+ offer_create=allow_create, back=True)
423
+ if typed is BACK:
424
+ continue # back into the browser, not out of it
425
+ return typed
426
+ elif picked == "mkdir":
427
+ name = text("New folder name", default="", back=True)
428
+ if name is BACK or not name:
429
+ continue
430
+ new_dir = current / name
431
+ try:
432
+ new_dir.mkdir(parents=True, exist_ok=True)
433
+ except OSError as e:
434
+ warn(f"Can't create {new_dir}: {e}")
435
+ continue
436
+ current = new_dir.resolve()
437
+ else:
438
+ current = picked[1]