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,120 @@
|
|
|
1
|
+
"""Command for regenerating the last AI response."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
8
|
+
from alloy_runtime_types.dtos.generation import (
|
|
9
|
+
AgentSource,
|
|
10
|
+
DirectModelSource,
|
|
11
|
+
GenerateTextRequest,
|
|
12
|
+
)
|
|
13
|
+
from alloy_runtime_types.enums.provider import Provider
|
|
14
|
+
from alloy_runtime_sdk.logging.config import get_logger
|
|
15
|
+
|
|
16
|
+
from cli.tui.chat.commands.base import CommandResult
|
|
17
|
+
from cli.tui.chat.store import ChatStore
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class RegenerateCommand:
|
|
24
|
+
"""Command to regenerate the last AI response.
|
|
25
|
+
|
|
26
|
+
Supports two modes:
|
|
27
|
+
- Agent mode: Uses agent_id to determine model and system instruction
|
|
28
|
+
- Model mode: Uses provider_key/provider_model_name/system_instruction directly
|
|
29
|
+
|
|
30
|
+
This command:
|
|
31
|
+
1. Transitions store to STREAMING phase
|
|
32
|
+
2. Sends a regenerate request to the API
|
|
33
|
+
3. Streams the new response chunks via callbacks (content and thinking)
|
|
34
|
+
4. Transitions back to IDLE when complete
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
client: Server client for API calls.
|
|
38
|
+
store: Chat store for state management.
|
|
39
|
+
session_id: ID of the current chat session.
|
|
40
|
+
on_chunk: Callback invoked with each streamed text chunk.
|
|
41
|
+
on_thinking_chunk: Callback invoked with each reasoning/thinking chunk.
|
|
42
|
+
agent_id: ID of the agent to use for regeneration (agent mode).
|
|
43
|
+
provider_key: Provider key (model mode).
|
|
44
|
+
provider_model_name: Model name (model mode).
|
|
45
|
+
system_instruction: System instruction (model mode).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
client: ApiClient
|
|
49
|
+
store: ChatStore
|
|
50
|
+
session_id: UUID
|
|
51
|
+
on_chunk: Callable[[str], None]
|
|
52
|
+
on_thinking_chunk: Callable[[str], None] | None = None
|
|
53
|
+
# Agent mode
|
|
54
|
+
agent_id: UUID | None = None
|
|
55
|
+
# Model mode
|
|
56
|
+
provider_key: str | None = None
|
|
57
|
+
provider_model_name: str | None = None
|
|
58
|
+
system_instruction: str | None = None
|
|
59
|
+
|
|
60
|
+
async def execute(self) -> CommandResult[None]:
|
|
61
|
+
"""Execute the regenerate response operation.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
CommandResult indicating success or failure.
|
|
65
|
+
"""
|
|
66
|
+
logger.info("regenerate_starting", session_id=str(self.session_id))
|
|
67
|
+
self.store.start_streaming()
|
|
68
|
+
|
|
69
|
+
was_cancelled = False
|
|
70
|
+
try:
|
|
71
|
+
# Build model source based on mode
|
|
72
|
+
if self.agent_id is not None:
|
|
73
|
+
model_source = AgentSource(agent=self.agent_id)
|
|
74
|
+
elif self.provider_key and self.provider_model_name:
|
|
75
|
+
model_source = DirectModelSource(
|
|
76
|
+
provider_key=Provider(self.provider_key),
|
|
77
|
+
provider_model_name=self.provider_model_name,
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
return CommandResult[None](
|
|
81
|
+
success=False,
|
|
82
|
+
error="No agent or model configured for this session",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
request = GenerateTextRequest(
|
|
86
|
+
model_source=model_source,
|
|
87
|
+
chat_session_id=self.session_id,
|
|
88
|
+
regenerate=True,
|
|
89
|
+
stream=True,
|
|
90
|
+
tags=[],
|
|
91
|
+
# Only include system instruction for model mode
|
|
92
|
+
system_instruction=self.system_instruction
|
|
93
|
+
if self.agent_id is None
|
|
94
|
+
else None,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async for chunk in self.client.generate_text_stream(request):
|
|
98
|
+
# Check for cancellation request
|
|
99
|
+
if self.store.state.cancel_requested:
|
|
100
|
+
logger.info("regenerate_cancelled_by_user")
|
|
101
|
+
was_cancelled = True
|
|
102
|
+
return CommandResult[None](success=True, data=None)
|
|
103
|
+
|
|
104
|
+
# Handle thinking/reasoning chunks
|
|
105
|
+
if chunk.chunk_type == "thinking" and chunk.reasoning_content:
|
|
106
|
+
if self.on_thinking_chunk:
|
|
107
|
+
self.on_thinking_chunk(chunk.reasoning_content)
|
|
108
|
+
# Handle regular text content chunks
|
|
109
|
+
elif chunk.content:
|
|
110
|
+
self.on_chunk(chunk.content)
|
|
111
|
+
|
|
112
|
+
logger.info("regenerate_completed")
|
|
113
|
+
return CommandResult[None](success=True, data=None)
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.warning("regenerate_failed", error=str(e))
|
|
117
|
+
return CommandResult[None](success=False, error=str(e))
|
|
118
|
+
|
|
119
|
+
finally:
|
|
120
|
+
self.store.finish_streaming(cancelled=was_cancelled)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Command for reloading the current session's messages."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
7
|
+
from alloy_runtime_types.dtos.sessions import MessageResponse
|
|
8
|
+
|
|
9
|
+
from cli.tui.chat.commands.base import CommandResult
|
|
10
|
+
from cli.tui.chat.store import ChatStore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ReloadSessionResult:
|
|
15
|
+
"""Result data for reload session operation.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
messages: Updated list of messages.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
messages: list[MessageResponse]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ReloadSessionCommand:
|
|
26
|
+
"""Command to reload the current session's messages.
|
|
27
|
+
|
|
28
|
+
This is a lightweight operation that only fetches messages,
|
|
29
|
+
without changing the session context. Useful after sending
|
|
30
|
+
a message or regenerating to get the updated message list.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
client: Server client for API calls.
|
|
34
|
+
store: Chat store for state management.
|
|
35
|
+
session_id: ID of the session to reload.
|
|
36
|
+
message_limit: Maximum number of messages to fetch.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
client: ApiClient
|
|
40
|
+
store: ChatStore
|
|
41
|
+
session_id: UUID
|
|
42
|
+
message_limit: int = 100
|
|
43
|
+
|
|
44
|
+
async def execute(self) -> CommandResult[ReloadSessionResult]:
|
|
45
|
+
"""Execute the reload session operation.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
CommandResult with updated messages on success.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
session = await self.client.get_session(
|
|
52
|
+
self.session_id, limit=self.message_limit
|
|
53
|
+
)
|
|
54
|
+
self.store.set_messages(list(session.messages))
|
|
55
|
+
|
|
56
|
+
return CommandResult[ReloadSessionResult](
|
|
57
|
+
success=True,
|
|
58
|
+
data=ReloadSessionResult(messages=list(session.messages)),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
# Silently fail for reload - the user already saw the response
|
|
63
|
+
return CommandResult[ReloadSessionResult](success=False, error=str(e))
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Command for sending a message and streaming the response."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
8
|
+
from alloy_runtime_types.dtos.generation import (
|
|
9
|
+
AgentSource,
|
|
10
|
+
DirectModelSource,
|
|
11
|
+
GenerateTextRequest,
|
|
12
|
+
)
|
|
13
|
+
from alloy_runtime_types.enums.provider import Provider
|
|
14
|
+
from alloy_runtime_sdk.logging.config import get_logger
|
|
15
|
+
|
|
16
|
+
from cli.infrastructure.injection.parser import has_injections
|
|
17
|
+
from cli.infrastructure.injection.resolver import ResolvedMessage
|
|
18
|
+
from cli.tui.chat.commands.base import CommandResult
|
|
19
|
+
from cli.tui.chat.services.injection import InjectionService
|
|
20
|
+
from cli.tui.chat.store import ChatStore
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SendMessageCommand:
|
|
27
|
+
"""Command to send a user message and stream the AI response.
|
|
28
|
+
|
|
29
|
+
Supports two modes:
|
|
30
|
+
- Agent mode: Uses agent_id to determine model and system instruction
|
|
31
|
+
- Model mode: Uses provider_key/provider_model_name/system_instruction directly
|
|
32
|
+
|
|
33
|
+
Also supports injection resolution:
|
|
34
|
+
- @template('name') - Inject rendered template
|
|
35
|
+
- @text('uuid') - Inject text content
|
|
36
|
+
- @json('uuid') - Inject JSON content
|
|
37
|
+
- @schema('name') - Inject schema definition
|
|
38
|
+
|
|
39
|
+
This command:
|
|
40
|
+
1. Resolves any injection patterns in the message
|
|
41
|
+
2. Transitions store to STREAMING phase
|
|
42
|
+
3. Builds and sends the generation request
|
|
43
|
+
4. Streams response chunks via callbacks (content and thinking)
|
|
44
|
+
5. Transitions back to IDLE when complete
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
client: Server client for API calls.
|
|
48
|
+
store: Chat store for state management.
|
|
49
|
+
session_id: ID of the current chat session.
|
|
50
|
+
user_message: The user's message to send.
|
|
51
|
+
on_chunk: Callback invoked with each streamed text chunk.
|
|
52
|
+
on_thinking_chunk: Callback invoked with each reasoning/thinking chunk.
|
|
53
|
+
agent_id: ID of the agent to use for generation (agent mode).
|
|
54
|
+
provider_key: Provider key (model mode).
|
|
55
|
+
provider_model_name: Model name (model mode).
|
|
56
|
+
system_instruction: System instruction (model mode).
|
|
57
|
+
injection_service: Optional service for resolving injection patterns.
|
|
58
|
+
on_injection_resolved: Callback invoked with resolution result.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
client: ApiClient
|
|
62
|
+
store: ChatStore
|
|
63
|
+
session_id: UUID
|
|
64
|
+
user_message: str
|
|
65
|
+
on_chunk: Callable[[str], None]
|
|
66
|
+
on_thinking_chunk: Callable[[str], None] | None = None
|
|
67
|
+
# Agent mode
|
|
68
|
+
agent_id: UUID | None = None
|
|
69
|
+
# Model mode
|
|
70
|
+
provider_key: str | None = None
|
|
71
|
+
provider_model_name: str | None = None
|
|
72
|
+
system_instruction: str | None = None
|
|
73
|
+
# Injection support
|
|
74
|
+
injection_service: InjectionService | None = None
|
|
75
|
+
on_injection_resolved: Callable[[ResolvedMessage], None] | None = None
|
|
76
|
+
|
|
77
|
+
async def execute(self) -> CommandResult[None]:
|
|
78
|
+
"""Execute the send message and stream operation.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
CommandResult indicating success or failure.
|
|
82
|
+
"""
|
|
83
|
+
message_to_send = self.user_message
|
|
84
|
+
|
|
85
|
+
# Resolve injections if service is available and message has patterns
|
|
86
|
+
if self.injection_service and has_injections(self.user_message):
|
|
87
|
+
try:
|
|
88
|
+
resolved = await self.injection_service.resolve_message(
|
|
89
|
+
self.user_message
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Check for errors
|
|
93
|
+
if resolved.has_errors:
|
|
94
|
+
error_messages = [e.message for e in resolved.errors]
|
|
95
|
+
return CommandResult[None](
|
|
96
|
+
success=False,
|
|
97
|
+
error=f"Injection resolution failed: {error_messages[0]}",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Notify callback about successful resolution
|
|
101
|
+
if self.on_injection_resolved:
|
|
102
|
+
self.on_injection_resolved(resolved)
|
|
103
|
+
|
|
104
|
+
# Use resolved text
|
|
105
|
+
message_to_send = resolved.text
|
|
106
|
+
|
|
107
|
+
logger.debug(
|
|
108
|
+
"injection_resolved",
|
|
109
|
+
extra={
|
|
110
|
+
"fragments_used": len(resolved.fragments_used),
|
|
111
|
+
"content_used": len(resolved.content_used),
|
|
112
|
+
"schemas_used": len(resolved.schemas_used),
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(
|
|
118
|
+
"injection_resolution_error",
|
|
119
|
+
extra={"error": str(e)},
|
|
120
|
+
)
|
|
121
|
+
return CommandResult[None](
|
|
122
|
+
success=False,
|
|
123
|
+
error=f"Failed to resolve injections: {e}",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Start streaming with the user message shown optimistically
|
|
127
|
+
# Note: We show the original message, not the resolved one
|
|
128
|
+
self.store.start_streaming(user_message=self.user_message)
|
|
129
|
+
|
|
130
|
+
was_cancelled = False
|
|
131
|
+
try:
|
|
132
|
+
# Build model source based on mode
|
|
133
|
+
if self.agent_id is not None:
|
|
134
|
+
model_source = AgentSource(agent=self.agent_id)
|
|
135
|
+
elif self.provider_key and self.provider_model_name:
|
|
136
|
+
model_source = DirectModelSource(
|
|
137
|
+
provider_key=Provider(self.provider_key),
|
|
138
|
+
provider_model_name=self.provider_model_name,
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
return CommandResult[None](
|
|
142
|
+
success=False,
|
|
143
|
+
error="No agent or model configured for this session",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
request = GenerateTextRequest(
|
|
147
|
+
model_source=model_source,
|
|
148
|
+
user_message=message_to_send, # Use resolved message
|
|
149
|
+
stream=True,
|
|
150
|
+
tags=[],
|
|
151
|
+
chat_session_id=self.session_id,
|
|
152
|
+
# Only include system instruction for model mode
|
|
153
|
+
system_instruction=self.system_instruction
|
|
154
|
+
if self.agent_id is None
|
|
155
|
+
else None,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
async for chunk in self.client.generate_text_stream(request):
|
|
159
|
+
# Check for cancellation request
|
|
160
|
+
if self.store.state.cancel_requested:
|
|
161
|
+
logger.info("stream_cancelled_by_user")
|
|
162
|
+
was_cancelled = True
|
|
163
|
+
return CommandResult[None](success=True, data=None)
|
|
164
|
+
|
|
165
|
+
# Debug: log chunk info for first few chunks
|
|
166
|
+
logger.debug(
|
|
167
|
+
"stream_chunk",
|
|
168
|
+
extra={
|
|
169
|
+
"chunk_type": chunk.chunk_type,
|
|
170
|
+
"has_content": bool(chunk.content),
|
|
171
|
+
"has_reasoning": bool(chunk.reasoning_content),
|
|
172
|
+
"sequence": chunk.sequence,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Handle thinking/reasoning chunks
|
|
177
|
+
if chunk.chunk_type == "thinking" and chunk.reasoning_content:
|
|
178
|
+
if self.on_thinking_chunk:
|
|
179
|
+
self.on_thinking_chunk(chunk.reasoning_content)
|
|
180
|
+
# Handle regular text content chunks
|
|
181
|
+
elif chunk.content:
|
|
182
|
+
self.on_chunk(chunk.content)
|
|
183
|
+
|
|
184
|
+
return CommandResult[None](success=True, data=None)
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return CommandResult[None](success=False, error=str(e))
|
|
188
|
+
|
|
189
|
+
finally:
|
|
190
|
+
self.store.finish_streaming(cancelled=was_cancelled)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Command for undoing the last conversation turn."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
6
|
+
from alloy_runtime_types.dtos.messages import UpdateMessageRequest
|
|
7
|
+
from alloy_runtime_types.dtos.sessions import MessageResponse
|
|
8
|
+
|
|
9
|
+
from cli.tui.chat.commands.base import CommandResult
|
|
10
|
+
from cli.tui.chat.types import extract_message_text
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class UndoResult:
|
|
15
|
+
"""Result data for undo operation.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
user_preview: Preview of the undone user message.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
user_preview: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class UndoCommand:
|
|
26
|
+
"""Command to undo the last conversation turn.
|
|
27
|
+
|
|
28
|
+
This marks both the last user message and assistant response as
|
|
29
|
+
excluded from context, effectively removing them from the conversation.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
client: Server client for API calls.
|
|
33
|
+
user_message: The user message to exclude.
|
|
34
|
+
assistant_message: The assistant message to exclude.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
client: ApiClient
|
|
38
|
+
user_message: MessageResponse
|
|
39
|
+
assistant_message: MessageResponse
|
|
40
|
+
|
|
41
|
+
async def execute(self) -> CommandResult[UndoResult]:
|
|
42
|
+
"""Execute the undo operation.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
CommandResult with preview of undone message on success.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
# Mark both messages as excluded from context
|
|
49
|
+
for msg in [self.user_message, self.assistant_message]:
|
|
50
|
+
await self.client.update_message(
|
|
51
|
+
msg.id, UpdateMessageRequest(include_in_context=False)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Get preview text for user feedback
|
|
55
|
+
user_text = extract_message_text(self.user_message)
|
|
56
|
+
user_preview = user_text[:50]
|
|
57
|
+
if len(user_text) > 50:
|
|
58
|
+
user_preview += "..."
|
|
59
|
+
|
|
60
|
+
return CommandResult[UndoResult](
|
|
61
|
+
success=True,
|
|
62
|
+
data=UndoResult(user_preview=user_preview),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return CommandResult[UndoResult](success=False, error=str(e))
|
cli/tui/chat/editor.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""External editor integration for the chat TUI.
|
|
2
|
+
|
|
3
|
+
Provides functionality to open text in $EDITOR for composition of long messages.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EditorError(Exception):
|
|
12
|
+
"""Raised when editor operations fail."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def open_in_editor(current_text: str = "") -> str | None:
|
|
18
|
+
"""Open text in $EDITOR and return the edited content.
|
|
19
|
+
|
|
20
|
+
Uses the editor specified by $EDITOR, falling back to $VISUAL, then 'vi'.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
current_text: The initial text to populate the editor with.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The edited text if successful, None if editor failed or was cancelled.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
EditorError: If the editor could not be found or execution failed.
|
|
30
|
+
"""
|
|
31
|
+
editor = os.environ.get("EDITOR", os.environ.get("VISUAL", "vi"))
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Create a temporary file with the current text
|
|
35
|
+
with tempfile.NamedTemporaryFile(
|
|
36
|
+
mode="w",
|
|
37
|
+
suffix=".md",
|
|
38
|
+
delete=False,
|
|
39
|
+
) as tmp_file:
|
|
40
|
+
tmp_file.write(current_text)
|
|
41
|
+
tmp_path = tmp_file.name
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
# Open the editor and wait for it to close
|
|
45
|
+
result = subprocess.run([editor, tmp_path], check=False)
|
|
46
|
+
|
|
47
|
+
if result.returncode != 0:
|
|
48
|
+
raise EditorError(f"Editor exited with code {result.returncode}")
|
|
49
|
+
|
|
50
|
+
# Read the edited content back
|
|
51
|
+
with open(tmp_path, encoding="utf-8") as f:
|
|
52
|
+
edited_text = f.read()
|
|
53
|
+
|
|
54
|
+
# Strip trailing newlines that editors often add
|
|
55
|
+
return edited_text.rstrip("\n")
|
|
56
|
+
|
|
57
|
+
finally:
|
|
58
|
+
# Clean up the temporary file
|
|
59
|
+
try:
|
|
60
|
+
os.unlink(tmp_path)
|
|
61
|
+
except OSError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
raise EditorError(
|
|
66
|
+
f"Editor '{editor}' not found. Set $EDITOR environment variable."
|
|
67
|
+
)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
if isinstance(e, EditorError):
|
|
70
|
+
raise
|
|
71
|
+
raise EditorError(f"Failed to open editor: {e}")
|