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,256 @@
|
|
|
1
|
+
"""Shorthand filter parser for JSONB structured content filtering.
|
|
2
|
+
|
|
3
|
+
Provides a concise syntax for building structured filters without writing JSON.
|
|
4
|
+
Multiple -w options are combined with AND logic.
|
|
5
|
+
|
|
6
|
+
Syntax Reference:
|
|
7
|
+
field=value Equal (string)
|
|
8
|
+
field:=value Equal (JSON: number, bool, null, array, object)
|
|
9
|
+
field!=value Not equal
|
|
10
|
+
field>value Greater than
|
|
11
|
+
field>=value Greater than or equal
|
|
12
|
+
field<value Less than
|
|
13
|
+
field<=value Less than or equal
|
|
14
|
+
field~=pattern Case-insensitive LIKE (ilike)
|
|
15
|
+
field~~pattern Case-sensitive LIKE
|
|
16
|
+
field?=a,b,c In list (comma-separated)
|
|
17
|
+
field!?=a,b,c Not in list
|
|
18
|
+
field? Key exists
|
|
19
|
+
field!? Key does not exist
|
|
20
|
+
field@=jsonb JSONB contains
|
|
21
|
+
|
|
22
|
+
Nested paths use dot notation:
|
|
23
|
+
data.count>=10
|
|
24
|
+
metadata.user.email~=%@gmail.com%
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
-w status=complete # status equals "complete"
|
|
28
|
+
-w "count:=10" # count equals 10 (number)
|
|
29
|
+
-w "active:=true" # active equals true (boolean)
|
|
30
|
+
-w "data.score>=85" # nested path, gte comparison
|
|
31
|
+
-w "email~=%@example.com%" # case-insensitive pattern match
|
|
32
|
+
-w "status?=pending,review,approved" # status in list
|
|
33
|
+
-w "error?" # error key exists
|
|
34
|
+
-w "deprecated!?" # deprecated key does not exist
|
|
35
|
+
|
|
36
|
+
Usage with Typer:
|
|
37
|
+
from cli.infrastructure.filter_parser import parse_where_filters
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def my_command(
|
|
41
|
+
where: Annotated[
|
|
42
|
+
list[str] | None,
|
|
43
|
+
typer.Option("--where", "-w", help="Filter conditions"),
|
|
44
|
+
] = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
filter_spec = parse_where_filters(where)
|
|
47
|
+
# filter_spec is ConditionSpec | LogicalSpec | None
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
import json
|
|
51
|
+
import re
|
|
52
|
+
from typing import Any, cast
|
|
53
|
+
|
|
54
|
+
import typer
|
|
55
|
+
|
|
56
|
+
from alloy_runtime_types.dtos.structured_filter import (
|
|
57
|
+
ConditionOperator,
|
|
58
|
+
ConditionSpec,
|
|
59
|
+
LogicalSpec,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Operator patterns ordered by specificity (longest match first)
|
|
63
|
+
# Format: (regex_pattern, literal_str, operator, has_value, value_parser)
|
|
64
|
+
# The regex_pattern is for matching, literal_str is the actual operator string
|
|
65
|
+
OPERATOR_PATTERNS: list[tuple[str, str, str, bool, str]] = [
|
|
66
|
+
# Multi-char operators first (order matters - longer/more specific first)
|
|
67
|
+
(r"!\?=", "!?=", "nin", True, "list"), # not in list
|
|
68
|
+
(r"\?=", "?=", "in", True, "list"), # in list
|
|
69
|
+
(r">=", ">=", "gte", True, "auto"), # greater than or equal
|
|
70
|
+
(r"<=", "<=", "lte", True, "auto"), # less than or equal
|
|
71
|
+
(r"~=", "~=", "ilike", True, "string"), # case-insensitive like
|
|
72
|
+
(r"~~", "~~", "like", True, "string"), # case-sensitive like
|
|
73
|
+
(r"@=", "@=", "contains", True, "json"), # jsonb contains
|
|
74
|
+
(r":=", ":=", "eq", True, "json"), # equal with JSON value
|
|
75
|
+
(r"!=", "!=", "ne", True, "auto"), # not equal
|
|
76
|
+
(r"!\?", "!?", "not_exists", False, "none"), # key does not exist
|
|
77
|
+
(r"\?", "?", "exists", False, "none"), # key exists (must be after ?=)
|
|
78
|
+
# Single-char operators last
|
|
79
|
+
(r">", ">", "gt", True, "auto"), # greater than
|
|
80
|
+
(r"<", "<", "lt", True, "auto"), # less than
|
|
81
|
+
(r"=", "=", "eq", True, "auto"), # equal (default string)
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Compile regex pattern for finding operators
|
|
85
|
+
# Matches field name, then operator, then optional value
|
|
86
|
+
FILTER_REGEX = re.compile(
|
|
87
|
+
r"^([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)" # field path
|
|
88
|
+
r"(" + "|".join(p[0] for p in OPERATOR_PATTERNS) + r")" # operator
|
|
89
|
+
r"(.*)$" # value (may be empty)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _parse_auto_value(value: str) -> Any:
|
|
94
|
+
"""Parse a value with automatic type detection.
|
|
95
|
+
|
|
96
|
+
Tries JSON parsing first, falls back to string.
|
|
97
|
+
"""
|
|
98
|
+
if not value:
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
# Try to parse as JSON (handles numbers, bools, null)
|
|
102
|
+
try:
|
|
103
|
+
return json.loads(value)
|
|
104
|
+
except json.JSONDecodeError:
|
|
105
|
+
# Return as string
|
|
106
|
+
return value
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _parse_json_value(value: str, field: str) -> Any:
|
|
110
|
+
"""Parse a value as JSON, raising error if invalid."""
|
|
111
|
+
try:
|
|
112
|
+
return json.loads(value)
|
|
113
|
+
except json.JSONDecodeError as e:
|
|
114
|
+
raise typer.BadParameter(
|
|
115
|
+
f"Invalid JSON value for field '{field}': {value}. Error: {e}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _parse_list_value(value: str) -> list[str]:
|
|
120
|
+
"""Parse a comma-separated list of values."""
|
|
121
|
+
if not value:
|
|
122
|
+
return []
|
|
123
|
+
return [v.strip() for v in value.split(",") if v.strip()]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def parse_single_filter(expr: str) -> ConditionSpec:
|
|
127
|
+
"""Parse a single filter expression into a ConditionSpec.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
expr: Filter expression (e.g., "status=complete", "count>=10")
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
ConditionSpec for the filter
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
typer.BadParameter: If expression is invalid
|
|
137
|
+
"""
|
|
138
|
+
expr = expr.strip()
|
|
139
|
+
if not expr:
|
|
140
|
+
raise typer.BadParameter("Empty filter expression")
|
|
141
|
+
|
|
142
|
+
match = FILTER_REGEX.match(expr)
|
|
143
|
+
if not match:
|
|
144
|
+
raise typer.BadParameter(
|
|
145
|
+
f"Invalid filter syntax: '{expr}'. "
|
|
146
|
+
"Expected format: field<op>value (e.g., status=complete, count>=10)"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
field = match.group(1)
|
|
150
|
+
op_str = match.group(2)
|
|
151
|
+
raw_value = match.group(3)
|
|
152
|
+
|
|
153
|
+
# Find the operator details by matching the literal string
|
|
154
|
+
op_name = None
|
|
155
|
+
has_value = True
|
|
156
|
+
value_parser = "auto"
|
|
157
|
+
|
|
158
|
+
for _regex_pattern, literal_str, name, needs_value, parser in OPERATOR_PATTERNS:
|
|
159
|
+
if op_str == literal_str:
|
|
160
|
+
op_name = name
|
|
161
|
+
has_value = needs_value
|
|
162
|
+
value_parser = parser
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
if op_name is None:
|
|
166
|
+
raise typer.BadParameter(f"Unknown operator in: '{expr}'")
|
|
167
|
+
|
|
168
|
+
# Validate value presence
|
|
169
|
+
if has_value and not raw_value:
|
|
170
|
+
raise typer.BadParameter(f"Missing value for operator '{op_str}' in: '{expr}'")
|
|
171
|
+
|
|
172
|
+
if not has_value and raw_value:
|
|
173
|
+
raise typer.BadParameter(
|
|
174
|
+
f"Operator '{op_str}' does not take a value, but got: '{raw_value}'"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Parse the value based on type
|
|
178
|
+
value: Any = None
|
|
179
|
+
if has_value:
|
|
180
|
+
if value_parser == "json":
|
|
181
|
+
value = _parse_json_value(raw_value, field)
|
|
182
|
+
elif value_parser == "list":
|
|
183
|
+
value = _parse_list_value(raw_value)
|
|
184
|
+
elif value_parser == "string":
|
|
185
|
+
value = raw_value
|
|
186
|
+
else: # auto
|
|
187
|
+
value = _parse_auto_value(raw_value)
|
|
188
|
+
|
|
189
|
+
# For exists/not_exists, value should be None (or True for consistency)
|
|
190
|
+
if op_name in ("exists", "not_exists"):
|
|
191
|
+
value = True
|
|
192
|
+
|
|
193
|
+
# Cast op_name to the literal type since we've validated it above
|
|
194
|
+
op_literal = cast(ConditionOperator, op_name)
|
|
195
|
+
return ConditionSpec(field=field, op=op_literal, value=value)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def parse_where_filters(
|
|
199
|
+
filters: list[str] | None,
|
|
200
|
+
) -> ConditionSpec | LogicalSpec | None:
|
|
201
|
+
"""Parse multiple -w filter expressions into a filter spec.
|
|
202
|
+
|
|
203
|
+
Multiple filters are combined with AND logic.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
filters: List of filter expressions from repeated -w options
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
- None if no filters provided
|
|
210
|
+
- ConditionSpec if single filter
|
|
211
|
+
- LogicalSpec (AND) if multiple filters
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
typer.BadParameter: If any expression is invalid
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
>>> parse_where_filters(["status=complete"])
|
|
218
|
+
ConditionSpec(field="status", op="eq", value="complete")
|
|
219
|
+
|
|
220
|
+
>>> parse_where_filters(["status=complete", "count>=10"])
|
|
221
|
+
LogicalSpec(op="and", conditions=[...])
|
|
222
|
+
"""
|
|
223
|
+
if not filters:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
conditions = [parse_single_filter(f) for f in filters]
|
|
227
|
+
|
|
228
|
+
if len(conditions) == 1:
|
|
229
|
+
return conditions[0]
|
|
230
|
+
|
|
231
|
+
# Cast to the expected type for LogicalSpec
|
|
232
|
+
return LogicalSpec(op="and", conditions=list(conditions))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def merge_filter_specs(
|
|
236
|
+
where_spec: ConditionSpec | LogicalSpec | None,
|
|
237
|
+
json_spec: ConditionSpec | LogicalSpec | None,
|
|
238
|
+
) -> ConditionSpec | LogicalSpec | None:
|
|
239
|
+
"""Merge filter specs from -w and --structured-filter options.
|
|
240
|
+
|
|
241
|
+
If both are provided, combines them with AND logic.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
where_spec: Filter spec from -w options
|
|
245
|
+
json_spec: Filter spec from --structured-filter JSON
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Combined filter spec, or None if both are None
|
|
249
|
+
"""
|
|
250
|
+
if where_spec is None:
|
|
251
|
+
return json_spec
|
|
252
|
+
if json_spec is None:
|
|
253
|
+
return where_spec
|
|
254
|
+
|
|
255
|
+
# Both exist - combine with AND
|
|
256
|
+
return LogicalSpec(op="and", conditions=[where_spec, json_spec])
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Base formatter interface for CLI output.
|
|
2
|
+
|
|
3
|
+
Defines the contract that all output formatters must implement.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ColumnMeta:
|
|
16
|
+
"""Metadata about a column for formatters that need layout info.
|
|
17
|
+
|
|
18
|
+
This is a simplified view of ColumnConfig passed to formatters.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
label: str
|
|
22
|
+
compact_line: int = 1
|
|
23
|
+
compact_style: str | None = None
|
|
24
|
+
width: int | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Formatter(ABC):
|
|
28
|
+
"""Abstract base class for output formatters.
|
|
29
|
+
|
|
30
|
+
Formatters transform data into specific output formats (Rich tables, JSON, YAML, etc.).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def format_entity(self, data: dict[str, Any], title: str | None = None) -> Any:
|
|
35
|
+
"""Format a single entity's details as key-value pairs.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
data: Dictionary mapping field labels to values
|
|
39
|
+
title: Optional title for the entity display
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Formatted output (str for JSON, Rich renderable for Rich, etc.)
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def format_list(
|
|
48
|
+
self,
|
|
49
|
+
items: list[dict[str, Any]],
|
|
50
|
+
title: str | None = None,
|
|
51
|
+
columns: list[ColumnMeta] | None = None,
|
|
52
|
+
) -> Any:
|
|
53
|
+
"""Format a list of items as a table or similar structure.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
items: List of dictionaries, each representing one row
|
|
57
|
+
title: Optional title for the list display
|
|
58
|
+
columns: Optional column metadata for layout-aware formatters
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Formatted output (str for JSON, Rich renderable for Rich, etc.)
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def format_success(self, message: str) -> str:
|
|
67
|
+
"""Format a success message.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
message: Success message text
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Formatted success message
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def format_info(self, message: str) -> str:
|
|
79
|
+
"""Format an informational message.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
message: Info message text
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Formatted info message
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def format_warning(self, message: str) -> str:
|
|
91
|
+
"""Format a warning message.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
message: Warning message text
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Formatted warning message
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Compact block formatter for copy-friendly terminal output.
|
|
2
|
+
|
|
3
|
+
Renders list items as 2-line blocks:
|
|
4
|
+
- Line 1 (primary): ID + key metadata (fixed-width, colored)
|
|
5
|
+
- Line 2 (detail): Description/content (dimmed, full-width)
|
|
6
|
+
|
|
7
|
+
No borders = easy copy-paste. Wraps naturally on narrow terminals.
|
|
8
|
+
Inspired by gh (GitHub CLI) and kubectl output styles.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from rich.console import Console, Group, RenderableType
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
|
|
16
|
+
from cli.infrastructure.console import get_console
|
|
17
|
+
from cli.infrastructure.formatters.base import ColumnMeta, Formatter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Default styles for compact format columns
|
|
21
|
+
DEFAULT_STYLES = {
|
|
22
|
+
"id": "cyan",
|
|
23
|
+
"uuid": "cyan",
|
|
24
|
+
"name": "white bold",
|
|
25
|
+
"type": "magenta",
|
|
26
|
+
"model": "green",
|
|
27
|
+
"provider": "green",
|
|
28
|
+
"date": "blue",
|
|
29
|
+
"created": "blue",
|
|
30
|
+
"time": "blue",
|
|
31
|
+
"version": "yellow",
|
|
32
|
+
"visibility": "dim",
|
|
33
|
+
"tags": "dim italic",
|
|
34
|
+
"description": "dim",
|
|
35
|
+
"content": "dim",
|
|
36
|
+
"status": "yellow",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _infer_style(label: str) -> str:
|
|
41
|
+
"""Infer a style based on column label.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
label: Column header label
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Rich style string
|
|
48
|
+
"""
|
|
49
|
+
label_lower = label.lower()
|
|
50
|
+
for key, style in DEFAULT_STYLES.items():
|
|
51
|
+
if key in label_lower:
|
|
52
|
+
return style
|
|
53
|
+
return "white"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CompactFormatter(Formatter):
|
|
57
|
+
"""Formatter that renders items as compact 2-line blocks.
|
|
58
|
+
|
|
59
|
+
Line 1: Primary metadata (ID, type, model, date) - colored, spaced
|
|
60
|
+
Line 2: Detail content (description, content preview) - dimmed, indented
|
|
61
|
+
|
|
62
|
+
Benefits:
|
|
63
|
+
- No borders = double-click to select any field
|
|
64
|
+
- Line 2 wraps naturally on narrow terminals
|
|
65
|
+
- Easy to scan vertically (IDs align, models align)
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, console: Console | None = None):
|
|
69
|
+
"""Initialize compact formatter.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
console: Optional Rich console instance. Uses shared console if not provided.
|
|
73
|
+
"""
|
|
74
|
+
self.console = console or get_console()
|
|
75
|
+
|
|
76
|
+
def format_entity(self, data: dict[str, Any], title: str | None = None) -> Text:
|
|
77
|
+
"""Format entity details as compact key-value block.
|
|
78
|
+
|
|
79
|
+
For single entities, we use a simple key: value format without borders.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
data: Dictionary mapping field labels to values
|
|
83
|
+
title: Optional title for the entity
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Rich Text object ready for rendering
|
|
87
|
+
"""
|
|
88
|
+
output = Text()
|
|
89
|
+
|
|
90
|
+
if title:
|
|
91
|
+
output.append(f"{title}\n", style="bold")
|
|
92
|
+
output.append("-" * len(title) + "\n", style="dim")
|
|
93
|
+
|
|
94
|
+
for field, value in data.items():
|
|
95
|
+
display_value = "-" if value is None else str(value)
|
|
96
|
+
output.append(f"{field}: ", style="cyan")
|
|
97
|
+
output.append(f"{display_value}\n", style="white")
|
|
98
|
+
|
|
99
|
+
return output
|
|
100
|
+
|
|
101
|
+
def format_list(
|
|
102
|
+
self,
|
|
103
|
+
items: list[dict[str, Any]],
|
|
104
|
+
title: str | None = None,
|
|
105
|
+
columns: list[ColumnMeta] | None = None,
|
|
106
|
+
) -> Group:
|
|
107
|
+
"""Format list of items as compact 2-line blocks.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
items: List of dictionaries representing rows
|
|
111
|
+
title: Optional title for the list
|
|
112
|
+
columns: Column metadata for layout (line assignment, styles)
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Rich Group of Text objects ready for rendering
|
|
116
|
+
"""
|
|
117
|
+
if not items:
|
|
118
|
+
return Group(Text(self.format_info("No items to display")))
|
|
119
|
+
|
|
120
|
+
renderables: list[RenderableType] = []
|
|
121
|
+
|
|
122
|
+
# Add title if provided
|
|
123
|
+
if title:
|
|
124
|
+
title_text = Text()
|
|
125
|
+
title_text.append(f"{title}\n", style="bold")
|
|
126
|
+
renderables.append(title_text)
|
|
127
|
+
|
|
128
|
+
# Build column lookup by label
|
|
129
|
+
col_meta_map: dict[str, ColumnMeta] = {}
|
|
130
|
+
if columns:
|
|
131
|
+
for col in columns:
|
|
132
|
+
col_meta_map[col.label] = col
|
|
133
|
+
|
|
134
|
+
# Separate columns into line 1 (primary) and line 2 (detail)
|
|
135
|
+
line1_labels: list[str] = []
|
|
136
|
+
line2_labels: list[str] = []
|
|
137
|
+
if columns:
|
|
138
|
+
for col in columns:
|
|
139
|
+
if col.compact_line == 2:
|
|
140
|
+
line2_labels.append(col.label)
|
|
141
|
+
else:
|
|
142
|
+
line1_labels.append(col.label)
|
|
143
|
+
else:
|
|
144
|
+
# Fallback: all columns on line 1
|
|
145
|
+
if items:
|
|
146
|
+
line1_labels = list(items[0].keys())
|
|
147
|
+
|
|
148
|
+
# Render each item as a 2-line block
|
|
149
|
+
for item in items:
|
|
150
|
+
block = Text()
|
|
151
|
+
|
|
152
|
+
# Line 1: Primary metadata (spaced columns)
|
|
153
|
+
line1_parts: list[tuple[str, str]] = []
|
|
154
|
+
for label in line1_labels:
|
|
155
|
+
raw_value = item.get(label)
|
|
156
|
+
if raw_value is None:
|
|
157
|
+
value = "-"
|
|
158
|
+
else:
|
|
159
|
+
value = str(raw_value)
|
|
160
|
+
|
|
161
|
+
# Get style from column meta or infer from label
|
|
162
|
+
meta = col_meta_map.get(label)
|
|
163
|
+
if meta and meta.compact_style:
|
|
164
|
+
style: str = meta.compact_style
|
|
165
|
+
else:
|
|
166
|
+
style = _infer_style(label)
|
|
167
|
+
|
|
168
|
+
# Apply width padding if specified
|
|
169
|
+
if meta and meta.width:
|
|
170
|
+
value = f"{value:<{meta.width}}"
|
|
171
|
+
|
|
172
|
+
line1_parts.append((value, style))
|
|
173
|
+
|
|
174
|
+
# Join line 1 parts with double space
|
|
175
|
+
for i, (value, style) in enumerate(line1_parts):
|
|
176
|
+
if i > 0:
|
|
177
|
+
block.append(" ", style="default")
|
|
178
|
+
block.append(value, style=style)
|
|
179
|
+
|
|
180
|
+
block.append("\n")
|
|
181
|
+
|
|
182
|
+
# Line 2: Detail content (indented, dimmed)
|
|
183
|
+
if line2_labels:
|
|
184
|
+
detail_parts: list[tuple[str, str]] = []
|
|
185
|
+
for label in line2_labels:
|
|
186
|
+
raw_value = item.get(label)
|
|
187
|
+
if raw_value is None or raw_value == "-":
|
|
188
|
+
continue
|
|
189
|
+
value = str(raw_value)
|
|
190
|
+
|
|
191
|
+
# Get style - default to dim for detail line
|
|
192
|
+
meta = col_meta_map.get(label)
|
|
193
|
+
if meta and meta.compact_style:
|
|
194
|
+
style = meta.compact_style
|
|
195
|
+
else:
|
|
196
|
+
style = "dim"
|
|
197
|
+
|
|
198
|
+
detail_parts.append((value, style))
|
|
199
|
+
|
|
200
|
+
if detail_parts:
|
|
201
|
+
block.append(" ") # 3-space indent
|
|
202
|
+
for i, (value, style) in enumerate(detail_parts):
|
|
203
|
+
if i > 0:
|
|
204
|
+
block.append(" · ", style="dim")
|
|
205
|
+
block.append(value, style=style)
|
|
206
|
+
block.append("\n")
|
|
207
|
+
|
|
208
|
+
# Add blank line between items
|
|
209
|
+
block.append("\n")
|
|
210
|
+
renderables.append(block)
|
|
211
|
+
|
|
212
|
+
return Group(*renderables)
|
|
213
|
+
|
|
214
|
+
def format_success(self, message: str) -> str:
|
|
215
|
+
"""Format success message with checkmark.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
message: Success message text
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Rich markup string
|
|
222
|
+
"""
|
|
223
|
+
return f"[green]✓[/green] {message}"
|
|
224
|
+
|
|
225
|
+
def format_info(self, message: str) -> str:
|
|
226
|
+
"""Format info message with info icon.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
message: Info message text
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Rich markup string
|
|
233
|
+
"""
|
|
234
|
+
return f"[blue]ℹ[/blue] {message}"
|
|
235
|
+
|
|
236
|
+
def format_warning(self, message: str) -> str:
|
|
237
|
+
"""Format warning message with warning icon.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
message: Warning message text
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Rich markup string
|
|
244
|
+
"""
|
|
245
|
+
return f"[yellow]⚠[/yellow] {message}"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""JSON formatter for machine-readable output.
|
|
2
|
+
|
|
3
|
+
Provides JSON output suitable for scripting and automation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from cli.infrastructure.formatters.base import ColumnMeta, Formatter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JsonFormatter(Formatter):
|
|
13
|
+
"""Formatter that outputs JSON for scripting and automation."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, indent: int = 2):
|
|
16
|
+
"""Initialize JSON formatter.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
indent: Number of spaces for JSON indentation
|
|
20
|
+
"""
|
|
21
|
+
self.indent = indent
|
|
22
|
+
|
|
23
|
+
def format_entity(self, data: dict[str, Any], title: str | None = None) -> str:
|
|
24
|
+
"""Format entity as JSON object.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
data: Dictionary mapping field labels to values
|
|
28
|
+
title: Ignored for JSON output
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
JSON string
|
|
32
|
+
"""
|
|
33
|
+
return json.dumps(data, indent=self.indent, default=str)
|
|
34
|
+
|
|
35
|
+
def format_list(
|
|
36
|
+
self,
|
|
37
|
+
items: list[dict[str, Any]],
|
|
38
|
+
title: str | None = None,
|
|
39
|
+
columns: list[ColumnMeta] | None = None,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Format list as JSON array.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
items: List of dictionaries
|
|
45
|
+
title: Ignored for JSON output
|
|
46
|
+
columns: Ignored for JSON output
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
JSON string
|
|
50
|
+
"""
|
|
51
|
+
return json.dumps(items, indent=self.indent, default=str)
|
|
52
|
+
|
|
53
|
+
def format_success(self, message: str) -> str:
|
|
54
|
+
"""Format success message as JSON.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
message: Success message text
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
JSON string with status and message
|
|
61
|
+
"""
|
|
62
|
+
return json.dumps({"status": "success", "message": message}, indent=self.indent)
|
|
63
|
+
|
|
64
|
+
def format_info(self, message: str) -> str:
|
|
65
|
+
"""Format info message as JSON.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
message: Info message text
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
JSON string with status and message
|
|
72
|
+
"""
|
|
73
|
+
return json.dumps({"status": "info", "message": message}, indent=self.indent)
|
|
74
|
+
|
|
75
|
+
def format_warning(self, message: str) -> str:
|
|
76
|
+
"""Format warning message as JSON.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
message: Warning message text
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
JSON string with status and message
|
|
83
|
+
"""
|
|
84
|
+
return json.dumps({"status": "warning", "message": message}, indent=self.indent)
|