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,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Settings - Permission Level Configuration
|
|
3
|
+
*
|
|
4
|
+
* Manages the permission button near the input area that controls
|
|
5
|
+
* the agent's permission mode (bypassPermissions/YOLO or plan).
|
|
6
|
+
*
|
|
7
|
+
* Permission levels are stored PER SESSION, allowing different modes
|
|
8
|
+
* for different conversations.
|
|
9
|
+
*
|
|
10
|
+
* The mode list itself is engine vocabulary: each provider self-describes
|
|
11
|
+
* its modes (value/label/desc/color), delivered alongside the permission
|
|
12
|
+
* endpoints (`GET /api/session/{id}/permission-mode`,
|
|
13
|
+
* `GET /api/app/default-permissions`). The strings.yaml level table is
|
|
14
|
+
* only the pre-fetch fallback, so e.g. the claude-sdk provider's extra
|
|
15
|
+
* "Ask" mode appears without any frontend edit.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { CONFIG } from './config.js';
|
|
19
|
+
import S from './strings.js';
|
|
20
|
+
// Engine registry lookup (status-bar owns the /api/providers cache; it does
|
|
21
|
+
// not import this module, so no cycle). Used to seed the mode vocabulary
|
|
22
|
+
// synchronously on tab switch — the fetches below only confirm.
|
|
23
|
+
import { engineInfo } from './status-bar.js';
|
|
24
|
+
|
|
25
|
+
// Pre-fetch fallback modes from strings.yaml (shape matches the provider's
|
|
26
|
+
// permission_modes() dicts). Replaced by server-delivered lists on load.
|
|
27
|
+
const FALLBACK_MODES = S.permissions.order.map(key => {
|
|
28
|
+
const lvl = S.permissions.levels[key];
|
|
29
|
+
return { value: key, label: lvl.popup_label, desc: lvl.description, color: lvl.color };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
class PermissionSettingsManager {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.btn = null;
|
|
35
|
+
this.popup = null;
|
|
36
|
+
this.labelEl = null;
|
|
37
|
+
this.isOpen = false;
|
|
38
|
+
this.currentLevel = 'dontAsk'; // mode value from this.modes
|
|
39
|
+
this.currentSessionId = null;
|
|
40
|
+
this.globalDefault = 'dontAsk'; // Default for normal chat
|
|
41
|
+
this.modes = FALLBACK_MODES; // active session's engine modes
|
|
42
|
+
this.defaultModes = FALLBACK_MODES; // effective default engine's modes
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the permission settings UI
|
|
47
|
+
*/
|
|
48
|
+
init() {
|
|
49
|
+
this.btn = document.getElementById('permission-btn');
|
|
50
|
+
this.popup = document.getElementById('permission-popup');
|
|
51
|
+
this.labelEl = this.btn?.querySelector('.permission-label');
|
|
52
|
+
|
|
53
|
+
if (!this.btn || !this.popup) {
|
|
54
|
+
console.warn('Permission settings UI elements not found');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Generate popup HTML from the current mode list
|
|
59
|
+
this._renderPopup();
|
|
60
|
+
|
|
61
|
+
// Toggle popup on button click
|
|
62
|
+
this.btn.addEventListener('click', (e) => {
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
this.toggle();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Handle preset selection + "Set as default"
|
|
68
|
+
this.popup.addEventListener('click', (e) => {
|
|
69
|
+
const preset = e.target.closest('.permission-preset');
|
|
70
|
+
if (preset) {
|
|
71
|
+
this.selectLevel(preset.dataset.level);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const def = e.target.closest('.permission-set-default');
|
|
75
|
+
if (def) this._saveAsGlobalDefault();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Close on outside click
|
|
79
|
+
document.addEventListener('click', (e) => {
|
|
80
|
+
if (this.isOpen && !this.btn.contains(e.target) && !this.popup.contains(e.target)) {
|
|
81
|
+
this.close();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Close on Escape
|
|
86
|
+
document.addEventListener('keydown', (e) => {
|
|
87
|
+
if (e.key === 'Escape' && this.isOpen) {
|
|
88
|
+
this.close();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Load global defaults
|
|
94
|
+
this.loadGlobalDefaults();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Look up a mode by value in the active list (fallback: strings.yaml set,
|
|
99
|
+
* so a level persisted under another engine still labels the button).
|
|
100
|
+
*/
|
|
101
|
+
_modeInfo(value) {
|
|
102
|
+
return this.modes.find(m => m.value === value)
|
|
103
|
+
|| FALLBACK_MODES.find(m => m.value === value)
|
|
104
|
+
|| null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Adopt a server-delivered mode list (the session engine's own vocabulary)
|
|
109
|
+
* and re-render the popup if it changed.
|
|
110
|
+
*/
|
|
111
|
+
_applyModes(modes) {
|
|
112
|
+
const next = (Array.isArray(modes) && modes.length) ? modes : this.defaultModes;
|
|
113
|
+
const same = next.length === this.modes.length
|
|
114
|
+
&& next.every((m, i) => m.value === this.modes[i].value);
|
|
115
|
+
this.modes = next;
|
|
116
|
+
if (!same) {
|
|
117
|
+
this._renderPopup();
|
|
118
|
+
if (this.isOpen) this.updatePresetSelection();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate popup HTML from the active mode list
|
|
124
|
+
*/
|
|
125
|
+
_renderPopup() {
|
|
126
|
+
const presets = this.modes.map(m => {
|
|
127
|
+
return `<button class="permission-preset" data-level="${m.value}">
|
|
128
|
+
<span class="preset-dot" style="background:${m.color}"></span>
|
|
129
|
+
<div class="preset-info">
|
|
130
|
+
<span class="preset-label">${m.label}<span class="preset-default-tag">default</span></span>
|
|
131
|
+
<span class="preset-desc">${m.desc}</span>
|
|
132
|
+
</div>
|
|
133
|
+
<span class="preset-check"></span>
|
|
134
|
+
</button>`;
|
|
135
|
+
}).join('');
|
|
136
|
+
|
|
137
|
+
this.popup.innerHTML = `
|
|
138
|
+
<div class="permission-popup-header">${S.permissions.header}</div>
|
|
139
|
+
<div class="permission-presets">${presets}</div>
|
|
140
|
+
<div class="permission-popup-footer">
|
|
141
|
+
<button class="permission-set-default">Set as default</button>
|
|
142
|
+
</div>`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Synchronous seed on session switch: adopt the session engine's mode
|
|
147
|
+
* vocabulary from the registry and the session's cached/pending level, so
|
|
148
|
+
* the first paint after a tab switch is engine-correct. The server fetch
|
|
149
|
+
* in setSession() then merely confirms (and refreshes the caches).
|
|
150
|
+
*/
|
|
151
|
+
_seedFromSession(opts = {}) {
|
|
152
|
+
const session = window.app?.activeSession;
|
|
153
|
+
const engName = session?.provider
|
|
154
|
+
|| (opts.pendingProvider !== undefined
|
|
155
|
+
? opts.pendingProvider
|
|
156
|
+
: session?.pendingProvider)
|
|
157
|
+
|| null;
|
|
158
|
+
const eng = engName ? engineInfo(engName) : null;
|
|
159
|
+
if (eng?.permission_modes?.length) {
|
|
160
|
+
this._applyModes(eng.permission_modes);
|
|
161
|
+
} else if (!engName) {
|
|
162
|
+
this._applyModes(this.defaultModes);
|
|
163
|
+
}
|
|
164
|
+
const cached = [session?.permissionLevel, session?.pendingPermission,
|
|
165
|
+
this.globalDefault].find(v => v && this._modeInfo(v));
|
|
166
|
+
this.currentLevel = cached
|
|
167
|
+
|| eng?.default_permission_mode
|
|
168
|
+
|| this.currentLevel;
|
|
169
|
+
this.updateButtonState();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set current session and load its permission level
|
|
174
|
+
* Called when switching tabs/sessions
|
|
175
|
+
*/
|
|
176
|
+
async setSession(sessionId, opts = {}) {
|
|
177
|
+
this.currentSessionId = sessionId;
|
|
178
|
+
this._seedFromSession(opts);
|
|
179
|
+
|
|
180
|
+
if (!sessionId) {
|
|
181
|
+
// Unconnected session — show the modes of the engine it WILL run
|
|
182
|
+
// on: the tab's picked engine (pendingProvider) if any, else the
|
|
183
|
+
// box default. Restore any pending choice stashed on the session
|
|
184
|
+
// object, or fall back to global default.
|
|
185
|
+
const session = window.app?.activeSession;
|
|
186
|
+
const pendingProvider = opts.pendingProvider !== undefined
|
|
187
|
+
? opts.pendingProvider
|
|
188
|
+
: (session?.pendingProvider || null);
|
|
189
|
+
if (pendingProvider) {
|
|
190
|
+
try {
|
|
191
|
+
const r = await fetch(`${CONFIG.API_BASE}/api/app/default-permissions?provider=${encodeURIComponent(pendingProvider)}`);
|
|
192
|
+
// Bail if a newer setSession() superseded us during the await.
|
|
193
|
+
if (this.currentSessionId !== sessionId) return;
|
|
194
|
+
if (r.ok) {
|
|
195
|
+
const data = await r.json();
|
|
196
|
+
if (Array.isArray(data.modes) && data.modes.length) {
|
|
197
|
+
this._applyModes(data.modes);
|
|
198
|
+
}
|
|
199
|
+
// A stashed choice only survives if this engine speaks it.
|
|
200
|
+
this.currentLevel = this._modeInfo(session?.pendingPermission)
|
|
201
|
+
? session.pendingPermission
|
|
202
|
+
: (this._modeInfo(data.default_level) ? data.default_level : this.currentLevel);
|
|
203
|
+
this.updateButtonState();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
} catch (err) { /* fall through to default-engine modes */ }
|
|
207
|
+
if (this.currentSessionId !== sessionId) return;
|
|
208
|
+
}
|
|
209
|
+
this._applyModes(this.defaultModes);
|
|
210
|
+
this.currentLevel = session?.pendingPermission || this.globalDefault;
|
|
211
|
+
this.updateButtonState();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/session/${sessionId}/permission-mode`);
|
|
217
|
+
// Bail if a newer setSession() superseded us during the await.
|
|
218
|
+
if (this.currentSessionId !== sessionId) return;
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
const data = await response.json();
|
|
221
|
+
this._applyModes(data.modes);
|
|
222
|
+
this.globalDefault = this._modeInfo(data.global_default) ? data.global_default : 'dontAsk';
|
|
223
|
+
// A brand-new session has no stored per-session level yet. Fall
|
|
224
|
+
// back to the global default (e.g. YOLO) rather than a hardcoded
|
|
225
|
+
// 'dontAsk', which would silently override it after first send.
|
|
226
|
+
// A global default this engine doesn't speak (cross-provider)
|
|
227
|
+
// falls through to the engine's own default.
|
|
228
|
+
this.currentLevel = this._modeInfo(data.permission_level)
|
|
229
|
+
? data.permission_level
|
|
230
|
+
: (this._modeInfo(this.globalDefault)
|
|
231
|
+
? this.globalDefault
|
|
232
|
+
: (data.provider_default || 'dontAsk'));
|
|
233
|
+
// Cache on the session so the next switch seeds synchronously.
|
|
234
|
+
const sess = window.app?.sessionManager?.sessions
|
|
235
|
+
?.find(s => s.storeId === sessionId);
|
|
236
|
+
if (sess) sess.permissionLevel = this.currentLevel;
|
|
237
|
+
this.updateButtonState();
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error('Error loading session permission mode:', err);
|
|
241
|
+
this.currentLevel = this.globalDefault;
|
|
242
|
+
this.updateButtonState();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Toggle popup visibility
|
|
248
|
+
*/
|
|
249
|
+
toggle() {
|
|
250
|
+
if (this.isOpen) {
|
|
251
|
+
this.close();
|
|
252
|
+
} else {
|
|
253
|
+
this.open();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Open popup
|
|
259
|
+
*/
|
|
260
|
+
open() {
|
|
261
|
+
// Position popup above the button (fixed positioning)
|
|
262
|
+
const btnRect = this.btn.getBoundingClientRect();
|
|
263
|
+
this.popup.style.bottom = `${window.innerHeight - btnRect.top + 8}px`;
|
|
264
|
+
this.popup.style.right = `${window.innerWidth - btnRect.right}px`;
|
|
265
|
+
|
|
266
|
+
this.popup.classList.add('open');
|
|
267
|
+
this.isOpen = true;
|
|
268
|
+
this.updatePresetSelection();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Close popup
|
|
273
|
+
*/
|
|
274
|
+
close() {
|
|
275
|
+
this.popup.classList.remove('open');
|
|
276
|
+
this.isOpen = false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Select a permission level and apply to session
|
|
281
|
+
*/
|
|
282
|
+
async selectLevel(level) {
|
|
283
|
+
if (level === this.currentLevel) {
|
|
284
|
+
this.close();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const previousLevel = this.currentLevel;
|
|
289
|
+
this.currentLevel = level;
|
|
290
|
+
this.updateButtonState();
|
|
291
|
+
this.close();
|
|
292
|
+
|
|
293
|
+
if (!this.currentSessionId) {
|
|
294
|
+
// No storeId yet — stash on the active session so the choice
|
|
295
|
+
// survives tab switches. Picked up on first connect. Use
|
|
296
|
+
// "Set as default" to promote to global.
|
|
297
|
+
const session = window.app?.activeSession;
|
|
298
|
+
if (session) session.pendingPermission = level;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Cache on the session so the next tab switch seeds synchronously.
|
|
303
|
+
{
|
|
304
|
+
const sess = window.app?.activeSession;
|
|
305
|
+
if (sess?.storeId === this.currentSessionId) sess.permissionLevel = level;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Save to session meta
|
|
309
|
+
try {
|
|
310
|
+
await fetch(`${CONFIG.API_BASE}/api/session/${this.currentSessionId}/permission-mode`, {
|
|
311
|
+
method: 'PUT',
|
|
312
|
+
headers: { 'Content-Type': 'application/json' },
|
|
313
|
+
body: JSON.stringify({ permission_level: level })
|
|
314
|
+
});
|
|
315
|
+
} catch (err) {
|
|
316
|
+
console.error('Error saving session permission mode:', err);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If there's a WebSocket connection, send permission mode change.
|
|
320
|
+
// On live-controls providers (claude-sdk) the server applies it to
|
|
321
|
+
// the running engine immediately — even mid-turn; elsewhere it takes
|
|
322
|
+
// effect on the next message via the lazy respawn. The reply's
|
|
323
|
+
// `applied` field says which one happened.
|
|
324
|
+
const session = window.app?.activeSession;
|
|
325
|
+
if (session?.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
326
|
+
session.ws.send(JSON.stringify({
|
|
327
|
+
type: 'set_permission_mode',
|
|
328
|
+
mode: level
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Save current level as the global default (explicit user action from
|
|
335
|
+
* the popup footer). Mirrors token-profile's _saveAsGlobalDefault.
|
|
336
|
+
*/
|
|
337
|
+
async _saveAsGlobalDefault() {
|
|
338
|
+
const level = this.currentLevel;
|
|
339
|
+
this.close();
|
|
340
|
+
await this.saveToGlobal(level);
|
|
341
|
+
|
|
342
|
+
// Sync Settings panel dropdown if open
|
|
343
|
+
const sel = document.querySelector('#default-permission-level');
|
|
344
|
+
if (sel) sel.value = level;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Save to global config (when no session)
|
|
349
|
+
*/
|
|
350
|
+
async saveToGlobal(level) {
|
|
351
|
+
try {
|
|
352
|
+
await fetch(`${CONFIG.API_BASE}/api/app/default-permissions`, {
|
|
353
|
+
method: 'PUT',
|
|
354
|
+
headers: { 'Content-Type': 'application/json' },
|
|
355
|
+
body: JSON.stringify({ permission_level: level })
|
|
356
|
+
});
|
|
357
|
+
this.globalDefault = level;
|
|
358
|
+
} catch (err) {
|
|
359
|
+
console.error('Error saving global permission level:', err);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Load global defaults from server
|
|
365
|
+
*/
|
|
366
|
+
async loadGlobalDefaults() {
|
|
367
|
+
try {
|
|
368
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/app/default-permissions`);
|
|
369
|
+
if (response.ok) {
|
|
370
|
+
const data = await response.json();
|
|
371
|
+
if (Array.isArray(data.modes) && data.modes.length) {
|
|
372
|
+
this.defaultModes = data.modes;
|
|
373
|
+
}
|
|
374
|
+
this._applyModes(this.defaultModes);
|
|
375
|
+
this.globalDefault = this._modeInfo(data.default_level) ? data.default_level : 'dontAsk';
|
|
376
|
+
this.currentLevel = this.globalDefault;
|
|
377
|
+
this.updateButtonState();
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error('Error loading global permission defaults:', err);
|
|
381
|
+
this.currentLevel = 'dontAsk';
|
|
382
|
+
this.updateButtonState();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Update button appearance based on current level
|
|
388
|
+
*/
|
|
389
|
+
updateButtonState() {
|
|
390
|
+
if (!this.btn) return;
|
|
391
|
+
|
|
392
|
+
const info = this._modeInfo(this.currentLevel)
|
|
393
|
+
|| this._modeInfo('bypassPermissions')
|
|
394
|
+
|| this.modes[0];
|
|
395
|
+
this.btn.dataset.level = this.currentLevel;
|
|
396
|
+
// Inline color so engine-specific modes need no per-mode CSS rule
|
|
397
|
+
this.btn.style.color = info.color || '';
|
|
398
|
+
// strings.yaml carries hand-written tooltips for the common modes;
|
|
399
|
+
// engine-specific extras fall back to the template.
|
|
400
|
+
const tooltip = S.permissions.levels[this.currentLevel]?.tooltip
|
|
401
|
+
|| S.permissions.tooltip_template
|
|
402
|
+
.replace('{label}', info.label)
|
|
403
|
+
.replace('{desc}', info.desc);
|
|
404
|
+
this.btn.setAttribute('data-tooltip', tooltip);
|
|
405
|
+
|
|
406
|
+
if (this.labelEl) {
|
|
407
|
+
this.labelEl.textContent = info.label;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Set permission color on input container for focus-within border
|
|
411
|
+
const container = document.getElementById('input-container');
|
|
412
|
+
if (container) {
|
|
413
|
+
container.style.setProperty('--permission-color', info.color);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Fresh-session setup panel mirrors this state — keep its pills in
|
|
417
|
+
// sync however the level changed (popup, cycle shortcut, session load).
|
|
418
|
+
window.sessionSetupPanel?.refresh();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Update which preset appears selected + which one carries the
|
|
423
|
+
* "default" tag (the global default). Called on every popup open.
|
|
424
|
+
*/
|
|
425
|
+
updatePresetSelection() {
|
|
426
|
+
const presets = this.popup.querySelectorAll('.permission-preset');
|
|
427
|
+
presets.forEach(preset => {
|
|
428
|
+
preset.classList.toggle('selected', preset.dataset.level === this.currentLevel);
|
|
429
|
+
preset.classList.toggle('is-default', preset.dataset.level === this.globalDefault);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Get current level key
|
|
435
|
+
*/
|
|
436
|
+
getLevel() {
|
|
437
|
+
return this.currentLevel;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Human label for a mode value (deny explainers, status lines)
|
|
442
|
+
*/
|
|
443
|
+
getModeLabel(value) {
|
|
444
|
+
return this._modeInfo(value)?.label || value;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get the value to pass to the agent CLI (null for unknown mode)
|
|
449
|
+
*/
|
|
450
|
+
getLevelValue() {
|
|
451
|
+
return this._modeInfo(this.currentLevel)?.value ?? null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Cycle through permission levels (for keyboard shortcut)
|
|
456
|
+
*/
|
|
457
|
+
cycle() {
|
|
458
|
+
const values = this.modes.map(m => m.value);
|
|
459
|
+
const idx = values.indexOf(this.currentLevel);
|
|
460
|
+
this.selectLevel(values[(idx + 1) % values.length]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Singleton instance
|
|
465
|
+
export const permissionSettings = new PermissionSettingsManager();
|
|
466
|
+
|
|
467
|
+
// Expose globally for config widget and session.js access
|
|
468
|
+
window.permissionSettings = permissionSettings;
|
|
469
|
+
|
|
470
|
+
// Auto-initialize when DOM is ready
|
|
471
|
+
if (document.readyState === 'loading') {
|
|
472
|
+
document.addEventListener('DOMContentLoaded', () => permissionSettings.init());
|
|
473
|
+
} else {
|
|
474
|
+
permissionSettings.init();
|
|
475
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointer modality — is a real cursor driving the UI right now?
|
|
3
|
+
*
|
|
4
|
+
* CSS cannot answer this on iPadOS. `(hover: none) and (pointer: coarse)`
|
|
5
|
+
* matches whether the user is prodding the glass with a finger or sweeping a
|
|
6
|
+
* Magic Keyboard trackpad cursor: the query describes the device's WEAKEST
|
|
7
|
+
* input, not the one in use. Only runtime pointer events disambiguate — a
|
|
8
|
+
* trackpad reports `pointerType: 'mouse'`, a finger reports `'touch'`.
|
|
9
|
+
*
|
|
10
|
+
* Owns `body.fine-pointer` so CSS can re-enable cursor-only affordances that
|
|
11
|
+
* must stay off for taps (see the shortcut-hint rows in 40-input.css).
|
|
12
|
+
*
|
|
13
|
+
* Deliberately NOT context-menu.js's `lastInputWasTouch()`, for two reasons:
|
|
14
|
+
* - That tracker defaults to 'mouse'. Here that would arm clickable rows
|
|
15
|
+
* before the first touch ever lands, which is the accidental-tap hazard
|
|
16
|
+
* the gate exists to prevent. This one starts unarmed on coarse devices.
|
|
17
|
+
* - It listens only to `pointerdown`. A click's target is resolved from its
|
|
18
|
+
* pointerdown/pointerup targets, so flipping a class at pointerdown is
|
|
19
|
+
* already too late — the press lands on whatever sat underneath. Tracking
|
|
20
|
+
* `pointermove` arms on hover, before any button goes down.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const COARSE_QUERY = '(hover: none) and (pointer: coarse)';
|
|
24
|
+
|
|
25
|
+
// Unarmed wherever the device CAN be finger-driven. A desktop with a real
|
|
26
|
+
// mouse matches nothing here and is armed from the first paint.
|
|
27
|
+
let fine = !(typeof matchMedia === 'function' && matchMedia(COARSE_QUERY).matches);
|
|
28
|
+
|
|
29
|
+
function apply() {
|
|
30
|
+
document.body?.classList.toggle('fine-pointer', fine);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function note(pointerType) {
|
|
34
|
+
if (!pointerType) return;
|
|
35
|
+
// 'pen' counts as coarse: an Apple Pencil tap carries the same
|
|
36
|
+
// fire-whatever-is-under-the-tip risk a fingertip does.
|
|
37
|
+
const next = pointerType === 'mouse';
|
|
38
|
+
if (next === fine) return;
|
|
39
|
+
fine = next;
|
|
40
|
+
apply();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof document !== 'undefined') {
|
|
44
|
+
// capture+passive: never interferes with anything downstream, and the
|
|
45
|
+
// pointermove flood costs one string compare that early-returns.
|
|
46
|
+
for (const type of ['pointermove', 'pointerdown', 'pointerover']) {
|
|
47
|
+
document.addEventListener(type, (e) => note(e.pointerType), { capture: true, passive: true });
|
|
48
|
+
}
|
|
49
|
+
if (document.readyState === 'loading') {
|
|
50
|
+
document.addEventListener('DOMContentLoaded', apply);
|
|
51
|
+
} else {
|
|
52
|
+
apply();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** True when a real cursor (mouse/trackpad) drove the most recent input. */
|
|
57
|
+
export function hasFinePointer() {
|
|
58
|
+
return fine;
|
|
59
|
+
}
|