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.
- painapple_code/__init__.py +30 -0
- painapple_code/__main__.py +18 -0
- painapple_code/_version.py +24 -0
- painapple_code/auth_middleware.py +930 -0
- painapple_code/cli/__init__.py +204 -0
- painapple_code/cli/deploy/__init__.py +15 -0
- painapple_code/cli/deploy/claude_seed.py +41 -0
- painapple_code/cli/deploy/config.py +260 -0
- painapple_code/cli/deploy/container.py +715 -0
- painapple_code/cli/deploy/launch.py +163 -0
- painapple_code/cli/deploy/runtime.py +223 -0
- painapple_code/cli/lifecycle_cmd.py +443 -0
- painapple_code/cli/list_cmd.py +572 -0
- painapple_code/cli/manage_cmd.py +670 -0
- painapple_code/cli/netinfo.py +125 -0
- painapple_code/cli/profiles.py +390 -0
- painapple_code/cli/serve_args.py +163 -0
- painapple_code/cli/serve_config.py +256 -0
- painapple_code/cli/setup_cmd.py +664 -0
- painapple_code/cli/ui.py +438 -0
- painapple_code/cost_analytics.py +879 -0
- painapple_code/data/models.defaults.yaml +18 -0
- painapple_code/data/models.yaml +18 -0
- painapple_code/data/presets.defaults.json +32 -0
- painapple_code/data/strings.yaml +2269 -0
- painapple_code/helpers.py +406 -0
- painapple_code/paths.py +1514 -0
- painapple_code/prompt_explorer.py +984 -0
- painapple_code/providers/__init__.py +224 -0
- painapple_code/providers/base.py +710 -0
- painapple_code/providers/claude/__init__.py +74 -0
- painapple_code/providers/claude/capabilities.py +218 -0
- painapple_code/providers/claude/errors.py +79 -0
- painapple_code/providers/claude/launch.py +50 -0
- painapple_code/providers/claude/summary.py +89 -0
- painapple_code/providers/claude/translate.py +74 -0
- painapple_code/providers/claude_sdk/__init__.py +139 -0
- painapple_code/providers/claude_sdk/driver.py +412 -0
- painapple_code/providers/codex/__init__.py +87 -0
- painapple_code/providers/codex/capabilities.py +321 -0
- painapple_code/providers/codex/errors.py +70 -0
- painapple_code/providers/codex/launch.py +142 -0
- painapple_code/providers/codex/summary.py +196 -0
- painapple_code/providers/codex/translate.py +204 -0
- painapple_code/providers/codex_app_server/__init__.py +100 -0
- painapple_code/providers/codex_app_server/capabilities.py +41 -0
- painapple_code/providers/codex_app_server/launch.py +202 -0
- painapple_code/providers/codex_app_server/summary.py +291 -0
- painapple_code/providers/codex_app_server/translate.py +263 -0
- painapple_code/providers/codex_app_server/transport.py +238 -0
- painapple_code/routes/__init__.py +0 -0
- painapple_code/routes/api_agents.py +441 -0
- painapple_code/routes/api_app.py +368 -0
- painapple_code/routes/api_app_commit_sections.py +127 -0
- painapple_code/routes/api_app_config.py +997 -0
- painapple_code/routes/api_app_session_prefs.py +217 -0
- painapple_code/routes/api_browser.py +690 -0
- painapple_code/routes/api_commands.py +374 -0
- painapple_code/routes/api_costs.py +128 -0
- painapple_code/routes/api_drafts.py +139 -0
- painapple_code/routes/api_exec.py +149 -0
- painapple_code/routes/api_files.py +542 -0
- painapple_code/routes/api_git.py +657 -0
- painapple_code/routes/api_logs.py +649 -0
- painapple_code/routes/api_plugins.py +275 -0
- painapple_code/routes/api_project_config.py +418 -0
- painapple_code/routes/api_prompts.py +231 -0
- painapple_code/routes/api_providers.py +53 -0
- painapple_code/routes/api_search.py +411 -0
- painapple_code/routes/api_session_stash.py +234 -0
- painapple_code/routes/api_session_welcome.py +151 -0
- painapple_code/routes/api_sessions.py +742 -0
- painapple_code/routes/api_shadow.py +323 -0
- painapple_code/routes/api_shadow_db.py +512 -0
- painapple_code/routes/api_shadow_files.py +332 -0
- painapple_code/routes/api_shadow_search.py +476 -0
- painapple_code/routes/api_skills.py +610 -0
- painapple_code/routes/api_tasks.py +147 -0
- painapple_code/routes/api_terminal.py +540 -0
- painapple_code/routes/api_upload.py +293 -0
- painapple_code/routes/api_viewer.py +229 -0
- painapple_code/routes/dependencies.py +109 -0
- painapple_code/routes/ws_chat.py +837 -0
- painapple_code/server.py +2208 -0
- painapple_code/server_logging.py +263 -0
- painapple_code/services/__init__.py +0 -0
- painapple_code/services/agent_session.py +2738 -0
- painapple_code/session_store.py +253 -0
- painapple_code/session_store_core.py +879 -0
- painapple_code/shadow_db.py +830 -0
- painapple_code/shadow_db_plans.py +369 -0
- painapple_code/shadow_db_queries.py +595 -0
- painapple_code/shadow_db_schema.py +676 -0
- painapple_code/shadow_git.py +1296 -0
- painapple_code/shadow_git_frontmatter.py +314 -0
- painapple_code/shadow_git_sections.py +520 -0
- painapple_code/shadow_git_summary.py +462 -0
- painapple_code/shadow_parser.py +560 -0
- painapple_code/static/css/00-variables.css +276 -0
- painapple_code/static/css/01-base.css +245 -0
- painapple_code/static/css/02-tooltips.css +36 -0
- painapple_code/static/css/09-left-rail.css +448 -0
- painapple_code/static/css/10-header.css +777 -0
- painapple_code/static/css/11-connection-bar.css +120 -0
- painapple_code/static/css/20-messages.css +750 -0
- painapple_code/static/css/21-tool-blocks.css +2340 -0
- painapple_code/static/css/22-edit-diff.css +619 -0
- painapple_code/static/css/23-thinking.css +1584 -0
- painapple_code/static/css/24-background-tasks.css +359 -0
- painapple_code/static/css/25-context-block.css +252 -0
- painapple_code/static/css/26-session-setup.css +144 -0
- painapple_code/static/css/30-markdown.css +854 -0
- painapple_code/static/css/40-input.css +2418 -0
- painapple_code/static/css/41-autocomplete.css +127 -0
- painapple_code/static/css/42-activity-strip.css +97 -0
- painapple_code/static/css/43-file-autocomplete.css +121 -0
- painapple_code/static/css/44-snippets-autocomplete.css +224 -0
- painapple_code/static/css/45-selection-bar.css +433 -0
- painapple_code/static/css/46-keyboard-bar.css +119 -0
- painapple_code/static/css/47-keyboard-longpress.css +116 -0
- painapple_code/static/css/47-token-profile.css +106 -0
- painapple_code/static/css/50-welcome.css +3095 -0
- painapple_code/static/css/51-modals.css +301 -0
- painapple_code/static/css/52-utilities.css +77 -0
- painapple_code/static/css/53-image-upload.css +456 -0
- painapple_code/static/css/54-todo-list.css +102 -0
- painapple_code/static/css/55-file-upload.css +113 -0
- painapple_code/static/css/56-chat-navigator.css +218 -0
- painapple_code/static/css/56-permission-card.css +332 -0
- painapple_code/static/css/56-plan-approval.css +204 -0
- painapple_code/static/css/56-question-form.css +602 -0
- painapple_code/static/css/57-swipe-indicator.css +95 -0
- painapple_code/static/css/58-session-families.css +627 -0
- painapple_code/static/css/59-about.css +196 -0
- painapple_code/static/css/61-log-explorer.css +723 -0
- painapple_code/static/css/62-json-tree.css +489 -0
- painapple_code/static/css/62-skills-widget.css +650 -0
- painapple_code/static/css/63-commands-widget.css +30 -0
- painapple_code/static/css/63-plugins-widget.css +174 -0
- painapple_code/static/css/63-terminal.css +516 -0
- painapple_code/static/css/64-search-files-widget.css +448 -0
- painapple_code/static/css/65-snippets-widget.css +426 -0
- painapple_code/static/css/66-active-sessions.css +979 -0
- painapple_code/static/css/66-agents-widget.css +265 -0
- painapple_code/static/css/66-file-explorer-widget.css +855 -0
- painapple_code/static/css/66-git-panel.css +791 -0
- painapple_code/static/css/66-history-explorer.css +1496 -0
- painapple_code/static/css/67-diff-viewer.css +602 -0
- painapple_code/static/css/67-editor-view.css +748 -0
- painapple_code/static/css/67-file-preview-widget.css +1873 -0
- painapple_code/static/css/68-browser-widget.css +337 -0
- painapple_code/static/css/68-compare-wizard.css +355 -0
- painapple_code/static/css/68-csv-preview.css +106 -0
- painapple_code/static/css/68-diff-preview.css +103 -0
- painapple_code/static/css/68-helpers-install-widget.css +491 -0
- painapple_code/static/css/68-image-annotate.css +346 -0
- painapple_code/static/css/68-terminal-view.css +98 -0
- painapple_code/static/css/68-uploads-widget.css +229 -0
- painapple_code/static/css/69-cost-analytics.css +537 -0
- painapple_code/static/css/70-config-panel.css +3736 -0
- painapple_code/static/css/70-excalidraw.css +136 -0
- painapple_code/static/css/70-prompt-explorer.css +890 -0
- painapple_code/static/css/71-chart.css +111 -0
- painapple_code/static/css/72-discussion-sidebar.css +678 -0
- painapple_code/static/css/74-debug-logs.css +656 -0
- painapple_code/static/css/80-open-dialog.css +146 -0
- painapple_code/static/css/80-quick-switcher.css +383 -0
- painapple_code/static/css/80-widget-system.css +1763 -0
- painapple_code/static/css/81-quick-actions.css +1020 -0
- painapple_code/static/css/82-context-popover.css +1233 -0
- painapple_code/static/css/83-context-menu.css +339 -0
- painapple_code/static/css/85-lazy-loading.css +102 -0
- painapple_code/static/css/86-zen-mode.css +708 -0
- painapple_code/static/css/87-grid-switcher.css +346 -0
- painapple_code/static/css/login.css +198 -0
- painapple_code/static/dist/app.bundle.js +7177 -0
- painapple_code/static/feature-triage.html +446 -0
- painapple_code/static/icons/apple-touch-icon.png +0 -0
- painapple_code/static/icons/favicon-32.png +0 -0
- painapple_code/static/icons/favicon.ico +0 -0
- painapple_code/static/icons/icon-128.png +0 -0
- painapple_code/static/icons/icon-144.png +0 -0
- painapple_code/static/icons/icon-152.png +0 -0
- painapple_code/static/icons/icon-180.png +0 -0
- painapple_code/static/icons/icon-192.png +0 -0
- painapple_code/static/icons/icon-384.png +0 -0
- painapple_code/static/icons/icon-512.png +0 -0
- painapple_code/static/icons/icon-72.png +0 -0
- painapple_code/static/icons/icon-96.png +0 -0
- painapple_code/static/js/action-delegate.js +187 -0
- painapple_code/static/js/activity-strip.js +193 -0
- painapple_code/static/js/app/context-menus.js +619 -0
- painapple_code/static/js/app/file-preview.js +163 -0
- painapple_code/static/js/app/init-ui.js +574 -0
- painapple_code/static/js/app/input-events.js +561 -0
- painapple_code/static/js/app/input-resize.js +313 -0
- painapple_code/static/js/app/message-actions.js +419 -0
- painapple_code/static/js/app/panels.js +210 -0
- painapple_code/static/js/app/rendering-delegators.js +246 -0
- painapple_code/static/js/app/server-loading.js +365 -0
- painapple_code/static/js/app/session-ops.js +287 -0
- painapple_code/static/js/app/session-switch.js +443 -0
- painapple_code/static/js/app/settings-helpers.js +280 -0
- painapple_code/static/js/app/startup-welcome.js +290 -0
- painapple_code/static/js/app-context.js +157 -0
- painapple_code/static/js/app.js +2134 -0
- painapple_code/static/js/auth-fetch.js +46 -0
- painapple_code/static/js/background-tasks.js +274 -0
- painapple_code/static/js/boot.js +73 -0
- painapple_code/static/js/caret-position.js +171 -0
- painapple_code/static/js/chat-navigator.js +710 -0
- painapple_code/static/js/chat-search.js +323 -0
- painapple_code/static/js/command-executor.js +176 -0
- painapple_code/static/js/command-store.js +497 -0
- painapple_code/static/js/components.js +1414 -0
- painapple_code/static/js/config.js +218 -0
- painapple_code/static/js/context-menu.js +740 -0
- painapple_code/static/js/controllers/chat-controller.js +4252 -0
- painapple_code/static/js/controllers/dialog-controller.js +217 -0
- painapple_code/static/js/controllers/tab-controller.js +1828 -0
- painapple_code/static/js/controllers/thinking-controller.js +915 -0
- painapple_code/static/js/diff-utils.js +486 -0
- painapple_code/static/js/drafts-pill.js +95 -0
- painapple_code/static/js/editor-view.js +815 -0
- painapple_code/static/js/effort-settings.js +430 -0
- painapple_code/static/js/feature-triage.js +811 -0
- painapple_code/static/js/file-autocomplete.js +516 -0
- painapple_code/static/js/file-tabs.js +123 -0
- painapple_code/static/js/gestures.js +448 -0
- painapple_code/static/js/grid-switcher.js +334 -0
- painapple_code/static/js/input-handler.js +1472 -0
- painapple_code/static/js/keyboard-bar.js +495 -0
- painapple_code/static/js/layout-switcher.js +109 -0
- painapple_code/static/js/linkify-utils.js +200 -0
- painapple_code/static/js/login.js +141 -0
- painapple_code/static/js/open-dialog.js +879 -0
- painapple_code/static/js/orphan-terminals.js +373 -0
- painapple_code/static/js/path-utils.js +179 -0
- painapple_code/static/js/perf-marks.js +102 -0
- painapple_code/static/js/permission-settings.js +475 -0
- painapple_code/static/js/pointer-input.js +59 -0
- painapple_code/static/js/preview/json-tree-menu.js +226 -0
- painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
- painapple_code/static/js/preview/json-tree.js +457 -0
- painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
- painapple_code/static/js/preview/preview-edit.js +367 -0
- painapple_code/static/js/preview/preview-events.js +361 -0
- painapple_code/static/js/preview/preview-history.js +567 -0
- painapple_code/static/js/preview/preview-inline-edit.js +869 -0
- painapple_code/static/js/preview/preview-poll.js +108 -0
- painapple_code/static/js/preview/preview-render.js +572 -0
- painapple_code/static/js/preview/preview-search.js +421 -0
- painapple_code/static/js/preview/preview-state.js +256 -0
- painapple_code/static/js/preview/preview-utils.js +251 -0
- painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
- painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
- painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
- painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
- painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
- painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
- painapple_code/static/js/preview-plugins/index.js +34 -0
- painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
- painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
- painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
- painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
- painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
- painapple_code/static/js/project-color-picker.js +110 -0
- painapple_code/static/js/project-colors.js +163 -0
- painapple_code/static/js/prompt-favorites.js +187 -0
- painapple_code/static/js/quick-actions-menu.js +1736 -0
- painapple_code/static/js/quick-actions-registry.js +1239 -0
- painapple_code/static/js/quick-switcher/controller.js +294 -0
- painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
- painapple_code/static/js/quick-switcher/index.js +30 -0
- painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
- painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
- painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
- painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
- painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
- painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
- painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
- painapple_code/static/js/quick-switcher/registry.js +69 -0
- painapple_code/static/js/quick-switcher/ui/item.js +54 -0
- painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
- painapple_code/static/js/recent-opens.js +40 -0
- painapple_code/static/js/scroll-manager.js +530 -0
- painapple_code/static/js/scroll-state-machine.js +359 -0
- painapple_code/static/js/selection/action-bar.js +793 -0
- painapple_code/static/js/selection/index.js +15 -0
- painapple_code/static/js/selection/selection-handler.js +877 -0
- painapple_code/static/js/selection/state.js +181 -0
- painapple_code/static/js/session/agent-progress.js +202 -0
- painapple_code/static/js/session/handle-agent-message.js +481 -0
- painapple_code/static/js/session/handle-message.js +550 -0
- painapple_code/static/js/session/interactive.js +587 -0
- painapple_code/static/js/session/message-store.js +271 -0
- painapple_code/static/js/session/messages.js +253 -0
- painapple_code/static/js/session/persistence.js +179 -0
- painapple_code/static/js/session/restore.js +136 -0
- painapple_code/static/js/session/sync.js +333 -0
- painapple_code/static/js/session-container-pool.js +371 -0
- painapple_code/static/js/session-setup-panel.js +274 -0
- painapple_code/static/js/session.js +1047 -0
- painapple_code/static/js/shortcut-hints.js +272 -0
- painapple_code/static/js/shortcuts.js +1146 -0
- painapple_code/static/js/skills-autocomplete.js +262 -0
- painapple_code/static/js/snippets-autocomplete.js +738 -0
- painapple_code/static/js/stash-refs-view.js +83 -0
- painapple_code/static/js/stash-ui.js +768 -0
- painapple_code/static/js/stash.js +785 -0
- painapple_code/static/js/status-bar.js +1184 -0
- painapple_code/static/js/token-profile.js +274 -0
- painapple_code/static/js/tool-renderer-blocks.js +1626 -0
- painapple_code/static/js/tool-renderer-thinking.js +813 -0
- painapple_code/static/js/tool-renderer.js +1500 -0
- painapple_code/static/js/tooltips.js +197 -0
- painapple_code/static/js/upload-manager.js +1019 -0
- painapple_code/static/js/utils.js +521 -0
- painapple_code/static/js/welcome/api.js +363 -0
- painapple_code/static/js/welcome/cards.js +626 -0
- painapple_code/static/js/welcome/context-menu.js +585 -0
- painapple_code/static/js/welcome/families.js +801 -0
- painapple_code/static/js/welcome/preview.js +234 -0
- painapple_code/static/js/welcome/state.js +129 -0
- painapple_code/static/js/welcome.js +1874 -0
- painapple_code/static/js/widget-system/base-widget.js +717 -0
- painapple_code/static/js/widget-system/device-manager.js +178 -0
- painapple_code/static/js/widget-system/event-bus.js +152 -0
- painapple_code/static/js/widget-system/icons.js +120 -0
- painapple_code/static/js/widget-system/index.js +49 -0
- painapple_code/static/js/widget-system/init.js +54 -0
- painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
- painapple_code/static/js/widget-system/types/floating.js +915 -0
- painapple_code/static/js/widget-system/types/index.js +40 -0
- painapple_code/static/js/widget-system/types/modal.js +147 -0
- painapple_code/static/js/widget-system/types/sidebar.js +158 -0
- painapple_code/static/js/widget-system/types/tab.js +146 -0
- painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
- painapple_code/static/js/widget-system/widget-manager.js +1187 -0
- painapple_code/static/js/widgets/about-widget.js +260 -0
- painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
- painapple_code/static/js/widgets/agents-widget.js +897 -0
- painapple_code/static/js/widgets/browser-widget.js +579 -0
- painapple_code/static/js/widgets/commands-widget.js +694 -0
- painapple_code/static/js/widgets/config/commit-sections.js +449 -0
- painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
- painapple_code/static/js/widgets/config/gestures.js +161 -0
- painapple_code/static/js/widgets/config/models-tab.js +988 -0
- painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
- painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
- painapple_code/static/js/widgets/config/state.js +517 -0
- painapple_code/static/js/widgets/config/system-controls.js +90 -0
- painapple_code/static/js/widgets/config-widget.js +1535 -0
- painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
- painapple_code/static/js/widgets/debug-widget.js +693 -0
- painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
- painapple_code/static/js/widgets/discussion-widget.js +1107 -0
- painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
- painapple_code/static/js/widgets/file-preview-widget.js +861 -0
- painapple_code/static/js/widgets/git-widget.js +794 -0
- painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
- painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
- painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
- painapple_code/static/js/widgets/image-preview-widget.js +692 -0
- painapple_code/static/js/widgets/index.js +176 -0
- painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
- painapple_code/static/js/widgets/plugins-widget.js +305 -0
- painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
- painapple_code/static/js/widgets/search-files-widget.js +529 -0
- painapple_code/static/js/widgets/skills-widget.js +843 -0
- painapple_code/static/js/widgets/snippets-widget.js +479 -0
- painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
- painapple_code/static/js/widgets/tasks-widget.js +379 -0
- painapple_code/static/js/widgets/terminal/connection.js +283 -0
- painapple_code/static/js/widgets/terminal/gestures.js +357 -0
- painapple_code/static/js/widgets/terminal/init.js +642 -0
- painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
- painapple_code/static/js/widgets/terminal/osc52.js +213 -0
- painapple_code/static/js/widgets/terminal/render.js +159 -0
- painapple_code/static/js/widgets/terminal/size.js +54 -0
- painapple_code/static/js/widgets/terminal/state.js +211 -0
- painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
- painapple_code/static/js/widgets/terminal-widget.js +793 -0
- painapple_code/static/js/widgets/uploads-widget.js +461 -0
- painapple_code/static/js/widgets/zen-widget.js +1001 -0
- painapple_code/static/login.html +51 -0
- painapple_code/static/manifest.json +73 -0
- painapple_code/static/sw.js +293 -0
- painapple_code/static/vendor/LICENSES.txt +769 -0
- painapple_code/static/vendor/README.md +103 -0
- painapple_code/static/vendor/codemirror.js +35 -0
- painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
- painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
- painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
- painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
- painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
- painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
- painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
- painapple_code/static/vendor/highlight.min.js +1213 -0
- painapple_code/static/vendor/marked.min.js +6 -0
- painapple_code/static/vendor/purify.min.js +3 -0
- painapple_code/static/vendor/xterm-addon-fit.js +2 -0
- painapple_code/static/vendor/xterm.css +209 -0
- painapple_code/static/vendor/xterm.js +2 -0
- painapple_code/static/web-client.html +635 -0
- painapple_code/subprocess_registry.py +535 -0
- painapple_code/tls_cert.py +79 -0
- painapple_code/tools/agents/shadow-git-helper.md +400 -0
- painapple_code/tools/excalidraw-to-svg.js +143 -0
- painapple_code/tools/install-helpers.sh +148 -0
- painapple_code/tools/shadow-git +320 -0
- painapple_code/tools/shadow-query +120 -0
- painapple_code/tools/vegalite-to-svg.js +79 -0
- painapple_code/turn_query.py +766 -0
- painapple_code/turn_tracker.py +221 -0
- painapple_code/utils/__init__.py +0 -0
- painapple_code/utils/agent_cli.py +306 -0
- painapple_code/utils/chart.py +103 -0
- painapple_code/utils/excalidraw.py +107 -0
- painapple_code/utils/file_lock.py +162 -0
- painapple_code/utils/file_paths.py +648 -0
- painapple_code/utils/generate_icons.py +251 -0
- painapple_code/utils/proc.py +205 -0
- painapple_code/utils/pty_backend.py +59 -0
- painapple_code/utils/pty_posix.py +160 -0
- painapple_code/utils/pty_windows.py +245 -0
- painapple_code/utils/token_profiles.py +91 -0
- painapple_code/viewer_templates.py +657 -0
- painapple_code/welcome_search.py +879 -0
- painapple_code-1.0.0.dist-info/METADATA +292 -0
- painapple_code-1.0.0.dist-info/RECORD +436 -0
- painapple_code-1.0.0.dist-info/WHEEL +5 -0
- painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
- painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
- painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
- painapple_code-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers - Track install state of bundled CLI/agent helpers.
|
|
3
|
+
|
|
4
|
+
Mirrors the FILES array in `tools/install-helpers.sh`. If you add a new
|
|
5
|
+
helper there, add it here too (and vice versa).
|
|
6
|
+
|
|
7
|
+
Surfaces install/freshness state via `helpers_status()`, used by the
|
|
8
|
+
GET /api/app/helpers/status endpoint to drive the install-prompt UI.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import stat as stat_mod
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from painapple_code import PACKAGE_DIR
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Source-in-package (relative to PACKAGE_DIR) → install target (absolute, expanded $HOME).
|
|
25
|
+
# Must mirror tools/install-helpers.sh:FILES.
|
|
26
|
+
HELPER_FILES = [
|
|
27
|
+
("tools/shadow-git", Path.home() / ".local" / "bin" / "shadow-git"),
|
|
28
|
+
("tools/shadow-query", Path.home() / ".local" / "bin" / "shadow-query"),
|
|
29
|
+
("tools/agents/shadow-git-helper.md",
|
|
30
|
+
Path.home() / ".claude" / "agents" / "shadow-git-helper.md"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Legacy install paths that earlier versions wrote — install + uninstall
|
|
34
|
+
# both clean these up so users don't end up with both old and new agents
|
|
35
|
+
# in `#` autocomplete after a rename. Safe to keep forever.
|
|
36
|
+
LEGACY_TARGETS = [
|
|
37
|
+
Path.home() / ".claude" / "agents" / "shadow-git-researcher.md",
|
|
38
|
+
Path.home() / ".local" / "bin" / "dbq",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# These two helpers are bash scripts, which used to mean "not on Windows".
|
|
42
|
+
# They do work there: Git for Windows ships bash and every coreutil they
|
|
43
|
+
# call (sha256sum, sed, cut, awk...), and anyone using shadow-git already
|
|
44
|
+
# has git. What they can't do is be *invoked* from the PowerShell the
|
|
45
|
+
# terminal tab runs, so on Windows each one also gets a .cmd shim.
|
|
46
|
+
SHELL_SCRIPT_SOURCES = {"tools/shadow-git", "tools/shadow-query"}
|
|
47
|
+
|
|
48
|
+
_NO_BASH_REASON = "needs Git for Windows (bash) — not found"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def find_bash() -> str | None:
|
|
52
|
+
"""Absolute path to a bash that can run the shell helpers, or None.
|
|
53
|
+
|
|
54
|
+
On Windows, resolve Git for Windows' own bash from git.exe rather than
|
|
55
|
+
trusting PATH. `C:\\Windows\\System32\\bash.exe` is the WSL launcher: it
|
|
56
|
+
would run the helper inside a Linux filesystem namespace where
|
|
57
|
+
~/.painapple-code is a DIFFERENT (empty) directory, so the helper would
|
|
58
|
+
succeed while querying nothing. Git ships bash at <root>/bin/bash.exe
|
|
59
|
+
and deliberately keeps it off PATH, so PATH alone finds the wrong one.
|
|
60
|
+
"""
|
|
61
|
+
if sys.platform != "win32":
|
|
62
|
+
return shutil.which("bash")
|
|
63
|
+
|
|
64
|
+
git = shutil.which("git")
|
|
65
|
+
if git:
|
|
66
|
+
root = Path(git).parent.parent # ...\Git\cmd\git.exe -> ...\Git
|
|
67
|
+
for cand in (root / "bin" / "bash.exe", root / "usr" / "bin" / "bash.exe"):
|
|
68
|
+
if cand.is_file():
|
|
69
|
+
return str(cand)
|
|
70
|
+
|
|
71
|
+
found = shutil.which("bash")
|
|
72
|
+
if found and Path(found).parent.name.lower() != "system32":
|
|
73
|
+
return found
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _shim_path(target: Path) -> Path:
|
|
78
|
+
"""The .cmd next to `target` that makes it callable from PowerShell."""
|
|
79
|
+
return target.with_name(target.name + ".cmd")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _shim_body(bash: str, target: Path) -> str:
|
|
83
|
+
"""A batch wrapper that hands the script to bash and forwards argv.
|
|
84
|
+
|
|
85
|
+
The bash path is resolved at install time, not runtime: a missing Git
|
|
86
|
+
then fails with a readable message from cmd instead of a silent
|
|
87
|
+
fall-through to whatever else answers to `bash`.
|
|
88
|
+
"""
|
|
89
|
+
return (
|
|
90
|
+
"@echo off\r\n"
|
|
91
|
+
"rem Generated by painapple-code helper install. Re-run install to refresh.\r\n"
|
|
92
|
+
f'"{bash}" "{target}" %*\r\n'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _supported_here(src_rel: str) -> bool:
|
|
97
|
+
if sys.platform == "win32" and src_rel in SHELL_SCRIPT_SOURCES:
|
|
98
|
+
return find_bash() is not None
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
# The shadow-git-helper agent's model is user-selectable (see
|
|
102
|
+
# paths.get_helper_agent_model). Its `model:` frontmatter line is kept
|
|
103
|
+
# orthogonal to the freshness check so a non-default choice doesn't read as
|
|
104
|
+
# "outdated", and is re-applied after every install (cp -f resets it).
|
|
105
|
+
AGENT_SRC_REL = "tools/agents/shadow-git-helper.md"
|
|
106
|
+
AGENT_TARGET = Path.home() / ".claude" / "agents" / "shadow-git-helper.md"
|
|
107
|
+
|
|
108
|
+
# Matches the leading YAML frontmatter block only (anchored at start, up to the
|
|
109
|
+
# first closing `---`), so example frontmatter later in the body is untouched.
|
|
110
|
+
_FRONTMATTER_RE = re.compile(r"\A(---[ \t]*\r?\n)(.*?\r?\n)(---[ \t]*\r?\n?)", re.DOTALL)
|
|
111
|
+
_MODEL_LINE_RE = re.compile(r"(?im)^[ \t]*model:.*\r?\n?")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _strip_model_line(text: str) -> str:
|
|
115
|
+
"""Remove the `model:` line from the leading frontmatter only."""
|
|
116
|
+
m = _FRONTMATTER_RE.match(text)
|
|
117
|
+
if not m:
|
|
118
|
+
return text
|
|
119
|
+
body = _MODEL_LINE_RE.sub("", m.group(2))
|
|
120
|
+
return m.group(1) + body + m.group(3) + text[m.end():]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _set_model_line(text: str, model: str) -> str:
|
|
124
|
+
"""Set `model: <model>` in the leading frontmatter, replacing any existing."""
|
|
125
|
+
m = _FRONTMATTER_RE.match(text)
|
|
126
|
+
if not m:
|
|
127
|
+
return text
|
|
128
|
+
body = _MODEL_LINE_RE.sub("", m.group(2))
|
|
129
|
+
if body and not body.endswith("\n"):
|
|
130
|
+
body += "\n"
|
|
131
|
+
body += f"model: {model}\n"
|
|
132
|
+
return m.group(1) + body + m.group(3) + text[m.end():]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def read_installed_agent_model() -> str | None:
|
|
136
|
+
"""Return the `model:` value parsed from the installed agent file, or None."""
|
|
137
|
+
try:
|
|
138
|
+
text = AGENT_TARGET.read_text(encoding="utf-8")
|
|
139
|
+
except Exception:
|
|
140
|
+
return None
|
|
141
|
+
m = _FRONTMATTER_RE.match(text)
|
|
142
|
+
if not m:
|
|
143
|
+
return None
|
|
144
|
+
mm = re.search(r"(?im)^[ \t]*model:[ \t]*(\S+)", m.group(2))
|
|
145
|
+
return mm.group(1).strip().lower() if mm else None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _sha256(path: Path) -> str | None:
|
|
149
|
+
"""Return hex sha256 of a file's contents, or None if unreadable."""
|
|
150
|
+
try:
|
|
151
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.debug(f"sha256 failed for {path}: {e}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _normalized_hash(path: Path) -> str | None:
|
|
158
|
+
"""sha256 of file text with the frontmatter `model:` line removed, so the
|
|
159
|
+
user's model choice does not read as a freshness mismatch."""
|
|
160
|
+
try:
|
|
161
|
+
text = path.read_text(encoding="utf-8")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.debug(f"read failed for {path}: {e}")
|
|
164
|
+
return None
|
|
165
|
+
return hashlib.sha256(_strip_model_line(text).encode("utf-8")).hexdigest()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def helpers_status() -> dict:
|
|
169
|
+
"""
|
|
170
|
+
Per-file install + freshness state, plus aggregate flags.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
{
|
|
174
|
+
"files": [
|
|
175
|
+
{"name": "shadow-git", "src": "tools/shadow-git", "target": "/...",
|
|
176
|
+
"installed": bool, "up_to_date": bool},
|
|
177
|
+
...
|
|
178
|
+
],
|
|
179
|
+
"all_installed": bool, # every target exists
|
|
180
|
+
"all_current": bool, # every target exists AND matches source hash
|
|
181
|
+
"any_outdated": bool, # at least one installed-but-stale file
|
|
182
|
+
}
|
|
183
|
+
"""
|
|
184
|
+
files = []
|
|
185
|
+
all_installed = True
|
|
186
|
+
all_current = True
|
|
187
|
+
any_outdated = False
|
|
188
|
+
|
|
189
|
+
for src_rel, target in HELPER_FILES:
|
|
190
|
+
src_abs = PACKAGE_DIR / src_rel
|
|
191
|
+
installed = target.exists()
|
|
192
|
+
up_to_date = False
|
|
193
|
+
|
|
194
|
+
if not _supported_here(src_rel):
|
|
195
|
+
# Don't let a helper we deliberately never install drag
|
|
196
|
+
# all_installed/all_current to False forever — that would
|
|
197
|
+
# show a permanent "helpers outdated" nag on Windows.
|
|
198
|
+
files.append({
|
|
199
|
+
"name": target.name,
|
|
200
|
+
"src": src_rel,
|
|
201
|
+
"target": str(target),
|
|
202
|
+
"installed": False,
|
|
203
|
+
"up_to_date": False,
|
|
204
|
+
"unsupported": True,
|
|
205
|
+
"unsupported_reason": _NO_BASH_REASON,
|
|
206
|
+
})
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if installed:
|
|
210
|
+
if src_rel == AGENT_SRC_REL:
|
|
211
|
+
# Model choice is orthogonal to freshness — compare content
|
|
212
|
+
# with the `model:` frontmatter line stripped from both sides.
|
|
213
|
+
src_hash = _normalized_hash(src_abs)
|
|
214
|
+
tgt_hash = _normalized_hash(target)
|
|
215
|
+
else:
|
|
216
|
+
src_hash = _sha256(src_abs)
|
|
217
|
+
tgt_hash = _sha256(target)
|
|
218
|
+
up_to_date = src_hash is not None and src_hash == tgt_hash
|
|
219
|
+
if (up_to_date and sys.platform == "win32"
|
|
220
|
+
and src_rel in SHELL_SCRIPT_SOURCES
|
|
221
|
+
and not _shim_path(target).exists()):
|
|
222
|
+
# Script content is current but the .cmd is missing — an
|
|
223
|
+
# install from before shims existed. It's unreachable from
|
|
224
|
+
# the terminal's PowerShell, so surface it as stale and let
|
|
225
|
+
# the normal Install button regenerate the wrapper.
|
|
226
|
+
up_to_date = False
|
|
227
|
+
if not up_to_date:
|
|
228
|
+
any_outdated = True
|
|
229
|
+
else:
|
|
230
|
+
all_installed = False
|
|
231
|
+
|
|
232
|
+
if not (installed and up_to_date):
|
|
233
|
+
all_current = False
|
|
234
|
+
|
|
235
|
+
files.append({
|
|
236
|
+
"name": target.name,
|
|
237
|
+
"src": src_rel,
|
|
238
|
+
"target": str(target),
|
|
239
|
+
"installed": installed,
|
|
240
|
+
"up_to_date": up_to_date,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
from painapple_code import paths
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
"files": files,
|
|
247
|
+
"all_installed": all_installed,
|
|
248
|
+
"all_current": all_current,
|
|
249
|
+
"any_outdated": any_outdated,
|
|
250
|
+
# How to actually invoke the helper in THIS platform's terminal.
|
|
251
|
+
# PowerShell resolves a bare name through PATHEXT and finds the
|
|
252
|
+
# .cmd, but `~/.local/bin/shadow-git` (no extension) silently does
|
|
253
|
+
# nothing there — it exits 0 having run no command at all, which is
|
|
254
|
+
# worse than an error. POSIX shells take the explicit path.
|
|
255
|
+
"usage_command": ("shadow-git log" if sys.platform == "win32"
|
|
256
|
+
else "~/.local/bin/shadow-git log"),
|
|
257
|
+
# Model the shadow-git-helper subagent runs on (full model ID or
|
|
258
|
+
# "inherit"); options mirror the main model selector + "inherit".
|
|
259
|
+
"agent_model": paths.get_helper_agent_model(),
|
|
260
|
+
"agent_model_options": paths.get_helper_agent_options(),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def install_helpers_script_path() -> Path:
|
|
265
|
+
"""Return the path to tools/install-helpers.sh — the equivalent shell
|
|
266
|
+
script users can run by hand. install_helpers() below does the same
|
|
267
|
+
work in-process; this stays for docs and manual installs."""
|
|
268
|
+
return PACKAGE_DIR / "tools" / "install-helpers.sh"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def install_helpers(force: bool = True) -> dict:
|
|
272
|
+
"""Copy the bundled helpers to their user-level targets.
|
|
273
|
+
|
|
274
|
+
A Python port of tools/install-helpers.sh --update (mkdir -p, cp -f,
|
|
275
|
+
chmod 755 when the source is executable). The endpoint used to shell
|
|
276
|
+
out to `bash`, which simply isn't there on a stock Windows box — and
|
|
277
|
+
uninstall was already pure Python, so the two halves now match.
|
|
278
|
+
|
|
279
|
+
Returns the same {ok, exit_code, stdout, stderr} shape the route
|
|
280
|
+
reports, so the frontend is unchanged.
|
|
281
|
+
"""
|
|
282
|
+
lines = [f"painapple-code helpers — install (pkg: {PACKAGE_DIR})", ""]
|
|
283
|
+
errors = []
|
|
284
|
+
|
|
285
|
+
bash = find_bash() if sys.platform == "win32" else None
|
|
286
|
+
|
|
287
|
+
for src_rel, target in HELPER_FILES:
|
|
288
|
+
if not _supported_here(src_rel):
|
|
289
|
+
lines.append(f" skip {target} ({_NO_BASH_REASON})")
|
|
290
|
+
continue
|
|
291
|
+
src_abs = PACKAGE_DIR / src_rel
|
|
292
|
+
if not src_abs.is_file():
|
|
293
|
+
errors.append(f"source missing: {src_abs}")
|
|
294
|
+
continue
|
|
295
|
+
existed = target.exists()
|
|
296
|
+
if existed and not force:
|
|
297
|
+
lines.append(f" skip {target} (exists — use --update to overwrite)")
|
|
298
|
+
continue
|
|
299
|
+
try:
|
|
300
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
301
|
+
# copyfile, not copy2: we deliberately do not carry over source
|
|
302
|
+
# mtimes (the freshness check hashes content instead).
|
|
303
|
+
shutil.copyfile(src_abs, target)
|
|
304
|
+
if os.access(src_abs, os.X_OK):
|
|
305
|
+
target.chmod(0o755) # mirrors the script's `chmod 755`
|
|
306
|
+
lines.append(f" {'update' if existed else 'install'} {target}")
|
|
307
|
+
if bash and src_rel in SHELL_SCRIPT_SOURCES:
|
|
308
|
+
# PowerShell (what the terminal tab runs) can't execute an
|
|
309
|
+
# extensionless bash script; the .cmd is what makes
|
|
310
|
+
# `shadow-git log` work there the way it does on a POSIX box.
|
|
311
|
+
shim = _shim_path(target)
|
|
312
|
+
shim.write_text(_shim_body(bash, target), encoding="ascii")
|
|
313
|
+
lines.append(f" {'update' if existed else 'install'} {shim}")
|
|
314
|
+
except OSError as e:
|
|
315
|
+
logger.warning(f"install: failed to write {target}: {e}")
|
|
316
|
+
errors.append(f"{target}: {e}")
|
|
317
|
+
|
|
318
|
+
for legacy in LEGACY_TARGETS:
|
|
319
|
+
if legacy.exists():
|
|
320
|
+
try:
|
|
321
|
+
legacy.unlink()
|
|
322
|
+
lines.append(f" remove {legacy} (legacy)")
|
|
323
|
+
except OSError as e:
|
|
324
|
+
errors.append(f"{legacy}: {e}")
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"ok": not errors,
|
|
328
|
+
"exit_code": 0 if not errors else 1,
|
|
329
|
+
"stdout": "\n".join(lines) + "\n",
|
|
330
|
+
"stderr": "\n".join(errors),
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def uninstall_helpers() -> dict:
|
|
335
|
+
"""
|
|
336
|
+
Remove every installed helper target from disk, plus any legacy
|
|
337
|
+
targets from earlier installs (renamed/moved files). Idempotent —
|
|
338
|
+
missing targets are skipped, not errors.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
{
|
|
342
|
+
"ok": bool, # True if every target is gone after the call
|
|
343
|
+
"removed": [str], # paths actually deleted this call
|
|
344
|
+
"missing": [str], # paths that were already absent
|
|
345
|
+
"errors": [{"path": str, "error": str}],
|
|
346
|
+
}
|
|
347
|
+
"""
|
|
348
|
+
removed: list[str] = []
|
|
349
|
+
missing: list[str] = []
|
|
350
|
+
errors: list[dict] = []
|
|
351
|
+
|
|
352
|
+
targets = [t for _, t in HELPER_FILES] + LEGACY_TARGETS
|
|
353
|
+
if sys.platform == "win32":
|
|
354
|
+
# Remove what install could have created — the PowerShell shims.
|
|
355
|
+
targets += [_shim_path(t) for src, t in HELPER_FILES
|
|
356
|
+
if src in SHELL_SCRIPT_SOURCES]
|
|
357
|
+
for target in targets:
|
|
358
|
+
if not target.exists():
|
|
359
|
+
missing.append(str(target))
|
|
360
|
+
continue
|
|
361
|
+
try:
|
|
362
|
+
target.unlink()
|
|
363
|
+
removed.append(str(target))
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.warning(f"uninstall: failed to remove {target}: {e}")
|
|
366
|
+
errors.append({"path": str(target), "error": str(e)})
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"ok": not errors,
|
|
370
|
+
"removed": removed,
|
|
371
|
+
"missing": missing,
|
|
372
|
+
"errors": errors,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def apply_agent_model(model: str) -> bool:
|
|
377
|
+
"""Write `model: <model>` into the installed shadow-git-helper frontmatter.
|
|
378
|
+
|
|
379
|
+
Returns True when the installed file exists and now carries that model
|
|
380
|
+
(whether it needed a write or was already correct), False otherwise.
|
|
381
|
+
"""
|
|
382
|
+
if not AGENT_TARGET.exists():
|
|
383
|
+
return False
|
|
384
|
+
try:
|
|
385
|
+
text = AGENT_TARGET.read_text(encoding="utf-8")
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.warning(f"apply_agent_model: read failed: {e}")
|
|
388
|
+
return False
|
|
389
|
+
new_text = _set_model_line(text, model)
|
|
390
|
+
if new_text != text:
|
|
391
|
+
try:
|
|
392
|
+
AGENT_TARGET.write_text(new_text, encoding="utf-8")
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logger.warning(f"apply_agent_model: write failed: {e}")
|
|
395
|
+
return False
|
|
396
|
+
return True
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def apply_agent_model_from_config() -> bool:
|
|
400
|
+
"""Re-apply the persisted model choice to the installed agent file.
|
|
401
|
+
|
|
402
|
+
Called after install-helpers.sh, whose `cp -f` overwrites the installed
|
|
403
|
+
copy with the bundled default (model: sonnet).
|
|
404
|
+
"""
|
|
405
|
+
from painapple_code import paths
|
|
406
|
+
return apply_agent_model(paths.get_helper_agent_model())
|