alloy-runtime-cli 0.1.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.
- alloy_runtime_cli-0.1.0.dist-info/METADATA +61 -0
- alloy_runtime_cli-0.1.0.dist-info/RECORD +451 -0
- alloy_runtime_cli-0.1.0.dist-info/WHEEL +5 -0
- alloy_runtime_cli-0.1.0.dist-info/entry_points.txt +2 -0
- alloy_runtime_cli-0.1.0.dist-info/top_level.txt +1 -0
- cli/__init__.py +0 -0
- cli/commands/__init__.py +0 -0
- cli/commands/admin/__init__.py +0 -0
- cli/commands/admin/bootstrap_command.py +118 -0
- cli/commands/admin/credentials/__init__.py +0 -0
- cli/commands/admin/credentials/create/__init__.py +0 -0
- cli/commands/admin/credentials/create/command.py +148 -0
- cli/commands/admin/credentials/create/presenter.py +16 -0
- cli/commands/admin/credentials/grant/__init__.py +0 -0
- cli/commands/admin/credentials/grant/command.py +119 -0
- cli/commands/admin/credentials/grant/fields.py +33 -0
- cli/commands/admin/credentials/grant/presenter.py +23 -0
- cli/commands/agents/__init__.py +0 -0
- cli/commands/agents/create/__init__.py +0 -0
- cli/commands/agents/create/command.py +475 -0
- cli/commands/agents/create/fields.py +64 -0
- cli/commands/agents/create/presenter.py +68 -0
- cli/commands/agents/delete/__init__.py +0 -0
- cli/commands/agents/delete/command.py +47 -0
- cli/commands/agents/delete/presenter.py +16 -0
- cli/commands/agents/get/command.py +37 -0
- cli/commands/agents/get/presenter.py +32 -0
- cli/commands/agents/list/__init__.py +1 -0
- cli/commands/agents/list/command.py +54 -0
- cli/commands/agents/list/presenter.py +82 -0
- cli/commands/agents/update/__init__.py +0 -0
- cli/commands/agents/update/command.py +435 -0
- cli/commands/agents/update/fields.py +40 -0
- cli/commands/agents/update/presenter.py +68 -0
- cli/commands/audio/__init__.py +0 -0
- cli/commands/audio/transcribe/__init__.py +0 -0
- cli/commands/audio/transcribe/command.py +144 -0
- cli/commands/audio/transcribe/presenter.py +15 -0
- cli/commands/auth/__init__.py +0 -0
- cli/commands/auth/login/__init__.py +0 -0
- cli/commands/auth/login/command.py +80 -0
- cli/commands/auth/signup/__init__.py +0 -0
- cli/commands/auth/signup/command.py +115 -0
- cli/commands/billing/__init__.py +1 -0
- cli/commands/billing/costs/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/command.py +57 -0
- cli/commands/billing/costs/by_agent/presenter.py +81 -0
- cli/commands/billing/costs/by_model/__init__.py +1 -0
- cli/commands/billing/costs/by_model/command.py +57 -0
- cli/commands/billing/costs/by_model/presenter.py +80 -0
- cli/commands/billing/costs/daily/__init__.py +1 -0
- cli/commands/billing/costs/daily/command.py +55 -0
- cli/commands/billing/costs/daily/presenter.py +75 -0
- cli/commands/billing/costs/summary/__init__.py +1 -0
- cli/commands/billing/costs/summary/command.py +57 -0
- cli/commands/billing/costs/summary/presenter.py +42 -0
- cli/commands/billing/projects/__init__.py +1 -0
- cli/commands/billing/projects/create/__init__.py +1 -0
- cli/commands/billing/projects/create/command.py +60 -0
- cli/commands/billing/projects/create/presenter.py +26 -0
- cli/commands/billing/projects/get/__init__.py +1 -0
- cli/commands/billing/projects/get/command.py +33 -0
- cli/commands/billing/projects/get/presenter.py +32 -0
- cli/commands/billing/projects/list/__init__.py +1 -0
- cli/commands/billing/projects/list/command.py +40 -0
- cli/commands/billing/projects/list/presenter.py +57 -0
- cli/commands/content/__init__.py +1 -0
- cli/commands/content/delete/__init__.py +0 -0
- cli/commands/content/delete/command.py +49 -0
- cli/commands/content/delete/presenter.py +18 -0
- cli/commands/content/edit/__init__.py +1 -0
- cli/commands/content/edit/command.py +155 -0
- cli/commands/content/edit/editor.py +150 -0
- cli/commands/content/edit/presenter.py +146 -0
- cli/commands/content/get/__init__.py +1 -0
- cli/commands/content/get/command.py +39 -0
- cli/commands/content/get/presenter.py +176 -0
- cli/commands/content/list/__init__.py +1 -0
- cli/commands/content/list/command.py +347 -0
- cli/commands/content/list/export_formatters.py +409 -0
- cli/commands/content/list/export_handler.py +165 -0
- cli/commands/content/list/presenter.py +190 -0
- cli/commands/credentials/__init__.py +0 -0
- cli/commands/credentials/create/__init__.py +0 -0
- cli/commands/credentials/create/command.py +165 -0
- cli/commands/credentials/create/fields.py +38 -0
- cli/commands/credentials/create/presenter.py +20 -0
- cli/commands/credentials/update/__init__.py +0 -0
- cli/commands/credentials/update/command.py +53 -0
- cli/commands/credentials/update/fields.py +71 -0
- cli/commands/credentials/update/presenter.py +16 -0
- cli/commands/flag_utils.py +366 -0
- cli/commands/generate/__init__.py +0 -0
- cli/commands/generate/cancel/__init__.py +1 -0
- cli/commands/generate/cancel/command.py +44 -0
- cli/commands/generate/cancel/presenter.py +26 -0
- cli/commands/generate/status/__init__.py +1 -0
- cli/commands/generate/status/command.py +58 -0
- cli/commands/generate/status/presenter.py +78 -0
- cli/commands/generate/text/__init__.py +0 -0
- cli/commands/generate/text/command.py +1325 -0
- cli/commands/generate/text/concurrent_renderer.py +355 -0
- cli/commands/generate/text/presenter.py +287 -0
- cli/commands/generate/text/stream_renderer.py +129 -0
- cli/commands/knowledge/__init__.py +0 -0
- cli/commands/knowledge/collections/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/command.py +64 -0
- cli/commands/knowledge/collections/cluster/presenter.py +74 -0
- cli/commands/knowledge/collections/cluster_status/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster_status/command.py +46 -0
- cli/commands/knowledge/collections/cluster_status/presenter.py +10 -0
- cli/commands/knowledge/collections/create/__init__.py +0 -0
- cli/commands/knowledge/collections/create/command.py +137 -0
- cli/commands/knowledge/collections/create/presenter.py +38 -0
- cli/commands/knowledge/collections/delete/__init__.py +1 -0
- cli/commands/knowledge/collections/delete/command.py +47 -0
- cli/commands/knowledge/collections/delete/presenter.py +20 -0
- cli/commands/knowledge/collections/get/__init__.py +1 -0
- cli/commands/knowledge/collections/get/command.py +30 -0
- cli/commands/knowledge/collections/get/presenter.py +44 -0
- cli/commands/knowledge/collections/list/__init__.py +1 -0
- cli/commands/knowledge/collections/list/command.py +41 -0
- cli/commands/knowledge/collections/list/presenter.py +68 -0
- cli/commands/knowledge/collections/update/__init__.py +0 -0
- cli/commands/knowledge/collections/update/command.py +97 -0
- cli/commands/knowledge/collections/update/presenter.py +42 -0
- cli/commands/knowledge/documents/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/command.py +119 -0
- cli/commands/knowledge/documents/bulk_metadata/presenter.py +36 -0
- cli/commands/knowledge/documents/delete/__init__.py +0 -0
- cli/commands/knowledge/documents/delete/command.py +47 -0
- cli/commands/knowledge/documents/delete/presenter.py +20 -0
- cli/commands/knowledge/documents/get/__init__.py +0 -0
- cli/commands/knowledge/documents/get/command.py +39 -0
- cli/commands/knowledge/documents/get/presenter.py +78 -0
- cli/commands/knowledge/documents/ingest/__init__.py +0 -0
- cli/commands/knowledge/documents/ingest/command.py +222 -0
- cli/commands/knowledge/documents/ingest/presenter.py +41 -0
- cli/commands/knowledge/documents/list/__init__.py +0 -0
- cli/commands/knowledge/documents/list/command.py +69 -0
- cli/commands/knowledge/documents/list/presenter.py +86 -0
- cli/commands/knowledge/documents/reingest/__init__.py +0 -0
- cli/commands/knowledge/documents/reingest/command.py +102 -0
- cli/commands/knowledge/documents/reingest/presenter.py +70 -0
- cli/commands/knowledge/documents/update/__init__.py +0 -0
- cli/commands/knowledge/documents/update/command.py +85 -0
- cli/commands/knowledge/documents/update/presenter.py +37 -0
- cli/commands/knowledge/recover/__init__.py +0 -0
- cli/commands/knowledge/recover/command.py +46 -0
- cli/commands/knowledge/recover/presenter.py +79 -0
- cli/commands/knowledge/search/__init__.py +0 -0
- cli/commands/knowledge/search/command.py +218 -0
- cli/commands/knowledge/search/presenter.py +111 -0
- cli/commands/knowledge/synthesis/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/command.py +127 -0
- cli/commands/knowledge/synthesis/create/presenter.py +33 -0
- cli/commands/knowledge/synthesis/delete/__init__.py +0 -0
- cli/commands/knowledge/synthesis/delete/command.py +53 -0
- cli/commands/knowledge/synthesis/delete/presenter.py +31 -0
- cli/commands/knowledge/synthesis/get/__init__.py +0 -0
- cli/commands/knowledge/synthesis/get/command.py +55 -0
- cli/commands/knowledge/synthesis/get/presenter.py +114 -0
- cli/commands/knowledge/synthesis/list/__init__.py +0 -0
- cli/commands/knowledge/synthesis/list/command.py +132 -0
- cli/commands/knowledge/synthesis/list/presenter.py +84 -0
- cli/commands/knowledge/synthesis/refresh/__init__.py +0 -0
- cli/commands/knowledge/synthesis/refresh/command.py +42 -0
- cli/commands/knowledge/synthesis/refresh/presenter.py +33 -0
- cli/commands/knowledge/synthesis/update/__init__.py +0 -0
- cli/commands/knowledge/synthesis/update/command.py +76 -0
- cli/commands/knowledge/synthesis/update/presenter.py +41 -0
- cli/commands/models/__init__.py +0 -0
- cli/commands/models/list/__init__.py +0 -0
- cli/commands/models/list/command.py +84 -0
- cli/commands/models/list/presenter.py +114 -0
- cli/commands/organizations/__init__.py +0 -0
- cli/commands/organizations/create/command.py +32 -0
- cli/commands/organizations/create/presenter.py +9 -0
- cli/commands/pipelines/__init__.py +1 -0
- cli/commands/pipelines/approvals/__init__.py +1 -0
- cli/commands/pipelines/approvals/decide_command.py +77 -0
- cli/commands/pipelines/approvals/get_command.py +44 -0
- cli/commands/pipelines/approvals/presenter.py +56 -0
- cli/commands/pipelines/costs/__init__.py +1 -0
- cli/commands/pipelines/costs/command.py +57 -0
- cli/commands/pipelines/costs/daily_command.py +54 -0
- cli/commands/pipelines/costs/daily_presenter.py +59 -0
- cli/commands/pipelines/costs/presenter.py +37 -0
- cli/commands/pipelines/create/__init__.py +1 -0
- cli/commands/pipelines/create/command.py +103 -0
- cli/commands/pipelines/create/presenter.py +22 -0
- cli/commands/pipelines/env_vars/__init__.py +1 -0
- cli/commands/pipelines/env_vars/command.py +51 -0
- cli/commands/pipelines/env_vars/presenter.py +16 -0
- cli/commands/pipelines/execute/__init__.py +1 -0
- cli/commands/pipelines/execute/command.py +142 -0
- cli/commands/pipelines/execute/presenter.py +47 -0
- cli/commands/pipelines/executions/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/command.py +48 -0
- cli/commands/pipelines/executions/costs/presenter.py +29 -0
- cli/commands/pipelines/executions/costs_by_model/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_model/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_model/presenter.py +78 -0
- cli/commands/pipelines/executions/costs_by_step/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_step/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_step/presenter.py +72 -0
- cli/commands/pipelines/executions/get_command.py +38 -0
- cli/commands/pipelines/executions/list_command.py +123 -0
- cli/commands/pipelines/executions/presenter.py +131 -0
- cli/commands/pipelines/executions/rerun_command.py +41 -0
- cli/commands/pipelines/executions/update/__init__.py +1 -0
- cli/commands/pipelines/executions/update/command.py +110 -0
- cli/commands/pipelines/executions/update/presenter.py +28 -0
- cli/commands/pipelines/get/__init__.py +1 -0
- cli/commands/pipelines/get/command.py +33 -0
- cli/commands/pipelines/get/presenter.py +48 -0
- cli/commands/pipelines/list/__init__.py +1 -0
- cli/commands/pipelines/list/command.py +53 -0
- cli/commands/pipelines/list/presenter.py +66 -0
- cli/commands/pipelines/schedules/__init__.py +1 -0
- cli/commands/pipelines/schedules/create_command.py +119 -0
- cli/commands/pipelines/schedules/create_presenter.py +35 -0
- cli/commands/pipelines/schedules/delete_command.py +52 -0
- cli/commands/pipelines/schedules/env_vars_command.py +59 -0
- cli/commands/pipelines/schedules/env_vars_presenter.py +16 -0
- cli/commands/pipelines/schedules/get_command.py +38 -0
- cli/commands/pipelines/schedules/list_command.py +33 -0
- cli/commands/pipelines/schedules/once_command.py +90 -0
- cli/commands/pipelines/schedules/once_presenter.py +30 -0
- cli/commands/pipelines/schedules/presenter.py +104 -0
- cli/commands/pipelines/schedules/update_command.py +139 -0
- cli/commands/pipelines/schedules/update_presenter.py +29 -0
- cli/commands/render/__init__.py +0 -0
- cli/commands/render/html_to_image/__init__.py +0 -0
- cli/commands/render/html_to_image/command.py +170 -0
- cli/commands/schemas/__init__.py +0 -0
- cli/commands/schemas/create/__init__.py +0 -0
- cli/commands/schemas/create/command.py +122 -0
- cli/commands/schemas/create/presenter.py +53 -0
- cli/commands/schemas/delete/command.py +45 -0
- cli/commands/schemas/delete/presenter.py +9 -0
- cli/commands/schemas/get/__init__.py +0 -0
- cli/commands/schemas/get/command.py +56 -0
- cli/commands/schemas/get/presenter.py +129 -0
- cli/commands/schemas/list/__init__.py +0 -0
- cli/commands/schemas/list/command.py +64 -0
- cli/commands/schemas/list/presenter.py +133 -0
- cli/commands/schemas/update/__init__.py +0 -0
- cli/commands/schemas/update/command.py +369 -0
- cli/commands/schemas/update/presenter.py +53 -0
- cli/commands/sessions/__init__.py +1 -0
- cli/commands/sessions/delete/__init__.py +1 -0
- cli/commands/sessions/delete/command.py +47 -0
- cli/commands/sessions/delete/presenter.py +10 -0
- cli/commands/sessions/get/__init__.py +1 -0
- cli/commands/sessions/get/command.py +42 -0
- cli/commands/sessions/get/presenter.py +59 -0
- cli/commands/sessions/list/__init__.py +1 -0
- cli/commands/sessions/list/command.py +61 -0
- cli/commands/sessions/list/presenter.py +68 -0
- cli/commands/sessions/messages/__init__.py +1 -0
- cli/commands/sessions/messages/command.py +78 -0
- cli/commands/sessions/messages/presenter.py +79 -0
- cli/commands/shared_flags.py +500 -0
- cli/commands/sync/__init__.py +0 -0
- cli/commands/sync/command.py +45 -0
- cli/commands/sync/presenter.py +49 -0
- cli/commands/tags/__init__.py +1 -0
- cli/commands/tags/create/__init__.py +1 -0
- cli/commands/tags/create/command.py +60 -0
- cli/commands/tags/delete/__init__.py +1 -0
- cli/commands/tags/delete/command.py +47 -0
- cli/commands/tags/delete/presenter.py +10 -0
- cli/commands/tags/get/command.py +31 -0
- cli/commands/tags/get/presenter.py +23 -0
- cli/commands/tags/list/__init__.py +1 -0
- cli/commands/tags/list/command.py +52 -0
- cli/commands/tags/list/presenter.py +49 -0
- cli/commands/tags/update/command.py +64 -0
- cli/commands/tags/update/presenter.py +9 -0
- cli/commands/templates/__init__.py +0 -0
- cli/commands/templates/create/__init__.py +0 -0
- cli/commands/templates/create/command.py +152 -0
- cli/commands/templates/create/presenter.py +86 -0
- cli/commands/templates/delete/__init__.py +0 -0
- cli/commands/templates/delete/command.py +47 -0
- cli/commands/templates/delete/presenter.py +16 -0
- cli/commands/templates/get/__init__.py +0 -0
- cli/commands/templates/get/command.py +52 -0
- cli/commands/templates/get/presenter.py +233 -0
- cli/commands/templates/get_by_version/command.py +32 -0
- cli/commands/templates/get_by_version/presenter.py +30 -0
- cli/commands/templates/list/__init__.py +1 -0
- cli/commands/templates/list/command.py +102 -0
- cli/commands/templates/list/presenter.py +93 -0
- cli/commands/templates/render/__init__.py +0 -0
- cli/commands/templates/render/command.py +115 -0
- cli/commands/templates/render/presenter.py +276 -0
- cli/commands/templates/update/__init__.py +0 -0
- cli/commands/templates/update/command.py +199 -0
- cli/commands/templates/update/presenter.py +94 -0
- cli/commands/templates/version/__init__.py +1 -0
- cli/commands/templates/version/command.py +116 -0
- cli/commands/templates/version/presenter.py +100 -0
- cli/commands/tool_configs/__init__.py +0 -0
- cli/commands/tool_configs/create/__init__.py +0 -0
- cli/commands/tool_configs/create/command.py +118 -0
- cli/commands/tool_configs/create/presenter.py +53 -0
- cli/commands/tool_configs/delete/__init__.py +0 -0
- cli/commands/tool_configs/delete/command.py +47 -0
- cli/commands/tool_configs/delete/presenter.py +18 -0
- cli/commands/tool_configs/get/__init__.py +0 -0
- cli/commands/tool_configs/get/command.py +31 -0
- cli/commands/tool_configs/get/presenter.py +62 -0
- cli/commands/tool_configs/list/__init__.py +0 -0
- cli/commands/tool_configs/list/command.py +59 -0
- cli/commands/tool_configs/list/presenter.py +60 -0
- cli/commands/tool_configs/update/__init__.py +0 -0
- cli/commands/tool_configs/update/command.py +128 -0
- cli/commands/tool_configs/update/presenter.py +53 -0
- cli/commands/tools/__init__.py +1 -0
- cli/commands/tools/get/__init__.py +1 -0
- cli/commands/tools/get/command.py +42 -0
- cli/commands/tools/get/presenter.py +45 -0
- cli/commands/tools/list/__init__.py +1 -0
- cli/commands/tools/list/command.py +56 -0
- cli/commands/tools/list/presenter.py +44 -0
- cli/commands/users/__init__.py +0 -0
- cli/commands/users/create/command.py +53 -0
- cli/commands/users/create/presenter.py +9 -0
- cli/commands/whoami/__init__.py +0 -0
- cli/commands/whoami/command.py +42 -0
- cli/infrastructure/__init__.py +0 -0
- cli/infrastructure/auth_storage.py +71 -0
- cli/infrastructure/client_factory.py +36 -0
- cli/infrastructure/command.py +75 -0
- cli/infrastructure/config.py +188 -0
- cli/infrastructure/console.py +27 -0
- cli/infrastructure/editor.py +138 -0
- cli/infrastructure/error_display.py +178 -0
- cli/infrastructure/field_extractor.py +360 -0
- cli/infrastructure/file_content.py +210 -0
- cli/infrastructure/filter_parser.py +256 -0
- cli/infrastructure/formatters/__init__.py +0 -0
- cli/infrastructure/formatters/base.py +99 -0
- cli/infrastructure/formatters/compact_formatter.py +245 -0
- cli/infrastructure/formatters/json_formatter.py +84 -0
- cli/infrastructure/formatters/lines_formatter.py +102 -0
- cli/infrastructure/formatting/__init__.py +0 -0
- cli/infrastructure/formatting/fields.py +193 -0
- cli/infrastructure/forms/__init__.py +0 -0
- cli/infrastructure/forms/agent_picker.py +123 -0
- cli/infrastructure/forms/agent_tool_editor.py +384 -0
- cli/infrastructure/forms/agent_tools_manager.py +212 -0
- cli/infrastructure/forms/base_picker.py +469 -0
- cli/infrastructure/forms/components.py +126 -0
- cli/infrastructure/forms/json_schema_builder.py +149 -0
- cli/infrastructure/forms/model_picker.py +134 -0
- cli/infrastructure/forms/parsers.py +173 -0
- cli/infrastructure/forms/resolution_modal.py +302 -0
- cli/infrastructure/forms/schema_picker.py +137 -0
- cli/infrastructure/forms/tag_management_modal.py +103 -0
- cli/infrastructure/forms/tag_picker.py +207 -0
- cli/infrastructure/forms/template_picker.py +131 -0
- cli/infrastructure/forms/tool_config_picker.py +130 -0
- cli/infrastructure/forms/tool_picker.py +103 -0
- cli/infrastructure/injection/__init__.py +0 -0
- cli/infrastructure/injection/parser.py +302 -0
- cli/infrastructure/injection/resolver.py +399 -0
- cli/infrastructure/kv_parser.py +130 -0
- cli/infrastructure/local_storage.py +227 -0
- cli/infrastructure/macro_parser.py +215 -0
- cli/infrastructure/output.py +192 -0
- cli/infrastructure/provider_setup.py +81 -0
- cli/infrastructure/renderers/__init__.py +0 -0
- cli/infrastructure/renderers/entity_renderer.py +77 -0
- cli/infrastructure/renderers/list_renderer.py +114 -0
- cli/infrastructure/scope_utils.py +47 -0
- cli/infrastructure/spinner.py +101 -0
- cli/infrastructure/tui/__init__.py +0 -0
- cli/infrastructure/tui/clipboard.py +41 -0
- cli/infrastructure/tui/formatters.py +105 -0
- cli/infrastructure/tui/preview.py +14 -0
- cli/infrastructure/tui/selectable.py +198 -0
- cli/infrastructure/validation/__init__.py +0 -0
- cli/infrastructure/validation/tag_validation.py +74 -0
- cli/main.py +759 -0
- cli/tui/__init__.py +0 -0
- cli/tui/app.py +199 -0
- cli/tui/app_store.py +73 -0
- cli/tui/chat/__init__.py +0 -0
- cli/tui/chat/commands/__init__.py +0 -0
- cli/tui/chat/commands/base.py +65 -0
- cli/tui/chat/commands/create_session.py +135 -0
- cli/tui/chat/commands/load_session.py +119 -0
- cli/tui/chat/commands/regenerate.py +120 -0
- cli/tui/chat/commands/reload_session.py +63 -0
- cli/tui/chat/commands/send_message.py +190 -0
- cli/tui/chat/commands/undo.py +66 -0
- cli/tui/chat/editor.py +71 -0
- cli/tui/chat/messages.py +223 -0
- cli/tui/chat/pane.py +141 -0
- cli/tui/chat/renderers/__init__.py +0 -0
- cli/tui/chat/renderers/base.py +72 -0
- cli/tui/chat/renderers/markdown.py +250 -0
- cli/tui/chat/renderers/plain.py +83 -0
- cli/tui/chat/screen.py +1155 -0
- cli/tui/chat/services/__init__.py +0 -0
- cli/tui/chat/services/injection.py +386 -0
- cli/tui/chat/services/name_generator.py +256 -0
- cli/tui/chat/slash_commands.py +424 -0
- cli/tui/chat/store.py +280 -0
- cli/tui/chat/types.py +220 -0
- cli/tui/chat/widgets/__init__.py +0 -0
- cli/tui/chat/widgets/chat_header.py +75 -0
- cli/tui/chat/widgets/chat_input.py +362 -0
- cli/tui/chat/widgets/injection_popup.py +161 -0
- cli/tui/chat/widgets/message_display.py +287 -0
- cli/tui/chat/widgets/session_sidebar.py +214 -0
- cli/tui/chat/widgets/welcome_screen.py +290 -0
- cli/tui/screens/__init__.py +0 -0
- cli/tui/screens/agents.py +344 -0
- cli/tui/screens/base.py +301 -0
- cli/tui/screens/content.py +508 -0
- cli/tui/screens/dashboard.py +89 -0
- cli/tui/screens/models.py +96 -0
- cli/tui/screens/nav_screen.py +186 -0
- cli/tui/screens/schemas.py +522 -0
- cli/tui/screens/templates.py +734 -0
- cli/tui/screens/tool_configs.py +335 -0
- cli/tui/styles/__init__.py +0 -0
- cli/tui/widgets/__init__.py +0 -0
- cli/tui/widgets/agent_create_modal.py +139 -0
- cli/tui/widgets/agent_form_modal.py +659 -0
- cli/tui/widgets/agent_update_modal.py +299 -0
- cli/tui/widgets/base_form_modal.py +77 -0
- cli/tui/widgets/confirm_modal.py +75 -0
- cli/tui/widgets/help_modal.py +145 -0
- cli/tui/widgets/new_session_modal.py +328 -0
- cli/tui/widgets/schema_create_modal.py +271 -0
- cli/tui/widgets/schema_update_modal.py +188 -0
- cli/tui/widgets/status_footer.py +147 -0
- cli/tui/widgets/template_create_modal.py +502 -0
- cli/tui/widgets/template_update_modal.py +308 -0
- cli/tui/widgets/tool_config_create_modal.py +216 -0
- cli/tui/widgets/tool_config_update_modal.py +208 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""Base search picker classes with common patterns.
|
|
2
|
+
|
|
3
|
+
Provides abstract base classes for building search pickers that fetch data
|
|
4
|
+
from the server API on each keystroke. Two variants are provided:
|
|
5
|
+
|
|
6
|
+
- SingleSelectPicker: For selecting a single item (agents, schemas, templates)
|
|
7
|
+
- MultiSelectPicker: For selecting multiple items (models, tags)
|
|
8
|
+
|
|
9
|
+
Both handle the common patterns of:
|
|
10
|
+
- Input field with search
|
|
11
|
+
- OptionList displaying results
|
|
12
|
+
- Status line showing search state
|
|
13
|
+
- Selected display showing current selection(s)
|
|
14
|
+
- Debounced API calls via @work(exclusive=True)
|
|
15
|
+
- Error handling for API failures
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from abc import abstractmethod
|
|
19
|
+
from typing import Any, Generic, TypeVar
|
|
20
|
+
|
|
21
|
+
from textual import on, work
|
|
22
|
+
from textual.app import ComposeResult
|
|
23
|
+
from textual.containers import Vertical
|
|
24
|
+
from textual.message import Message
|
|
25
|
+
from textual.widget import Widget
|
|
26
|
+
from textual.widgets import Input, OptionList, Static
|
|
27
|
+
from textual.widgets.option_list import Option
|
|
28
|
+
|
|
29
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
30
|
+
from alloy_runtime_sdk.exceptions.errors import RequestError
|
|
31
|
+
|
|
32
|
+
# Type variable for the item type stored in search results
|
|
33
|
+
TItem = TypeVar("TItem")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseSearchPicker(Widget, Generic[TItem]):
|
|
37
|
+
"""Abstract base class for search pickers.
|
|
38
|
+
|
|
39
|
+
Subclasses must implement:
|
|
40
|
+
- _fetch_items(query): Fetch items from API
|
|
41
|
+
- _format_item(item): Format item for display in option list
|
|
42
|
+
- _get_item_id(item): Get unique identifier for an item
|
|
43
|
+
- _is_item_selected(item): Check if item is currently selected
|
|
44
|
+
- _handle_item_selection(index): Handle selection of item at index
|
|
45
|
+
- _format_selection_display(): Format current selection for display
|
|
46
|
+
- _get_result_count_message(count): Get status message for result count
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# All pickers use the shared .picker CSS class from forms.tcss
|
|
50
|
+
DEFAULT_CLASSES = "picker"
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
client: ApiClient,
|
|
55
|
+
placeholder: str = "Type to search...",
|
|
56
|
+
empty_message: str = "No selection",
|
|
57
|
+
fetch_on_mount: bool = False,
|
|
58
|
+
**kwargs: Any,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Initialize the picker.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
client: ApiClient instance for API calls
|
|
64
|
+
placeholder: Placeholder text for search input
|
|
65
|
+
empty_message: Message shown when nothing is selected
|
|
66
|
+
fetch_on_mount: If True, fetch initial results on mount (e.g., recent items)
|
|
67
|
+
"""
|
|
68
|
+
super().__init__(**kwargs)
|
|
69
|
+
self._client = client
|
|
70
|
+
self._placeholder = placeholder
|
|
71
|
+
self._empty_message = empty_message
|
|
72
|
+
self._search_results: list[TItem] = []
|
|
73
|
+
self._last_query: str = ""
|
|
74
|
+
self._fetch_on_mount = fetch_on_mount
|
|
75
|
+
|
|
76
|
+
def compose(self) -> ComposeResult:
|
|
77
|
+
with Vertical():
|
|
78
|
+
yield Input(placeholder=self._placeholder, id="search-input")
|
|
79
|
+
yield OptionList(id="search-options")
|
|
80
|
+
yield Static("Type to search...", classes="status-line", id="status")
|
|
81
|
+
yield Static(self._empty_message, classes="selected-display", id="selected")
|
|
82
|
+
|
|
83
|
+
def on_mount(self) -> None:
|
|
84
|
+
"""Optionally fetch initial results on mount."""
|
|
85
|
+
if self._fetch_on_mount:
|
|
86
|
+
self._fetch_initial()
|
|
87
|
+
|
|
88
|
+
@on(Input.Changed, "#search-input")
|
|
89
|
+
def on_search_changed(self, event: Input.Changed) -> None:
|
|
90
|
+
"""Fetch results from API on each keystroke."""
|
|
91
|
+
query = event.value.strip()
|
|
92
|
+
if query != self._last_query:
|
|
93
|
+
self._last_query = query
|
|
94
|
+
self._search(query)
|
|
95
|
+
|
|
96
|
+
@work(exclusive=True, group="picker_search")
|
|
97
|
+
async def _fetch_initial(self) -> None:
|
|
98
|
+
"""Fetch initial results on mount (e.g., recent items)."""
|
|
99
|
+
status = self.query_one("#status", Static)
|
|
100
|
+
status.update("Loading...")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
self._search_results = await self._fetch_items("")
|
|
104
|
+
self._update_options()
|
|
105
|
+
|
|
106
|
+
count = len(self._search_results)
|
|
107
|
+
if count > 0:
|
|
108
|
+
status.update(f"{count} recent - type to filter")
|
|
109
|
+
else:
|
|
110
|
+
status.update("Type to search...")
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
status.update(f"[red]Error: {e}[/]")
|
|
114
|
+
self._search_results = []
|
|
115
|
+
|
|
116
|
+
@work(exclusive=True, group="picker_search")
|
|
117
|
+
async def _search(self, query: str) -> None:
|
|
118
|
+
"""Fetch items matching query from the API."""
|
|
119
|
+
status = self.query_one("#status", Static)
|
|
120
|
+
options = self.query_one("#search-options", OptionList)
|
|
121
|
+
|
|
122
|
+
if not query:
|
|
123
|
+
if self._fetch_on_mount:
|
|
124
|
+
# Re-fetch initial results when query is cleared
|
|
125
|
+
await self._do_search("")
|
|
126
|
+
return
|
|
127
|
+
else:
|
|
128
|
+
status.update("Type to search...")
|
|
129
|
+
options.clear_options()
|
|
130
|
+
self._search_results = []
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
await self._do_search(query)
|
|
134
|
+
|
|
135
|
+
async def _do_search(self, query: str) -> None:
|
|
136
|
+
"""Perform the actual search."""
|
|
137
|
+
status = self.query_one("#status", Static)
|
|
138
|
+
options = self.query_one("#search-options", OptionList)
|
|
139
|
+
|
|
140
|
+
status.update("Searching...")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
self._search_results = await self._fetch_items(query)
|
|
144
|
+
self._update_options()
|
|
145
|
+
|
|
146
|
+
count = len(self._search_results)
|
|
147
|
+
if query:
|
|
148
|
+
status.update(self._get_result_count_message(count))
|
|
149
|
+
elif count > 0:
|
|
150
|
+
status.update(f"{count} recent - type to filter")
|
|
151
|
+
else:
|
|
152
|
+
status.update("Type to search...")
|
|
153
|
+
|
|
154
|
+
except RequestError as e:
|
|
155
|
+
status.update(f"[red]Error: {e}[/]")
|
|
156
|
+
self._search_results = []
|
|
157
|
+
options.clear_options()
|
|
158
|
+
except Exception as e:
|
|
159
|
+
status.update(f"[red]Error: {e}[/]")
|
|
160
|
+
self._search_results = []
|
|
161
|
+
options.clear_options()
|
|
162
|
+
|
|
163
|
+
def _update_options(self) -> None:
|
|
164
|
+
"""Update the option list with current search results."""
|
|
165
|
+
options = self.query_one("#search-options", OptionList)
|
|
166
|
+
options.clear_options()
|
|
167
|
+
|
|
168
|
+
for i, item in enumerate(self._search_results):
|
|
169
|
+
display_text = self._format_item(item)
|
|
170
|
+
if self._is_item_selected(item):
|
|
171
|
+
display_text = f"[green]✓[/] {display_text}"
|
|
172
|
+
else:
|
|
173
|
+
display_text = f" {display_text}"
|
|
174
|
+
options.add_option(Option(display_text, id=f"opt-{i}"))
|
|
175
|
+
|
|
176
|
+
if self._search_results:
|
|
177
|
+
options.highlighted = 0
|
|
178
|
+
|
|
179
|
+
def _update_selected_display(self) -> None:
|
|
180
|
+
"""Update the display of selected item(s)."""
|
|
181
|
+
display = self.query_one("#selected", Static)
|
|
182
|
+
selection_text = self._format_selection_display()
|
|
183
|
+
if selection_text:
|
|
184
|
+
display.update(selection_text)
|
|
185
|
+
display.add_class("has-selection")
|
|
186
|
+
else:
|
|
187
|
+
display.update(self._empty_message)
|
|
188
|
+
display.remove_class("has-selection")
|
|
189
|
+
|
|
190
|
+
@on(OptionList.OptionSelected, "#search-options")
|
|
191
|
+
def on_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
192
|
+
"""Handle option selection via click or enter."""
|
|
193
|
+
self._handle_item_selection(event.option_index)
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
async def _fetch_items(self, query: str) -> list[TItem]:
|
|
197
|
+
"""Fetch items from API matching query.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
query: Search query string
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of items matching the query
|
|
204
|
+
"""
|
|
205
|
+
...
|
|
206
|
+
|
|
207
|
+
@abstractmethod
|
|
208
|
+
def _format_item(self, item: TItem) -> str:
|
|
209
|
+
"""Format item for display in option list.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
item: Item to format
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Formatted string for display
|
|
216
|
+
"""
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
@abstractmethod
|
|
220
|
+
def _get_item_id(self, item: TItem) -> str:
|
|
221
|
+
"""Get unique identifier for an item.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
item: Item to get ID for
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Unique identifier string
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def _is_item_selected(self, item: TItem) -> bool:
|
|
233
|
+
"""Check if an item is currently selected.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
item: Item to check
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
True if item is selected
|
|
240
|
+
"""
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
@abstractmethod
|
|
244
|
+
def _handle_item_selection(self, index: int) -> None:
|
|
245
|
+
"""Handle selection of item at the given index.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
index: Index of item in search results
|
|
249
|
+
"""
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
@abstractmethod
|
|
253
|
+
def _format_selection_display(self) -> str | None:
|
|
254
|
+
"""Format current selection for display.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Formatted string for display, or None if nothing selected
|
|
258
|
+
"""
|
|
259
|
+
...
|
|
260
|
+
|
|
261
|
+
def _get_result_count_message(self, count: int) -> str:
|
|
262
|
+
"""Get status message for result count.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
count: Number of results found
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Status message string
|
|
269
|
+
"""
|
|
270
|
+
if count == 0:
|
|
271
|
+
return "No results found"
|
|
272
|
+
return f"{count} result{'s' if count != 1 else ''} - Enter to select"
|
|
273
|
+
|
|
274
|
+
@abstractmethod
|
|
275
|
+
def clear(self) -> None:
|
|
276
|
+
"""Clear the current selection."""
|
|
277
|
+
...
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class SingleSelectPicker(BaseSearchPicker[TItem], Generic[TItem]):
|
|
281
|
+
"""Base class for single-selection pickers.
|
|
282
|
+
|
|
283
|
+
Handles the common pattern of selecting one item from search results
|
|
284
|
+
with toggle behavior (selecting same item deselects it).
|
|
285
|
+
|
|
286
|
+
Subclasses must implement:
|
|
287
|
+
- _fetch_items(query): Fetch items from API
|
|
288
|
+
- _format_item(item): Format item for display
|
|
289
|
+
- _get_item_id(item): Get unique identifier for item
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
class SelectionChanged(Message):
|
|
293
|
+
"""Posted when selection changes."""
|
|
294
|
+
|
|
295
|
+
def __init__(self, selected_id: str | None, selected_item: Any | None) -> None:
|
|
296
|
+
self.selected_id = selected_id
|
|
297
|
+
self.selected_item = selected_item
|
|
298
|
+
super().__init__()
|
|
299
|
+
|
|
300
|
+
def __init__(
|
|
301
|
+
self,
|
|
302
|
+
client: ApiClient,
|
|
303
|
+
placeholder: str = "Type to search...",
|
|
304
|
+
empty_message: str = "No selection",
|
|
305
|
+
**kwargs: Any,
|
|
306
|
+
) -> None:
|
|
307
|
+
super().__init__(
|
|
308
|
+
client=client,
|
|
309
|
+
placeholder=placeholder,
|
|
310
|
+
empty_message=empty_message,
|
|
311
|
+
**kwargs,
|
|
312
|
+
)
|
|
313
|
+
self._selected_item: TItem | None = None
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def selected_id(self) -> str | None:
|
|
317
|
+
"""Get the ID of the selected item, or None if nothing selected."""
|
|
318
|
+
if self._selected_item is None:
|
|
319
|
+
return None
|
|
320
|
+
return self._get_item_id(self._selected_item)
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def selected_item(self) -> TItem | None:
|
|
324
|
+
"""Get the full selected item, or None if nothing selected."""
|
|
325
|
+
return self._selected_item
|
|
326
|
+
|
|
327
|
+
def _is_item_selected(self, item: TItem) -> bool:
|
|
328
|
+
"""Check if an item is currently selected."""
|
|
329
|
+
if self._selected_item is None:
|
|
330
|
+
return False
|
|
331
|
+
return self._get_item_id(item) == self._get_item_id(self._selected_item)
|
|
332
|
+
|
|
333
|
+
def _handle_item_selection(self, index: int) -> None:
|
|
334
|
+
"""Handle selection of item at the given index with toggle behavior."""
|
|
335
|
+
if index < 0 or index >= len(self._search_results):
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
item = self._search_results[index]
|
|
339
|
+
|
|
340
|
+
# Toggle selection if clicking the same item
|
|
341
|
+
if self._selected_item is not None and self._get_item_id(
|
|
342
|
+
item
|
|
343
|
+
) == self._get_item_id(self._selected_item):
|
|
344
|
+
self._selected_item = None
|
|
345
|
+
else:
|
|
346
|
+
self._selected_item = item
|
|
347
|
+
|
|
348
|
+
self._update_selected_display()
|
|
349
|
+
self._update_options()
|
|
350
|
+
self.post_message(self.SelectionChanged(self.selected_id, self._selected_item))
|
|
351
|
+
|
|
352
|
+
def _format_selection_display(self) -> str | None:
|
|
353
|
+
"""Format current selection for display."""
|
|
354
|
+
if self._selected_item is None:
|
|
355
|
+
return None
|
|
356
|
+
display_text = self._format_item(self._selected_item)
|
|
357
|
+
return f"[bold green]Selected:[/] {display_text}"
|
|
358
|
+
|
|
359
|
+
def clear(self) -> None:
|
|
360
|
+
"""Clear the selection."""
|
|
361
|
+
self._selected_item = None
|
|
362
|
+
self._update_selected_display()
|
|
363
|
+
self.post_message(self.SelectionChanged(None, None))
|
|
364
|
+
|
|
365
|
+
def set_initial_selection(self, item: TItem) -> None:
|
|
366
|
+
"""Set initial selection without triggering search.
|
|
367
|
+
|
|
368
|
+
Use this to pre-populate the picker with an existing value.
|
|
369
|
+
Should be called after mount.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
item: Item to set as initial selection
|
|
373
|
+
"""
|
|
374
|
+
self._selected_item = item
|
|
375
|
+
self._update_selected_display()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class MultiSelectPicker(BaseSearchPicker[TItem], Generic[TItem]):
|
|
379
|
+
"""Base class for multi-selection pickers.
|
|
380
|
+
|
|
381
|
+
Handles the common pattern of selecting multiple items from search results.
|
|
382
|
+
Items can be toggled on/off independently.
|
|
383
|
+
|
|
384
|
+
Subclasses must implement:
|
|
385
|
+
- _fetch_items(query): Fetch items from API
|
|
386
|
+
- _format_item(item): Format item for display
|
|
387
|
+
- _get_item_id(item): Get unique identifier for item
|
|
388
|
+
- _format_selection_display(): Format the multi-selection display
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
class SelectionChanged(Message):
|
|
392
|
+
"""Posted when selection changes."""
|
|
393
|
+
|
|
394
|
+
def __init__(self, selected_items: list[Any]) -> None:
|
|
395
|
+
self.selected_items = selected_items
|
|
396
|
+
super().__init__()
|
|
397
|
+
|
|
398
|
+
def __init__(
|
|
399
|
+
self,
|
|
400
|
+
client: ApiClient,
|
|
401
|
+
placeholder: str = "Type to search...",
|
|
402
|
+
empty_message: str = "No selection",
|
|
403
|
+
**kwargs: Any,
|
|
404
|
+
) -> None:
|
|
405
|
+
super().__init__(
|
|
406
|
+
client=client,
|
|
407
|
+
placeholder=placeholder,
|
|
408
|
+
empty_message=empty_message,
|
|
409
|
+
**kwargs,
|
|
410
|
+
)
|
|
411
|
+
# Store selected items as dict for O(1) lookup by ID
|
|
412
|
+
self._selected_items: dict[str, TItem] = {}
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def selected_ids(self) -> list[str]:
|
|
416
|
+
"""Get the IDs of all selected items."""
|
|
417
|
+
return list(self._selected_items.keys())
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def selected_items(self) -> list[TItem]:
|
|
421
|
+
"""Get all selected items."""
|
|
422
|
+
return list(self._selected_items.values())
|
|
423
|
+
|
|
424
|
+
def _is_item_selected(self, item: TItem) -> bool:
|
|
425
|
+
"""Check if an item is currently selected."""
|
|
426
|
+
return self._get_item_id(item) in self._selected_items
|
|
427
|
+
|
|
428
|
+
def _handle_item_selection(self, index: int) -> None:
|
|
429
|
+
"""Handle selection of item at the given index with toggle behavior."""
|
|
430
|
+
if index < 0 or index >= len(self._search_results):
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
item = self._search_results[index]
|
|
434
|
+
item_id = self._get_item_id(item)
|
|
435
|
+
|
|
436
|
+
# Toggle selection
|
|
437
|
+
if item_id in self._selected_items:
|
|
438
|
+
del self._selected_items[item_id]
|
|
439
|
+
else:
|
|
440
|
+
self._selected_items[item_id] = item
|
|
441
|
+
|
|
442
|
+
self._update_selected_display()
|
|
443
|
+
self._update_options()
|
|
444
|
+
self._post_selection_changed()
|
|
445
|
+
|
|
446
|
+
def _post_selection_changed(self) -> None:
|
|
447
|
+
"""Post selection changed message. Override for custom message types."""
|
|
448
|
+
self.post_message(self.SelectionChanged(self.selected_items))
|
|
449
|
+
|
|
450
|
+
def clear(self) -> None:
|
|
451
|
+
"""Clear all selections."""
|
|
452
|
+
self._selected_items.clear()
|
|
453
|
+
self._update_selected_display()
|
|
454
|
+
self._post_selection_changed()
|
|
455
|
+
|
|
456
|
+
def set_initial_selections(self, items: list[TItem]) -> None:
|
|
457
|
+
"""Set initial selections without triggering search.
|
|
458
|
+
|
|
459
|
+
Use this to pre-populate the picker with existing values.
|
|
460
|
+
Should be called after mount.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
items: Items to set as initial selections (order is preserved)
|
|
464
|
+
"""
|
|
465
|
+
self._selected_items.clear()
|
|
466
|
+
for item in items:
|
|
467
|
+
item_id = self._get_item_id(item)
|
|
468
|
+
self._selected_items[item_id] = item
|
|
469
|
+
self._update_selected_display()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Reusable styled form components for Textual apps.
|
|
2
|
+
|
|
3
|
+
This module provides low-level, styled form components that can be composed
|
|
4
|
+
to create consistent-looking forms across the CLI. Each component is just
|
|
5
|
+
a styled wrapper - you wire up your own logic.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from cli.infrastructure.forms.components import (
|
|
9
|
+
FormContainer,
|
|
10
|
+
FormTitle,
|
|
11
|
+
FormSection,
|
|
12
|
+
FormField,
|
|
13
|
+
FormRow,
|
|
14
|
+
FormButtons,
|
|
15
|
+
FormError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def compose(self) -> ComposeResult:
|
|
19
|
+
with FormContainer():
|
|
20
|
+
yield FormTitle("Create Something")
|
|
21
|
+
yield FormError(id="error")
|
|
22
|
+
|
|
23
|
+
yield FormSection("Required")
|
|
24
|
+
with FormRow():
|
|
25
|
+
yield FormField("Name", Input(id="name"))
|
|
26
|
+
yield FormField("Type", Select(..., id="type"))
|
|
27
|
+
|
|
28
|
+
yield FormSection("Optional")
|
|
29
|
+
with FormRow():
|
|
30
|
+
yield FormField("Description", Input(id="desc"))
|
|
31
|
+
|
|
32
|
+
yield FormButtons()
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from textual.app import ComposeResult
|
|
38
|
+
from textual.containers import Center, Horizontal, Vertical, VerticalScroll
|
|
39
|
+
from textual.widget import Widget
|
|
40
|
+
from textual.widgets import Button, Static
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FormContainer(VerticalScroll):
|
|
44
|
+
"""Scrollable container for a form."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
47
|
+
super().__init__(**kwargs)
|
|
48
|
+
self.add_class("form-container")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FormTitle(Static):
|
|
52
|
+
"""Centered form title."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, title: str, **kwargs: Any) -> None:
|
|
55
|
+
super().__init__(title, **kwargs)
|
|
56
|
+
self.add_class("form-title")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class FormSection(Static):
|
|
60
|
+
"""Section header within a form."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, title: str, **kwargs: Any) -> None:
|
|
63
|
+
super().__init__(title, **kwargs)
|
|
64
|
+
self.add_class("form-section")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FormField(Vertical):
|
|
68
|
+
"""A labeled form field container. Pass any widget as the input."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, label: str, input_widget: Widget, **kwargs: Any) -> None:
|
|
71
|
+
super().__init__(**kwargs)
|
|
72
|
+
self.add_class("form-field")
|
|
73
|
+
self._label = label
|
|
74
|
+
self._input_widget = input_widget
|
|
75
|
+
|
|
76
|
+
def compose(self) -> ComposeResult:
|
|
77
|
+
yield Static(self._label, classes="form-field-label")
|
|
78
|
+
yield self._input_widget
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class FormRow(Horizontal):
|
|
82
|
+
"""Horizontal row for side-by-side fields."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
85
|
+
super().__init__(**kwargs)
|
|
86
|
+
self.add_class("form-row")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FormError(Static):
|
|
90
|
+
"""Error message display. Call show(msg) and hide()."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
93
|
+
super().__init__("", **kwargs)
|
|
94
|
+
self.add_class("form-error")
|
|
95
|
+
|
|
96
|
+
def show(self, message: str) -> None:
|
|
97
|
+
"""Show an error message."""
|
|
98
|
+
self.update(f"Error: {message}")
|
|
99
|
+
self.add_class("visible")
|
|
100
|
+
|
|
101
|
+
def hide(self) -> None:
|
|
102
|
+
"""Hide the error message."""
|
|
103
|
+
self.remove_class("visible")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class FormButtons(Center):
|
|
107
|
+
"""Centered submit/cancel button row."""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
submit_label: str = "Submit",
|
|
112
|
+
cancel_label: str = "Cancel",
|
|
113
|
+
submit_id: str = "submit-btn",
|
|
114
|
+
cancel_id: str = "cancel-btn",
|
|
115
|
+
**kwargs: Any,
|
|
116
|
+
) -> None:
|
|
117
|
+
super().__init__(**kwargs)
|
|
118
|
+
self.add_class("form-buttons")
|
|
119
|
+
self._submit_label = submit_label
|
|
120
|
+
self._cancel_label = cancel_label
|
|
121
|
+
self._submit_id = submit_id
|
|
122
|
+
self._cancel_id = cancel_id
|
|
123
|
+
|
|
124
|
+
def compose(self) -> ComposeResult:
|
|
125
|
+
yield Button(self._submit_label, id=self._submit_id, variant="success")
|
|
126
|
+
yield Button(self._cancel_label, id=self._cancel_id, variant="error")
|