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,419 @@
1
+ /**
2
+ * Message-action mixin — clipboard copy (copyCode / copyToolOutput / copyMessage),
3
+ * toggleMessageFavorite, the AskUserQuestion answer flow (submitQuestionAnswers /
4
+ * ignoreQuestion / updateQuestionMessage), and plan approve/reject/preview.
5
+ * Extracted from app.js; applied to App.prototype via Object.assign. Uses `this`
6
+ * (App instance) plus the imports below.
7
+ */
8
+ import { $ } from '../utils.js';
9
+ import { FilePreviewWidget } from '../widget-system/init.js';
10
+
11
+ export const messageActionMethods = {
12
+ copyCode(codeId) {
13
+ const codeEl = $(`#${codeId}`);
14
+ if (codeEl) {
15
+ navigator.clipboard.writeText(codeEl.textContent);
16
+ const btn = $(`[data-code-id="${codeId}"]`);
17
+ if (btn) {
18
+ btn.classList.add('copied');
19
+ btn.innerHTML = `
20
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
21
+ <path d="M20 6L9 17l-5-5"/>
22
+ </svg>
23
+ Copied!
24
+ `;
25
+ setTimeout(() => {
26
+ btn.classList.remove('copied');
27
+ btn.innerHTML = `
28
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
29
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
30
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
31
+ </svg>
32
+ Copy
33
+ `;
34
+ }, 2000);
35
+ }
36
+ }
37
+ },
38
+
39
+ copyToolOutput(msgId) {
40
+ const msg = this.activeSession?.messages.find(m => m.id === msgId);
41
+ if (msg && (msg.toolOutput || msg.toolError)) {
42
+ navigator.clipboard.writeText(msg.toolOutput || msg.toolError);
43
+ }
44
+ },
45
+
46
+ copyMessage(msgId) {
47
+ const msg = this.activeSession?.messages.find(m => m.id === msgId);
48
+ if (msg && msg.content) {
49
+ navigator.clipboard.writeText(msg.content);
50
+ const btn = $(`[data-msg-id="${msgId}"].message-copy-btn`);
51
+ if (btn) {
52
+ btn.classList.add('copied');
53
+ btn.innerHTML = `
54
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
55
+ <path d="M20 6L9 17l-5-5"/>
56
+ </svg>
57
+ <span class="copy-label">Copied!</span>
58
+ `;
59
+ setTimeout(() => {
60
+ btn.classList.remove('copied');
61
+ btn.innerHTML = `
62
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
63
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
64
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
65
+ </svg>
66
+ <span class="copy-label">Copy</span>
67
+ `;
68
+ }, 2000);
69
+ }
70
+ }
71
+ },
72
+
73
+ /**
74
+ * Toggle favorite state for a user message
75
+ * Called from the favorite button in message header
76
+ * @param {HTMLElement} btn - The button element
77
+ */
78
+ async toggleMessageFavorite(btn) {
79
+ const msgId = btn.dataset.msgId;
80
+ const promptId = btn.dataset.promptId;
81
+
82
+ if (!promptId) {
83
+ console.warn('[App] Cannot favorite message without promptId');
84
+ return;
85
+ }
86
+
87
+ // Find the message in active session (use window.app to avoid this binding issues)
88
+ const app = window.app;
89
+ const msg = app?.activeSession?.messages?.find(m => m.id === msgId);
90
+
91
+ // Optimistic UI update
92
+ const wasActive = btn.classList.contains('active');
93
+ btn.classList.toggle('active');
94
+ btn.setAttribute('data-tooltip', wasActive ? 'Add to favorites' : 'Remove from favorites');
95
+
96
+ // Import and call the API
97
+ try {
98
+ const { toggleFavorite } = await import('../prompt-favorites.js');
99
+ // Get content preview from message if found, otherwise from button's parent message
100
+ const contentPreview = msg?.content?.substring(0, 100) || '';
101
+ const newState = await toggleFavorite(promptId, contentPreview);
102
+
103
+ // Update session message state if found
104
+ if (msg) {
105
+ msg.isFavorite = newState;
106
+ app?.sessionManager?.saveSessions();
107
+ }
108
+
109
+ // Revert UI if API state doesn't match expected
110
+ if (newState === wasActive) {
111
+ btn.classList.toggle('active');
112
+ btn.setAttribute('data-tooltip', newState ? 'Remove from favorites' : 'Add to favorites');
113
+ }
114
+ } catch (err) {
115
+ console.error('[App] Failed to toggle favorite:', err);
116
+ // Revert UI on error
117
+ btn.classList.toggle('active');
118
+ btn.setAttribute('data-tooltip', wasActive ? 'Remove from favorites' : 'Add to favorites');
119
+ }
120
+ },
121
+
122
+ // ═══════════════════════════════════════════════════════════════
123
+ // ASKUSERQUESTION HANDLING
124
+ // ═══════════════════════════════════════════════════════════════
125
+
126
+ /**
127
+ * Submit answers for AskUserQuestion tool (supports both single and grouped wizard mode)
128
+ * Called from the submit button in the question form
129
+ * @param {string} msgId - The message ID containing question(s)
130
+ */
131
+ submitQuestionAnswers(msgId) {
132
+ const session = this.activeSession;
133
+ if (!session) {
134
+ console.error('No active session');
135
+ return;
136
+ }
137
+
138
+ // Find the question message
139
+ const questionMsg = session.messages.find(m => m.id === msgId && m.role === 'question');
140
+ if (!questionMsg) {
141
+ console.error('Question message not found:', msgId);
142
+ return;
143
+ }
144
+
145
+ const form = document.querySelector(`.question-form[data-msg-id="${msgId}"]`);
146
+ if (!form) {
147
+ console.error('Question form not found:', msgId);
148
+ return;
149
+ }
150
+
151
+ // Get entries (support both old single format and new grouped format)
152
+ const entries = questionMsg.entries || [{
153
+ toolId: questionMsg.toolId,
154
+ questions: questionMsg.questions || [],
155
+ answers: {}
156
+ }];
157
+
158
+ // Collect answers from all entries
159
+ let hasAnyAnswer = false;
160
+
161
+ form.querySelectorAll('.question-entry').forEach((entryEl, entryIdx) => {
162
+ const entry = entries[entryIdx];
163
+ if (!entry) return;
164
+
165
+ entryEl.querySelectorAll('.question-item').forEach(item => {
166
+ const header = item.dataset.header;
167
+ const isMulti = item.dataset.multi === 'true';
168
+ const selectedOptions = item.querySelectorAll('.question-option.selected');
169
+
170
+ if (isMulti) {
171
+ const values = Array.from(selectedOptions)
172
+ .map(btn => {
173
+ if (btn.dataset.value === '__other__') {
174
+ const input = item.querySelector('.question-other-input');
175
+ return input?.value?.trim() || '';
176
+ }
177
+ return btn.dataset.value;
178
+ })
179
+ .filter(v => v);
180
+ if (values.length > 0) {
181
+ entry.answers[header] = values.join(', ');
182
+ hasAnyAnswer = true;
183
+ }
184
+ } else {
185
+ const selected = selectedOptions[0];
186
+ if (selected) {
187
+ if (selected.dataset.value === '__other__') {
188
+ const input = item.querySelector('.question-other-input');
189
+ entry.answers[header] = input?.value?.trim() || 'Other';
190
+ } else {
191
+ entry.answers[header] = selected.dataset.value;
192
+ }
193
+ hasAnyAnswer = true;
194
+ }
195
+ }
196
+ });
197
+ });
198
+
199
+ // Free-text comment (shared across the card, independent of the options).
200
+ const commentEl = form.querySelector('.question-comment');
201
+ const comment = commentEl?.value?.trim() || '';
202
+
203
+ // Need at least one selected answer OR a comment — a wholly empty submit
204
+ // shakes the form.
205
+ if (!hasAnyAnswer && !comment) {
206
+ form.classList.add('shake');
207
+ setTimeout(() => form.classList.remove('shake'), 500);
208
+ return;
209
+ }
210
+
211
+ // Persist the comment so it re-renders on the answered card / restore.
212
+ questionMsg.comment = comment;
213
+
214
+ // Send answers for each entry (each has its own tool_id)
215
+ session.sendToolAnswers(entries, questionMsg, comment);
216
+ },
217
+
218
+ /**
219
+ * Dismiss an AskUserQuestion without sending anything to Claude.
220
+ * Marks the message as answered+ignored locally so the banner clears
221
+ * and the form collapses, but no tool_answer is sent over the WS.
222
+ */
223
+ ignoreQuestion(msgId) {
224
+ const session = this.activeSession;
225
+ if (!session) return;
226
+
227
+ const questionMsg = session.messages.find(m => m.id === msgId && m.role === 'question');
228
+ if (!questionMsg || questionMsg.answered) return;
229
+
230
+ questionMsg.answered = true;
231
+ questionMsg.ignored = true;
232
+
233
+ this.updateQuestionMessage(questionMsg);
234
+
235
+ session.pendingQuestionId = null;
236
+ session.updateTab();
237
+ this.updateQuestionIndicator();
238
+ this.sessionManager.saveSessions();
239
+ },
240
+
241
+ /**
242
+ * Re-open an already-answered AskUserQuestion card so the user can change
243
+ * their choice and resend it to Claude. Sets a transient `_editing` flag and
244
+ * re-renders — the form comes back interactive with the previous answers
245
+ * pre-selected (see _renderQuestionForm / findAnswerRecord). No WS traffic
246
+ * until the user actually submits.
247
+ */
248
+ editQuestionAnswer(msgId) {
249
+ const session = this.activeSession;
250
+ if (!session) return;
251
+
252
+ const questionMsg = session.messages.find(m => m.id === msgId && m.role === 'question');
253
+ if (!questionMsg || !questionMsg.answered) return;
254
+ // An ignored/skipped card was never actually answered — nothing to edit.
255
+ if (questionMsg.ignored || questionMsg.skipped) return;
256
+
257
+ questionMsg._editing = true;
258
+ this.updateQuestionMessage(questionMsg);
259
+ },
260
+
261
+ /** Abandon an in-progress answer edit and restore the answered view. */
262
+ cancelEditQuestion(msgId) {
263
+ const session = this.activeSession;
264
+ if (!session) return;
265
+
266
+ const questionMsg = session.messages.find(m => m.id === msgId && m.role === 'question');
267
+ if (!questionMsg) return;
268
+
269
+ questionMsg._editing = false;
270
+ this.updateQuestionMessage(questionMsg);
271
+ },
272
+
273
+ /** @delegate ChatController */
274
+ updateQuestionMessage(msg) {
275
+ this.chatCtrl.updateQuestionMessage(msg);
276
+ },
277
+
278
+ /**
279
+ * Answer an interactive permission card (claude-sdk can_use_tool ask).
280
+ * Reads the optional deny-guidance input and relays the decision over the
281
+ * session WebSocket; the provider process resumes as soon as it lands.
282
+ */
283
+ respondPermission(msgId, behavior, suggestionIndex) {
284
+ const session = this.activeSession;
285
+ if (!session) return;
286
+ const feedback = behavior === 'deny'
287
+ ? (document.getElementById(`perm-feedback-${msgId}`)?.value || '').trim()
288
+ : '';
289
+ session.sendPermissionResponse(msgId, behavior, feedback, suggestionIndex);
290
+ this.updateQuestionIndicator();
291
+ },
292
+
293
+ /**
294
+ * The newest unanswered interactive permission card in the active session,
295
+ * or null. Backs the Enter=Allow / Esc=Deny keyboard shortcuts so the user
296
+ * can clear a pending ask without reaching for the mouse.
297
+ */
298
+ pendingPermissionCard() {
299
+ const session = this.activeSession;
300
+ if (!session) return null;
301
+ for (let i = session.messages.length - 1; i >= 0; i--) {
302
+ const m = session.messages[i];
303
+ if (m.role === 'permission' && !m.answered) return m;
304
+ }
305
+ return null;
306
+ },
307
+
308
+ /**
309
+ * Allow the pending permission card via keyboard (Enter). No-op when there
310
+ * is no card waiting — the shortcut's `permissionPending` context already
311
+ * gates this, so the guard is just belt-and-suspenders.
312
+ */
313
+ allowPendingPermission() {
314
+ const msg = this.pendingPermissionCard();
315
+ if (!msg) return;
316
+ this.respondPermission(msg.id, 'allow');
317
+ },
318
+
319
+ /**
320
+ * Deny the pending permission card via keyboard (Esc). Any text typed into
321
+ * the deny-guidance field rides along as feedback. Returns true when a card
322
+ * was denied so handleEscape() can stop its priority chain.
323
+ */
324
+ denyPendingPermission() {
325
+ const msg = this.pendingPermissionCard();
326
+ if (!msg) return false;
327
+ this.respondPermission(msg.id, 'deny');
328
+ return true;
329
+ },
330
+
331
+ /**
332
+ * Approve plan from ExitPlanMode approval card
333
+ * Sends approval as a regular user message and resumes Claude
334
+ */
335
+ approvePlan(msgId) {
336
+ const session = this.activeSession;
337
+ if (!session) return;
338
+
339
+ const msg = session.messages.find(m => m.id === msgId && m.role === 'plan_approval');
340
+ if (!msg || msg.answered) return;
341
+
342
+ msg.answered = true;
343
+ msg.decision = 'approve';
344
+
345
+ // Re-render the approval card
346
+ const el = document.getElementById(`msg-${msg.id}`);
347
+ if (el) {
348
+ const newEl = this.chatCtrl.createMessageElement(msg);
349
+ el.replaceWith(newEl);
350
+ }
351
+
352
+ // Clear pending question state
353
+ session.pendingQuestionId = null;
354
+ session.updateTab();
355
+ this.updateQuestionIndicator();
356
+ this.sessionManager.saveSessions();
357
+
358
+ // Send approval as a regular user message
359
+ this.sendMessage('Approved. Proceed with the plan implementation.');
360
+ },
361
+
362
+ /**
363
+ * Reject plan from ExitPlanMode approval card
364
+ * Re-enters plan mode and lets user type feedback
365
+ */
366
+ rejectPlan(msgId) {
367
+ const session = this.activeSession;
368
+ if (!session) return;
369
+
370
+ const msg = session.messages.find(m => m.id === msgId && m.role === 'plan_approval');
371
+ if (!msg || msg.answered) return;
372
+
373
+ // Soft state — user is revising, but can still Approve later
374
+ msg.decision = 'revise';
375
+
376
+ // Re-render the approval card (shows revise status + Approve button)
377
+ const el = document.getElementById(`msg-${msg.id}`);
378
+ if (el) {
379
+ const newEl = this.chatCtrl.createMessageElement(msg);
380
+ el.replaceWith(newEl);
381
+ }
382
+
383
+ // Re-enter plan mode on client
384
+ session.permissionMode = 'plan';
385
+ session.pendingQuestionId = null;
386
+ session.updateTab();
387
+ this.updateInputPlaceholder();
388
+ this.updateQuestionIndicator();
389
+ this.sessionManager.saveSessions();
390
+
391
+ // Re-enter plan mode on server (so restarts use --permission-mode plan)
392
+ if (session.ws?.readyState === WebSocket.OPEN) {
393
+ session.ws.send(JSON.stringify({
394
+ type: 'set_permission_mode',
395
+ mode: 'plan'
396
+ }));
397
+ }
398
+
399
+ // Focus input so user can type feedback
400
+ this.els.messageInput?.focus();
401
+ },
402
+
403
+ /**
404
+ * Preview the plan file for the current session.
405
+ * Plan file path is stored on the plan_approval message by handleExitPlanMode.
406
+ */
407
+ async previewPlan() {
408
+ const session = this.activeSession;
409
+ if (!session) return;
410
+
411
+ // Find the latest plan_approval message with a planFile
412
+ const msg = [...session.messages].reverse().find(m => m.role === 'plan_approval' && m.planFile);
413
+ if (!msg?.planFile) return;
414
+
415
+ // Use the already-imported FilePreviewWidget (not dynamic import, which would
416
+ // load a separate module instance due to cache-bust query params)
417
+ FilePreviewWidget.preview(msg.planFile);
418
+ },
419
+ };
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Panel & widget toggle mixin — thin delegators that open/close/cycle the
3
+ * various floating widgets, panels, and switchers from keyboard shortcuts,
4
+ * rail buttons, and quick actions. Applied to App.prototype via Object.assign
5
+ * in app.js; every method uses `this` (App instance) and the imports below.
6
+ */
7
+ import { WidgetManager, WidgetBus } from '../widget-system/init.js';
8
+ import { QuickSwitcher } from '../quick-switcher/index.js';
9
+ import { OpenDialog } from '../open-dialog.js';
10
+ import { QuickActionsRegistry } from '../quick-actions-registry.js';
11
+ import { QuickActionsMenu } from '../widgets/index.js';
12
+ import { bgTaskTracker } from '../background-tasks.js';
13
+ import { effortSettings } from '../effort-settings.js';
14
+
15
+ export const panelMethods = {
16
+ toggleFileExplorer() {
17
+ this.fileExplorer?.toggle();
18
+ },
19
+
20
+ toggleSearchFiles() {
21
+ WidgetManager.toggle('search-files');
22
+ },
23
+
24
+ toggleQuickSwitcher() {
25
+ QuickSwitcher.toggle();
26
+ },
27
+
28
+ // Command palette — same picker as the quick switcher, opened straight
29
+ // into command mode via the '>' prefix (VS Code / Cursor / Zed idiom).
30
+ // Pressing again while open just hides (toggle semantics).
31
+ openCommandPalette() {
32
+ if (QuickSwitcher.isOpen()) QuickSwitcher.hide();
33
+ else QuickSwitcher.show('>');
34
+ },
35
+
36
+ toggleOpenDialog() {
37
+ OpenDialog.toggle();
38
+ },
39
+
40
+ toggleGridSwitcher() {
41
+ this.gridSwitcher?.toggle();
42
+ },
43
+
44
+ /**
45
+ * Cycle through cards in the grid switcher. If the grid is closed, opens it.
46
+ * If already open, advances focus by `direction` (+1 forward, -1 backward).
47
+ * Wired to Alt+Tab (forward) and Alt+Shift+Tab (backward).
48
+ */
49
+ cycleGridSwitcher(direction = 1) {
50
+ if (!this.gridSwitcher) return;
51
+ if (this.gridSwitcher.visible) {
52
+ this.gridSwitcher.advance(direction);
53
+ } else {
54
+ // Mac/Windows app-switcher feel: open with the next session
55
+ // pre-selected (skipping the currently-active one) and commit
56
+ // on Alt-release.
57
+ this.gridSwitcher.show({
58
+ commitOnAltRelease: true,
59
+ initialDirection: direction,
60
+ });
61
+ }
62
+ },
63
+
64
+ toggleLogExplorer() {
65
+ WidgetManager.toggle('log-explorer');
66
+ },
67
+
68
+ toggleGitPanel() {
69
+ WidgetManager.toggle('git');
70
+ },
71
+
72
+ toggleActiveSessions() {
73
+ WidgetManager.toggle('active-sessions');
74
+ },
75
+
76
+ toggleCostAnalytics() {
77
+ WidgetManager.toggle('cost-analytics');
78
+ },
79
+
80
+ toggleDiscussion() {
81
+ WidgetManager.toggle('discussion');
82
+ },
83
+
84
+ toggleDebugConsole() {
85
+ WidgetManager.toggle('debug-logs');
86
+ },
87
+
88
+ toggleHistoryExplorer() {
89
+ WidgetManager.toggle('history-explorer');
90
+ },
91
+
92
+ togglePromptExplorer() {
93
+ WidgetManager.toggle('prompt-explorer');
94
+ },
95
+
96
+ // Bank the current input as a server-side draft (Ctrl+Shift+S).
97
+ // Retrieval lives in the Prompt Explorer's Drafts tab.
98
+ savePromptDraft() {
99
+ this.inputHandler?.saveAsDraft();
100
+ },
101
+
102
+ // Explicit reload so Cmd+R works in the iOS/PWA standalone wrapper,
103
+ // where there's no browser chrome to handle it natively. In a normal
104
+ // browser this is equivalent to the native reload; Cmd+Shift+R (hard
105
+ // reload) is a different key and still passes through.
106
+ reloadPage() {
107
+ window.location.reload();
108
+ },
109
+
110
+ toggleSkills() {
111
+ WidgetManager.toggle('skills');
112
+ },
113
+
114
+ toggleCommands() {
115
+ WidgetManager.toggle('commands');
116
+ },
117
+
118
+ async togglePreviewInlineEdit() {
119
+ // Use module namespace (not destructuring) so state is the live binding
120
+ // — activateState(tabId) reassigns the module-level state variable
121
+ const mod = await import('../preview/preview-state.js');
122
+
123
+ // Find file-preview container — could be floating widget OR a tab.
124
+ // Prefer the ACTIVE instance's own container (state.container is set on
125
+ // every render): a bare document.querySelector takes the first
126
+ // .file-preview-widget in DOM order, which with a floating preview +
127
+ // hidden tab previews open could be a pane the user isn't looking at —
128
+ // the viewMode check below and the toggle would then disagree
129
+ // ("e does nothing" / toggles a hidden pane).
130
+ const stateContainer = mod.state.container?.isConnected ? mod.state.container : null;
131
+ const container = stateContainer?.querySelector('.file-preview-widget')
132
+ || stateContainer
133
+ || document.querySelector('.file-preview-widget');
134
+ if (!container) return;
135
+
136
+ if (mod.state.viewMode === 'code' && mod.isEditable()) {
137
+ // Code view → enter edit mode, capturing visible line for scroll restoration
138
+ const scrollContainer = container.querySelector('.preview-body');
139
+ if (scrollContainer) {
140
+ const scrollTop = scrollContainer.scrollTop;
141
+ const lineRows = scrollContainer.querySelectorAll('.preview-line');
142
+ for (const row of lineRows) {
143
+ const rowTop = row.getBoundingClientRect().top - scrollContainer.getBoundingClientRect().top + scrollTop;
144
+ if (rowTop + row.offsetHeight > scrollTop) {
145
+ mod.state.scrollToLine = parseInt(row.dataset.line, 10);
146
+ mod.state.scrollOptions = { flash: false, position: 'center' };
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ const { switchToEditView } = await import('../preview/preview-edit.js');
152
+ switchToEditView();
153
+ } else if (mod.isEditMode()) {
154
+ // Edit mode → leave to code view (leaveEditView captures cursor line)
155
+ const { leaveEditView } = await import('../preview/preview-edit.js');
156
+ leaveEditView('code');
157
+ } else {
158
+ // Rendered/other view → toggle inline edit
159
+ const { toggleInlineEdit } = await import('../preview/preview-inline-edit.js');
160
+ toggleInlineEdit(container);
161
+ }
162
+ },
163
+
164
+ toggleBackgroundTasks() {
165
+ WidgetManager.toggle('background-tasks');
166
+ },
167
+
168
+ toggleBrowser() {
169
+ WidgetManager.toggle('browser');
170
+ },
171
+
172
+ toggleZenMode() {
173
+ window.toggleZenMode?.();
174
+ },
175
+
176
+ openBackgroundTask(taskId) {
177
+ WidgetManager.open('background-tasks');
178
+ WidgetBus.emit('background-task:focus', { taskId });
179
+ },
180
+
181
+ _updateBgTasksBadge() {
182
+ const badge = document.getElementById('tasks-badge');
183
+ if (!badge) return;
184
+ const running = bgTaskTracker.runningCount();
185
+ badge.textContent = running > 0 ? running : '';
186
+ },
187
+
188
+ /**
189
+ * Update the running agents header badge.
190
+ * Called from session._handleTaskProgress / _onAgentCompleted.
191
+ */
192
+ updateAgentsBadge(count) {
193
+ const badge = document.getElementById('agents-badge');
194
+ if (!badge) return;
195
+ badge.textContent = count > 0 ? count : '';
196
+ },
197
+
198
+ toggleThinkingSettings() {
199
+ effortSettings.cycle();
200
+ },
201
+
202
+ cycleEffortOneShot() {
203
+ effortSettings.cycleOneShot();
204
+ },
205
+
206
+
207
+ toggleQuickActions() {
208
+ QuickActionsMenu.toggle();
209
+ },
210
+ };