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,290 @@
|
|
|
1
|
+
"""Welcome screen widget for the chat interface.
|
|
2
|
+
|
|
3
|
+
Displays quick access to frequently used agents and recent sessions,
|
|
4
|
+
allowing users to start chatting with minimal friction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from textual import on, work
|
|
12
|
+
from textual.app import ComposeResult
|
|
13
|
+
from textual.binding import Binding
|
|
14
|
+
from textual.containers import Vertical
|
|
15
|
+
from textual.widget import Widget
|
|
16
|
+
from textual.widgets import OptionList, Static
|
|
17
|
+
from textual.widgets.option_list import Option
|
|
18
|
+
|
|
19
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
20
|
+
from alloy_runtime_types.dtos.agents import AgentSummary
|
|
21
|
+
from alloy_runtime_types.dtos.sessions import SessionSummary
|
|
22
|
+
|
|
23
|
+
from cli.tui.chat.messages import AgentQuickSelected, RecentSessionSelected
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from cli.tui.app import AlloyRuntimeApp
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WelcomeScreen(Widget):
|
|
30
|
+
"""Welcome screen with quick access to agents and recent sessions.
|
|
31
|
+
|
|
32
|
+
Features:
|
|
33
|
+
- Shows top agents by usage (named session count)
|
|
34
|
+
- Shows recent named sessions for quick resumption
|
|
35
|
+
- Number keys (1-9) for instant agent selection
|
|
36
|
+
- Keyboard navigation with arrow keys
|
|
37
|
+
|
|
38
|
+
Posts Messages:
|
|
39
|
+
- AgentQuickSelected: When user selects an agent
|
|
40
|
+
- RecentSessionSelected: When user selects a recent session
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
app: "AlloyRuntimeApp"
|
|
44
|
+
|
|
45
|
+
BINDINGS = [
|
|
46
|
+
Binding("1", "select_agent(0)", "Agent 1", show=False),
|
|
47
|
+
Binding("2", "select_agent(1)", "Agent 2", show=False),
|
|
48
|
+
Binding("3", "select_agent(2)", "Agent 3", show=False),
|
|
49
|
+
Binding("4", "select_agent(3)", "Agent 4", show=False),
|
|
50
|
+
Binding("5", "select_agent(4)", "Agent 5", show=False),
|
|
51
|
+
Binding("6", "select_agent(5)", "Agent 6", show=False),
|
|
52
|
+
Binding("7", "select_agent(6)", "Agent 7", show=False),
|
|
53
|
+
Binding("8", "select_agent(7)", "Agent 8", show=False),
|
|
54
|
+
Binding("9", "select_agent(8)", "Agent 9", show=False),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
DEFAULT_CSS = """
|
|
58
|
+
WelcomeScreen {
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: auto;
|
|
61
|
+
padding: 1 2;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
WelcomeScreen #welcome-title {
|
|
65
|
+
text-align: center;
|
|
66
|
+
text-style: bold;
|
|
67
|
+
margin-bottom: 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
WelcomeScreen .section-title {
|
|
71
|
+
margin-top: 1;
|
|
72
|
+
margin-bottom: 0;
|
|
73
|
+
text-style: bold;
|
|
74
|
+
color: $accent;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
WelcomeScreen .section-divider {
|
|
78
|
+
color: $text-muted;
|
|
79
|
+
margin-bottom: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
WelcomeScreen #agents-list {
|
|
83
|
+
height: auto;
|
|
84
|
+
max-height: 12;
|
|
85
|
+
margin-bottom: 1;
|
|
86
|
+
background: transparent;
|
|
87
|
+
border: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
WelcomeScreen #sessions-list {
|
|
91
|
+
height: auto;
|
|
92
|
+
max-height: 6;
|
|
93
|
+
margin-bottom: 1;
|
|
94
|
+
background: transparent;
|
|
95
|
+
border: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
WelcomeScreen .empty-hint {
|
|
99
|
+
color: $text-muted;
|
|
100
|
+
text-style: italic;
|
|
101
|
+
padding: 0 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
WelcomeScreen #footer-hints {
|
|
105
|
+
margin-top: 1;
|
|
106
|
+
color: $text-muted;
|
|
107
|
+
}
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
111
|
+
super().__init__(**kwargs)
|
|
112
|
+
self._agents: list[AgentSummary] = []
|
|
113
|
+
self._sessions: list[SessionSummary] = []
|
|
114
|
+
self._loading = True
|
|
115
|
+
|
|
116
|
+
def compose(self) -> ComposeResult:
|
|
117
|
+
with Vertical():
|
|
118
|
+
yield Static("Welcome to Alloy Runtime", id="welcome-title")
|
|
119
|
+
|
|
120
|
+
yield Static("Quick Start", classes="section-title")
|
|
121
|
+
yield Static("─" * 40, classes="section-divider")
|
|
122
|
+
yield OptionList(id="agents-list")
|
|
123
|
+
|
|
124
|
+
yield Static("Recent Sessions", classes="section-title")
|
|
125
|
+
yield Static("─" * 40, classes="section-divider")
|
|
126
|
+
yield OptionList(id="sessions-list")
|
|
127
|
+
|
|
128
|
+
yield Static(
|
|
129
|
+
"[dim][Ctrl+N][/] New session [dim]/[/] Search sessions [dim][?][/] Help",
|
|
130
|
+
id="footer-hints",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def on_mount(self) -> None:
|
|
134
|
+
"""Load agents and sessions when mounted."""
|
|
135
|
+
self._load_data()
|
|
136
|
+
|
|
137
|
+
@work(exclusive=True)
|
|
138
|
+
async def _load_data(self) -> None:
|
|
139
|
+
"""Load frequently used agents and recent sessions."""
|
|
140
|
+
try:
|
|
141
|
+
client = await self._get_client()
|
|
142
|
+
|
|
143
|
+
# Load agents sorted by usage (last 90 days)
|
|
144
|
+
agents_response = await client.list_agents(
|
|
145
|
+
sort_by="usage",
|
|
146
|
+
usage_days=90,
|
|
147
|
+
limit=7,
|
|
148
|
+
)
|
|
149
|
+
self._agents = list(agents_response.agents)
|
|
150
|
+
|
|
151
|
+
# Load recent named sessions
|
|
152
|
+
sessions_response = await client.list_sessions(
|
|
153
|
+
named_only=True,
|
|
154
|
+
limit=5,
|
|
155
|
+
)
|
|
156
|
+
self._sessions = list(sessions_response.sessions)
|
|
157
|
+
|
|
158
|
+
self._loading = False
|
|
159
|
+
self._update_agents_list()
|
|
160
|
+
self._update_sessions_list()
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
self._loading = False
|
|
164
|
+
self._show_error(str(e))
|
|
165
|
+
|
|
166
|
+
async def _get_client(self) -> ApiClient:
|
|
167
|
+
"""Get the shared server client."""
|
|
168
|
+
return await self.app.store.get_client()
|
|
169
|
+
|
|
170
|
+
def _update_agents_list(self) -> None:
|
|
171
|
+
"""Update the agents option list."""
|
|
172
|
+
agents_list = self.query_one("#agents-list", OptionList)
|
|
173
|
+
agents_list.clear_options()
|
|
174
|
+
|
|
175
|
+
if not self._agents:
|
|
176
|
+
agents_list.add_option(
|
|
177
|
+
Option(
|
|
178
|
+
"[dim italic]No agents yet. Press Ctrl+N to start.[/]",
|
|
179
|
+
disabled=True,
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
for i, agent in enumerate(self._agents):
|
|
185
|
+
# Format: [1] Agent Name (N chats) - description
|
|
186
|
+
number = i + 1
|
|
187
|
+
usage_text = ""
|
|
188
|
+
if agent.usage_count is not None and agent.usage_count > 0:
|
|
189
|
+
usage_text = f" ({agent.usage_count} chat{'s' if agent.usage_count != 1 else ''})"
|
|
190
|
+
|
|
191
|
+
desc_text = ""
|
|
192
|
+
if agent.description:
|
|
193
|
+
desc = (
|
|
194
|
+
agent.description[:40] + "..."
|
|
195
|
+
if len(agent.description) > 40
|
|
196
|
+
else agent.description
|
|
197
|
+
)
|
|
198
|
+
desc_text = f" [dim]- {desc}[/]"
|
|
199
|
+
|
|
200
|
+
label = (
|
|
201
|
+
f"[bold cyan][{number}][/] {agent.name}[dim]{usage_text}[/]{desc_text}"
|
|
202
|
+
)
|
|
203
|
+
agents_list.add_option(Option(label, id=str(agent.id)))
|
|
204
|
+
|
|
205
|
+
def _update_sessions_list(self) -> None:
|
|
206
|
+
"""Update the sessions option list."""
|
|
207
|
+
sessions_list = self.query_one("#sessions-list", OptionList)
|
|
208
|
+
sessions_list.clear_options()
|
|
209
|
+
|
|
210
|
+
if not self._sessions:
|
|
211
|
+
sessions_list.add_option(
|
|
212
|
+
Option(
|
|
213
|
+
"[dim italic]No recent sessions[/]",
|
|
214
|
+
disabled=True,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
for session in self._sessions:
|
|
220
|
+
# Format: Session Name - time ago
|
|
221
|
+
name = session.name or "Untitled"
|
|
222
|
+
if len(name) > 35:
|
|
223
|
+
name = name[:32] + "..."
|
|
224
|
+
|
|
225
|
+
time_ago = self._format_time_ago(session.last_activity_at)
|
|
226
|
+
label = f" {name} [dim]{time_ago}[/]"
|
|
227
|
+
sessions_list.add_option(Option(label, id=str(session.id)))
|
|
228
|
+
|
|
229
|
+
def _format_time_ago(self, dt: datetime) -> str:
|
|
230
|
+
"""Format datetime as human-readable time ago."""
|
|
231
|
+
now = datetime.now(timezone.utc)
|
|
232
|
+
if dt.tzinfo is None:
|
|
233
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
234
|
+
|
|
235
|
+
delta = now - dt
|
|
236
|
+
seconds = delta.total_seconds()
|
|
237
|
+
|
|
238
|
+
if seconds < 60:
|
|
239
|
+
return "just now"
|
|
240
|
+
elif seconds < 3600:
|
|
241
|
+
mins = int(seconds / 60)
|
|
242
|
+
return f"{mins}m ago"
|
|
243
|
+
elif seconds < 86400:
|
|
244
|
+
hours = int(seconds / 3600)
|
|
245
|
+
return f"{hours}h ago"
|
|
246
|
+
elif seconds < 604800:
|
|
247
|
+
days = int(seconds / 86400)
|
|
248
|
+
return f"{days}d ago"
|
|
249
|
+
else:
|
|
250
|
+
weeks = int(seconds / 604800)
|
|
251
|
+
return f"{weeks}w ago"
|
|
252
|
+
|
|
253
|
+
def _show_error(self, error: str) -> None:
|
|
254
|
+
"""Show error in the agents list."""
|
|
255
|
+
agents_list = self.query_one("#agents-list", OptionList)
|
|
256
|
+
agents_list.clear_options()
|
|
257
|
+
agents_list.add_option(Option(f"[red]Error loading: {error}[/]", disabled=True))
|
|
258
|
+
|
|
259
|
+
def action_select_agent(self, index: int) -> None:
|
|
260
|
+
"""Select an agent by number key (0-indexed)."""
|
|
261
|
+
if index < len(self._agents):
|
|
262
|
+
agent = self._agents[index]
|
|
263
|
+
self.post_message(AgentQuickSelected(agent.id, agent.name))
|
|
264
|
+
|
|
265
|
+
@on(OptionList.OptionSelected, "#agents-list")
|
|
266
|
+
def on_agent_selected(self, event: OptionList.OptionSelected) -> None:
|
|
267
|
+
"""Handle agent selection from option list."""
|
|
268
|
+
if event.option.id is None:
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
agent_id = UUID(event.option.id)
|
|
272
|
+
# Find the agent name
|
|
273
|
+
for agent in self._agents:
|
|
274
|
+
if agent.id == agent_id:
|
|
275
|
+
self.post_message(AgentQuickSelected(agent_id, agent.name))
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
@on(OptionList.OptionSelected, "#sessions-list")
|
|
279
|
+
def on_session_selected(self, event: OptionList.OptionSelected) -> None:
|
|
280
|
+
"""Handle session selection from option list."""
|
|
281
|
+
if event.option.id is None:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
session_id = UUID(event.option.id)
|
|
285
|
+
self.post_message(RecentSessionSelected(session_id))
|
|
286
|
+
|
|
287
|
+
def refresh_data(self) -> None:
|
|
288
|
+
"""Refresh the agents and sessions data."""
|
|
289
|
+
self._loading = True
|
|
290
|
+
self._load_data()
|
|
File without changes
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Agents screen for browsing and managing AI agents.
|
|
2
|
+
|
|
3
|
+
This screen provides:
|
|
4
|
+
- Search and browse agents
|
|
5
|
+
- View agent details in preview panel
|
|
6
|
+
- Create new agents
|
|
7
|
+
- Copy agent names
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from textual import on, work
|
|
13
|
+
from textual.app import ComposeResult
|
|
14
|
+
from textual.binding import Binding
|
|
15
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
16
|
+
from textual.widgets import Button, DataTable, Input, Static
|
|
17
|
+
|
|
18
|
+
from alloy_runtime_types.dtos.agents import AgentSummary
|
|
19
|
+
|
|
20
|
+
from cli.infrastructure.scope_utils import format_scope_label
|
|
21
|
+
from cli.infrastructure.tui.clipboard import copy_to_clipboard
|
|
22
|
+
from cli.infrastructure.tui.formatters import format_datetime, truncate_text
|
|
23
|
+
from cli.tui.screens.base import BrowserWidget
|
|
24
|
+
from cli.tui.screens.nav_screen import NavScreen
|
|
25
|
+
from cli.tui.widgets.agent_create_modal import AgentCreateModal
|
|
26
|
+
from cli.tui.widgets.agent_form_modal import AgentFormResult
|
|
27
|
+
from cli.tui.widgets.agent_update_modal import AgentUpdateModal
|
|
28
|
+
from cli.tui.widgets.confirm_modal import ConfirmModal
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from cli.tui.app import AlloyRuntimeApp
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentsWidget(BrowserWidget[AgentSummary]):
|
|
35
|
+
"""Widget for browsing and managing agents."""
|
|
36
|
+
|
|
37
|
+
app: "AlloyRuntimeApp"
|
|
38
|
+
|
|
39
|
+
TITLE = "Agents"
|
|
40
|
+
TABLE_COLUMNS = ["Name", "Scope", "Models", "Updated"]
|
|
41
|
+
|
|
42
|
+
def compose(self) -> ComposeResult:
|
|
43
|
+
"""Compose the browser layout with a New Agent button."""
|
|
44
|
+
with Vertical(id="browser-root"):
|
|
45
|
+
with Vertical(id="search-container"):
|
|
46
|
+
with Horizontal(id="search-row"):
|
|
47
|
+
yield Input(
|
|
48
|
+
placeholder=f"Search {self.TITLE.lower()}...",
|
|
49
|
+
id="search-input",
|
|
50
|
+
)
|
|
51
|
+
yield Button("+ New", id="new-agent-btn", variant="primary")
|
|
52
|
+
yield Static("Loading...", id="status-bar")
|
|
53
|
+
|
|
54
|
+
with Horizontal(id="main-container"):
|
|
55
|
+
with Vertical(id="list-container"):
|
|
56
|
+
yield DataTable(id="data-table", cursor_type="row")
|
|
57
|
+
|
|
58
|
+
with Vertical(id="detail-container"):
|
|
59
|
+
yield Static("Preview", id="preview-title")
|
|
60
|
+
with VerticalScroll(id="preview-scroll"):
|
|
61
|
+
yield Static(
|
|
62
|
+
"Select an item to view details",
|
|
63
|
+
id="preview-content",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@on(Button.Pressed, "#new-agent-btn")
|
|
67
|
+
def _on_new_agent_btn_pressed(self, event: Button.Pressed) -> None:
|
|
68
|
+
"""Handle New Agent button press."""
|
|
69
|
+
self.open_new_agent_modal()
|
|
70
|
+
|
|
71
|
+
async def _fetch_items(self, query: str) -> tuple[list[AgentSummary], int]:
|
|
72
|
+
"""Fetch agents from the API."""
|
|
73
|
+
client = await self._ensure_client()
|
|
74
|
+
response = await client.list_agents(
|
|
75
|
+
search=query if query else None,
|
|
76
|
+
limit=50,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Update agent cache for cross-screen use (e.g., chat agent picker)
|
|
80
|
+
self.app.store.agent_cache = list(response.agents)
|
|
81
|
+
|
|
82
|
+
return response.agents, response.total
|
|
83
|
+
|
|
84
|
+
def _format_row(self, item: AgentSummary) -> tuple[str, ...]:
|
|
85
|
+
"""Format an agent as a table row."""
|
|
86
|
+
return (
|
|
87
|
+
truncate_text(item.name, max_length=30),
|
|
88
|
+
format_scope_label(
|
|
89
|
+
str(item.organization_id) if item.organization_id else None,
|
|
90
|
+
str(item.user_id) if item.user_id else None,
|
|
91
|
+
),
|
|
92
|
+
f"{item.model_count} models",
|
|
93
|
+
format_datetime(item.updated_at),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def _update_preview(self, item: AgentSummary) -> None:
|
|
97
|
+
"""Update the preview pane with agent details."""
|
|
98
|
+
preview = self.query_one("#preview-content", Static)
|
|
99
|
+
|
|
100
|
+
scope = format_scope_label(
|
|
101
|
+
str(item.organization_id) if item.organization_id else None,
|
|
102
|
+
str(item.user_id) if item.user_id else None,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
created = format_datetime(item.created_at)
|
|
106
|
+
updated = format_datetime(item.updated_at)
|
|
107
|
+
|
|
108
|
+
system_instruction_id = (
|
|
109
|
+
str(item.system_instruction_version_id)
|
|
110
|
+
if item.system_instruction_version_id
|
|
111
|
+
else "-"
|
|
112
|
+
)
|
|
113
|
+
input_schema_id = str(item.input_schema_id) if item.input_schema_id else "-"
|
|
114
|
+
output_schema_id = str(item.output_schema_id) if item.output_schema_id else "-"
|
|
115
|
+
|
|
116
|
+
preview_text = f"""[bold]Agent ID:[/] {item.id}
|
|
117
|
+
[bold]Name:[/] {item.name}
|
|
118
|
+
[bold]Scope:[/] {scope}
|
|
119
|
+
[bold]Models:[/] {item.model_count}
|
|
120
|
+
|
|
121
|
+
[bold]Description:[/]
|
|
122
|
+
{item.description or "(no description)"}
|
|
123
|
+
|
|
124
|
+
[bold]System Instruction Version ID:[/]
|
|
125
|
+
{system_instruction_id}
|
|
126
|
+
|
|
127
|
+
[bold]Input Schema ID:[/]
|
|
128
|
+
{input_schema_id}
|
|
129
|
+
|
|
130
|
+
[bold]Output Schema ID:[/]
|
|
131
|
+
{output_schema_id}
|
|
132
|
+
|
|
133
|
+
[bold]Created:[/] {created}
|
|
134
|
+
[bold]Updated:[/] {updated}"""
|
|
135
|
+
|
|
136
|
+
preview.update(preview_text)
|
|
137
|
+
|
|
138
|
+
# =========================================================================
|
|
139
|
+
# Public methods for parent Screen to call
|
|
140
|
+
# =========================================================================
|
|
141
|
+
|
|
142
|
+
def copy_agent_name(self) -> None:
|
|
143
|
+
"""Copy the agent name to clipboard."""
|
|
144
|
+
agent = self.get_selected_item()
|
|
145
|
+
if not agent:
|
|
146
|
+
self.app.notify("No agent selected", severity="warning")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
copy_to_clipboard(self.app, agent.name, "Agent name")
|
|
150
|
+
|
|
151
|
+
def open_new_agent_modal(self) -> None:
|
|
152
|
+
"""Open the agent creation modal."""
|
|
153
|
+
self._open_agent_create_modal()
|
|
154
|
+
|
|
155
|
+
def open_edit_agent_modal(self) -> None:
|
|
156
|
+
"""Open the agent update modal for the selected agent."""
|
|
157
|
+
agent = self.get_selected_item()
|
|
158
|
+
if not agent:
|
|
159
|
+
self.app.notify("No agent selected", severity="warning")
|
|
160
|
+
return
|
|
161
|
+
self._open_agent_update_modal(agent)
|
|
162
|
+
|
|
163
|
+
def delete_selected_agent(self) -> None:
|
|
164
|
+
"""Delete the currently selected agent."""
|
|
165
|
+
agent = self.get_selected_item()
|
|
166
|
+
if not agent:
|
|
167
|
+
self.app.notify("No agent selected", severity="warning")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
agent_name = agent.name
|
|
171
|
+
self.app.push_screen(
|
|
172
|
+
ConfirmModal(
|
|
173
|
+
title="Delete Agent",
|
|
174
|
+
message=f"Delete agent '{agent_name}'? This cannot be undone.",
|
|
175
|
+
),
|
|
176
|
+
lambda confirmed: self._on_delete_confirmed(confirmed, agent_name),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# =========================================================================
|
|
180
|
+
# Internal async operations
|
|
181
|
+
# =========================================================================
|
|
182
|
+
|
|
183
|
+
@work(exclusive=True)
|
|
184
|
+
async def _open_agent_create_modal(self) -> None:
|
|
185
|
+
"""Open the agent creation modal."""
|
|
186
|
+
try:
|
|
187
|
+
client = await self._ensure_client()
|
|
188
|
+
self.app.push_screen(
|
|
189
|
+
AgentCreateModal(client=client),
|
|
190
|
+
self._on_agent_created,
|
|
191
|
+
)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
self.app.notify(f"Failed to open agent creator: {e}", severity="error")
|
|
194
|
+
|
|
195
|
+
def _on_agent_created(self, result: AgentFormResult | None) -> None:
|
|
196
|
+
"""Callback when agent creation modal is dismissed."""
|
|
197
|
+
if result is not None:
|
|
198
|
+
self.app.notify(
|
|
199
|
+
f"Created agent: {result.agent_name}",
|
|
200
|
+
severity="information",
|
|
201
|
+
)
|
|
202
|
+
# Refresh the agent list
|
|
203
|
+
self._do_search(self._last_search)
|
|
204
|
+
|
|
205
|
+
@work(exclusive=True)
|
|
206
|
+
async def _open_agent_update_modal(self, agent_summary: AgentSummary) -> None:
|
|
207
|
+
"""Fetch full agent details and open the update modal."""
|
|
208
|
+
try:
|
|
209
|
+
client = await self._ensure_client()
|
|
210
|
+
# Fetch full agent details (summary doesn't have all fields)
|
|
211
|
+
agent = await client.get_agent(str(agent_summary.id))
|
|
212
|
+
self.app.push_screen(
|
|
213
|
+
AgentUpdateModal(client=client, agent=agent),
|
|
214
|
+
self._on_agent_updated,
|
|
215
|
+
)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
self.app.notify(f"Failed to open agent editor: {e}", severity="error")
|
|
218
|
+
|
|
219
|
+
def _on_agent_updated(self, result: AgentFormResult | None) -> None:
|
|
220
|
+
"""Callback when agent update modal is dismissed."""
|
|
221
|
+
if result is not None:
|
|
222
|
+
self.app.notify(
|
|
223
|
+
f"Updated agent: {result.agent_name}",
|
|
224
|
+
severity="information",
|
|
225
|
+
)
|
|
226
|
+
# Refresh the agent list
|
|
227
|
+
self._do_search(self._last_search)
|
|
228
|
+
|
|
229
|
+
def _on_delete_confirmed(self, confirmed: bool | None, agent_name: str) -> None:
|
|
230
|
+
"""Callback when delete confirmation modal is dismissed."""
|
|
231
|
+
if confirmed:
|
|
232
|
+
self._execute_delete(agent_name)
|
|
233
|
+
|
|
234
|
+
@work(exclusive=True)
|
|
235
|
+
async def _execute_delete(self, agent_name: str) -> None:
|
|
236
|
+
"""Execute the delete operation."""
|
|
237
|
+
try:
|
|
238
|
+
client = await self._ensure_client()
|
|
239
|
+
await client.delete_agent(agent_name)
|
|
240
|
+
|
|
241
|
+
self.app.notify(
|
|
242
|
+
f"Agent '{agent_name}' deleted successfully",
|
|
243
|
+
severity="information",
|
|
244
|
+
)
|
|
245
|
+
# Refresh the agent list
|
|
246
|
+
self._do_search(self._last_search)
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
error_msg = str(e)
|
|
250
|
+
if "pipeline" in error_msg.lower():
|
|
251
|
+
self.app.notify(
|
|
252
|
+
"Cannot delete: agent is referenced by pipelines",
|
|
253
|
+
severity="error",
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
self.app.notify(f"Failed to delete agent: {e}", severity="error")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class AgentsScreen(NavScreen):
|
|
260
|
+
"""Screen for browsing and managing agents.
|
|
261
|
+
|
|
262
|
+
This is a proper Textual Screen that wraps the AgentsWidget
|
|
263
|
+
and provides keybindings that work regardless of focus.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
app: "AlloyRuntimeApp"
|
|
267
|
+
|
|
268
|
+
SCREEN_ID = "agents"
|
|
269
|
+
|
|
270
|
+
DEFAULT_CSS = """
|
|
271
|
+
AgentsScreen #screen-root {
|
|
272
|
+
height: 1fr;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
AgentsScreen .browser-widget {
|
|
276
|
+
height: 100%;
|
|
277
|
+
width: 100%;
|
|
278
|
+
}
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
# Screen-specific bindings (Ctrl+ prefixed)
|
|
282
|
+
# NavScreen.BINDINGS provides global navigation (c, a, t, s, m, d, ?)
|
|
283
|
+
BINDINGS = NavScreen.BINDINGS + [
|
|
284
|
+
Binding("ctrl+f", "focus_search", "Search", show=True),
|
|
285
|
+
Binding("ctrl+r", "refresh", "Refresh", show=True),
|
|
286
|
+
Binding("ctrl+y", "copy_name", "Copy Name", show=True),
|
|
287
|
+
Binding("ctrl+n", "new_agent", "New", show=True),
|
|
288
|
+
Binding("ctrl+e", "edit_agent", "Edit", show=True),
|
|
289
|
+
Binding("ctrl+d", "delete_agent", "Delete", show=True),
|
|
290
|
+
Binding("escape", "focus_table", "Focus List", show=False),
|
|
291
|
+
Binding("enter", "copy_name", "Copy Name", show=False),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
def compose_content(self) -> ComposeResult:
|
|
295
|
+
"""Compose the screen content."""
|
|
296
|
+
yield AgentsWidget(id="agents-widget")
|
|
297
|
+
|
|
298
|
+
def on_screen_resume(self) -> None:
|
|
299
|
+
"""Focus the data table when screen becomes active."""
|
|
300
|
+
self.call_after_refresh(self._focus_table)
|
|
301
|
+
|
|
302
|
+
def _focus_table(self) -> None:
|
|
303
|
+
"""Focus the data table."""
|
|
304
|
+
try:
|
|
305
|
+
widget = self.query_one("#agents-widget", AgentsWidget)
|
|
306
|
+
widget.focus_table()
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def _get_widget(self) -> AgentsWidget:
|
|
311
|
+
"""Get the agents widget."""
|
|
312
|
+
return self.query_one("#agents-widget", AgentsWidget)
|
|
313
|
+
|
|
314
|
+
# =========================================================================
|
|
315
|
+
# Actions (keybinding handlers)
|
|
316
|
+
# =========================================================================
|
|
317
|
+
|
|
318
|
+
def action_focus_search(self) -> None:
|
|
319
|
+
"""Focus the search input."""
|
|
320
|
+
self._get_widget().focus_search()
|
|
321
|
+
|
|
322
|
+
def action_focus_table(self) -> None:
|
|
323
|
+
"""Focus the data table."""
|
|
324
|
+
self._get_widget().focus_table()
|
|
325
|
+
|
|
326
|
+
def action_refresh(self) -> None:
|
|
327
|
+
"""Refresh the current search results."""
|
|
328
|
+
self._get_widget().refresh_data()
|
|
329
|
+
|
|
330
|
+
def action_copy_name(self) -> None:
|
|
331
|
+
"""Copy the agent name to clipboard."""
|
|
332
|
+
self._get_widget().copy_agent_name()
|
|
333
|
+
|
|
334
|
+
def action_new_agent(self) -> None:
|
|
335
|
+
"""Open the agent creation modal."""
|
|
336
|
+
self._get_widget().open_new_agent_modal()
|
|
337
|
+
|
|
338
|
+
def action_edit_agent(self) -> None:
|
|
339
|
+
"""Open the agent update modal."""
|
|
340
|
+
self._get_widget().open_edit_agent_modal()
|
|
341
|
+
|
|
342
|
+
def action_delete_agent(self) -> None:
|
|
343
|
+
"""Delete the selected agent."""
|
|
344
|
+
self._get_widget().delete_selected_agent()
|