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
painapple_code/cli/ui.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""Terminal UI helpers shared by CLI subcommands.
|
|
2
|
+
|
|
3
|
+
Two layers:
|
|
4
|
+
|
|
5
|
+
1. Plain ANSI output helpers (``say``/``info``/``ok``/``warn``/``err``) —
|
|
6
|
+
zero dependencies, safe to import in non-TTY contexts. Mirrors the
|
|
7
|
+
look of painapple-docker.sh so the two tools feel like one.
|
|
8
|
+
|
|
9
|
+
2. Interactive prompt widgets built on questionary/prompt_toolkit —
|
|
10
|
+
imported lazily so `docker up`/`logs`/etc. never pay the import and
|
|
11
|
+
never break in a pipe. Every widget understands "back": selects get
|
|
12
|
+
a dedicated ``← Back`` entry, text/path prompts bind Esc. A widget
|
|
13
|
+
returns the sentinel :data:`BACK` when the user backs out, so wizard
|
|
14
|
+
step machines can just compare against it.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Unique sentinel: "user asked to go back one step".
|
|
23
|
+
class _Back:
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return "<BACK>"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
BACK = _Back()
|
|
29
|
+
|
|
30
|
+
# Legacy Windows conhost ignores ANSI escapes until VT processing is
|
|
31
|
+
# switched on; the empty os.system() call does exactly that (Windows
|
|
32
|
+
# Terminal and every unix terminal don't need it, and it's a no-op there).
|
|
33
|
+
if os.name == "nt" and sys.stdout.isatty():
|
|
34
|
+
os.system("")
|
|
35
|
+
|
|
36
|
+
_COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
|
|
37
|
+
|
|
38
|
+
BOLD = "\033[1m" if _COLOR else ""
|
|
39
|
+
DIM = "\033[2m" if _COLOR else ""
|
|
40
|
+
GREEN = "\033[32m" if _COLOR else ""
|
|
41
|
+
YELLOW = "\033[33m" if _COLOR else ""
|
|
42
|
+
RED = "\033[31m" if _COLOR else ""
|
|
43
|
+
BLUE = "\033[34m" if _COLOR else ""
|
|
44
|
+
CYAN = "\033[36m" if _COLOR else ""
|
|
45
|
+
RESET = "\033[0m" if _COLOR else ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def say(msg=""):
|
|
49
|
+
print(msg)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def info(msg):
|
|
53
|
+
print(f"{BLUE}→{RESET} {msg}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def ok(msg):
|
|
57
|
+
print(f"{GREEN}✓{RESET} {msg}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def warn(msg):
|
|
61
|
+
print(f"{YELLOW}⚠{RESET} {msg}", file=sys.stderr)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def err(msg):
|
|
65
|
+
print(f"{RED}✗{RESET} {msg}", file=sys.stderr)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def die(msg, hint=None):
|
|
69
|
+
err(msg)
|
|
70
|
+
if hint:
|
|
71
|
+
say(hint)
|
|
72
|
+
raise SystemExit(1)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def sanitize(value):
|
|
76
|
+
"""Strip control bytes some terminals smuggle into typed input."""
|
|
77
|
+
return re.sub(r"[\x00-\x1f\x7f]", "", value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def print_credentials(urls, pw, token):
|
|
81
|
+
"""The credential block behind `painapple password` (host AND docker).
|
|
82
|
+
|
|
83
|
+
One shared body so the two paths can't drift. Labels carry each
|
|
84
|
+
credential's purpose — users copy the line they need, not the footer
|
|
85
|
+
hints. ``token`` is "" on a config written by a pre-WP-02 build; the
|
|
86
|
+
URL is then a bare address (manual login form) and the API-token
|
|
87
|
+
row/hint are dropped.
|
|
88
|
+
"""
|
|
89
|
+
width = len("Password (login form):") + 2
|
|
90
|
+
|
|
91
|
+
def _row(label, value):
|
|
92
|
+
say(f"{BOLD}{label}{RESET}{' ' * (width - len(label))}{value}")
|
|
93
|
+
|
|
94
|
+
url_label = "Auto-Login URL:" if token else "URL:"
|
|
95
|
+
for url in urls:
|
|
96
|
+
_row(url_label, url)
|
|
97
|
+
url_label = ""
|
|
98
|
+
_row("Password (login form):", pw)
|
|
99
|
+
if token:
|
|
100
|
+
_row("API token (scripts):", token)
|
|
101
|
+
say(f"{DIM} Open the URL once; the cookie keeps you logged in after that.{RESET}")
|
|
102
|
+
say(f"{DIM} Scripts use the API token (Bearer / ?tkn=), never the password.{RESET}")
|
|
103
|
+
else:
|
|
104
|
+
say(f"{DIM} Open the URL and log in with the password; the cookie keeps you logged in.{RESET}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def require_tty(what="interactive setup", alternative=None):
|
|
108
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
109
|
+
die(f"{what} needs a terminal.",
|
|
110
|
+
alternative or
|
|
111
|
+
f"{DIM}Non-interactive alternative: painapple profile set "
|
|
112
|
+
f"NAME KEY=VALUE …{RESET}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ──── Interactive widgets (lazy questionary import) ─────────────────────────
|
|
116
|
+
|
|
117
|
+
_BACK_TITLE = "← Back"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _questionary():
|
|
121
|
+
try:
|
|
122
|
+
import questionary
|
|
123
|
+
return questionary
|
|
124
|
+
except ImportError:
|
|
125
|
+
die("The interactive setup needs the 'questionary' package.",
|
|
126
|
+
f"{DIM}Install it with: pip install questionary{RESET}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _style():
|
|
130
|
+
from prompt_toolkit.styles import Style
|
|
131
|
+
return Style([
|
|
132
|
+
("qmark", "fg:ansicyan bold"),
|
|
133
|
+
("question", "bold"),
|
|
134
|
+
("answer", "fg:ansicyan"),
|
|
135
|
+
("pointer", "fg:ansicyan bold"),
|
|
136
|
+
("highlighted", "fg:ansicyan bold"),
|
|
137
|
+
("instruction", "fg:ansibrightblack"),
|
|
138
|
+
("text", ""),
|
|
139
|
+
("dim", "fg:ansibrightblack"),
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Choice:
|
|
144
|
+
"""One selectable option: a value, a short title, an optional dim hint."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, value, title, hint=None):
|
|
147
|
+
self.value = value
|
|
148
|
+
self.title = title
|
|
149
|
+
self.hint = hint
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def select(message, choices, default=None, back=False, instruction=None,
|
|
153
|
+
use_search=False, on_left=None, right_pick=None):
|
|
154
|
+
"""Arrow-key menu. Returns the picked Choice's value, or BACK.
|
|
155
|
+
|
|
156
|
+
``choices`` is a list of :class:`Choice` (or plain strings, used as
|
|
157
|
+
both value and title). ``default`` pre-highlights the choice with
|
|
158
|
+
that value. ``back=True`` appends a dim ``← Back`` entry — and makes
|
|
159
|
+
the ← key return BACK too (previous step), unless ``on_left``
|
|
160
|
+
overrides it with a different value (the directory browser maps ← to
|
|
161
|
+
"go to parent"). ``right_pick`` is a predicate on choice values: when
|
|
162
|
+
it accepts the highlighted value, the → key picks it (browser: enter
|
|
163
|
+
the highlighted folder). ``use_search=True`` enables type-to-filter.
|
|
164
|
+
"""
|
|
165
|
+
questionary = _questionary()
|
|
166
|
+
|
|
167
|
+
if on_left is None and back:
|
|
168
|
+
on_left = BACK
|
|
169
|
+
|
|
170
|
+
normalized = [c if isinstance(c, Choice) else Choice(c, str(c)) for c in choices]
|
|
171
|
+
q_choices = []
|
|
172
|
+
default_choice = None
|
|
173
|
+
for c in normalized:
|
|
174
|
+
# questionary's search filter calls .lower() on the title, so
|
|
175
|
+
# searchable selects need plain-string titles (hint folded in,
|
|
176
|
+
# un-dimmed — and conveniently filterable too).
|
|
177
|
+
if use_search:
|
|
178
|
+
title = f"{c.title} {c.hint}" if c.hint else c.title
|
|
179
|
+
else:
|
|
180
|
+
title = [("class:text", c.title)]
|
|
181
|
+
if c.hint:
|
|
182
|
+
title.append(("class:dim", f" {c.hint}"))
|
|
183
|
+
qc = questionary.Choice(title=title, value=c.value)
|
|
184
|
+
q_choices.append(qc)
|
|
185
|
+
if default is not None and c.value == default:
|
|
186
|
+
default_choice = qc
|
|
187
|
+
if back:
|
|
188
|
+
q_choices.append(questionary.Choice(
|
|
189
|
+
title=_BACK_TITLE if use_search else [("class:dim", _BACK_TITLE)],
|
|
190
|
+
value=BACK))
|
|
191
|
+
|
|
192
|
+
if instruction is None:
|
|
193
|
+
base = "type to filter, ↑↓ + Enter" if use_search else "↑↓ + Enter"
|
|
194
|
+
instruction = f"({base}, ← back)" if on_left is BACK else f"({base})"
|
|
195
|
+
|
|
196
|
+
question = questionary.select(
|
|
197
|
+
message,
|
|
198
|
+
choices=q_choices,
|
|
199
|
+
default=default_choice,
|
|
200
|
+
style=_style(),
|
|
201
|
+
instruction=instruction,
|
|
202
|
+
use_shortcuts=False,
|
|
203
|
+
use_search_filter=use_search,
|
|
204
|
+
use_jk_keys=False,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# ←/→ bindings ride on top of questionary's own. The application
|
|
208
|
+
# consults key_bindings dynamically, so merging before run() works.
|
|
209
|
+
if on_left is not None or right_pick is not None:
|
|
210
|
+
from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
|
|
211
|
+
extra = KeyBindings()
|
|
212
|
+
app = question.application
|
|
213
|
+
if on_left is not None:
|
|
214
|
+
@extra.add("left")
|
|
215
|
+
def _(event):
|
|
216
|
+
event.app.exit(result=on_left)
|
|
217
|
+
if right_pick is not None:
|
|
218
|
+
ic = _find_inquirer_control(app)
|
|
219
|
+
if ic is not None:
|
|
220
|
+
@extra.add("right")
|
|
221
|
+
def _(event):
|
|
222
|
+
pointed = ic.get_pointed_at()
|
|
223
|
+
if pointed is not None and right_pick(pointed.value):
|
|
224
|
+
event.app.exit(result=pointed.value)
|
|
225
|
+
app.key_bindings = (merge_key_bindings([app.key_bindings, extra])
|
|
226
|
+
if app.key_bindings else extra)
|
|
227
|
+
|
|
228
|
+
return question.unsafe_ask()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _find_inquirer_control(app):
|
|
232
|
+
"""The list control inside a questionary select — needed to read the
|
|
233
|
+
highlighted choice from a custom key binding."""
|
|
234
|
+
from questionary.prompts.common import InquirerControl
|
|
235
|
+
for control in app.layout.find_all_controls():
|
|
236
|
+
if isinstance(control, InquirerControl):
|
|
237
|
+
return control
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _prompt_session(completer=None):
|
|
242
|
+
from prompt_toolkit import PromptSession
|
|
243
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
244
|
+
|
|
245
|
+
bindings = KeyBindings()
|
|
246
|
+
|
|
247
|
+
class _BackRequested(Exception):
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
@bindings.add("escape", eager=True)
|
|
251
|
+
def _(event):
|
|
252
|
+
event.app.exit(exception=_BackRequested())
|
|
253
|
+
|
|
254
|
+
return PromptSession(key_bindings=bindings, completer=completer), _BackRequested
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def text(message, default="", validate=None, back=True, completer=None,
|
|
258
|
+
hint=None):
|
|
259
|
+
"""One-line input with the default pre-filled and editable.
|
|
260
|
+
|
|
261
|
+
The user edits the default in place (arrows/Home/End work) instead
|
|
262
|
+
of retyping it. Esc returns BACK (when ``back``). ``validate`` is a
|
|
263
|
+
callable returning an error string (re-prompt) or None (accept).
|
|
264
|
+
"""
|
|
265
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
266
|
+
|
|
267
|
+
session, back_exc = _prompt_session(completer)
|
|
268
|
+
esc_part = "Esc = back, " if back else ""
|
|
269
|
+
suffix = hint or ""
|
|
270
|
+
prompt_msg = FormattedText([
|
|
271
|
+
("fg:ansicyan bold", "? "),
|
|
272
|
+
("bold", message),
|
|
273
|
+
("fg:ansibrightblack", f" ({esc_part}Enter = accept){suffix}"),
|
|
274
|
+
("", "\n "),
|
|
275
|
+
])
|
|
276
|
+
while True:
|
|
277
|
+
try:
|
|
278
|
+
reply = session.prompt(prompt_msg, default=default)
|
|
279
|
+
except back_exc:
|
|
280
|
+
if back:
|
|
281
|
+
return BACK
|
|
282
|
+
continue
|
|
283
|
+
reply = sanitize(reply).strip()
|
|
284
|
+
if validate:
|
|
285
|
+
problem = validate(reply)
|
|
286
|
+
if problem:
|
|
287
|
+
warn(problem)
|
|
288
|
+
default = reply # keep what they typed for correction
|
|
289
|
+
continue
|
|
290
|
+
return reply
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def path_input(message, default="", must_exist=False, back=True,
|
|
294
|
+
offer_create=False):
|
|
295
|
+
"""Path input: pre-filled editable default + filesystem tab-completion.
|
|
296
|
+
|
|
297
|
+
``must_exist`` re-prompts (keeping the typed value) until the
|
|
298
|
+
directory exists; with ``offer_create`` the user is asked whether to
|
|
299
|
+
mkdir -p it instead.
|
|
300
|
+
"""
|
|
301
|
+
from prompt_toolkit.completion import PathCompleter
|
|
302
|
+
from pathlib import Path
|
|
303
|
+
|
|
304
|
+
completer = PathCompleter(expanduser=True)
|
|
305
|
+
current_default = default
|
|
306
|
+
while True:
|
|
307
|
+
reply = text(message, default=current_default, back=back,
|
|
308
|
+
completer=completer, hint=" Tab = complete")
|
|
309
|
+
if reply is BACK:
|
|
310
|
+
return BACK
|
|
311
|
+
if not reply:
|
|
312
|
+
warn("Path can't be empty.")
|
|
313
|
+
continue
|
|
314
|
+
p = Path(reply).expanduser()
|
|
315
|
+
if must_exist and not p.is_dir():
|
|
316
|
+
if offer_create and confirm(f"Directory does not exist: {p} — create it?",
|
|
317
|
+
default=False):
|
|
318
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
else:
|
|
320
|
+
warn(f"Directory does not exist: {p}")
|
|
321
|
+
current_default = reply
|
|
322
|
+
continue
|
|
323
|
+
return str(p.resolve() if p.exists() else p)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def int_input(message, default, lo=None, hi=None, back=True):
|
|
327
|
+
def _validate(v):
|
|
328
|
+
if not v.isdigit():
|
|
329
|
+
return f"Need a number{f' between {lo} and {hi}' if lo else ''}."
|
|
330
|
+
n = int(v)
|
|
331
|
+
if lo is not None and n < lo or hi is not None and n > hi:
|
|
332
|
+
return f"Must be between {lo} and {hi}."
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
reply = text(message, default=str(default), validate=_validate, back=back)
|
|
336
|
+
return reply if reply is BACK else int(reply)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def confirm(message, default=True):
|
|
340
|
+
questionary = _questionary()
|
|
341
|
+
return questionary.confirm(message, default=default, style=_style()).unsafe_ask()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def browse_dir(message, start=None, allow_create=False, back=True,
|
|
345
|
+
suggest=None):
|
|
346
|
+
"""Interactive directory picker (mc/ncdu-style): descend into
|
|
347
|
+
subdirectories, go up with `..`, type letters to filter the list.
|
|
348
|
+
|
|
349
|
+
Escape hatches: "Type a path instead…" drops to the completing text
|
|
350
|
+
prompt; ``allow_create`` adds a "New folder here…" entry. ``suggest``
|
|
351
|
+
keeps a not-yet-existing default one Enter away (shown as its own
|
|
352
|
+
entry, created later by the caller). Returns the chosen absolute
|
|
353
|
+
path as str, or BACK.
|
|
354
|
+
"""
|
|
355
|
+
from pathlib import Path
|
|
356
|
+
|
|
357
|
+
suggest_path = None
|
|
358
|
+
if suggest:
|
|
359
|
+
suggest_path = Path(suggest).expanduser()
|
|
360
|
+
if suggest_path.is_dir():
|
|
361
|
+
suggest_path = None # it exists — the browser just starts there
|
|
362
|
+
|
|
363
|
+
current = Path(start).expanduser() if start else Path.cwd()
|
|
364
|
+
try:
|
|
365
|
+
current = current.resolve()
|
|
366
|
+
except OSError:
|
|
367
|
+
current = Path.home()
|
|
368
|
+
# A saved-but-vanished default shouldn't strand the browser — walk up
|
|
369
|
+
# to the nearest ancestor that still exists.
|
|
370
|
+
while not current.is_dir() and current.parent != current:
|
|
371
|
+
current = current.parent
|
|
372
|
+
if not current.is_dir():
|
|
373
|
+
current = Path.home()
|
|
374
|
+
|
|
375
|
+
show_hidden = False
|
|
376
|
+
while True:
|
|
377
|
+
try:
|
|
378
|
+
subdirs = sorted(
|
|
379
|
+
(p for p in current.iterdir()
|
|
380
|
+
if p.is_dir() and (show_hidden or not p.name.startswith("."))),
|
|
381
|
+
key=lambda p: p.name.lower())
|
|
382
|
+
except OSError as e:
|
|
383
|
+
warn(f"Can't list {current}: {e}")
|
|
384
|
+
subdirs = []
|
|
385
|
+
|
|
386
|
+
choices = []
|
|
387
|
+
if suggest_path is not None:
|
|
388
|
+
choices.append(Choice("suggest", f"✓ Use {suggest_path}",
|
|
389
|
+
"default — will be created"))
|
|
390
|
+
choices += [Choice("use", "✓ Use this directory", str(current)),
|
|
391
|
+
Choice("type", "✎ Type a path instead…")]
|
|
392
|
+
if current.parent != current:
|
|
393
|
+
choices.append(Choice("up", "↑ ..", "up one level"))
|
|
394
|
+
choices.extend(Choice(("cd", p), f"{p.name}/") for p in subdirs)
|
|
395
|
+
if allow_create:
|
|
396
|
+
choices.append(Choice("mkdir", "+ New folder here…"))
|
|
397
|
+
choices.append(Choice("hidden", "· Hide hidden folders" if show_hidden
|
|
398
|
+
else "· Show hidden folders"))
|
|
399
|
+
|
|
400
|
+
# Plain string — questionary messages go through prompt_toolkit,
|
|
401
|
+
# which renders raw ANSI escapes as literal text.
|
|
402
|
+
# ← climbs to the parent (not "previous step" like elsewhere) and
|
|
403
|
+
# → enters the highlighted folder — mc-style navigation.
|
|
404
|
+
picked = select(
|
|
405
|
+
f"{message} [{current}]", choices, back=back, use_search=True,
|
|
406
|
+
on_left="up",
|
|
407
|
+
right_pick=lambda v: v == "up" or (isinstance(v, tuple) and v[0] == "cd"),
|
|
408
|
+
instruction="(type to filter, ↑↓ + Enter, ←/→ = parent/enter dir)")
|
|
409
|
+
if picked is BACK:
|
|
410
|
+
return BACK
|
|
411
|
+
if picked == "suggest":
|
|
412
|
+
return str(suggest_path)
|
|
413
|
+
if picked == "use":
|
|
414
|
+
return str(current)
|
|
415
|
+
if picked == "up":
|
|
416
|
+
current = current.parent
|
|
417
|
+
elif picked == "hidden":
|
|
418
|
+
show_hidden = not show_hidden
|
|
419
|
+
elif picked == "type":
|
|
420
|
+
typed = path_input(message, default=str(current),
|
|
421
|
+
must_exist=not allow_create,
|
|
422
|
+
offer_create=allow_create, back=True)
|
|
423
|
+
if typed is BACK:
|
|
424
|
+
continue # back into the browser, not out of it
|
|
425
|
+
return typed
|
|
426
|
+
elif picked == "mkdir":
|
|
427
|
+
name = text("New folder name", default="", back=True)
|
|
428
|
+
if name is BACK or not name:
|
|
429
|
+
continue
|
|
430
|
+
new_dir = current / name
|
|
431
|
+
try:
|
|
432
|
+
new_dir.mkdir(parents=True, exist_ok=True)
|
|
433
|
+
except OSError as e:
|
|
434
|
+
warn(f"Can't create {new_dir}: {e}")
|
|
435
|
+
continue
|
|
436
|
+
current = new_dir.resolve()
|
|
437
|
+
else:
|
|
438
|
+
current = picked[1]
|