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,360 @@
|
|
|
1
|
+
"""Field extraction and formatting for CLI output.
|
|
2
|
+
|
|
3
|
+
Extracts specific fields from data objects using dot notation paths,
|
|
4
|
+
with support for nested fields, arrays, and multiple output formats.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from cli.infrastructure.field_extractor import FieldExtractor, OutputFormat
|
|
8
|
+
|
|
9
|
+
extractor = FieldExtractor(["id", "execution_metadata.total_tokens"])
|
|
10
|
+
results = extractor.extract_many(content_parts)
|
|
11
|
+
output = extractor.format(results, OutputFormat.TSV)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import csv
|
|
15
|
+
import io
|
|
16
|
+
import json
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Any, cast
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OutputFormat(str, Enum):
|
|
24
|
+
"""Supported output formats for field extraction."""
|
|
25
|
+
|
|
26
|
+
TSV = "tsv" # Tab-separated values (default for multi-field)
|
|
27
|
+
CSV = "csv" # Comma-separated with headers
|
|
28
|
+
JSON = "json" # JSON array
|
|
29
|
+
JSONL = "jsonl" # JSON Lines (one object per line)
|
|
30
|
+
VALUES = "values" # Raw values, newline-separated (single field only)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_nested_value(obj: Any, path: str) -> Any:
|
|
34
|
+
"""Extract a value from an object using dot notation path.
|
|
35
|
+
|
|
36
|
+
Supports:
|
|
37
|
+
- Simple fields: "id", "name"
|
|
38
|
+
- Nested fields: "execution_metadata.total_tokens"
|
|
39
|
+
- Dict access: "content_structured.status"
|
|
40
|
+
- Array field collection: "tags.tag_path" -> joins all tag_path values
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
obj: Object to extract from (Pydantic model, dict, or dataclass)
|
|
44
|
+
path: Dot-notation path to the field
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The extracted value, or None if not found
|
|
48
|
+
"""
|
|
49
|
+
parts = path.split(".")
|
|
50
|
+
current: Any = obj
|
|
51
|
+
|
|
52
|
+
for i, part in enumerate(parts):
|
|
53
|
+
if current is None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
# Handle Pydantic models
|
|
57
|
+
if isinstance(current, BaseModel):
|
|
58
|
+
if hasattr(current, part):
|
|
59
|
+
current = getattr(current, part)
|
|
60
|
+
else:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
# Handle dicts
|
|
64
|
+
elif isinstance(current, dict):
|
|
65
|
+
current_dict = cast(dict[str, Any], current)
|
|
66
|
+
current = current_dict.get(part)
|
|
67
|
+
|
|
68
|
+
# Handle lists - collect the field from all items
|
|
69
|
+
elif isinstance(current, list):
|
|
70
|
+
remaining_path = ".".join(parts[i:])
|
|
71
|
+
values: list[Any] = []
|
|
72
|
+
for item in cast(list[Any], current):
|
|
73
|
+
val = _get_nested_value(item, remaining_path)
|
|
74
|
+
if val is not None:
|
|
75
|
+
values.append(val)
|
|
76
|
+
return ", ".join(str(v) for v in values) if values else None
|
|
77
|
+
|
|
78
|
+
# Handle other objects with attributes
|
|
79
|
+
else:
|
|
80
|
+
current_obj = cast(object, current)
|
|
81
|
+
if hasattr(current_obj, part):
|
|
82
|
+
current = getattr(current_obj, part)
|
|
83
|
+
else:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
return current
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _format_value(value: Any) -> str:
|
|
90
|
+
"""Format a value for display.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
value: Value to format
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
String representation
|
|
97
|
+
"""
|
|
98
|
+
if value is None:
|
|
99
|
+
return ""
|
|
100
|
+
if isinstance(value, bool):
|
|
101
|
+
return str(value).lower()
|
|
102
|
+
if isinstance(value, (dict, list)):
|
|
103
|
+
return json.dumps(value, ensure_ascii=False, default=str)
|
|
104
|
+
return str(value)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FieldExtractor:
|
|
108
|
+
"""Extracts and formats specific fields from data objects.
|
|
109
|
+
|
|
110
|
+
Supports two modes:
|
|
111
|
+
1. Basic field extraction: extract top-level or nested fields
|
|
112
|
+
2. Sub-selection mode: extract specific keys from within JSON fields
|
|
113
|
+
|
|
114
|
+
Sub-selection (jq_keys) works like jq's key selection. When provided,
|
|
115
|
+
any dict/JSON field in the extracted results will be filtered to only
|
|
116
|
+
include the specified keys.
|
|
117
|
+
|
|
118
|
+
Example:
|
|
119
|
+
# Extract id and content_structured, but only show 'profession' from content_structured
|
|
120
|
+
extractor = FieldExtractor(["id", "content_structured"], jq_keys=["profession"])
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, fields: list[str], jq_keys: list[str] | None = None):
|
|
124
|
+
"""Initialize with field paths to extract.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
fields: List of dot-notation field paths (e.g., ["id", "execution_metadata.total_tokens"])
|
|
128
|
+
jq_keys: Optional list of keys to extract from JSON/dict fields (jq-style sub-selection)
|
|
129
|
+
"""
|
|
130
|
+
self.fields = fields
|
|
131
|
+
self.jq_keys = jq_keys
|
|
132
|
+
|
|
133
|
+
def extract_one(self, obj: Any) -> dict[str, Any]:
|
|
134
|
+
"""Extract fields from a single object.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
obj: Object to extract from
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dict mapping field paths to extracted values
|
|
141
|
+
"""
|
|
142
|
+
result = {field: _get_nested_value(obj, field) for field in self.fields}
|
|
143
|
+
|
|
144
|
+
# Apply jq-style sub-selection if specified
|
|
145
|
+
if self.jq_keys:
|
|
146
|
+
result = self._apply_jq_selection(result)
|
|
147
|
+
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
def _apply_jq_selection(self, result: dict[str, Any]) -> dict[str, Any]:
|
|
151
|
+
"""Apply jq-style key selection to dict/JSON fields.
|
|
152
|
+
|
|
153
|
+
For each dict value in the result, filter to only include the specified keys.
|
|
154
|
+
Supports nested key paths (e.g., "user.email" to get {"user": {"email": ...}}).
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
result: Extracted field values
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Result with dict fields filtered to only include jq_keys
|
|
161
|
+
"""
|
|
162
|
+
filtered: dict[str, Any] = {}
|
|
163
|
+
for field, value in result.items():
|
|
164
|
+
if isinstance(value, dict) and self.jq_keys:
|
|
165
|
+
filtered[field] = self._select_keys(
|
|
166
|
+
cast(dict[str, Any], value), self.jq_keys
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
filtered[field] = value
|
|
170
|
+
return filtered
|
|
171
|
+
|
|
172
|
+
def _select_keys(self, data: dict[str, Any], keys: list[str]) -> dict[str, Any]:
|
|
173
|
+
"""Select specific keys from a dict, supporting nested paths.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
data: Source dict
|
|
177
|
+
keys: Keys to select (can include nested paths like "user.email")
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Dict containing only the selected keys
|
|
181
|
+
"""
|
|
182
|
+
result: dict[str, Any] = {}
|
|
183
|
+
|
|
184
|
+
for key in keys:
|
|
185
|
+
if "." in key:
|
|
186
|
+
# Nested path - drill down
|
|
187
|
+
parts = key.split(".", 1)
|
|
188
|
+
root, rest = parts[0], parts[1]
|
|
189
|
+
if root in data and isinstance(data[root], dict):
|
|
190
|
+
nested_result = self._select_keys(data[root], [rest])
|
|
191
|
+
if nested_result:
|
|
192
|
+
if root not in result:
|
|
193
|
+
result[root] = {}
|
|
194
|
+
result[root].update(nested_result)
|
|
195
|
+
elif key in data:
|
|
196
|
+
result[key] = data[key]
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def extract_many(self, objects: list[Any]) -> list[dict[str, Any]]:
|
|
201
|
+
"""Extract fields from multiple objects.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
objects: List of objects to extract from
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
List of dicts mapping field paths to extracted values
|
|
208
|
+
"""
|
|
209
|
+
return [self.extract_one(obj) for obj in objects]
|
|
210
|
+
|
|
211
|
+
def format(
|
|
212
|
+
self,
|
|
213
|
+
results: list[dict[str, Any]],
|
|
214
|
+
output_format: OutputFormat = OutputFormat.TSV,
|
|
215
|
+
) -> str:
|
|
216
|
+
"""Format extracted results in the specified format.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
results: List of extracted field dicts
|
|
220
|
+
output_format: Output format to use
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Formatted string output
|
|
224
|
+
"""
|
|
225
|
+
if output_format == OutputFormat.JSON:
|
|
226
|
+
return self._format_json(results)
|
|
227
|
+
elif output_format == OutputFormat.JSONL:
|
|
228
|
+
return self._format_jsonl(results)
|
|
229
|
+
elif output_format == OutputFormat.CSV:
|
|
230
|
+
return self._format_csv(results)
|
|
231
|
+
elif output_format == OutputFormat.TSV:
|
|
232
|
+
return self._format_tsv(results)
|
|
233
|
+
elif output_format == OutputFormat.VALUES:
|
|
234
|
+
return self._format_values(results)
|
|
235
|
+
else:
|
|
236
|
+
return self._format_tsv(results)
|
|
237
|
+
|
|
238
|
+
def _format_json(self, results: list[dict[str, Any]]) -> str:
|
|
239
|
+
"""Format as JSON array."""
|
|
240
|
+
return json.dumps(results, indent=2, ensure_ascii=False, default=str)
|
|
241
|
+
|
|
242
|
+
def _format_jsonl(self, results: list[dict[str, Any]]) -> str:
|
|
243
|
+
"""Format as JSON Lines (one object per line)."""
|
|
244
|
+
lines = [json.dumps(r, ensure_ascii=False, default=str) for r in results]
|
|
245
|
+
return "\n".join(lines)
|
|
246
|
+
|
|
247
|
+
def _format_csv(self, results: list[dict[str, Any]]) -> str:
|
|
248
|
+
"""Format as CSV with headers."""
|
|
249
|
+
if not results:
|
|
250
|
+
return ""
|
|
251
|
+
|
|
252
|
+
output = io.StringIO()
|
|
253
|
+
writer = csv.writer(output)
|
|
254
|
+
|
|
255
|
+
# Write header
|
|
256
|
+
writer.writerow(self.fields)
|
|
257
|
+
|
|
258
|
+
# Write rows
|
|
259
|
+
for result in results:
|
|
260
|
+
row = [_format_value(result.get(field)) for field in self.fields]
|
|
261
|
+
writer.writerow(row)
|
|
262
|
+
|
|
263
|
+
return output.getvalue().rstrip("\n")
|
|
264
|
+
|
|
265
|
+
def _format_tsv(self, results: list[dict[str, Any]]) -> str:
|
|
266
|
+
"""Format as tab-separated values (no header)."""
|
|
267
|
+
lines: list[str] = []
|
|
268
|
+
for result in results:
|
|
269
|
+
values = [_format_value(result.get(field)) for field in self.fields]
|
|
270
|
+
lines.append("\t".join(values))
|
|
271
|
+
return "\n".join(lines)
|
|
272
|
+
|
|
273
|
+
def _format_values(self, results: list[dict[str, Any]]) -> str:
|
|
274
|
+
"""Format as raw values, one per line.
|
|
275
|
+
|
|
276
|
+
Best for single-field extraction for piping.
|
|
277
|
+
"""
|
|
278
|
+
lines: list[str] = []
|
|
279
|
+
for result in results:
|
|
280
|
+
# For single field, just output the value
|
|
281
|
+
if len(self.fields) == 1:
|
|
282
|
+
lines.append(_format_value(result.get(self.fields[0])))
|
|
283
|
+
else:
|
|
284
|
+
# For multiple fields, space-separate on each line
|
|
285
|
+
values = [_format_value(result.get(field)) for field in self.fields]
|
|
286
|
+
lines.append(" ".join(values))
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def parse_fields_option(fields_str: str) -> list[str]:
|
|
291
|
+
"""Parse the --fields option value into a list of field paths.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
fields_str: Comma-separated field paths (e.g., "id,content_text,execution_metadata.total_tokens")
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
List of field paths
|
|
298
|
+
"""
|
|
299
|
+
return [f.strip() for f in fields_str.split(",") if f.strip()]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def parse_jq_keys_option(jq_str: str) -> list[str]:
|
|
303
|
+
"""Parse the --jq option value into a list of keys.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
jq_str: Comma-separated keys to select from JSON fields (e.g., "profession,name,skills.primary")
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
List of key paths
|
|
310
|
+
"""
|
|
311
|
+
return [k.strip() for k in jq_str.split(",") if k.strip()]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# Available fields documentation for help text
|
|
315
|
+
AVAILABLE_FIELDS = """
|
|
316
|
+
Available fields for --fields/-F option:
|
|
317
|
+
|
|
318
|
+
Top-level fields:
|
|
319
|
+
id Content part UUID
|
|
320
|
+
message_id Parent message UUID
|
|
321
|
+
part_index Index within message (0-based)
|
|
322
|
+
content_type_id Content type (text, code, json, etc.)
|
|
323
|
+
content_text Plain text content
|
|
324
|
+
content_structured Full structured JSON content
|
|
325
|
+
content_structured.<key> Access nested keys in structured content
|
|
326
|
+
created_at Creation timestamp
|
|
327
|
+
|
|
328
|
+
Session fields:
|
|
329
|
+
session.id Chat session UUID
|
|
330
|
+
session.name Session name
|
|
331
|
+
session.last_activity_at Last activity timestamp
|
|
332
|
+
|
|
333
|
+
Tag fields:
|
|
334
|
+
tags.tag_path All tag paths (comma-separated)
|
|
335
|
+
tags.display_name All tag display names
|
|
336
|
+
|
|
337
|
+
Execution metadata fields:
|
|
338
|
+
execution_metadata.agent_execution_id Execution UUID
|
|
339
|
+
execution_metadata.agent_id Agent UUID
|
|
340
|
+
execution_metadata.agent_name Agent name
|
|
341
|
+
execution_metadata.provider_key Provider (anthropic, openai, etc.)
|
|
342
|
+
execution_metadata.provider_model_name Model name
|
|
343
|
+
execution_metadata.input_tokens Input token count
|
|
344
|
+
execution_metadata.output_tokens Output token count
|
|
345
|
+
execution_metadata.total_tokens Total token count
|
|
346
|
+
execution_metadata.total_cost_usd Total cost in USD
|
|
347
|
+
execution_metadata.total_duration_ms Execution duration in ms
|
|
348
|
+
execution_metadata.status_id Execution status
|
|
349
|
+
|
|
350
|
+
Examples:
|
|
351
|
+
-F id,content_text # ID and text
|
|
352
|
+
-F execution_metadata.total_tokens # Just token count
|
|
353
|
+
-F id,content_structured.status # ID and nested field
|
|
354
|
+
-F tags.tag_path # All tag paths
|
|
355
|
+
|
|
356
|
+
Sub-selection with --jq:
|
|
357
|
+
-F id,content_structured --jq profession # Show only 'profession' from structured
|
|
358
|
+
-F content_structured --jq name,skills # Multiple keys
|
|
359
|
+
-F content_structured --jq user.email # Nested key path
|
|
360
|
+
"""
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""File content resolver for CLI options.
|
|
2
|
+
|
|
3
|
+
Provides support for reading content from files using the @file syntax,
|
|
4
|
+
similar to curl and httpie. This allows users to pass file contents to
|
|
5
|
+
CLI options without using shell command substitution.
|
|
6
|
+
|
|
7
|
+
Syntax:
|
|
8
|
+
@filepath -> reads content from filepath
|
|
9
|
+
@- -> reads content from stdin
|
|
10
|
+
text -> literal text (no @ prefix)
|
|
11
|
+
@@text -> escaped @ (literal @text)
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
--system @prompt.txt -> reads content from prompt.txt
|
|
15
|
+
--system @- -> reads content from stdin
|
|
16
|
+
--system "You are helpful" -> literal text
|
|
17
|
+
--system @@handle -> literal "@handle" (escaped)
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
from cli.infrastructure.file_content import resolve_content
|
|
21
|
+
|
|
22
|
+
# In command function
|
|
23
|
+
system_instruction = resolve_content(system_instruction)
|
|
24
|
+
|
|
25
|
+
# Or use the typer callback for automatic resolution
|
|
26
|
+
from cli.infrastructure.file_content import file_content_callback
|
|
27
|
+
|
|
28
|
+
@app.command()
|
|
29
|
+
def my_command(
|
|
30
|
+
system: Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
typer.Option("--system", "-s", callback=file_content_callback),
|
|
33
|
+
] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
# system is already resolved (file content or literal)
|
|
36
|
+
...
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import sys
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FileContentError(Exception):
|
|
44
|
+
"""Raised when file content cannot be read."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def resolve_content(value: str | None) -> str | None:
|
|
50
|
+
"""Resolve a value that may be a file reference or literal text.
|
|
51
|
+
|
|
52
|
+
Supports the @file syntax for reading content from files:
|
|
53
|
+
- @filepath: reads content from the specified file
|
|
54
|
+
- @-: reads content from stdin
|
|
55
|
+
- @@text: escaped @ (returns literal @text)
|
|
56
|
+
- other: returned as-is (literal text)
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
value: The value to resolve. May be a file reference (@file),
|
|
60
|
+
stdin reference (@-), escaped @ (@@), or literal text.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The resolved content, or None if input was None.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
FileContentError: If the file cannot be read or doesn't exist.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
>>> resolve_content("@prompt.txt") # reads file
|
|
70
|
+
"You are a helpful assistant..."
|
|
71
|
+
|
|
72
|
+
>>> resolve_content("@-") # reads stdin
|
|
73
|
+
"Content from stdin..."
|
|
74
|
+
|
|
75
|
+
>>> resolve_content("@@username") # escaped
|
|
76
|
+
"@username"
|
|
77
|
+
|
|
78
|
+
>>> resolve_content("literal text") # passthrough
|
|
79
|
+
"literal text"
|
|
80
|
+
|
|
81
|
+
>>> resolve_content(None)
|
|
82
|
+
None
|
|
83
|
+
"""
|
|
84
|
+
if value is None:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
# Not a file reference - return as-is
|
|
88
|
+
if not value.startswith("@"):
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
# Escaped @ - return without the escape character
|
|
92
|
+
if value.startswith("@@"):
|
|
93
|
+
return value[1:]
|
|
94
|
+
|
|
95
|
+
# Extract the path (everything after @)
|
|
96
|
+
path_str = value[1:]
|
|
97
|
+
|
|
98
|
+
# Handle stdin
|
|
99
|
+
if path_str == "-":
|
|
100
|
+
return _read_stdin()
|
|
101
|
+
|
|
102
|
+
# Handle file path
|
|
103
|
+
return _read_file(path_str)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _read_stdin() -> str:
|
|
107
|
+
"""Read content from stdin.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Content read from stdin.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
FileContentError: If stdin cannot be read or is a TTY with no input.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# Check if stdin is a TTY (interactive terminal with no piped input)
|
|
117
|
+
if sys.stdin.isatty():
|
|
118
|
+
raise FileContentError(
|
|
119
|
+
"No input provided on stdin. "
|
|
120
|
+
"Use @- with piped input: echo 'content' | ai2 ... -s @-"
|
|
121
|
+
)
|
|
122
|
+
return sys.stdin.read()
|
|
123
|
+
except FileContentError:
|
|
124
|
+
raise
|
|
125
|
+
except Exception as e:
|
|
126
|
+
raise FileContentError(f"Failed to read from stdin: {e}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _read_file(path_str: str) -> str:
|
|
130
|
+
"""Read content from a file.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
path_str: Path to the file to read.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Content of the file.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
FileContentError: If the file doesn't exist or cannot be read.
|
|
140
|
+
"""
|
|
141
|
+
path = Path(path_str).expanduser()
|
|
142
|
+
|
|
143
|
+
if not path.exists():
|
|
144
|
+
raise FileContentError(f"File not found: {path_str}")
|
|
145
|
+
|
|
146
|
+
if not path.is_file():
|
|
147
|
+
raise FileContentError(f"Not a file: {path_str}")
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
return path.read_text(encoding="utf-8")
|
|
151
|
+
except PermissionError:
|
|
152
|
+
raise FileContentError(f"Permission denied: {path_str}")
|
|
153
|
+
except UnicodeDecodeError:
|
|
154
|
+
raise FileContentError(
|
|
155
|
+
f"File is not valid UTF-8 text: {path_str}. Only text files are supported."
|
|
156
|
+
)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise FileContentError(f"Failed to read file '{path_str}': {e}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def resolve_content_or_raise(value: str | None) -> str | None:
|
|
162
|
+
"""Resolve content, converting FileContentError to typer.BadParameter.
|
|
163
|
+
|
|
164
|
+
This is a convenience wrapper for use in command functions where you want
|
|
165
|
+
file errors to be displayed as CLI parameter errors.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
value: The value to resolve.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
The resolved content, or None if input was None.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
typer.BadParameter: If the file cannot be read.
|
|
175
|
+
"""
|
|
176
|
+
import typer
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
return resolve_content(value)
|
|
180
|
+
except FileContentError as e:
|
|
181
|
+
raise typer.BadParameter(str(e))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def file_content_callback(value: str | None) -> str | None:
|
|
185
|
+
"""Typer callback that resolves file content references.
|
|
186
|
+
|
|
187
|
+
Use this as a callback for typer.Option to automatically resolve
|
|
188
|
+
@file references before the value reaches your command function.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
value: The option value, possibly a file reference.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The resolved content.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
typer.BadParameter: If the file cannot be read.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
@app.command()
|
|
201
|
+
def cmd(
|
|
202
|
+
system: Annotated[
|
|
203
|
+
str | None,
|
|
204
|
+
typer.Option("--system", "-s", callback=file_content_callback),
|
|
205
|
+
] = None,
|
|
206
|
+
):
|
|
207
|
+
# system contains file content if @file was used
|
|
208
|
+
print(system)
|
|
209
|
+
"""
|
|
210
|
+
return resolve_content_or_raise(value)
|