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,155 @@
|
|
|
1
|
+
"""Content edit command implementation."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from cli.commands.content.edit.editor import (
|
|
10
|
+
ContentValidationError,
|
|
11
|
+
format_content_for_editor,
|
|
12
|
+
open_editor_for_content,
|
|
13
|
+
parse_edited_content,
|
|
14
|
+
)
|
|
15
|
+
from cli.infrastructure.editor import EditorError
|
|
16
|
+
from cli.commands.content.edit.presenter import present_content_edit_success
|
|
17
|
+
from cli.commands.flag_utils import parse_tags, require_exclusive, validate_uuid
|
|
18
|
+
from cli.infrastructure.command import async_command, authenticated_client
|
|
19
|
+
from cli.infrastructure.error_display import display_error
|
|
20
|
+
from cli.infrastructure.output import OutputService
|
|
21
|
+
from alloy_runtime_types.dtos.content import UpdateContentPartRequest
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def content_edit_command(
|
|
25
|
+
content_part_id: str = typer.Argument(
|
|
26
|
+
...,
|
|
27
|
+
help="Content part UUID",
|
|
28
|
+
),
|
|
29
|
+
tags: str | None = typer.Option(
|
|
30
|
+
None,
|
|
31
|
+
"-t",
|
|
32
|
+
"--tags",
|
|
33
|
+
help="Replace tags (comma-separated)",
|
|
34
|
+
),
|
|
35
|
+
no_edit: bool = typer.Option(
|
|
36
|
+
False,
|
|
37
|
+
"--no-edit",
|
|
38
|
+
help="Skip editor, only update tags",
|
|
39
|
+
),
|
|
40
|
+
content: str | None = typer.Option(
|
|
41
|
+
None,
|
|
42
|
+
"-c",
|
|
43
|
+
"--content",
|
|
44
|
+
help="Inline content replacement",
|
|
45
|
+
),
|
|
46
|
+
content_file: Path | None = typer.Option(
|
|
47
|
+
None,
|
|
48
|
+
"-f",
|
|
49
|
+
"--file",
|
|
50
|
+
help="Read replacement from file",
|
|
51
|
+
exists=True,
|
|
52
|
+
readable=True,
|
|
53
|
+
),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Edit a content part.
|
|
56
|
+
|
|
57
|
+
By default opens $EDITOR. Use --content or --file to bypass.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
ai content edit 019405e0-...
|
|
61
|
+
ai content edit <id> -t "email.final,reviewed"
|
|
62
|
+
ai content edit <id> --no-edit -t "archived"
|
|
63
|
+
ai content edit <id> -c "New content"
|
|
64
|
+
ai content edit <id> -f output.txt
|
|
65
|
+
"""
|
|
66
|
+
content_uuid = validate_uuid(content_part_id, "content")
|
|
67
|
+
|
|
68
|
+
# Validate exclusive content modes
|
|
69
|
+
require_exclusive(
|
|
70
|
+
("--no-edit", no_edit),
|
|
71
|
+
("--content", content),
|
|
72
|
+
("--file", content_file),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Parse tags
|
|
76
|
+
tag_paths: list[str] | None = None
|
|
77
|
+
if tags is not None:
|
|
78
|
+
tag_paths = parse_tags(tags)
|
|
79
|
+
|
|
80
|
+
# Validate at least one operation
|
|
81
|
+
if no_edit and tag_paths is None:
|
|
82
|
+
raise typer.BadParameter("--tags required with --no-edit")
|
|
83
|
+
|
|
84
|
+
_execute_edit(
|
|
85
|
+
content_part_id=content_uuid,
|
|
86
|
+
tag_paths=tag_paths,
|
|
87
|
+
no_edit=no_edit,
|
|
88
|
+
inline_content=content,
|
|
89
|
+
content_file=content_file,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@async_command
|
|
94
|
+
async def _execute_edit(
|
|
95
|
+
content_part_id: UUID,
|
|
96
|
+
tag_paths: list[str] | None,
|
|
97
|
+
no_edit: bool,
|
|
98
|
+
inline_content: str | None,
|
|
99
|
+
content_file: Path | None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Execute edit content part operation."""
|
|
102
|
+
async with authenticated_client() as (_config, client):
|
|
103
|
+
current = await client.get_content_part(content_part_id)
|
|
104
|
+
|
|
105
|
+
new_content_text: str | None = None
|
|
106
|
+
new_content_structured: dict[str, Any] | None = None
|
|
107
|
+
updated_content = False
|
|
108
|
+
|
|
109
|
+
if not no_edit:
|
|
110
|
+
try:
|
|
111
|
+
if inline_content is not None:
|
|
112
|
+
edited_content = inline_content
|
|
113
|
+
is_structured = current.content_structured is not None
|
|
114
|
+
elif content_file is not None:
|
|
115
|
+
edited_content = content_file.read_text()
|
|
116
|
+
is_structured = current.content_structured is not None
|
|
117
|
+
else:
|
|
118
|
+
initial_content, is_structured = format_content_for_editor(
|
|
119
|
+
current.content_text,
|
|
120
|
+
current.content_structured,
|
|
121
|
+
)
|
|
122
|
+
edited_content = open_editor_for_content(
|
|
123
|
+
initial_content, is_structured
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if edited_content is None:
|
|
127
|
+
output = OutputService.get()
|
|
128
|
+
output.warning("Editor cancelled")
|
|
129
|
+
raise typer.Exit(code=1)
|
|
130
|
+
|
|
131
|
+
new_content_text, new_content_structured = parse_edited_content(
|
|
132
|
+
edited_content, is_structured
|
|
133
|
+
)
|
|
134
|
+
updated_content = True
|
|
135
|
+
|
|
136
|
+
except EditorError as e:
|
|
137
|
+
display_error(e)
|
|
138
|
+
raise typer.Exit(code=1)
|
|
139
|
+
except ContentValidationError as e:
|
|
140
|
+
display_error(e)
|
|
141
|
+
raise typer.Exit(code=1)
|
|
142
|
+
|
|
143
|
+
request = UpdateContentPartRequest(
|
|
144
|
+
content_text=new_content_text,
|
|
145
|
+
content_structured=new_content_structured,
|
|
146
|
+
tag_paths=tag_paths,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
response = await client.update_content_part(content_part_id, request)
|
|
150
|
+
|
|
151
|
+
present_content_edit_success(
|
|
152
|
+
response,
|
|
153
|
+
updated_content=updated_content,
|
|
154
|
+
updated_tags=(tag_paths is not None),
|
|
155
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""External editor integration for content part editing.
|
|
2
|
+
|
|
3
|
+
Opens the user's preferred $EDITOR to edit content,
|
|
4
|
+
with content-type-aware file extensions for syntax highlighting.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
from cli.infrastructure.editor import get_editor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContentValidationError(Exception):
|
|
17
|
+
"""Raised when edited content fails validation."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ContentValidationError",
|
|
22
|
+
"open_editor_for_content",
|
|
23
|
+
"format_content_for_editor",
|
|
24
|
+
"parse_edited_content",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def open_editor_for_content(
|
|
29
|
+
initial_content: str,
|
|
30
|
+
is_structured: bool,
|
|
31
|
+
) -> str | None:
|
|
32
|
+
"""Open the user's editor with content, using appropriate file extension.
|
|
33
|
+
|
|
34
|
+
Creates a temporary file with the right extension for syntax highlighting:
|
|
35
|
+
- .json for structured content
|
|
36
|
+
- .txt for text content
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
initial_content: Initial content to populate the file with
|
|
40
|
+
is_structured: Whether the content is JSON (structured) or plain text
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The edited content from the file, or None if the editor failed
|
|
44
|
+
or the user quit without saving.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
EditorError: If no editor is available
|
|
48
|
+
ContentValidationError: If JSON content is malformed after editing
|
|
49
|
+
"""
|
|
50
|
+
editor = get_editor()
|
|
51
|
+
|
|
52
|
+
# Choose extension based on content type
|
|
53
|
+
suffix = ".json" if is_structured else ".txt"
|
|
54
|
+
|
|
55
|
+
# Create temp file with appropriate extension
|
|
56
|
+
with tempfile.NamedTemporaryFile(
|
|
57
|
+
mode="w",
|
|
58
|
+
suffix=suffix,
|
|
59
|
+
delete=False,
|
|
60
|
+
) as f:
|
|
61
|
+
f.write(initial_content)
|
|
62
|
+
temp_path = Path(f.name)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Get file modification time before editing
|
|
66
|
+
mtime_before = temp_path.stat().st_mtime
|
|
67
|
+
|
|
68
|
+
# Open editor and wait for it to close
|
|
69
|
+
editor_parts = editor.split()
|
|
70
|
+
cmd = editor_parts + [str(temp_path)]
|
|
71
|
+
|
|
72
|
+
result = subprocess.call(cmd)
|
|
73
|
+
|
|
74
|
+
if result != 0:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
# Check if file was modified
|
|
78
|
+
mtime_after = temp_path.stat().st_mtime
|
|
79
|
+
if mtime_after == mtime_before:
|
|
80
|
+
# File wasn't modified - still return content in case
|
|
81
|
+
# user just didn't change anything
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Read the edited content
|
|
85
|
+
content = temp_path.read_text()
|
|
86
|
+
|
|
87
|
+
# Validate JSON if structured content
|
|
88
|
+
if is_structured:
|
|
89
|
+
try:
|
|
90
|
+
json.loads(content)
|
|
91
|
+
except json.JSONDecodeError as e:
|
|
92
|
+
raise ContentValidationError(
|
|
93
|
+
f"Invalid JSON after editing: {e.msg} at line {e.lineno}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return content
|
|
97
|
+
|
|
98
|
+
finally:
|
|
99
|
+
# Clean up temp file
|
|
100
|
+
try:
|
|
101
|
+
temp_path.unlink()
|
|
102
|
+
except OSError:
|
|
103
|
+
pass # Best effort cleanup
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def format_content_for_editor(
|
|
107
|
+
content_text: str | None, content_structured: dict[str, Any] | None
|
|
108
|
+
) -> tuple[str, bool]:
|
|
109
|
+
"""Format content for editing in $EDITOR.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
content_text: Plain text content (if text type)
|
|
113
|
+
content_structured: Structured JSON content (if structured type)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Tuple of (formatted_content, is_structured)
|
|
117
|
+
"""
|
|
118
|
+
if content_structured is not None:
|
|
119
|
+
# Format JSON with indentation for readability
|
|
120
|
+
return json.dumps(content_structured, indent=2, ensure_ascii=False), True
|
|
121
|
+
elif content_text is not None:
|
|
122
|
+
return content_text, False
|
|
123
|
+
else:
|
|
124
|
+
return "", False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def parse_edited_content(
|
|
128
|
+
edited_content: str,
|
|
129
|
+
is_structured: bool,
|
|
130
|
+
) -> tuple[str | None, dict[str, Any] | None]:
|
|
131
|
+
"""Parse edited content back to appropriate type.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
edited_content: Content string from editor
|
|
135
|
+
is_structured: Whether content should be parsed as JSON
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Tuple of (content_text, content_structured) with appropriate field set
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ContentValidationError: If JSON parsing fails for structured content
|
|
142
|
+
"""
|
|
143
|
+
if is_structured:
|
|
144
|
+
try:
|
|
145
|
+
parsed = json.loads(edited_content)
|
|
146
|
+
return None, cast(dict[str, Any], parsed)
|
|
147
|
+
except json.JSONDecodeError as e:
|
|
148
|
+
raise ContentValidationError(f"Invalid JSON: {e.msg} at line {e.lineno}")
|
|
149
|
+
else:
|
|
150
|
+
return edited_content, None
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Presenter for content edit command output."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from cli.infrastructure.formatting.fields import (
|
|
11
|
+
format_cost,
|
|
12
|
+
format_datetime,
|
|
13
|
+
format_optional,
|
|
14
|
+
format_tokens,
|
|
15
|
+
format_uuid,
|
|
16
|
+
)
|
|
17
|
+
from alloy_runtime_types.dtos.content import UpdateContentPartResponse
|
|
18
|
+
from alloy_runtime_types.dtos.templates import TagResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def present_content_edit_success(
|
|
22
|
+
response: UpdateContentPartResponse, updated_content: bool, updated_tags: bool
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Present content edit success with piping support.
|
|
25
|
+
|
|
26
|
+
Metadata goes to stderr (visible in terminal but not piped).
|
|
27
|
+
Content goes to stdout (for piping to other commands).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
response: Updated content part response from server
|
|
31
|
+
updated_content: Whether content was updated
|
|
32
|
+
updated_tags: Whether tags were updated
|
|
33
|
+
"""
|
|
34
|
+
# Create console for stderr output (metadata)
|
|
35
|
+
stderr_console = Console(file=sys.stderr, force_terminal=True)
|
|
36
|
+
|
|
37
|
+
# Build update summary
|
|
38
|
+
updates: list[str] = []
|
|
39
|
+
if updated_content:
|
|
40
|
+
updates.append("content")
|
|
41
|
+
if updated_tags:
|
|
42
|
+
updates.append("tags")
|
|
43
|
+
update_summary = " and ".join(updates) if updates else "no changes"
|
|
44
|
+
|
|
45
|
+
# Print success message to stderr
|
|
46
|
+
stderr_console.print(
|
|
47
|
+
f"[green]✓[/green] Content part updated ({update_summary}): {format_uuid(response.id, short=True)}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Build and display metadata table to stderr
|
|
51
|
+
_display_metadata(response, stderr_console)
|
|
52
|
+
|
|
53
|
+
# Print content to stdout for piping
|
|
54
|
+
_display_content(response)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _display_metadata(response: UpdateContentPartResponse, console: Console) -> None:
|
|
58
|
+
"""Display content part metadata to stderr.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
response: Content part response
|
|
62
|
+
console: Console for stderr output
|
|
63
|
+
"""
|
|
64
|
+
meta_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
65
|
+
meta_table.add_column("Field", style="dim", width=20)
|
|
66
|
+
meta_table.add_column("Value")
|
|
67
|
+
|
|
68
|
+
# Basic info
|
|
69
|
+
meta_table.add_row("ID", str(response.id))
|
|
70
|
+
meta_table.add_row("Message ID", format_uuid(response.message_id, short=True))
|
|
71
|
+
meta_table.add_row("Part Index", str(response.part_index))
|
|
72
|
+
meta_table.add_row("Content Type", response.content_type_id)
|
|
73
|
+
meta_table.add_row(
|
|
74
|
+
"Storage Type",
|
|
75
|
+
"structured" if response.content_structured else "text",
|
|
76
|
+
)
|
|
77
|
+
meta_table.add_row("Created", format_datetime(response.created_at))
|
|
78
|
+
|
|
79
|
+
# Tags
|
|
80
|
+
meta_table.add_row("Tags", _format_tags(response.tags))
|
|
81
|
+
|
|
82
|
+
# Execution metadata
|
|
83
|
+
exec_meta = response.execution_metadata
|
|
84
|
+
meta_table.add_row("", "") # Separator
|
|
85
|
+
meta_table.add_row("[bold]Execution Info[/bold]", "")
|
|
86
|
+
meta_table.add_row(
|
|
87
|
+
"Execution ID",
|
|
88
|
+
format_uuid(exec_meta.agent_execution_id, short=True),
|
|
89
|
+
)
|
|
90
|
+
meta_table.add_row(
|
|
91
|
+
"Agent",
|
|
92
|
+
format_optional(exec_meta.agent_name, placeholder="(direct model call)"),
|
|
93
|
+
)
|
|
94
|
+
meta_table.add_row("Provider", exec_meta.provider_key)
|
|
95
|
+
meta_table.add_row("Model", exec_meta.provider_model_name)
|
|
96
|
+
meta_table.add_row(
|
|
97
|
+
"Cost",
|
|
98
|
+
format_optional(exec_meta.total_cost_usd, formatter=format_cost),
|
|
99
|
+
)
|
|
100
|
+
meta_table.add_row(
|
|
101
|
+
"Tokens",
|
|
102
|
+
format_optional(exec_meta.total_tokens, formatter=format_tokens),
|
|
103
|
+
)
|
|
104
|
+
meta_table.add_row("Queued At", format_datetime(exec_meta.queued_at))
|
|
105
|
+
|
|
106
|
+
meta_panel = Panel(meta_table, title="Content Part Updated", border_style="green")
|
|
107
|
+
console.print(meta_panel)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _format_tags(tags: list[TagResponse]) -> str:
|
|
111
|
+
"""Format tags list for display.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
tags: List of tag responses
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Formatted tags string
|
|
118
|
+
"""
|
|
119
|
+
if not tags:
|
|
120
|
+
return "(no tags)"
|
|
121
|
+
|
|
122
|
+
tag_paths = [t.tag_path for t in tags]
|
|
123
|
+
return ", ".join(tag_paths)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _display_content(response: UpdateContentPartResponse) -> None:
|
|
127
|
+
"""Display content to stdout for piping.
|
|
128
|
+
|
|
129
|
+
Text content is printed directly.
|
|
130
|
+
Structured content is formatted as indented JSON.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
response: Content part response
|
|
134
|
+
"""
|
|
135
|
+
if response.content_text is not None:
|
|
136
|
+
# Plain text content
|
|
137
|
+
print(response.content_text, file=sys.stdout)
|
|
138
|
+
elif response.content_structured is not None:
|
|
139
|
+
# Structured JSON content - pretty print
|
|
140
|
+
print(
|
|
141
|
+
json.dumps(response.content_structured, indent=2, ensure_ascii=False),
|
|
142
|
+
file=sys.stdout,
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
# No content (shouldn't happen but handle gracefully)
|
|
146
|
+
print("(no content)", file=sys.stdout)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Content get command implementation."""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from cli.commands.content.get.presenter import present_content_part
|
|
8
|
+
from cli.commands.flag_utils import validate_uuid
|
|
9
|
+
from cli.infrastructure.command import async_command, authenticated_client
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def content_get_command(
|
|
13
|
+
content_part_id: str = typer.Argument(
|
|
14
|
+
...,
|
|
15
|
+
help="Content part UUID",
|
|
16
|
+
),
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Get a content part by UUID.
|
|
19
|
+
|
|
20
|
+
Output is piping-friendly:
|
|
21
|
+
- Content goes to stdout
|
|
22
|
+
- Metadata goes to stderr
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
ai content get 019405e0-e000-7000-f100-000000000001
|
|
26
|
+
ai content get <id> | jq .
|
|
27
|
+
ai content get <id> > output.txt
|
|
28
|
+
"""
|
|
29
|
+
content_uuid = validate_uuid(content_part_id, "content")
|
|
30
|
+
_execute_get(content_part_id=content_uuid)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@async_command
|
|
34
|
+
async def _execute_get(content_part_id: UUID) -> None:
|
|
35
|
+
"""Execute get content part operation."""
|
|
36
|
+
async with authenticated_client() as (_config, client):
|
|
37
|
+
response = await client.get_content_part(content_part_id)
|
|
38
|
+
|
|
39
|
+
present_content_part(response)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Presenter for content get command output."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from cli.infrastructure.formatting.fields import (
|
|
11
|
+
format_cost,
|
|
12
|
+
format_datetime,
|
|
13
|
+
format_optional,
|
|
14
|
+
format_tokens,
|
|
15
|
+
format_uuid,
|
|
16
|
+
)
|
|
17
|
+
from alloy_runtime_types.dtos.content import ContentPartItemResponse
|
|
18
|
+
from alloy_runtime_types.dtos.templates import TagResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def present_content_part(response: ContentPartItemResponse) -> None:
|
|
22
|
+
"""Present content part details with piping support.
|
|
23
|
+
|
|
24
|
+
Metadata goes to stderr (visible in terminal but not piped).
|
|
25
|
+
Content goes to stdout (for piping to other commands).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
response: Content part response from server
|
|
29
|
+
"""
|
|
30
|
+
# Write content to stdout FIRST - this is what gets piped
|
|
31
|
+
# Must happen before any stderr output to avoid blocking issues with vim/less
|
|
32
|
+
_display_content(response)
|
|
33
|
+
|
|
34
|
+
# Now write metadata to stderr (won't be piped)
|
|
35
|
+
# Wrap in try/except since stderr can also block when stdout is piped
|
|
36
|
+
try:
|
|
37
|
+
stderr_console = Console(file=sys.stderr)
|
|
38
|
+
stderr_console.print(
|
|
39
|
+
f"[green]✓[/green] Content part retrieved: {format_uuid(response.id, short=True)}"
|
|
40
|
+
)
|
|
41
|
+
_display_metadata(response, stderr_console)
|
|
42
|
+
except (BlockingIOError, BrokenPipeError):
|
|
43
|
+
# stderr blocked or closed - that's fine, content already went to stdout
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _display_metadata(response: ContentPartItemResponse, console: Console) -> None:
|
|
48
|
+
"""Display content part metadata to stderr.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
response: Content part response
|
|
52
|
+
console: Console for stderr output
|
|
53
|
+
"""
|
|
54
|
+
meta_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
55
|
+
meta_table.add_column("Field", style="dim", width=20)
|
|
56
|
+
meta_table.add_column("Value")
|
|
57
|
+
|
|
58
|
+
# Basic info
|
|
59
|
+
meta_table.add_row("ID", str(response.id))
|
|
60
|
+
meta_table.add_row("Message ID", format_uuid(response.message_id, short=True))
|
|
61
|
+
meta_table.add_row("Part Index", str(response.part_index))
|
|
62
|
+
meta_table.add_row("Content Type", response.content_type_id)
|
|
63
|
+
meta_table.add_row(
|
|
64
|
+
"Storage Type",
|
|
65
|
+
"structured" if response.content_structured else "text",
|
|
66
|
+
)
|
|
67
|
+
meta_table.add_row("Created", format_datetime(response.created_at))
|
|
68
|
+
|
|
69
|
+
# Tags
|
|
70
|
+
meta_table.add_row("Tags", _format_tags(response.tags))
|
|
71
|
+
|
|
72
|
+
# Execution metadata
|
|
73
|
+
exec_meta = response.execution_metadata
|
|
74
|
+
meta_table.add_row("", "") # Separator
|
|
75
|
+
meta_table.add_row("[bold]Execution Info[/bold]", "")
|
|
76
|
+
meta_table.add_row(
|
|
77
|
+
"Execution ID",
|
|
78
|
+
format_uuid(exec_meta.agent_execution_id, short=True),
|
|
79
|
+
)
|
|
80
|
+
meta_table.add_row(
|
|
81
|
+
"Agent",
|
|
82
|
+
format_optional(exec_meta.agent_name, placeholder="(direct model call)"),
|
|
83
|
+
)
|
|
84
|
+
meta_table.add_row("Provider", exec_meta.provider_key)
|
|
85
|
+
meta_table.add_row("Model", exec_meta.provider_model_name)
|
|
86
|
+
meta_table.add_row(
|
|
87
|
+
"Cost",
|
|
88
|
+
format_optional(exec_meta.total_cost_usd, formatter=format_cost),
|
|
89
|
+
)
|
|
90
|
+
meta_table.add_row(
|
|
91
|
+
"Tokens",
|
|
92
|
+
format_optional(exec_meta.total_tokens, formatter=format_tokens),
|
|
93
|
+
)
|
|
94
|
+
meta_table.add_row("Queued At", format_datetime(exec_meta.queued_at))
|
|
95
|
+
|
|
96
|
+
meta_panel = Panel(meta_table, title="Content Part Details", border_style="blue")
|
|
97
|
+
console.print(meta_panel)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _format_tags(tags: list[TagResponse]) -> str:
|
|
101
|
+
"""Format tags list for display.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
tags: List of tag responses
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Formatted tags string
|
|
108
|
+
"""
|
|
109
|
+
if not tags:
|
|
110
|
+
return "(no tags)"
|
|
111
|
+
|
|
112
|
+
tag_paths = [t.tag_path for t in tags]
|
|
113
|
+
return ", ".join(tag_paths)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _display_content(response: ContentPartItemResponse) -> None:
|
|
117
|
+
"""Display content to stdout for piping.
|
|
118
|
+
|
|
119
|
+
Text content is printed directly.
|
|
120
|
+
Structured content is formatted as indented JSON.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
response: Content part response
|
|
124
|
+
"""
|
|
125
|
+
if response.content_text is not None:
|
|
126
|
+
content = response.content_text
|
|
127
|
+
elif response.content_structured is not None:
|
|
128
|
+
content = json.dumps(response.content_structured, indent=2, ensure_ascii=False)
|
|
129
|
+
else:
|
|
130
|
+
content = "(no content)"
|
|
131
|
+
|
|
132
|
+
_write_stdout_safe(content)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _write_stdout_safe(content: str) -> None:
|
|
136
|
+
"""Write content to stdout safely, handling non-blocking pipes.
|
|
137
|
+
|
|
138
|
+
When piping to programs like vim that don't immediately consume stdin,
|
|
139
|
+
the pipe can be set to non-blocking mode, causing BlockingIOError.
|
|
140
|
+
This function handles that by using a binary write with proper encoding.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
content: Text content to write
|
|
144
|
+
"""
|
|
145
|
+
# Check if stdout has a binary buffer (real stdout does, StringIO in tests doesn't)
|
|
146
|
+
stdout_bin = getattr(sys.stdout, "buffer", None)
|
|
147
|
+
|
|
148
|
+
if stdout_bin is None:
|
|
149
|
+
# Fallback for testing with StringIO or other text-mode streams
|
|
150
|
+
print(content)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
# Encode the content
|
|
154
|
+
data = (content + "\n").encode("utf-8")
|
|
155
|
+
|
|
156
|
+
# Write in a loop to handle partial writes
|
|
157
|
+
written = 0
|
|
158
|
+
while written < len(data):
|
|
159
|
+
try:
|
|
160
|
+
n = stdout_bin.write(data[written:])
|
|
161
|
+
if n is None:
|
|
162
|
+
# Non-blocking mode returned None, flush and retry
|
|
163
|
+
stdout_bin.flush()
|
|
164
|
+
continue
|
|
165
|
+
written += n
|
|
166
|
+
except BlockingIOError:
|
|
167
|
+
# Pipe is full, flush and continue
|
|
168
|
+
stdout_bin.flush()
|
|
169
|
+
continue
|
|
170
|
+
except BrokenPipeError:
|
|
171
|
+
# Consumer closed the pipe (e.g., head -n 1)
|
|
172
|
+
# This is expected behavior, exit quietly
|
|
173
|
+
sys.stderr.close()
|
|
174
|
+
sys.exit(0)
|
|
175
|
+
|
|
176
|
+
stdout_bin.flush()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|