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,409 @@
|
|
|
1
|
+
"""Export formatters for content list results.
|
|
2
|
+
|
|
3
|
+
Provides various output formats for exporting content items:
|
|
4
|
+
- markdown: Markdown list or grouped headers
|
|
5
|
+
- xml: XML tags with content
|
|
6
|
+
- numbered: Numbered list
|
|
7
|
+
- plain: Plain text (content only)
|
|
8
|
+
- json: JSON array
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from alloy_runtime_types.dtos.content import ContentPartItemResponse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ContentExportFormatter(ABC):
|
|
19
|
+
"""Base class for content export formatters."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def format(
|
|
23
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
24
|
+
) -> str:
|
|
25
|
+
"""Format content items for export.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
items: List of content part items to format
|
|
29
|
+
group_by: Optional tag prefix to group items by
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Formatted string ready for output
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def _get_content(self, item: ContentPartItemResponse) -> str:
|
|
37
|
+
"""Extract content text from item.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
item: Content part item
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Content as string (text or JSON-serialized structured content)
|
|
44
|
+
"""
|
|
45
|
+
if item.content_text:
|
|
46
|
+
return item.content_text
|
|
47
|
+
if item.content_structured:
|
|
48
|
+
return json.dumps(item.content_structured, ensure_ascii=False)
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
def _group_items(
|
|
52
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
53
|
+
) -> dict[str, list[ContentPartItemResponse]]:
|
|
54
|
+
"""Group items by tag path matching the group_by prefix.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
group_by="ads" groups by first subtag: ads.headline -> "headline"
|
|
58
|
+
group_by="ads.headline" groups by exact match -> "ads.headline"
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
items: List of content items to group
|
|
62
|
+
group_by: Tag prefix to group by
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary mapping group names to lists of items
|
|
66
|
+
"""
|
|
67
|
+
groups: dict[str, list[ContentPartItemResponse]] = {}
|
|
68
|
+
ungrouped: list[ContentPartItemResponse] = []
|
|
69
|
+
|
|
70
|
+
for item in items:
|
|
71
|
+
matched = False
|
|
72
|
+
for tag in item.tags:
|
|
73
|
+
if tag.tag_path.startswith(group_by):
|
|
74
|
+
# Extract the grouping key
|
|
75
|
+
remaining = tag.tag_path[len(group_by) :].lstrip(".")
|
|
76
|
+
if remaining:
|
|
77
|
+
# Use first component after prefix
|
|
78
|
+
key = remaining.split(".")[0]
|
|
79
|
+
else:
|
|
80
|
+
# Exact match - use full tag path
|
|
81
|
+
key = tag.tag_path
|
|
82
|
+
|
|
83
|
+
if key not in groups:
|
|
84
|
+
groups[key] = []
|
|
85
|
+
groups[key].append(item)
|
|
86
|
+
matched = True
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if not matched:
|
|
90
|
+
ungrouped.append(item)
|
|
91
|
+
|
|
92
|
+
if ungrouped:
|
|
93
|
+
groups["(ungrouped)"] = ungrouped
|
|
94
|
+
|
|
95
|
+
return groups
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MarkdownFormatter(ContentExportFormatter):
|
|
99
|
+
"""Format content as markdown with numbered headers.
|
|
100
|
+
|
|
101
|
+
Output format (flat):
|
|
102
|
+
## Item 1
|
|
103
|
+
|
|
104
|
+
<content>
|
|
105
|
+
|
|
106
|
+
## Item 2
|
|
107
|
+
|
|
108
|
+
<content>
|
|
109
|
+
|
|
110
|
+
Output format (grouped):
|
|
111
|
+
## group_name
|
|
112
|
+
|
|
113
|
+
### Item 1
|
|
114
|
+
|
|
115
|
+
<content>
|
|
116
|
+
|
|
117
|
+
### Item 2
|
|
118
|
+
|
|
119
|
+
<content>
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def format(
|
|
123
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Format items as markdown.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
items: Content items to format
|
|
129
|
+
group_by: Optional tag prefix for grouping
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Markdown formatted string
|
|
133
|
+
"""
|
|
134
|
+
if group_by:
|
|
135
|
+
return self._format_grouped(items, group_by)
|
|
136
|
+
return self._format_flat(items)
|
|
137
|
+
|
|
138
|
+
def _format_flat(self, items: list[ContentPartItemResponse]) -> str:
|
|
139
|
+
"""Format items with numbered headers."""
|
|
140
|
+
sections: list[str] = []
|
|
141
|
+
for i, item in enumerate(items, 1):
|
|
142
|
+
content = self._get_content(item)
|
|
143
|
+
sections.append(f"## Item {i}\n\n{content}")
|
|
144
|
+
return "\n\n".join(sections)
|
|
145
|
+
|
|
146
|
+
def _format_grouped(
|
|
147
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
148
|
+
) -> str:
|
|
149
|
+
"""Format items grouped by tag prefix with nested headers."""
|
|
150
|
+
groups = self._group_items(items, group_by)
|
|
151
|
+
sections: list[str] = []
|
|
152
|
+
for group_name, group_items in groups.items():
|
|
153
|
+
group_sections: list[str] = [f"## {group_name}"]
|
|
154
|
+
for i, item in enumerate(group_items, 1):
|
|
155
|
+
content = self._get_content(item)
|
|
156
|
+
group_sections.append(f"### Item {i}\n\n{content}")
|
|
157
|
+
sections.append("\n\n".join(group_sections))
|
|
158
|
+
return "\n\n".join(sections)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class XmlFormatter(ContentExportFormatter):
|
|
162
|
+
"""Format content as XML tags.
|
|
163
|
+
|
|
164
|
+
Uses CDATA sections to preserve raw content without escaping,
|
|
165
|
+
which is important when content itself contains XML (e.g., system prompts).
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def format(
|
|
169
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Format items as XML.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
items: Content items to format
|
|
175
|
+
group_by: Optional tag prefix for grouping
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
XML formatted string
|
|
179
|
+
"""
|
|
180
|
+
if group_by:
|
|
181
|
+
return self._format_grouped(items, group_by)
|
|
182
|
+
return self._format_flat(items)
|
|
183
|
+
|
|
184
|
+
def _wrap_cdata(self, text: str) -> str:
|
|
185
|
+
"""Wrap content in CDATA section to preserve raw content.
|
|
186
|
+
|
|
187
|
+
Handles the edge case where content contains ']]>' by splitting
|
|
188
|
+
into multiple CDATA sections.
|
|
189
|
+
"""
|
|
190
|
+
# Handle edge case: content contains CDATA end marker
|
|
191
|
+
if "]]>" in text:
|
|
192
|
+
# Split at ]]> and rejoin with separate CDATA sections
|
|
193
|
+
text = text.replace("]]>", "]]]]><![CDATA[>")
|
|
194
|
+
return f"<![CDATA[{text}]]>"
|
|
195
|
+
|
|
196
|
+
def _safe_tag_name(self, name: str) -> str:
|
|
197
|
+
"""Convert a name to a valid XML tag name."""
|
|
198
|
+
# Replace dots and other invalid chars with underscores
|
|
199
|
+
safe = name.replace(".", "_").replace("-", "_").replace(" ", "_")
|
|
200
|
+
# Ensure starts with letter or underscore
|
|
201
|
+
if safe and not (safe[0].isalpha() or safe[0] == "_"):
|
|
202
|
+
safe = "_" + safe
|
|
203
|
+
return safe or "item"
|
|
204
|
+
|
|
205
|
+
def _format_flat(self, items: list[ContentPartItemResponse]) -> str:
|
|
206
|
+
"""Format items as a flat XML list."""
|
|
207
|
+
lines = ["<content_items>"]
|
|
208
|
+
for item in items:
|
|
209
|
+
content = self._get_content(item)
|
|
210
|
+
# Use first tag as element name, or "item"
|
|
211
|
+
tag_name = (
|
|
212
|
+
self._safe_tag_name(item.tags[0].tag_path) if item.tags else "item"
|
|
213
|
+
)
|
|
214
|
+
lines.append(f" <{tag_name}>{self._wrap_cdata(content)}</{tag_name}>")
|
|
215
|
+
lines.append("</content_items>")
|
|
216
|
+
return "\n".join(lines)
|
|
217
|
+
|
|
218
|
+
def _format_grouped(
|
|
219
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Format items grouped by tag prefix."""
|
|
222
|
+
groups = self._group_items(items, group_by)
|
|
223
|
+
lines = ["<content_items>"]
|
|
224
|
+
for group_name, group_items in groups.items():
|
|
225
|
+
safe_name = self._safe_tag_name(group_name)
|
|
226
|
+
lines.append(f" <{safe_name}>")
|
|
227
|
+
for item in group_items:
|
|
228
|
+
content = self._get_content(item)
|
|
229
|
+
lines.append(f" <item>{self._wrap_cdata(content)}</item>")
|
|
230
|
+
lines.append(f" </{safe_name}>")
|
|
231
|
+
lines.append("</content_items>")
|
|
232
|
+
return "\n".join(lines)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class NumberedFormatter(ContentExportFormatter):
|
|
236
|
+
"""Format content as numbered list."""
|
|
237
|
+
|
|
238
|
+
def format(
|
|
239
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
240
|
+
) -> str:
|
|
241
|
+
"""Format items as numbered list.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
items: Content items to format
|
|
245
|
+
group_by: Optional tag prefix for grouping
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Numbered list string
|
|
249
|
+
"""
|
|
250
|
+
if group_by:
|
|
251
|
+
return self._format_grouped(items, group_by)
|
|
252
|
+
return self._format_flat(items)
|
|
253
|
+
|
|
254
|
+
def _format_flat(self, items: list[ContentPartItemResponse]) -> str:
|
|
255
|
+
"""Format items as a flat numbered list."""
|
|
256
|
+
lines: list[str] = []
|
|
257
|
+
for i, item in enumerate(items, 1):
|
|
258
|
+
content = self._get_content(item)
|
|
259
|
+
# Handle multi-line content
|
|
260
|
+
content_lines = content.split("\n")
|
|
261
|
+
if len(content_lines) == 1:
|
|
262
|
+
lines.append(f"{i}. {content}")
|
|
263
|
+
else:
|
|
264
|
+
lines.append(f"{i}. {content_lines[0]}")
|
|
265
|
+
padding = " " * (len(str(i)) + 2)
|
|
266
|
+
for continuation in content_lines[1:]:
|
|
267
|
+
lines.append(f"{padding}{continuation}")
|
|
268
|
+
return "\n".join(lines)
|
|
269
|
+
|
|
270
|
+
def _format_grouped(
|
|
271
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
272
|
+
) -> str:
|
|
273
|
+
"""Format items grouped by tag prefix."""
|
|
274
|
+
groups = self._group_items(items, group_by)
|
|
275
|
+
lines: list[str] = []
|
|
276
|
+
for group_name, group_items in groups.items():
|
|
277
|
+
lines.append(f"[{group_name}]")
|
|
278
|
+
for i, item in enumerate(group_items, 1):
|
|
279
|
+
content = self._get_content(item)
|
|
280
|
+
content_lines = content.split("\n")
|
|
281
|
+
if len(content_lines) == 1:
|
|
282
|
+
lines.append(f" {i}. {content}")
|
|
283
|
+
else:
|
|
284
|
+
lines.append(f" {i}. {content_lines[0]}")
|
|
285
|
+
padding = " " * (len(str(i)) + 4)
|
|
286
|
+
for continuation in content_lines[1:]:
|
|
287
|
+
lines.append(f"{padding}{continuation}")
|
|
288
|
+
lines.append("")
|
|
289
|
+
return "\n".join(lines).rstrip()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class PlainFormatter(ContentExportFormatter):
|
|
293
|
+
"""Format content as plain text (content only, one per line)."""
|
|
294
|
+
|
|
295
|
+
def format(
|
|
296
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
297
|
+
) -> str:
|
|
298
|
+
"""Format items as plain text.
|
|
299
|
+
|
|
300
|
+
Grouping adds blank lines between groups but no headers.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
items: Content items to format
|
|
304
|
+
group_by: Optional tag prefix for grouping
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Plain text string with content only
|
|
308
|
+
"""
|
|
309
|
+
if group_by:
|
|
310
|
+
return self._format_grouped(items, group_by)
|
|
311
|
+
return self._format_flat(items)
|
|
312
|
+
|
|
313
|
+
def _format_flat(self, items: list[ContentPartItemResponse]) -> str:
|
|
314
|
+
"""Format items as plain text."""
|
|
315
|
+
lines: list[str] = []
|
|
316
|
+
for item in items:
|
|
317
|
+
content = self._get_content(item)
|
|
318
|
+
lines.append(content)
|
|
319
|
+
return "\n".join(lines)
|
|
320
|
+
|
|
321
|
+
def _format_grouped(
|
|
322
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
323
|
+
) -> str:
|
|
324
|
+
"""Format items grouped by tag prefix with blank line separators."""
|
|
325
|
+
groups = self._group_items(items, group_by)
|
|
326
|
+
sections: list[str] = []
|
|
327
|
+
for group_items in groups.values():
|
|
328
|
+
section_lines: list[str] = []
|
|
329
|
+
for item in group_items:
|
|
330
|
+
content = self._get_content(item)
|
|
331
|
+
section_lines.append(content)
|
|
332
|
+
sections.append("\n".join(section_lines))
|
|
333
|
+
return "\n\n".join(sections)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class JsonExportFormatter(ContentExportFormatter):
|
|
337
|
+
"""Format content as JSON array."""
|
|
338
|
+
|
|
339
|
+
def format(
|
|
340
|
+
self, items: list[ContentPartItemResponse], group_by: str | None = None
|
|
341
|
+
) -> str:
|
|
342
|
+
"""Format items as JSON.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
items: Content items to format
|
|
346
|
+
group_by: Optional tag prefix for grouping
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
JSON string (array or object if grouped)
|
|
350
|
+
"""
|
|
351
|
+
if group_by:
|
|
352
|
+
return self._format_grouped(items, group_by)
|
|
353
|
+
return self._format_flat(items)
|
|
354
|
+
|
|
355
|
+
def _item_to_dict(self, item: ContentPartItemResponse) -> dict[str, Any]:
|
|
356
|
+
"""Convert content item to dictionary for JSON serialization."""
|
|
357
|
+
return {
|
|
358
|
+
"id": str(item.id),
|
|
359
|
+
"content_type": item.content_type_id,
|
|
360
|
+
"content": item.content_text or item.content_structured,
|
|
361
|
+
"tags": [t.tag_path for t in item.tags],
|
|
362
|
+
"created_at": item.created_at.isoformat() if item.created_at else None,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
def _format_flat(self, items: list[ContentPartItemResponse]) -> str:
|
|
366
|
+
"""Format items as JSON array."""
|
|
367
|
+
data = [self._item_to_dict(item) for item in items]
|
|
368
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
369
|
+
|
|
370
|
+
def _format_grouped(
|
|
371
|
+
self, items: list[ContentPartItemResponse], group_by: str
|
|
372
|
+
) -> str:
|
|
373
|
+
"""Format items as JSON object with groups."""
|
|
374
|
+
groups = self._group_items(items, group_by)
|
|
375
|
+
data = {
|
|
376
|
+
group_name: [self._item_to_dict(item) for item in group_items]
|
|
377
|
+
for group_name, group_items in groups.items()
|
|
378
|
+
}
|
|
379
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# Registry of available export formatters
|
|
383
|
+
EXPORT_FORMATTERS: dict[str, type[ContentExportFormatter]] = {
|
|
384
|
+
"markdown": MarkdownFormatter,
|
|
385
|
+
"xml": XmlFormatter,
|
|
386
|
+
"numbered": NumberedFormatter,
|
|
387
|
+
"plain": PlainFormatter,
|
|
388
|
+
"json": JsonExportFormatter,
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def get_export_formatter(format_name: str) -> ContentExportFormatter | None:
|
|
393
|
+
"""Get an export formatter instance by name.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
format_name: Name of the format (markdown, xml, numbered, plain, json)
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Formatter instance or None if format not found
|
|
400
|
+
"""
|
|
401
|
+
formatter_cls = EXPORT_FORMATTERS.get(format_name)
|
|
402
|
+
if formatter_cls:
|
|
403
|
+
return formatter_cls()
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def get_available_formats() -> list[str]:
|
|
408
|
+
"""Get list of available export format names."""
|
|
409
|
+
return list(EXPORT_FORMATTERS.keys())
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Export handler for content list results.
|
|
2
|
+
|
|
3
|
+
Routes formatted content to various destinations:
|
|
4
|
+
- console: Print to stdout
|
|
5
|
+
- clipboard: Copy using pyperclip
|
|
6
|
+
- file: Write to specified path
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from alloy_runtime_types.dtos.content import ContentPartItemResponse
|
|
14
|
+
|
|
15
|
+
from cli.commands.content.list.export_formatters import (
|
|
16
|
+
EXPORT_FORMATTERS,
|
|
17
|
+
get_available_formats,
|
|
18
|
+
)
|
|
19
|
+
from cli.infrastructure.output import OutputService
|
|
20
|
+
|
|
21
|
+
# Import pyperclip at module level for easier mocking in tests
|
|
22
|
+
pyperclip: Any | None = None
|
|
23
|
+
try:
|
|
24
|
+
import pyperclip as _pyperclip
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
else:
|
|
28
|
+
pyperclip = _pyperclip
|
|
29
|
+
|
|
30
|
+
PYPERCLIP_AVAILABLE = pyperclip is not None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def export_content(
|
|
34
|
+
items: list[ContentPartItemResponse],
|
|
35
|
+
export_format: str,
|
|
36
|
+
output_mode: str,
|
|
37
|
+
output_file: str | None,
|
|
38
|
+
group_by: str | None,
|
|
39
|
+
) -> bool:
|
|
40
|
+
"""Export content items to specified destination.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
items: List of content part items to export
|
|
44
|
+
export_format: Format name (markdown, xml, numbered, plain, json)
|
|
45
|
+
output_mode: Destination (console, clipboard, file)
|
|
46
|
+
output_file: File path for file output mode
|
|
47
|
+
group_by: Optional tag prefix to group items by
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True on success, False on failure
|
|
51
|
+
"""
|
|
52
|
+
output = OutputService.get()
|
|
53
|
+
|
|
54
|
+
# Validate format
|
|
55
|
+
formatter_cls = EXPORT_FORMATTERS.get(export_format)
|
|
56
|
+
if not formatter_cls:
|
|
57
|
+
available = ", ".join(get_available_formats())
|
|
58
|
+
output.warning(
|
|
59
|
+
f"Unknown export format: '{export_format}'. Available: {available}"
|
|
60
|
+
)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
# Check for empty items
|
|
64
|
+
if not items:
|
|
65
|
+
output.info("No content items to export")
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Format the content
|
|
69
|
+
formatter = formatter_cls()
|
|
70
|
+
formatted_content = formatter.format(items, group_by)
|
|
71
|
+
|
|
72
|
+
# Route to destination
|
|
73
|
+
if output_mode == "clipboard":
|
|
74
|
+
return _copy_to_clipboard(formatted_content, len(items), output)
|
|
75
|
+
elif output_mode == "file":
|
|
76
|
+
return _save_to_file(formatted_content, output_file, len(items), output)
|
|
77
|
+
else: # console (default)
|
|
78
|
+
return _print_to_console(formatted_content, output)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _print_to_console(content: str, _output: OutputService) -> bool:
|
|
82
|
+
"""Print formatted content to stdout.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
content: Formatted content string
|
|
86
|
+
output: Output service for messages
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
True on success
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
print(content, file=sys.stdout)
|
|
93
|
+
return True
|
|
94
|
+
except (BrokenPipeError, IOError):
|
|
95
|
+
# Pipe was closed (e.g., piping to head)
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _copy_to_clipboard(content: str, item_count: int, output: OutputService) -> bool:
|
|
100
|
+
"""Copy formatted content to clipboard.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
content: Formatted content string
|
|
104
|
+
item_count: Number of items exported (for status message)
|
|
105
|
+
output: Output service for messages
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True on success, False on failure
|
|
109
|
+
"""
|
|
110
|
+
if not PYPERCLIP_AVAILABLE or pyperclip is None:
|
|
111
|
+
output.warning("pyperclip not installed. Install with: pip install pyperclip")
|
|
112
|
+
output.info("Falling back to console output...")
|
|
113
|
+
print(content, file=sys.stdout)
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
pyperclip.copy(content)
|
|
118
|
+
output.success(
|
|
119
|
+
f"Exported {item_count} item(s) to clipboard ({len(content):,} characters)"
|
|
120
|
+
)
|
|
121
|
+
return True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
output.warning(f"Failed to copy to clipboard: {e}")
|
|
124
|
+
output.info("Falling back to console output...")
|
|
125
|
+
print(content, file=sys.stdout)
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _save_to_file(
|
|
130
|
+
content: str, output_file: str | None, item_count: int, output: OutputService
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""Save formatted content to a file.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
content: Formatted content string
|
|
136
|
+
output_file: Target file path
|
|
137
|
+
item_count: Number of items exported (for status message)
|
|
138
|
+
output: Output service for messages
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
True on success, False on failure
|
|
142
|
+
"""
|
|
143
|
+
if not output_file:
|
|
144
|
+
output.warning("No output file specified (use --output-file)")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
file_path = Path(output_file).expanduser().resolve()
|
|
149
|
+
|
|
150
|
+
# Ensure parent directory exists
|
|
151
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
# Write the content
|
|
154
|
+
file_path.write_text(content, encoding="utf-8")
|
|
155
|
+
|
|
156
|
+
output.success(
|
|
157
|
+
f"Exported {item_count} item(s) to {file_path} ({len(content):,} characters)"
|
|
158
|
+
)
|
|
159
|
+
return True
|
|
160
|
+
except PermissionError:
|
|
161
|
+
output.warning(f"Permission denied: Cannot write to '{output_file}'")
|
|
162
|
+
return False
|
|
163
|
+
except OSError as e:
|
|
164
|
+
output.warning(f"Failed to save file: {e}")
|
|
165
|
+
return False
|