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,508 @@
|
|
|
1
|
+
"""Content screen for browsing and managing content parts.
|
|
2
|
+
|
|
3
|
+
This screen provides:
|
|
4
|
+
- Search and browse AI-generated content parts
|
|
5
|
+
- View content details in preview panel
|
|
6
|
+
- Manage tags on content
|
|
7
|
+
- Copy content text/JSON
|
|
8
|
+
- Open content in external editor
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
import tempfile
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
18
|
+
from textual import on, work
|
|
19
|
+
from textual.app import ComposeResult
|
|
20
|
+
from textual.binding import Binding
|
|
21
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
22
|
+
from textual.widgets import DataTable, Input, Static
|
|
23
|
+
|
|
24
|
+
from alloy_runtime_types.dtos.content import UpdateContentPartRequest
|
|
25
|
+
from alloy_runtime_types.enums.content_enums import ContentSortField
|
|
26
|
+
|
|
27
|
+
from cli.infrastructure.forms.tag_management_modal import TagManagementModal
|
|
28
|
+
from cli.infrastructure.tui.clipboard import copy_to_clipboard
|
|
29
|
+
from cli.tui.widgets.confirm_modal import ConfirmModal
|
|
30
|
+
from cli.infrastructure.tui.formatters import (
|
|
31
|
+
format_datetime,
|
|
32
|
+
format_tags_list,
|
|
33
|
+
truncate_text,
|
|
34
|
+
)
|
|
35
|
+
from cli.tui.screens.base import BrowserWidget
|
|
36
|
+
from cli.tui.screens.nav_screen import NavScreen
|
|
37
|
+
from cli.tui.screens.models import ContentPartView
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from cli.tui.app import AlloyRuntimeApp
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ContentWidget(BrowserWidget[ContentPartView]):
|
|
44
|
+
"""Widget for browsing and managing content parts."""
|
|
45
|
+
|
|
46
|
+
app: "AlloyRuntimeApp"
|
|
47
|
+
|
|
48
|
+
TITLE = "Content"
|
|
49
|
+
TABLE_COLUMNS = ["Type", "Content", "Tags", "Model", "Updated"]
|
|
50
|
+
|
|
51
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
52
|
+
"""Initialize the content widget."""
|
|
53
|
+
super().__init__(**kwargs)
|
|
54
|
+
self._pending_tag_update_content_id: str | None = None
|
|
55
|
+
# Additional filter state for three-input search
|
|
56
|
+
self._last_tag_filter: str = ""
|
|
57
|
+
self._last_external_id: str = ""
|
|
58
|
+
|
|
59
|
+
def compose(self) -> ComposeResult:
|
|
60
|
+
"""Compose the browser layout with three search inputs."""
|
|
61
|
+
with Vertical(id="browser-root"):
|
|
62
|
+
with Vertical(id="search-container"):
|
|
63
|
+
with Horizontal(id="search-row"):
|
|
64
|
+
yield Input(
|
|
65
|
+
placeholder="Search content...",
|
|
66
|
+
id="search-input",
|
|
67
|
+
)
|
|
68
|
+
yield Input(
|
|
69
|
+
placeholder="Tag filter (e.g. code.*)",
|
|
70
|
+
id="tag-input",
|
|
71
|
+
)
|
|
72
|
+
yield Input(
|
|
73
|
+
placeholder="External ID",
|
|
74
|
+
id="external-id-input",
|
|
75
|
+
)
|
|
76
|
+
yield Static("Loading...", id="status-bar")
|
|
77
|
+
|
|
78
|
+
with Horizontal(id="main-container"):
|
|
79
|
+
with Vertical(id="list-container"):
|
|
80
|
+
yield DataTable(id="data-table", cursor_type="row")
|
|
81
|
+
|
|
82
|
+
with Vertical(id="detail-container"):
|
|
83
|
+
yield Static("Preview", id="preview-title")
|
|
84
|
+
with VerticalScroll(id="preview-scroll"):
|
|
85
|
+
yield Static(
|
|
86
|
+
"Select an item to view details",
|
|
87
|
+
id="preview-content",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@on(Input.Changed, "#tag-input")
|
|
91
|
+
def _on_tag_input_changed(self, event: Input.Changed) -> None:
|
|
92
|
+
"""Handle tag filter input changes."""
|
|
93
|
+
tag_filter = event.value.strip()
|
|
94
|
+
if tag_filter != self._last_tag_filter:
|
|
95
|
+
self._last_tag_filter = tag_filter
|
|
96
|
+
self._trigger_search()
|
|
97
|
+
|
|
98
|
+
@on(Input.Changed, "#external-id-input")
|
|
99
|
+
def _on_external_id_input_changed(self, event: Input.Changed) -> None:
|
|
100
|
+
"""Handle external ID input changes."""
|
|
101
|
+
external_id = event.value.strip()
|
|
102
|
+
if external_id != self._last_external_id:
|
|
103
|
+
self._last_external_id = external_id
|
|
104
|
+
self._trigger_search()
|
|
105
|
+
|
|
106
|
+
def _trigger_search(self) -> None:
|
|
107
|
+
"""Trigger a search with all current filter values."""
|
|
108
|
+
self._do_search(self._last_search)
|
|
109
|
+
|
|
110
|
+
async def _fetch_items(self, query: str) -> tuple[list[ContentPartView], int]:
|
|
111
|
+
"""Fetch content parts from the API with all filters."""
|
|
112
|
+
client = await self._ensure_client()
|
|
113
|
+
response = await client.list_content_parts(
|
|
114
|
+
search=query if query else None,
|
|
115
|
+
tag_path=self._last_tag_filter if self._last_tag_filter else None,
|
|
116
|
+
external_id=self._last_external_id if self._last_external_id else None,
|
|
117
|
+
sort_by=ContentSortField.UPDATED_AT,
|
|
118
|
+
limit=50,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
items = [ContentPartView.from_response(part) for part in response.content_parts]
|
|
122
|
+
return items, response.total_count
|
|
123
|
+
|
|
124
|
+
def _format_row(self, item: ContentPartView) -> tuple[str, ...]:
|
|
125
|
+
"""Format a content part as a table row."""
|
|
126
|
+
tags_dicts = [{"tag_path": t.tag_path} for t in item.tags]
|
|
127
|
+
return (
|
|
128
|
+
item.content_type_id,
|
|
129
|
+
self._format_content_preview(item),
|
|
130
|
+
format_tags_list(tags_dicts, max_display=2),
|
|
131
|
+
self._format_model_info(item),
|
|
132
|
+
format_datetime(item.updated_at),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _format_content_preview(self, item: ContentPartView) -> str:
|
|
136
|
+
"""Format content for table display."""
|
|
137
|
+
if item.content_text:
|
|
138
|
+
text = item.content_text.replace("\n", " ").strip()
|
|
139
|
+
return truncate_text(text, max_length=40)
|
|
140
|
+
|
|
141
|
+
if item.content_structured:
|
|
142
|
+
try:
|
|
143
|
+
json_str = json.dumps(item.content_structured, ensure_ascii=False)
|
|
144
|
+
return truncate_text(json_str, max_length=40)
|
|
145
|
+
except (TypeError, ValueError):
|
|
146
|
+
return "{...}"
|
|
147
|
+
|
|
148
|
+
return "-"
|
|
149
|
+
|
|
150
|
+
def _format_model_info(self, item: ContentPartView) -> str:
|
|
151
|
+
"""Format model info for table display."""
|
|
152
|
+
if not item.execution_metadata:
|
|
153
|
+
return "-"
|
|
154
|
+
|
|
155
|
+
provider = item.execution_metadata.provider_key
|
|
156
|
+
model = item.execution_metadata.provider_model_name
|
|
157
|
+
|
|
158
|
+
if provider and model:
|
|
159
|
+
if len(model) > 15:
|
|
160
|
+
model = model[:12] + "..."
|
|
161
|
+
return f"{provider}/{model}"
|
|
162
|
+
elif model:
|
|
163
|
+
return model[:15] if len(model) > 15 else model
|
|
164
|
+
elif provider:
|
|
165
|
+
return provider
|
|
166
|
+
return "-"
|
|
167
|
+
|
|
168
|
+
def _update_preview(self, item: ContentPartView) -> None:
|
|
169
|
+
"""Update the preview pane with content details."""
|
|
170
|
+
preview = self.query_one("#preview-content", Static)
|
|
171
|
+
|
|
172
|
+
storage = "structured" if item.content_structured else "text"
|
|
173
|
+
created_at = format_datetime(item.created_at)
|
|
174
|
+
|
|
175
|
+
if item.content_structured:
|
|
176
|
+
try:
|
|
177
|
+
content_display = json.dumps(item.content_structured, indent=2)
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
content_display = str(item.content_structured)
|
|
180
|
+
elif item.content_text:
|
|
181
|
+
content_display = item.content_text
|
|
182
|
+
else:
|
|
183
|
+
content_display = "(empty)"
|
|
184
|
+
|
|
185
|
+
tags_display = ", ".join(t.tag_path for t in item.tags) if item.tags else "-"
|
|
186
|
+
|
|
187
|
+
if item.execution_metadata:
|
|
188
|
+
model_info = (
|
|
189
|
+
f"{item.execution_metadata.provider_key}/"
|
|
190
|
+
f"{item.execution_metadata.provider_model_name}"
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
model_info = "-"
|
|
194
|
+
|
|
195
|
+
# Display external_id prominently if present
|
|
196
|
+
external_id_display = item.external_id or "-"
|
|
197
|
+
|
|
198
|
+
preview_text = f"""[bold]External ID:[/] {external_id_display}
|
|
199
|
+
[bold]ID:[/] {item.id}
|
|
200
|
+
[bold]Type:[/] {item.content_type_id}
|
|
201
|
+
[bold]Storage:[/] {storage}
|
|
202
|
+
[bold]Model:[/] {model_info}
|
|
203
|
+
[bold]Tags:[/] {tags_display}
|
|
204
|
+
[bold]Created:[/] {created_at}
|
|
205
|
+
|
|
206
|
+
[bold]Content:[/]
|
|
207
|
+
{content_display}"""
|
|
208
|
+
|
|
209
|
+
preview.update(preview_text)
|
|
210
|
+
|
|
211
|
+
# =========================================================================
|
|
212
|
+
# Public methods for parent Screen to call
|
|
213
|
+
# =========================================================================
|
|
214
|
+
|
|
215
|
+
def copy_content_id(self) -> None:
|
|
216
|
+
"""Copy the selected content ID to clipboard.
|
|
217
|
+
|
|
218
|
+
Prefers external_id if available, otherwise falls back to the internal UUID.
|
|
219
|
+
"""
|
|
220
|
+
part = self.get_selected_item()
|
|
221
|
+
if not part:
|
|
222
|
+
self.app.notify("No content selected", severity="warning")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Prefer external_id, fallback to internal id
|
|
226
|
+
if part.external_id:
|
|
227
|
+
copy_to_clipboard(self.app, part.external_id, "External ID")
|
|
228
|
+
else:
|
|
229
|
+
copy_to_clipboard(self.app, str(part.id), "ID")
|
|
230
|
+
|
|
231
|
+
def copy_content(self) -> None:
|
|
232
|
+
"""Copy the selected content to clipboard."""
|
|
233
|
+
part = self.get_selected_item()
|
|
234
|
+
if not part:
|
|
235
|
+
self.app.notify("No content to copy", severity="warning")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
if part.content_structured:
|
|
239
|
+
try:
|
|
240
|
+
content = json.dumps(
|
|
241
|
+
part.content_structured, indent=2, ensure_ascii=False
|
|
242
|
+
)
|
|
243
|
+
except (TypeError, ValueError):
|
|
244
|
+
content = str(part.content_structured)
|
|
245
|
+
elif part.content_text:
|
|
246
|
+
content = part.content_text
|
|
247
|
+
else:
|
|
248
|
+
self.app.notify("Content is empty", severity="warning")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
preview_str = content[:50].replace("\n", " ")
|
|
252
|
+
if len(content) > 50:
|
|
253
|
+
preview_str += "..."
|
|
254
|
+
copy_to_clipboard(self.app, content, f"content: {preview_str}")
|
|
255
|
+
|
|
256
|
+
def manage_tags(self) -> None:
|
|
257
|
+
"""Open tag management modal for selected content."""
|
|
258
|
+
part = self.get_selected_item()
|
|
259
|
+
if not part:
|
|
260
|
+
self.app.notify("No content to tag", severity="warning")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
self._pending_tag_update_content_id = part.id
|
|
264
|
+
|
|
265
|
+
# Convert tags to dict format expected by modal
|
|
266
|
+
current_tags = [{"tag_path": t.tag_path} for t in part.tags]
|
|
267
|
+
self._show_tag_management_modal(str(part.id), current_tags)
|
|
268
|
+
|
|
269
|
+
def delete_content(self) -> None:
|
|
270
|
+
"""Delete the currently selected content part."""
|
|
271
|
+
part = self.get_selected_item()
|
|
272
|
+
if not part:
|
|
273
|
+
self.app.notify("No content selected", severity="warning")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Create a preview of the content for the confirmation message
|
|
277
|
+
if part.content_text:
|
|
278
|
+
preview = part.content_text[:50].replace("\n", " ")
|
|
279
|
+
if len(part.content_text) > 50:
|
|
280
|
+
preview += "..."
|
|
281
|
+
elif part.content_structured:
|
|
282
|
+
preview = f"[{part.content_type_id}]"
|
|
283
|
+
else:
|
|
284
|
+
preview = "(empty)"
|
|
285
|
+
|
|
286
|
+
self.app.push_screen(
|
|
287
|
+
ConfirmModal(
|
|
288
|
+
title="Delete Content",
|
|
289
|
+
message=f"Delete content '{preview}'? This cannot be undone.",
|
|
290
|
+
),
|
|
291
|
+
lambda confirmed: self._on_delete_confirmed(confirmed, part.id),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def _on_delete_confirmed(self, confirmed: bool | None, content_id: str) -> None:
|
|
295
|
+
"""Callback when delete confirmation modal is dismissed."""
|
|
296
|
+
if confirmed:
|
|
297
|
+
self._execute_delete(content_id)
|
|
298
|
+
|
|
299
|
+
@work(exclusive=True)
|
|
300
|
+
async def _execute_delete(self, content_id: str) -> None:
|
|
301
|
+
"""Execute the delete operation."""
|
|
302
|
+
try:
|
|
303
|
+
client = await self._ensure_client()
|
|
304
|
+
response = await client.delete_content_part(UUID(content_id))
|
|
305
|
+
|
|
306
|
+
# Build notification message with cascade info
|
|
307
|
+
messages = ["Content deleted"]
|
|
308
|
+
if response.deleted_message_id:
|
|
309
|
+
messages.append("orphaned message removed")
|
|
310
|
+
if response.deleted_session_id:
|
|
311
|
+
messages.append("orphaned session removed")
|
|
312
|
+
|
|
313
|
+
self.app.notify(
|
|
314
|
+
", ".join(messages),
|
|
315
|
+
severity="information",
|
|
316
|
+
)
|
|
317
|
+
# Refresh the content list
|
|
318
|
+
self._do_search(self._last_search)
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
self.app.notify(f"Failed to delete content: {e}", severity="error")
|
|
322
|
+
|
|
323
|
+
def open_in_editor(self) -> None:
|
|
324
|
+
"""Open content in external editor (read-only view)."""
|
|
325
|
+
part = self.get_selected_item()
|
|
326
|
+
if not part:
|
|
327
|
+
self.app.notify("No content to open", severity="warning")
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
if part.content_structured:
|
|
331
|
+
try:
|
|
332
|
+
content = json.dumps(
|
|
333
|
+
part.content_structured, indent=2, ensure_ascii=False
|
|
334
|
+
)
|
|
335
|
+
suffix = ".json"
|
|
336
|
+
except (TypeError, ValueError):
|
|
337
|
+
content = str(part.content_structured)
|
|
338
|
+
suffix = ".txt"
|
|
339
|
+
elif part.content_text:
|
|
340
|
+
content = part.content_text
|
|
341
|
+
suffix = ".txt"
|
|
342
|
+
else:
|
|
343
|
+
self.app.notify("Content is empty", severity="warning")
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
editor = os.environ.get("EDITOR") or os.environ.get("VISUAL") or "vi"
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
with tempfile.NamedTemporaryFile(
|
|
350
|
+
mode="w",
|
|
351
|
+
suffix=suffix,
|
|
352
|
+
prefix="content_",
|
|
353
|
+
delete=False,
|
|
354
|
+
encoding="utf-8",
|
|
355
|
+
) as f:
|
|
356
|
+
f.write(content)
|
|
357
|
+
temp_path = f.name
|
|
358
|
+
|
|
359
|
+
with self.app.suspend():
|
|
360
|
+
subprocess.run([editor, temp_path], check=False)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
os.unlink(temp_path)
|
|
364
|
+
except OSError:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
self.app.notify("Editor closed", severity="information")
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
self.app.notify(f"Failed to open editor: {e}", severity="error")
|
|
371
|
+
|
|
372
|
+
# =========================================================================
|
|
373
|
+
# Internal async operations
|
|
374
|
+
# =========================================================================
|
|
375
|
+
|
|
376
|
+
@work(exclusive=True)
|
|
377
|
+
async def _show_tag_management_modal(
|
|
378
|
+
self, content_part_id: str, current_tags: list[dict[str, Any]]
|
|
379
|
+
) -> None:
|
|
380
|
+
"""Show the tag management modal (async to get client)."""
|
|
381
|
+
client = await self._ensure_client()
|
|
382
|
+
modal = TagManagementModal(
|
|
383
|
+
client=client,
|
|
384
|
+
content_part_id=content_part_id,
|
|
385
|
+
current_tags=current_tags,
|
|
386
|
+
)
|
|
387
|
+
self.app.push_screen(modal, self._on_tags_updated)
|
|
388
|
+
|
|
389
|
+
def _on_tags_updated(self, result: list[str] | None) -> None:
|
|
390
|
+
"""Handle tag update modal result."""
|
|
391
|
+
if result is not None:
|
|
392
|
+
self._save_tags(result)
|
|
393
|
+
|
|
394
|
+
@work(exclusive=True)
|
|
395
|
+
async def _save_tags(self, tag_paths: list[str]) -> None:
|
|
396
|
+
"""Save updated tags to the server."""
|
|
397
|
+
if not self._pending_tag_update_content_id:
|
|
398
|
+
self.app.notify("No content selected for tag update", severity="error")
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
content_id = UUID(self._pending_tag_update_content_id)
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
client = await self._ensure_client()
|
|
405
|
+
request = UpdateContentPartRequest(
|
|
406
|
+
content_text=None, content_structured=None, tag_paths=tag_paths
|
|
407
|
+
)
|
|
408
|
+
await client.update_content_part(content_id, request)
|
|
409
|
+
|
|
410
|
+
self.app.notify("Tags updated successfully", severity="information")
|
|
411
|
+
self._do_search(self._last_search)
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
self.app.notify(f"Failed to update tags: {e}", severity="error")
|
|
415
|
+
finally:
|
|
416
|
+
self._pending_tag_update_content_id = None
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class ContentScreen(NavScreen):
|
|
420
|
+
"""Screen for browsing and managing content parts.
|
|
421
|
+
|
|
422
|
+
This is a proper Textual Screen that wraps the ContentWidget
|
|
423
|
+
and provides keybindings that work regardless of focus.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
app: "AlloyRuntimeApp"
|
|
427
|
+
|
|
428
|
+
SCREEN_ID = "content"
|
|
429
|
+
|
|
430
|
+
DEFAULT_CSS = """
|
|
431
|
+
ContentScreen #screen-root {
|
|
432
|
+
height: 1fr;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
ContentScreen .browser-widget {
|
|
436
|
+
height: 100%;
|
|
437
|
+
width: 100%;
|
|
438
|
+
}
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
# Screen-specific bindings (Ctrl+ prefixed)
|
|
442
|
+
BINDINGS = NavScreen.BINDINGS + [
|
|
443
|
+
Binding("ctrl+f", "focus_search", "Search", show=True),
|
|
444
|
+
Binding("ctrl+r", "refresh", "Refresh", show=True),
|
|
445
|
+
Binding("ctrl+y", "copy_content", "Copy Content", show=True),
|
|
446
|
+
Binding("ctrl+i", "copy_id", "Copy ID", show=True),
|
|
447
|
+
Binding("ctrl+t", "manage_tags", "Tags", show=True),
|
|
448
|
+
Binding("ctrl+e", "open_in_editor", "View", show=True),
|
|
449
|
+
Binding("ctrl+d", "delete_content", "Delete", show=True),
|
|
450
|
+
Binding("escape", "focus_table", "Focus List", show=False),
|
|
451
|
+
Binding("enter", "copy_content", "Copy Content", show=False),
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
def compose_content(self) -> ComposeResult:
|
|
455
|
+
"""Compose the screen content."""
|
|
456
|
+
yield ContentWidget(id="content-widget")
|
|
457
|
+
|
|
458
|
+
def on_screen_resume(self) -> None:
|
|
459
|
+
"""Focus the data table when screen becomes active."""
|
|
460
|
+
self.call_after_refresh(self._focus_table)
|
|
461
|
+
|
|
462
|
+
def _focus_table(self) -> None:
|
|
463
|
+
"""Focus the data table."""
|
|
464
|
+
try:
|
|
465
|
+
widget = self.query_one("#content-widget", ContentWidget)
|
|
466
|
+
widget.focus_table()
|
|
467
|
+
except Exception:
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
def _get_widget(self) -> ContentWidget:
|
|
471
|
+
"""Get the content widget."""
|
|
472
|
+
return self.query_one("#content-widget", ContentWidget)
|
|
473
|
+
|
|
474
|
+
# =========================================================================
|
|
475
|
+
# Actions (keybinding handlers)
|
|
476
|
+
# =========================================================================
|
|
477
|
+
|
|
478
|
+
def action_focus_search(self) -> None:
|
|
479
|
+
"""Focus the search input."""
|
|
480
|
+
self._get_widget().focus_search()
|
|
481
|
+
|
|
482
|
+
def action_focus_table(self) -> None:
|
|
483
|
+
"""Focus the data table."""
|
|
484
|
+
self._get_widget().focus_table()
|
|
485
|
+
|
|
486
|
+
def action_refresh(self) -> None:
|
|
487
|
+
"""Refresh the current search results."""
|
|
488
|
+
self._get_widget().refresh_data()
|
|
489
|
+
|
|
490
|
+
def action_copy_id(self) -> None:
|
|
491
|
+
"""Copy the content ID to clipboard."""
|
|
492
|
+
self._get_widget().copy_content_id()
|
|
493
|
+
|
|
494
|
+
def action_copy_content(self) -> None:
|
|
495
|
+
"""Copy the content to clipboard."""
|
|
496
|
+
self._get_widget().copy_content()
|
|
497
|
+
|
|
498
|
+
def action_manage_tags(self) -> None:
|
|
499
|
+
"""Open tag management modal."""
|
|
500
|
+
self._get_widget().manage_tags()
|
|
501
|
+
|
|
502
|
+
def action_open_in_editor(self) -> None:
|
|
503
|
+
"""Open content in external editor."""
|
|
504
|
+
self._get_widget().open_in_editor()
|
|
505
|
+
|
|
506
|
+
def action_delete_content(self) -> None:
|
|
507
|
+
"""Delete the selected content."""
|
|
508
|
+
self._get_widget().delete_content()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Dashboard screen for quick navigation.
|
|
2
|
+
|
|
3
|
+
A minimal, keyboard-first landing screen that allows instant navigation
|
|
4
|
+
with single keystrokes. Shown on app launch and accessible via keybinding.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from textual.app import ComposeResult
|
|
10
|
+
from textual.binding import Binding
|
|
11
|
+
from textual.containers import Container
|
|
12
|
+
from textual.screen import Screen
|
|
13
|
+
from textual.widgets import Static
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from cli.tui.app import AlloyRuntimeApp
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DASHBOARD_CONTENT = r"""
|
|
20
|
+
_ ___ ____ _ _ _
|
|
21
|
+
/ \ |_ _| / ___| |__ __ _| |_| |_ ___ _ __
|
|
22
|
+
/ _ \ | | | | | '_ \ / _` | __| __/ _ \ '__|
|
|
23
|
+
/ ___ \ | | | |___| | | | (_| | |_| || __/ |
|
|
24
|
+
/_/ \_\___| \____|_| |_|\__,_|\__|\__\___|_|
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[C] Chat start a conversation
|
|
28
|
+
[A] Agents browse & manage
|
|
29
|
+
[T] Templates prompt library
|
|
30
|
+
[M] Content media objects
|
|
31
|
+
[S] Schemas output structures
|
|
32
|
+
|
|
33
|
+
[?] help [Q] quit
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DashboardScreen(Screen[str]):
|
|
38
|
+
"""Quick-launch dashboard for instant navigation.
|
|
39
|
+
|
|
40
|
+
Displays on app launch and allows single-keystroke navigation to any
|
|
41
|
+
main section. Returns the selected screen ID when dismissed.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
app: "AlloyRuntimeApp"
|
|
45
|
+
|
|
46
|
+
BINDINGS = [
|
|
47
|
+
Binding("c", "goto('chat')", "Chat", show=False),
|
|
48
|
+
Binding("a", "goto('agents')", "Agents", show=False),
|
|
49
|
+
Binding("t", "goto('templates')", "Templates", show=False),
|
|
50
|
+
Binding("m", "goto('content')", "Content", show=False),
|
|
51
|
+
Binding("s", "goto('schemas')", "Schemas", show=False),
|
|
52
|
+
Binding("q", "quit", "Quit", show=False),
|
|
53
|
+
Binding("question_mark", "show_help", "Help", show=False),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
DEFAULT_CSS = """
|
|
57
|
+
DashboardScreen {
|
|
58
|
+
align: center middle;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
DashboardScreen > Container {
|
|
62
|
+
width: auto;
|
|
63
|
+
height: auto;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
DashboardScreen Static {
|
|
67
|
+
width: auto;
|
|
68
|
+
height: auto;
|
|
69
|
+
text-align: center;
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def compose(self) -> ComposeResult:
|
|
74
|
+
with Container():
|
|
75
|
+
yield Static(DASHBOARD_CONTENT, id="dashboard-content")
|
|
76
|
+
|
|
77
|
+
def action_goto(self, screen_id: str) -> None:
|
|
78
|
+
"""Navigate to a screen by dismissing with the screen ID."""
|
|
79
|
+
self.dismiss(screen_id)
|
|
80
|
+
|
|
81
|
+
def action_quit(self) -> None:
|
|
82
|
+
"""Quit the application."""
|
|
83
|
+
self.app.exit()
|
|
84
|
+
|
|
85
|
+
def action_show_help(self) -> None:
|
|
86
|
+
"""Show help modal."""
|
|
87
|
+
from cli.tui.widgets.help_modal import HelpModal
|
|
88
|
+
|
|
89
|
+
self.app.push_screen(HelpModal())
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Pydantic models for TUI screen data types.
|
|
2
|
+
|
|
3
|
+
This module contains view models used by screens to represent
|
|
4
|
+
data in a structured, type-safe manner.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _tag_list_factory() -> list["TagView"]:
|
|
14
|
+
return []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TagView(BaseModel):
|
|
18
|
+
"""View model for a tag."""
|
|
19
|
+
|
|
20
|
+
tag_path: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExecutionMetadataView(BaseModel):
|
|
24
|
+
"""View model for execution metadata."""
|
|
25
|
+
|
|
26
|
+
external_id: str | None = None
|
|
27
|
+
provider_key: str = ""
|
|
28
|
+
provider_model_name: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ContentPartView(BaseModel):
|
|
32
|
+
"""View model for content parts in the Content browser.
|
|
33
|
+
|
|
34
|
+
This provides a flattened, UI-friendly representation of
|
|
35
|
+
ContentPartItemResponse for use in the ContentScreen.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
id: str = Field(description="Content part UUID as string")
|
|
39
|
+
content_type_id: str = Field(description="Content type (text, code, json, etc.)")
|
|
40
|
+
content_text: str | None = Field(
|
|
41
|
+
default=None, description="Plain text content (null for structured/media types)"
|
|
42
|
+
)
|
|
43
|
+
content_structured: dict[str, Any] | None = Field(
|
|
44
|
+
default=None, description="Structured JSON content (null for text types)"
|
|
45
|
+
)
|
|
46
|
+
tags: list[TagView] = Field(
|
|
47
|
+
default_factory=_tag_list_factory,
|
|
48
|
+
description="Associated tags",
|
|
49
|
+
)
|
|
50
|
+
execution_metadata: ExecutionMetadataView | None = Field(
|
|
51
|
+
default=None, description="Execution metadata"
|
|
52
|
+
)
|
|
53
|
+
created_at: datetime | None = Field(default=None, description="Creation timestamp")
|
|
54
|
+
updated_at: datetime | None = Field(
|
|
55
|
+
default=None, description="Last update timestamp"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def external_id(self) -> str | None:
|
|
60
|
+
"""Get the external_id from execution metadata if available."""
|
|
61
|
+
if self.execution_metadata:
|
|
62
|
+
return self.execution_metadata.external_id
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_response(cls, part: Any) -> "ContentPartView":
|
|
67
|
+
"""Create a ContentPartView from a ContentPartItemResponse.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
part: A ContentPartItemResponse from the server client
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A ContentPartView instance
|
|
74
|
+
"""
|
|
75
|
+
raw_tags = cast(list[Any], part.tags or [])
|
|
76
|
+
tags: list[TagView] = []
|
|
77
|
+
for tag in raw_tags:
|
|
78
|
+
tags.append(TagView(tag_path=cast(str, tag.tag_path)))
|
|
79
|
+
exec_meta = None
|
|
80
|
+
if part.execution_metadata:
|
|
81
|
+
exec_meta = ExecutionMetadataView(
|
|
82
|
+
external_id=part.execution_metadata.external_id,
|
|
83
|
+
provider_key=part.execution_metadata.provider_key,
|
|
84
|
+
provider_model_name=part.execution_metadata.provider_model_name,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return cls(
|
|
88
|
+
id=str(part.id),
|
|
89
|
+
content_type_id=part.content_type_id,
|
|
90
|
+
content_text=part.content_text,
|
|
91
|
+
content_structured=part.content_structured,
|
|
92
|
+
tags=tags,
|
|
93
|
+
execution_metadata=exec_meta,
|
|
94
|
+
created_at=part.created_at,
|
|
95
|
+
updated_at=part.updated_at,
|
|
96
|
+
)
|