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,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Excalidraw rendering utilities — converts .excalidraw / .excalidraw.md to SVG.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- Raw .excalidraw JSON (from excalidraw.com)
|
|
6
|
+
- Obsidian .excalidraw.md (LZ-string compressed-json format)
|
|
7
|
+
|
|
8
|
+
Uses @excalidraw/utils via Node.js subprocess (tools/excalidraw-to-svg.js).
|
|
9
|
+
Includes in-memory cache keyed by file content hash.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import hashlib
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from painapple_code import PACKAGE_DIR
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Path to the Node renderer script (shipped in package)
|
|
22
|
+
_SCRIPT = PACKAGE_DIR / "tools" / "excalidraw-to-svg.js"
|
|
23
|
+
|
|
24
|
+
# Simple in-memory cache: content_hash -> svg_string
|
|
25
|
+
_cache: dict[str, str] = {}
|
|
26
|
+
_MAX_CACHE = 50 # max cached renderings
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def render_excalidraw(source: str | bytes, dark_mode: bool = False) -> str:
|
|
30
|
+
"""Render excalidraw JSON to SVG string.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
source: Excalidraw JSON string or bytes
|
|
34
|
+
dark_mode: If True, render with dark mode filter
|
|
35
|
+
Returns:
|
|
36
|
+
SVG string
|
|
37
|
+
Raises:
|
|
38
|
+
RuntimeError: If rendering fails
|
|
39
|
+
"""
|
|
40
|
+
if isinstance(source, str):
|
|
41
|
+
source = source.encode('utf-8')
|
|
42
|
+
|
|
43
|
+
# Check cache
|
|
44
|
+
content_hash = hashlib.md5(source + (b'dark' if dark_mode else b'')).hexdigest()
|
|
45
|
+
if content_hash in _cache:
|
|
46
|
+
return _cache[content_hash]
|
|
47
|
+
|
|
48
|
+
if not _SCRIPT.exists():
|
|
49
|
+
raise RuntimeError(f"Excalidraw renderer not found: {_SCRIPT}")
|
|
50
|
+
|
|
51
|
+
cmd = ["node", str(_SCRIPT)]
|
|
52
|
+
if dark_mode:
|
|
53
|
+
cmd.append("--dark")
|
|
54
|
+
|
|
55
|
+
proc = await asyncio.create_subprocess_exec(
|
|
56
|
+
*cmd,
|
|
57
|
+
stdin=asyncio.subprocess.PIPE,
|
|
58
|
+
stdout=asyncio.subprocess.PIPE,
|
|
59
|
+
stderr=asyncio.subprocess.PIPE,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
stdout, stderr = await asyncio.wait_for(
|
|
64
|
+
proc.communicate(input=source), timeout=15.0
|
|
65
|
+
)
|
|
66
|
+
except asyncio.TimeoutError:
|
|
67
|
+
proc.kill()
|
|
68
|
+
raise RuntimeError("Excalidraw render timed out (15s)")
|
|
69
|
+
|
|
70
|
+
if proc.returncode != 0:
|
|
71
|
+
error_msg = stderr.decode('utf-8', errors='replace').strip()
|
|
72
|
+
raise RuntimeError(f"Excalidraw render failed: {error_msg}")
|
|
73
|
+
|
|
74
|
+
svg = stdout.decode('utf-8')
|
|
75
|
+
|
|
76
|
+
# Cache result (evict oldest if full)
|
|
77
|
+
if len(_cache) >= _MAX_CACHE:
|
|
78
|
+
oldest = next(iter(_cache))
|
|
79
|
+
del _cache[oldest]
|
|
80
|
+
_cache[content_hash] = svg
|
|
81
|
+
|
|
82
|
+
# Log non-fatal warnings from stderr
|
|
83
|
+
if stderr:
|
|
84
|
+
for line in stderr.decode('utf-8', errors='replace').strip().split('\n'):
|
|
85
|
+
if line:
|
|
86
|
+
logger.debug("excalidraw-to-svg: %s", line)
|
|
87
|
+
|
|
88
|
+
return svg
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def is_excalidraw_file(path: str | Path) -> bool:
|
|
92
|
+
"""Check if a file is an excalidraw file (.excalidraw or .excalidraw.md)."""
|
|
93
|
+
name = str(path).lower()
|
|
94
|
+
return name.endswith('.excalidraw') or name.endswith('.excalidraw.md')
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def render_excalidraw_file(path: Path, dark_mode: bool = False) -> str:
|
|
98
|
+
"""Render an .excalidraw or .excalidraw.md file to SVG string.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
path: Path to .excalidraw or .excalidraw.md file
|
|
102
|
+
dark_mode: If True, render with dark mode filter
|
|
103
|
+
Returns:
|
|
104
|
+
SVG string
|
|
105
|
+
"""
|
|
106
|
+
content = path.read_bytes()
|
|
107
|
+
return await render_excalidraw(content, dark_mode=dark_mode)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Cross-platform advisory file locking.
|
|
2
|
+
|
|
3
|
+
`fcntl` doesn't exist on Windows, and a bare module-level ``import fcntl``
|
|
4
|
+
is an import-time crash for everything that transitively imports the
|
|
5
|
+
module (this killed `shadow_git` → `agent_session` → `server` on win32;
|
|
6
|
+
see docs-ai/plans/2026-08-08-windows-native-port.md §1.1).
|
|
7
|
+
|
|
8
|
+
One context manager, exclusive + blocking, replacing the direct
|
|
9
|
+
``fcntl.flock(LOCK_EX)`` pattern:
|
|
10
|
+
|
|
11
|
+
with FileLock(path.with_suffix(".lock")):
|
|
12
|
+
...critical section...
|
|
13
|
+
|
|
14
|
+
POSIX backs it with ``fcntl.flock``; Windows with ``msvcrt.locking`` on
|
|
15
|
+
the first byte (region locks may extend past EOF, so an empty lock file
|
|
16
|
+
is fine). The win32 branch polls ``LK_NBLCK`` with backoff rather than
|
|
17
|
+
calling ``LK_LOCK``: LK_LOCK blocks ~10s inside the CRT (one attempt per
|
|
18
|
+
second) before raising, so a caller-requested 2s timeout actually waited
|
|
19
|
+
9-10s and the 5s slow-lock warning could never fire on time (measured on
|
|
20
|
+
Windows 11 2026-08-09 — 9.2s to a ``timeout=2`` TimeoutError). The
|
|
21
|
+
non-blocking probe returns immediately, putting the deadline and warn
|
|
22
|
+
schedule under our own loop's control.
|
|
23
|
+
|
|
24
|
+
Why that matters: ``FileLock`` is entered synchronously from
|
|
25
|
+
``ShadowGit.track_modification``, which ``AgentManager`` calls on the
|
|
26
|
+
asyncio event loop for the first Edit/Write of a turn. A retry loop that
|
|
27
|
+
swallows every ``OSError`` spins the whole server at 100% CPU forever the
|
|
28
|
+
moment the lock file is unopenable/unlockable for a non-contention
|
|
29
|
+
reason (EACCES/EINVAL/EBADF all return immediately, with no internal
|
|
30
|
+
sleep to throttle the loop). Anything not-contention is re-raised;
|
|
31
|
+
contention waits with bounded backoff and finally gives up with a
|
|
32
|
+
``TimeoutError`` (an ``OSError`` subclass, so existing handlers cover it)
|
|
33
|
+
rather than pinning a core until the process is killed.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
import sys
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
__all__ = ["FileLock"]
|
|
43
|
+
|
|
44
|
+
# Contention on this lock means "another process is mid JSON read+write" —
|
|
45
|
+
# milliseconds. A minute of it means something is genuinely wedged, and
|
|
46
|
+
# hanging the event loop forever is worse than failing loudly.
|
|
47
|
+
LOCK_TIMEOUT = 60.0
|
|
48
|
+
LOCK_WARN_AFTER = 5.0
|
|
49
|
+
_BACKOFF_START = 0.02
|
|
50
|
+
_BACKOFF_MAX = 0.5
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if sys.platform == "win32":
|
|
54
|
+
import errno
|
|
55
|
+
import msvcrt
|
|
56
|
+
|
|
57
|
+
# MS CRT `_locking` failure codes (docs: "_locking"): EACCES is the
|
|
58
|
+
# locking violation LK_NBLCK reports on contention — the retry case.
|
|
59
|
+
# EDEADLOCK (LK_LOCK's give-up code) is kept for safety should the
|
|
60
|
+
# mode ever change; EBADF/EINVAL are programming errors and must
|
|
61
|
+
# propagate.
|
|
62
|
+
_RETRYABLE = {
|
|
63
|
+
getattr(errno, "EDEADLOCK", None),
|
|
64
|
+
getattr(errno, "EDEADLK", None),
|
|
65
|
+
errno.EACCES,
|
|
66
|
+
} - {None}
|
|
67
|
+
|
|
68
|
+
class FileLock:
|
|
69
|
+
"""Exclusive, blocking advisory lock on a dedicated lock file."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, path, timeout: float = LOCK_TIMEOUT):
|
|
72
|
+
self._path = path
|
|
73
|
+
self._timeout = timeout
|
|
74
|
+
self._fh = None
|
|
75
|
+
|
|
76
|
+
def __enter__(self):
|
|
77
|
+
# "a" creates without truncating — truncation would race other
|
|
78
|
+
# holders of the same lock file. Nothing is ever read or written
|
|
79
|
+
# through this handle (it exists only to carry the lock), but the
|
|
80
|
+
# encoding is stated anyway so the file never depends on the
|
|
81
|
+
# machine's locale codec.
|
|
82
|
+
fh = open(self._path, "a", encoding="utf-8")
|
|
83
|
+
try:
|
|
84
|
+
self._acquire(fh)
|
|
85
|
+
except BaseException:
|
|
86
|
+
# __exit__ never runs when __enter__ raises, so the handle
|
|
87
|
+
# would leak for the life of the process.
|
|
88
|
+
fh.close()
|
|
89
|
+
raise
|
|
90
|
+
self._fh = fh
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def _acquire(self, fh):
|
|
94
|
+
started = time.monotonic()
|
|
95
|
+
deadline = started + self._timeout
|
|
96
|
+
backoff = _BACKOFF_START
|
|
97
|
+
warned = False
|
|
98
|
+
while True:
|
|
99
|
+
try:
|
|
100
|
+
fh.seek(0)
|
|
101
|
+
msvcrt.locking(fh.fileno(), msvcrt.LK_NBLCK, 1)
|
|
102
|
+
return
|
|
103
|
+
except OSError as e:
|
|
104
|
+
if e.errno not in _RETRYABLE:
|
|
105
|
+
raise
|
|
106
|
+
waited = time.monotonic() - started
|
|
107
|
+
if waited >= self._timeout:
|
|
108
|
+
raise TimeoutError(
|
|
109
|
+
f"could not lock {self._path} after "
|
|
110
|
+
f"{waited:.0f}s (held by another process)"
|
|
111
|
+
) from e
|
|
112
|
+
if not warned and waited >= LOCK_WARN_AFTER:
|
|
113
|
+
warned = True
|
|
114
|
+
logger.warning(
|
|
115
|
+
f"still waiting on {self._path} after "
|
|
116
|
+
f"{waited:.0f}s — another process is holding it"
|
|
117
|
+
)
|
|
118
|
+
# Bounded backoff — LK_NBLCK returns immediately, so this
|
|
119
|
+
# sleep is the only thing between attempts and the only
|
|
120
|
+
# thing keeping the loop off the CPU.
|
|
121
|
+
time.sleep(min(backoff, max(0.0, deadline - time.monotonic())))
|
|
122
|
+
backoff = min(backoff * 2, _BACKOFF_MAX)
|
|
123
|
+
|
|
124
|
+
def __exit__(self, exc_type, exc, tb):
|
|
125
|
+
try:
|
|
126
|
+
self._fh.seek(0)
|
|
127
|
+
msvcrt.locking(self._fh.fileno(), msvcrt.LK_UNLCK, 1)
|
|
128
|
+
finally:
|
|
129
|
+
self._fh.close()
|
|
130
|
+
self._fh = None
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
import fcntl
|
|
135
|
+
|
|
136
|
+
class FileLock:
|
|
137
|
+
"""Exclusive, blocking advisory lock on a dedicated lock file."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, path, timeout: float = LOCK_TIMEOUT):
|
|
140
|
+
self._path = path
|
|
141
|
+
self._timeout = timeout # accepted for API parity; flock blocks
|
|
142
|
+
self._fh = None
|
|
143
|
+
|
|
144
|
+
def __enter__(self):
|
|
145
|
+
# Lock-carrying handle only; see the win32 branch above.
|
|
146
|
+
fh = open(self._path, "a", encoding="utf-8")
|
|
147
|
+
try:
|
|
148
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
149
|
+
except BaseException:
|
|
150
|
+
# Same leak as the win32 branch: __exit__ won't run.
|
|
151
|
+
fh.close()
|
|
152
|
+
raise
|
|
153
|
+
self._fh = fh
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def __exit__(self, exc_type, exc, tb):
|
|
157
|
+
try:
|
|
158
|
+
fcntl.flock(self._fh.fileno(), fcntl.LOCK_UN)
|
|
159
|
+
finally:
|
|
160
|
+
self._fh.close()
|
|
161
|
+
self._fh = None
|
|
162
|
+
return False
|