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,738 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snippets Autocomplete Module
|
|
3
|
+
* Provides suggestions for agents and user-defined text snippets when typing # in chat input
|
|
4
|
+
*
|
|
5
|
+
* Data is stored server-side in ~/.painapple-code/config.json
|
|
6
|
+
* with localStorage as cache/fallback for offline use.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import S from './strings.js';
|
|
10
|
+
import { escapeHtml } from './utils.js';
|
|
11
|
+
import { CONFIG } from './config.js';
|
|
12
|
+
import { anchorAbove } from './caret-position.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get user-facing agents — discovered from ~/.claude/agents/ and the
|
|
16
|
+
* project's `<cwd>/.claude/agents/`. Claude's hardcoded built-ins
|
|
17
|
+
* (Explore, Plan, general-purpose) are intentionally excluded — they
|
|
18
|
+
* are agents Claude uses internally, not ones humans typically invoke
|
|
19
|
+
* via `#`.
|
|
20
|
+
*/
|
|
21
|
+
export function getBuiltInAgents() {
|
|
22
|
+
return loadDiscoveredAgents();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// localStorage cache keys (fallback for offline use)
|
|
26
|
+
const SNIPPETS_CACHE_KEY = 'claude-code-snippets-cache';
|
|
27
|
+
const LEGACY_SNIPPETS_CACHE_KEY = 'claude-code-favorites-cache';
|
|
28
|
+
const DISABLED_AGENTS_CACHE_KEY = 'claude-code-disabled-agents-cache';
|
|
29
|
+
const AGENT_PATTERNS_CACHE_KEY = 'claude-code-agent-patterns-cache';
|
|
30
|
+
const DISCOVERED_AGENTS_CACHE_KEY = 'claude-code-discovered-agents-cache';
|
|
31
|
+
|
|
32
|
+
// Default pattern - {agent} is replaced with agent name
|
|
33
|
+
export const DEFAULT_AGENT_PATTERN = S.prompts.agents.default_pattern;
|
|
34
|
+
|
|
35
|
+
// In-memory cache (populated from server or localStorage)
|
|
36
|
+
let _snippetsCache = null;
|
|
37
|
+
let _disabledAgentsCache = null;
|
|
38
|
+
let _agentPatternsCache = null;
|
|
39
|
+
let _discoveredAgentsCache = null;
|
|
40
|
+
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
+
// Server API Functions
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fetch user snippets from server
|
|
47
|
+
*/
|
|
48
|
+
export async function fetchUserSnippets() {
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/user/snippets`);
|
|
51
|
+
if (response.ok) {
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
_snippetsCache = data.snippets || [];
|
|
54
|
+
_disabledAgentsCache = new Set(data.disabled_agents || []);
|
|
55
|
+
localStorage.setItem(SNIPPETS_CACHE_KEY, JSON.stringify(_snippetsCache));
|
|
56
|
+
localStorage.setItem(DISABLED_AGENTS_CACHE_KEY, JSON.stringify([..._disabledAgentsCache]));
|
|
57
|
+
localStorage.removeItem(LEGACY_SNIPPETS_CACHE_KEY);
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error('Failed to fetch snippets from server:', e);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save user snippets to server
|
|
68
|
+
*/
|
|
69
|
+
export async function saveUserSnippets(snippets, disabledAgents) {
|
|
70
|
+
_snippetsCache = snippets;
|
|
71
|
+
_disabledAgentsCache = disabledAgents instanceof Set ? disabledAgents : new Set(disabledAgents);
|
|
72
|
+
localStorage.setItem(SNIPPETS_CACHE_KEY, JSON.stringify(_snippetsCache));
|
|
73
|
+
localStorage.setItem(DISABLED_AGENTS_CACHE_KEY, JSON.stringify([..._disabledAgentsCache]));
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/user/snippets`, {
|
|
77
|
+
method: 'PUT',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
snippets: snippets,
|
|
81
|
+
disabled_agents: [..._disabledAgentsCache]
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
return response.ok;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error('Failed to save snippets to server:', e);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fetch agent patterns from server
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchAgentPatterns() {
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/user/agent-patterns`);
|
|
97
|
+
if (response.ok) {
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
_agentPatternsCache = data;
|
|
100
|
+
localStorage.setItem(AGENT_PATTERNS_CACHE_KEY, JSON.stringify(data));
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error('Failed to fetch agent patterns from server:', e);
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Save agent patterns to server
|
|
111
|
+
*/
|
|
112
|
+
export async function saveAgentPatternsToServer(patterns) {
|
|
113
|
+
_agentPatternsCache = patterns;
|
|
114
|
+
localStorage.setItem(AGENT_PATTERNS_CACHE_KEY, JSON.stringify(patterns));
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/user/agent-patterns`, {
|
|
118
|
+
method: 'PUT',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify(patterns)
|
|
121
|
+
});
|
|
122
|
+
return response.ok;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error('Failed to save agent patterns to server:', e);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Fetch discovered agents from ~/.claude/agents/ and optionally {cwd}/.claude/agents/
|
|
131
|
+
* These are user-defined agents with YAML frontmatter
|
|
132
|
+
* @param {string} [cwd] - Optional project directory to scan for project-local agents
|
|
133
|
+
*/
|
|
134
|
+
export async function fetchDiscoveredAgents(cwd = null) {
|
|
135
|
+
try {
|
|
136
|
+
let url = `${CONFIG.API_BASE}/api/agents`;
|
|
137
|
+
if (cwd) {
|
|
138
|
+
url += `?cwd=${encodeURIComponent(cwd)}`;
|
|
139
|
+
}
|
|
140
|
+
const response = await fetch(url);
|
|
141
|
+
if (response.ok) {
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
const agents = (data.agents || []).map(agent => ({
|
|
144
|
+
id: agent.id,
|
|
145
|
+
name: agent.name,
|
|
146
|
+
desc: agent.description || '',
|
|
147
|
+
type: 'agent',
|
|
148
|
+
file: agent.file,
|
|
149
|
+
source: agent.source || 'global'
|
|
150
|
+
}));
|
|
151
|
+
_discoveredAgentsCache = agents;
|
|
152
|
+
localStorage.setItem(DISCOVERED_AGENTS_CACHE_KEY, JSON.stringify(agents));
|
|
153
|
+
return agents;
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error('Failed to fetch discovered agents:', e);
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
|
+
// Synchronous Access Functions (use cache/localStorage)
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Load disabled agents set (from cache or localStorage)
|
|
167
|
+
*/
|
|
168
|
+
export function loadDisabledAgents() {
|
|
169
|
+
if (_disabledAgentsCache) {
|
|
170
|
+
return _disabledAgentsCache;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const saved = localStorage.getItem(DISABLED_AGENTS_CACHE_KEY);
|
|
174
|
+
if (saved) {
|
|
175
|
+
_disabledAgentsCache = new Set(JSON.parse(saved));
|
|
176
|
+
return _disabledAgentsCache;
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
console.error('Failed to load disabled agents:', e);
|
|
180
|
+
}
|
|
181
|
+
return new Set();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Save disabled agents set (updates cache and server)
|
|
186
|
+
*/
|
|
187
|
+
export function saveDisabledAgents(disabledSet) {
|
|
188
|
+
_disabledAgentsCache = disabledSet;
|
|
189
|
+
localStorage.setItem(DISABLED_AGENTS_CACHE_KEY, JSON.stringify([...disabledSet]));
|
|
190
|
+
saveUserSnippets(loadSnippets(), disabledSet);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Load user snippets (from cache or localStorage)
|
|
195
|
+
*/
|
|
196
|
+
export function loadSnippets() {
|
|
197
|
+
if (_snippetsCache) {
|
|
198
|
+
return _snippetsCache;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const saved = localStorage.getItem(SNIPPETS_CACHE_KEY)
|
|
202
|
+
?? localStorage.getItem(LEGACY_SNIPPETS_CACHE_KEY);
|
|
203
|
+
if (saved) {
|
|
204
|
+
_snippetsCache = JSON.parse(saved);
|
|
205
|
+
return _snippetsCache;
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error('Failed to load snippets:', e);
|
|
209
|
+
}
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Save user snippets (updates cache and server)
|
|
215
|
+
*/
|
|
216
|
+
export function saveSnippets(snippets) {
|
|
217
|
+
_snippetsCache = snippets;
|
|
218
|
+
localStorage.setItem(SNIPPETS_CACHE_KEY, JSON.stringify(snippets));
|
|
219
|
+
saveUserSnippets(snippets, loadDisabledAgents());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Load discovered agents (from cache or localStorage)
|
|
224
|
+
* These are agents from ~/.claude/agents/
|
|
225
|
+
*/
|
|
226
|
+
export function loadDiscoveredAgents() {
|
|
227
|
+
if (_discoveredAgentsCache) {
|
|
228
|
+
return _discoveredAgentsCache;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const saved = localStorage.getItem(DISCOVERED_AGENTS_CACHE_KEY);
|
|
232
|
+
if (saved) {
|
|
233
|
+
_discoveredAgentsCache = JSON.parse(saved);
|
|
234
|
+
return _discoveredAgentsCache;
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error('Failed to load discovered agents:', e);
|
|
238
|
+
}
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Load agent patterns (from cache or localStorage)
|
|
244
|
+
*/
|
|
245
|
+
export function loadAgentPatterns() {
|
|
246
|
+
if (_agentPatternsCache) {
|
|
247
|
+
return _agentPatternsCache;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const saved = localStorage.getItem(AGENT_PATTERNS_CACHE_KEY);
|
|
251
|
+
if (saved) {
|
|
252
|
+
_agentPatternsCache = JSON.parse(saved);
|
|
253
|
+
return _agentPatternsCache;
|
|
254
|
+
}
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.error('Failed to load agent patterns:', e);
|
|
257
|
+
}
|
|
258
|
+
return { global: DEFAULT_AGENT_PATTERN, agents: {} };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Save agent patterns (updates cache and server)
|
|
263
|
+
*/
|
|
264
|
+
export function saveAgentPatterns(patterns) {
|
|
265
|
+
_agentPatternsCache = patterns;
|
|
266
|
+
localStorage.setItem(AGENT_PATTERNS_CACHE_KEY, JSON.stringify(patterns));
|
|
267
|
+
saveAgentPatternsToServer(patterns);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get the pattern to use for a specific agent
|
|
272
|
+
*/
|
|
273
|
+
export function getAgentPattern(agentId) {
|
|
274
|
+
const patterns = loadAgentPatterns();
|
|
275
|
+
return patterns.agents[agentId] || patterns.global || DEFAULT_AGENT_PATTERN;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Initialize snippets data from server
|
|
280
|
+
* Call this on app startup
|
|
281
|
+
* @param {string} [cwd] - Optional project directory for project-local agents
|
|
282
|
+
*/
|
|
283
|
+
export async function initSnippetsData(cwd = null) {
|
|
284
|
+
await Promise.all([
|
|
285
|
+
fetchUserSnippets(),
|
|
286
|
+
fetchAgentPatterns(),
|
|
287
|
+
fetchDiscoveredAgents(cwd)
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Refresh agents for a new cwd (call when session cwd changes)
|
|
293
|
+
* @param {string} cwd - The new project directory
|
|
294
|
+
*/
|
|
295
|
+
export async function refreshAgentsForCwd(cwd) {
|
|
296
|
+
return fetchDiscoveredAgents(cwd);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* SnippetsAutocomplete - Suggestions for # trigger
|
|
301
|
+
*/
|
|
302
|
+
export class SnippetsAutocomplete {
|
|
303
|
+
/**
|
|
304
|
+
* @param {HTMLTextAreaElement} input - The message input element
|
|
305
|
+
* @param {Object} options - Configuration options
|
|
306
|
+
*/
|
|
307
|
+
constructor(input, options = {}) {
|
|
308
|
+
this.input = input;
|
|
309
|
+
|
|
310
|
+
this.visible = false;
|
|
311
|
+
this.items = [];
|
|
312
|
+
this.selectedIndex = 0;
|
|
313
|
+
this.triggerPos = -1;
|
|
314
|
+
this.query = '';
|
|
315
|
+
|
|
316
|
+
this.container = document.createElement('div');
|
|
317
|
+
this.container.id = 'snippets-autocomplete';
|
|
318
|
+
this.container.className = 'snippets-autocomplete';
|
|
319
|
+
|
|
320
|
+
this.input.parentElement.insertBefore(this.container, this.input);
|
|
321
|
+
|
|
322
|
+
this._handleClick = this._handleClick.bind(this);
|
|
323
|
+
this.container.addEventListener('click', this._handleClick);
|
|
324
|
+
|
|
325
|
+
this._handleModifier = this._handleModifier.bind(this);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Track Alt / Ctrl-Cmd modifier state while the dropdown is visible
|
|
330
|
+
* so the highlighted action icon reflects what Enter will do.
|
|
331
|
+
*
|
|
332
|
+
* Also fires the corresponding action on Enter+modifier here in capture
|
|
333
|
+
* phase, so the action can't be swallowed by an upstream global keydown
|
|
334
|
+
* listener (this kept biting on iPad and Mac where Cmd+Enter goes
|
|
335
|
+
* through document-level shortcut dispatchers).
|
|
336
|
+
*/
|
|
337
|
+
_handleModifier(e) {
|
|
338
|
+
let mod = '';
|
|
339
|
+
if (e.ctrlKey || e.metaKey) mod = 'mod-ctrl';
|
|
340
|
+
else if (e.altKey) mod = 'mod-alt';
|
|
341
|
+
if (mod !== this._currentMod) {
|
|
342
|
+
this.container.classList.remove('mod-alt', 'mod-ctrl');
|
|
343
|
+
if (mod) this.container.classList.add(mod);
|
|
344
|
+
this._currentMod = mod;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (e.type === 'keydown' && e.key === 'Enter' && this.hasSelection()) {
|
|
348
|
+
if (e.ctrlKey || e.metaKey) {
|
|
349
|
+
e.preventDefault();
|
|
350
|
+
e.stopImmediatePropagation();
|
|
351
|
+
this.selectAndSend();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (e.altKey) {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
e.stopImmediatePropagation();
|
|
357
|
+
this.select(undefined, { short: true });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get all items (discovered agents + user snippets)
|
|
365
|
+
*/
|
|
366
|
+
getAllItems() {
|
|
367
|
+
const disabled = loadDisabledAgents();
|
|
368
|
+
const agents = getBuiltInAgents().filter(a => !disabled.has(a.id));
|
|
369
|
+
|
|
370
|
+
const userSnippets = loadSnippets()
|
|
371
|
+
.filter(s => !s.hidden)
|
|
372
|
+
.map(s => ({
|
|
373
|
+
...s,
|
|
374
|
+
type: 'snippet'
|
|
375
|
+
}));
|
|
376
|
+
|
|
377
|
+
return [...agents, ...userSnippets];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Fuzzy match score between query and text
|
|
382
|
+
*/
|
|
383
|
+
_fuzzyScore(text, query) {
|
|
384
|
+
if (!query) return 1;
|
|
385
|
+
|
|
386
|
+
const textLower = text.toLowerCase();
|
|
387
|
+
const queryLower = query.toLowerCase();
|
|
388
|
+
|
|
389
|
+
if (textLower.startsWith(queryLower)) {
|
|
390
|
+
return 100 + (queryLower.length * 10);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const words = textLower.split(/[\s-_]/);
|
|
394
|
+
for (const word of words) {
|
|
395
|
+
if (word.startsWith(queryLower)) {
|
|
396
|
+
return 80 + (queryLower.length * 5);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let score = 0;
|
|
401
|
+
let textIdx = 0;
|
|
402
|
+
let prevMatchIdx = -1;
|
|
403
|
+
let consecutiveBonus = 0;
|
|
404
|
+
|
|
405
|
+
for (const char of queryLower) {
|
|
406
|
+
const idx = textLower.indexOf(char, textIdx);
|
|
407
|
+
if (idx === -1) return 0;
|
|
408
|
+
|
|
409
|
+
if (prevMatchIdx !== -1 && idx === prevMatchIdx + 1) {
|
|
410
|
+
consecutiveBonus += 2;
|
|
411
|
+
score += 5 + consecutiveBonus;
|
|
412
|
+
} else {
|
|
413
|
+
consecutiveBonus = 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
score += 1;
|
|
417
|
+
prevMatchIdx = idx;
|
|
418
|
+
textIdx = idx + 1;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return score;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Search items with fuzzy matching
|
|
426
|
+
*/
|
|
427
|
+
search(query) {
|
|
428
|
+
const items = this.getAllItems();
|
|
429
|
+
|
|
430
|
+
if (!query) {
|
|
431
|
+
return items.slice(0, 12);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const scored = items
|
|
435
|
+
.map(item => {
|
|
436
|
+
const nameScore = this._fuzzyScore(item.name, query);
|
|
437
|
+
const descScore = this._fuzzyScore(item.desc || '', query) * 0.5;
|
|
438
|
+
const score = Math.max(nameScore, descScore);
|
|
439
|
+
|
|
440
|
+
if (score === 0) return null;
|
|
441
|
+
return { ...item, score };
|
|
442
|
+
})
|
|
443
|
+
.filter(Boolean);
|
|
444
|
+
|
|
445
|
+
scored.sort((a, b) => b.score - a.score);
|
|
446
|
+
|
|
447
|
+
return scored.slice(0, 10);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Show the autocomplete dropdown
|
|
452
|
+
*/
|
|
453
|
+
show(query, triggerPos) {
|
|
454
|
+
this.triggerPos = triggerPos;
|
|
455
|
+
this.query = query;
|
|
456
|
+
|
|
457
|
+
this.items = this.search(query);
|
|
458
|
+
|
|
459
|
+
if (this.items.length === 0) {
|
|
460
|
+
this.hide();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
this.selectedIndex = this.noPreselectOnce ? -1 : 0;
|
|
465
|
+
this.noPreselectOnce = false;
|
|
466
|
+
this.visible = true;
|
|
467
|
+
this.render();
|
|
468
|
+
this.container.classList.add('visible');
|
|
469
|
+
this.container.scrollTop = 0;
|
|
470
|
+
this._detachAnchor?.();
|
|
471
|
+
this._detachAnchor = anchorAbove(this.container, this.input, triggerPos);
|
|
472
|
+
window.addEventListener('keydown', this._handleModifier, true);
|
|
473
|
+
window.addEventListener('keyup', this._handleModifier, true);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Hide the autocomplete dropdown
|
|
478
|
+
*/
|
|
479
|
+
hide() {
|
|
480
|
+
this.visible = false;
|
|
481
|
+
this.container.classList.remove('visible', 'mod-alt', 'mod-ctrl');
|
|
482
|
+
this._currentMod = '';
|
|
483
|
+
this.items = [];
|
|
484
|
+
this.selectedIndex = 0;
|
|
485
|
+
this.triggerPos = -1;
|
|
486
|
+
this.query = '';
|
|
487
|
+
this._detachAnchor?.();
|
|
488
|
+
this._detachAnchor = null;
|
|
489
|
+
window.removeEventListener('keydown', this._handleModifier, true);
|
|
490
|
+
window.removeEventListener('keyup', this._handleModifier, true);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Render the dropdown
|
|
495
|
+
*/
|
|
496
|
+
render() {
|
|
497
|
+
const hasAgents = this.items.some(i => i.type === 'agent');
|
|
498
|
+
const hasSnippets = this.items.some(i => i.type === 'snippet');
|
|
499
|
+
const showSnippetsHeader = hasSnippets || loadSnippets().length === 0;
|
|
500
|
+
|
|
501
|
+
let html = '';
|
|
502
|
+
|
|
503
|
+
if (hasAgents) {
|
|
504
|
+
html += '<div class="snippets-section"><span>Agents</span><a class="snippets-edit-link" href="#" data-widget="agents">Edit</a></div>';
|
|
505
|
+
this.items.forEach((item, i) => {
|
|
506
|
+
if (item.type !== 'agent') return;
|
|
507
|
+
html += this._renderItem(item, i);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (showSnippetsHeader) {
|
|
512
|
+
html += '<div class="snippets-section"><span>Snippets</span><a class="snippets-edit-link" href="#" data-widget="snippets">Edit</a></div>';
|
|
513
|
+
this.items.forEach((item, i) => {
|
|
514
|
+
if (item.type !== 'snippet') return;
|
|
515
|
+
html += this._renderItem(item, i);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!hasAgents && !showSnippetsHeader) {
|
|
520
|
+
this.items.forEach((item, i) => {
|
|
521
|
+
html += this._renderItem(item, i);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.container.innerHTML = html;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Render a single item
|
|
530
|
+
*/
|
|
531
|
+
_renderItem(item, index) {
|
|
532
|
+
const highlighted = this.highlightMatch(item.name, this.query);
|
|
533
|
+
let typeBadge = '';
|
|
534
|
+
if (item.type === 'agent' && item.source === 'project') {
|
|
535
|
+
typeBadge = '<span class="snippets-type-badge project">project</span>';
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return `
|
|
539
|
+
<div class="snippets-item ${index === this.selectedIndex ? 'selected' : ''}"
|
|
540
|
+
data-index="${index}">
|
|
541
|
+
<div class="snippets-content">
|
|
542
|
+
<span class="snippets-name">${highlighted}</span>
|
|
543
|
+
${item.desc ? `<span class="snippets-desc">${escapeHtml(item.desc)}</span>` : ''}
|
|
544
|
+
</div>
|
|
545
|
+
${typeBadge}
|
|
546
|
+
<div class="snippets-actions">
|
|
547
|
+
<button class="snippets-insert-btn" data-index="${index}" data-tooltip="Insert (Enter)">
|
|
548
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
549
|
+
<polyline points="9 10 4 15 9 20"/>
|
|
550
|
+
<path d="M20 4v7a4 4 0 0 1-4 4H4"/>
|
|
551
|
+
</svg>
|
|
552
|
+
</button>
|
|
553
|
+
<button class="snippets-insertname-btn" data-index="${index}" data-tooltip="Insert name only (Alt+Enter)">
|
|
554
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
555
|
+
<polyline points="4 7 4 4 20 4 20 7"/>
|
|
556
|
+
<line x1="9" y1="20" x2="15" y2="20"/>
|
|
557
|
+
<line x1="12" y1="4" x2="12" y2="20"/>
|
|
558
|
+
</svg>
|
|
559
|
+
</button>
|
|
560
|
+
<button class="snippets-send-btn" data-index="${index}" data-tooltip="Send now (Ctrl+Enter)">
|
|
561
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
562
|
+
<line x1="22" y1="2" x2="11" y2="13"/>
|
|
563
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
564
|
+
</svg>
|
|
565
|
+
</button>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Highlight matching characters
|
|
573
|
+
*/
|
|
574
|
+
highlightMatch(text, query) {
|
|
575
|
+
if (!query) return escapeHtml(text);
|
|
576
|
+
|
|
577
|
+
const textLower = text.toLowerCase();
|
|
578
|
+
const queryLower = query.toLowerCase();
|
|
579
|
+
|
|
580
|
+
let result = '';
|
|
581
|
+
let textIdx = 0;
|
|
582
|
+
let queryIdx = 0;
|
|
583
|
+
|
|
584
|
+
while (textIdx < text.length) {
|
|
585
|
+
if (queryIdx < queryLower.length && textLower[textIdx] === queryLower[queryIdx]) {
|
|
586
|
+
result += `<mark>${escapeHtml(text[textIdx])}</mark>`;
|
|
587
|
+
queryIdx++;
|
|
588
|
+
} else {
|
|
589
|
+
result += escapeHtml(text[textIdx]);
|
|
590
|
+
}
|
|
591
|
+
textIdx++;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Move selection up or down
|
|
599
|
+
*/
|
|
600
|
+
moveSelection(delta) {
|
|
601
|
+
if (!this.visible || this.items.length === 0) return;
|
|
602
|
+
|
|
603
|
+
this.selectedIndex += delta;
|
|
604
|
+
if (this.selectedIndex < 0) this.selectedIndex = this.items.length - 1;
|
|
605
|
+
if (this.selectedIndex >= this.items.length) this.selectedIndex = 0;
|
|
606
|
+
|
|
607
|
+
this.render();
|
|
608
|
+
|
|
609
|
+
const selectedEl = this.container.querySelector('.snippets-item.selected');
|
|
610
|
+
if (selectedEl) {
|
|
611
|
+
selectedEl.scrollIntoView({ block: 'nearest' });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Select the current item
|
|
617
|
+
* @param {number} index - Item index to select
|
|
618
|
+
* @param {object} options - { short: true } to insert just the name instead of full pattern
|
|
619
|
+
*/
|
|
620
|
+
select(index = this.selectedIndex, { short = false } = {}) {
|
|
621
|
+
if (index < 0 || index >= this.items.length) return;
|
|
622
|
+
|
|
623
|
+
const item = this.items[index];
|
|
624
|
+
|
|
625
|
+
let insertText;
|
|
626
|
+
if (short) {
|
|
627
|
+
insertText = item.name + ' ';
|
|
628
|
+
} else if (item.type === 'agent') {
|
|
629
|
+
const pattern = getAgentPattern(item.id);
|
|
630
|
+
insertText = pattern.replace('{agent}', item.name);
|
|
631
|
+
if (!insertText.endsWith(' ')) {
|
|
632
|
+
insertText += ' ';
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
insertText = (item.text || item.name) + ' ';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const value = this.input.value;
|
|
639
|
+
const before = value.slice(0, this.triggerPos);
|
|
640
|
+
const after = value.slice(this.triggerPos + 1 + this.query.length);
|
|
641
|
+
|
|
642
|
+
this.input.value = before + insertText + after;
|
|
643
|
+
this.input.focus();
|
|
644
|
+
|
|
645
|
+
const cursorPos = before.length + insertText.length;
|
|
646
|
+
this.input.selectionStart = this.input.selectionEnd = cursorPos;
|
|
647
|
+
|
|
648
|
+
this.hide();
|
|
649
|
+
|
|
650
|
+
this.input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Handle click on item
|
|
655
|
+
*/
|
|
656
|
+
_handleClick(e) {
|
|
657
|
+
const editLink = e.target.closest('.snippets-edit-link');
|
|
658
|
+
if (editLink) {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
e.stopPropagation();
|
|
661
|
+
this.hide();
|
|
662
|
+
const widgetId = editLink.dataset.widget || 'snippets';
|
|
663
|
+
window.WidgetManager?.open(widgetId);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const sendBtn = e.target.closest('.snippets-send-btn');
|
|
668
|
+
if (sendBtn) {
|
|
669
|
+
e.stopPropagation();
|
|
670
|
+
const index = parseInt(sendBtn.dataset.index, 10);
|
|
671
|
+
this.selectAndSend(index);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const insertNameBtn = e.target.closest('.snippets-insertname-btn');
|
|
676
|
+
if (insertNameBtn) {
|
|
677
|
+
e.stopPropagation();
|
|
678
|
+
const index = parseInt(insertNameBtn.dataset.index, 10);
|
|
679
|
+
this.select(index, { short: true });
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const item = e.target.closest('.snippets-item');
|
|
684
|
+
if (item) {
|
|
685
|
+
const index = parseInt(item.dataset.index, 10);
|
|
686
|
+
this.select(index);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Select an item and immediately send it
|
|
692
|
+
*/
|
|
693
|
+
selectAndSend(index = this.selectedIndex) {
|
|
694
|
+
if (index < 0 || index >= this.items.length) return;
|
|
695
|
+
|
|
696
|
+
const item = this.items[index];
|
|
697
|
+
|
|
698
|
+
let sendText;
|
|
699
|
+
if (item.type === 'agent') {
|
|
700
|
+
const pattern = getAgentPattern(item.id);
|
|
701
|
+
sendText = pattern.replace('{agent}', item.name);
|
|
702
|
+
if (!sendText.endsWith(' ')) {
|
|
703
|
+
sendText += ' ';
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
sendText = (item.text || item.name);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const value = this.input.value;
|
|
710
|
+
const before = value.slice(0, this.triggerPos);
|
|
711
|
+
const after = value.slice(this.triggerPos + 1 + this.query.length);
|
|
712
|
+
this.input.value = before + after;
|
|
713
|
+
|
|
714
|
+
this.hide();
|
|
715
|
+
|
|
716
|
+
if (window.app) {
|
|
717
|
+
const remainingText = this.input.value.trim();
|
|
718
|
+
const finalMessage = remainingText ? `${sendText.trim()} ${remainingText}` : sendText.trim();
|
|
719
|
+
this.input.value = '';
|
|
720
|
+
window.app.sendMessage(finalMessage);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Check if there's a valid selection
|
|
726
|
+
*/
|
|
727
|
+
hasSelection() {
|
|
728
|
+
return this.selectedIndex >= 0 && this.selectedIndex < this.items.length;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Get the currently selected item
|
|
733
|
+
*/
|
|
734
|
+
getSelectedItem() {
|
|
735
|
+
if (!this.hasSelection()) return null;
|
|
736
|
+
return this.items[this.selectedIndex];
|
|
737
|
+
}
|
|
738
|
+
}
|