@scemoon/cdh 1.0.0
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.
- package/ai-dlc-skill/SKILL.md +75 -0
- package/ai-dlc-skill/agents/deliver-agent.md +24 -0
- package/ai-dlc-skill/agents/master.md +44 -0
- package/ai-dlc-skill/agents/plan-agent.md +23 -0
- package/ai-dlc-skill/agents/understand-agent.md +23 -0
- package/ai-dlc-skill/agents/verify-agent.md +27 -0
- package/ai-dlc-skill/architecture/project-structure.md +78 -0
- package/ai-dlc-skill/brownfield/README.md +30 -0
- package/ai-dlc-skill/brownfield/generate-context.sh +64 -0
- package/ai-dlc-skill/brownfield/scripts/deps.sh +27 -0
- package/ai-dlc-skill/brownfield/scripts/discover.sh +34 -0
- package/ai-dlc-skill/brownfield/scripts/extract-api.sh +45 -0
- package/ai-dlc-skill/components/backend.md +34 -0
- package/ai-dlc-skill/components/desktop.md +30 -0
- package/ai-dlc-skill/components/mya.md +29 -0
- package/ai-dlc-skill/components/native.md +30 -0
- package/ai-dlc-skill/components/tta.md +29 -0
- package/ai-dlc-skill/components/web.md +31 -0
- package/ai-dlc-skill/components/wxa.md +29 -0
- package/ai-dlc-skill/contracts/README.md +25 -0
- package/ai-dlc-skill/core/adaptive-flow.md +36 -0
- package/ai-dlc-skill/core/security.md +29 -0
- package/ai-dlc-skill/cross-tool/cline/export.sh +31 -0
- package/ai-dlc-skill/cross-tool/copilot/export.sh +47 -0
- package/ai-dlc-skill/cross-tool/cursor/export.sh +32 -0
- package/ai-dlc-skill/cross-tool/export.sh +22 -0
- package/ai-dlc-skill/cross-tool/onecode/config.yaml.tpl +17 -0
- package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +31 -0
- package/ai-dlc-skill/cross-tool/onecode/export.sh +48 -0
- package/ai-dlc-skill/phases/deliver/entry.md +26 -0
- package/ai-dlc-skill/phases/deliver/lifecycle.md +185 -0
- package/ai-dlc-skill/phases/deliver/prompt.md +40 -0
- package/ai-dlc-skill/phases/deliver/rules.md +61 -0
- package/ai-dlc-skill/phases/plan/entry.md +25 -0
- package/ai-dlc-skill/phases/plan/lifecycle.md +204 -0
- package/ai-dlc-skill/phases/plan/prompt.md +38 -0
- package/ai-dlc-skill/phases/plan/rules.md +25 -0
- package/ai-dlc-skill/phases/understand/entry.md +26 -0
- package/ai-dlc-skill/phases/understand/lifecycle.md +184 -0
- package/ai-dlc-skill/phases/understand/prompt.md +41 -0
- package/ai-dlc-skill/phases/understand/rules.md +37 -0
- package/ai-dlc-skill/phases/verify/entry.md +26 -0
- package/ai-dlc-skill/phases/verify/lifecycle.md +192 -0
- package/ai-dlc-skill/phases/verify/prompt.md +43 -0
- package/ai-dlc-skill/phases/verify/rules.md +73 -0
- package/ai-dlc-skill/practices/bdd.md +91 -0
- package/ai-dlc-skill/practices/sdd.md +84 -0
- package/ai-dlc-skill/practices/tdd.md +95 -0
- package/ai-dlc-skill/providers/README.md +114 -0
- package/ai-dlc-skill/providers/aliyun/deployment.yaml +66 -0
- package/ai-dlc-skill/providers/aliyun/preview.yaml +84 -0
- package/ai-dlc-skill/providers/aliyun/provider.yaml +160 -0
- package/ai-dlc-skill/providers/tcb/deployment.yaml +69 -0
- package/ai-dlc-skill/providers/tcb/preview.yaml +83 -0
- package/ai-dlc-skill/providers/tcb/provider.yaml +172 -0
- package/ai-dlc-skill/requirements.md +140 -0
- package/ai-dlc-skill/skill.yaml +147 -0
- package/ai-dlc-skill/templates/integration/CHANGELOG.md +41 -0
- package/ai-dlc-skill/templates/integration/asyncapi.yaml +43 -0
- package/ai-dlc-skill/templates/integration/contract-diff.md +63 -0
- package/ai-dlc-skill/templates/integration/contract-spec.md +49 -0
- package/ai-dlc-skill/templates/integration/openapi.yaml +18 -0
- package/ai-dlc-skill/templates/integration/runtime-contract.yaml +36 -0
- package/ai-dlc-skill/templates/plan/design.md +49 -0
- package/ai-dlc-skill/templates/plan/task-list.md +32 -0
- package/ai-dlc-skill/templates/project/README.md +127 -0
- package/ai-dlc-skill/templates/project/template.md +119 -0
- package/ai-dlc-skill/templates/understand/feature.feature +23 -0
- package/ai-dlc-skill/templates/understand/intent.md +29 -0
- package/ai-dlc-skill/templates/understand/spec-delta.md +47 -0
- package/ai-dlc-skill/templates/verify/test-unit.py +27 -0
- package/ai-dlc-skill/walkthrough/collect.sh +50 -0
- package/ai-dlc-skill/walkthrough/template.md +54 -0
- package/ai-dlc-skill/workflows/ai-dlc.yaml +110 -0
- package/ai-dlc-skill/workflows/deployment.yaml +199 -0
- package/cdh/__init__.py +0 -0
- package/cdh/__main__.py +4 -0
- package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
- package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +361 -0
- package/cdh/scaffold.py +312 -0
- package/cli.js +2 -0
- package/onecode/__init__.py +1 -0
- package/onecode/__main__.py +4 -0
- package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
- package/onecode/agent/__init__.py +0 -0
- package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/permissions_store.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__init__.py +29 -0
- package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +662 -0
- package/onecode/agent/attachments.py +113 -0
- package/onecode/agent/cdh_loader.py +262 -0
- package/onecode/agent/context.py +338 -0
- package/onecode/agent/engine.py +1894 -0
- package/onecode/agent/hooks.py +86 -0
- package/onecode/agent/onecode_agent_acp.py +2203 -0
- package/onecode/agent/permissions.py +151 -0
- package/onecode/agent/permissions_store.py +62 -0
- package/onecode/agent/session.py +186 -0
- package/onecode/agent/snapshot.py +145 -0
- package/onecode/agent/tools/__init__.py +17 -0
- package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/permissions.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +134 -0
- package/onecode/agent/tools/apply_patch_tool.py +144 -0
- package/onecode/agent/tools/bash_tool.py +91 -0
- package/onecode/agent/tools/communication_tools.py +149 -0
- package/onecode/agent/tools/config_tool.py +130 -0
- package/onecode/agent/tools/cron_tools.py +179 -0
- package/onecode/agent/tools/errors.py +17 -0
- package/onecode/agent/tools/file_ops.py +187 -0
- package/onecode/agent/tools/file_tools.py +226 -0
- package/onecode/agent/tools/git_tools.py +88 -0
- package/onecode/agent/tools/lsp_tools.py +404 -0
- package/onecode/agent/tools/mcp_tools.py +129 -0
- package/onecode/agent/tools/permission_handler.py +46 -0
- package/onecode/agent/tools/permissions.py +77 -0
- package/onecode/agent/tools/protocol.py +20 -0
- package/onecode/agent/tools/registry.py +134 -0
- package/onecode/agent/tools/sandbox.py +264 -0
- package/onecode/agent/tools/schema_validation.py +100 -0
- package/onecode/agent/tools/skill_tools.py +52 -0
- package/onecode/agent/tools/task_tools.py +249 -0
- package/onecode/agent/tools/web_tools.py +170 -0
- package/onecode/builtin_skills/git/SKILL.md +64 -0
- package/onecode/builtin_skills/git/skill.yaml +7 -0
- package/onecode/builtin_skills/shell/SKILL.md +58 -0
- package/onecode/builtin_skills/shell/skill.yaml +8 -0
- package/onecode/cli.py +755 -0
- package/onecode/cloud/__init__.py +0 -0
- package/onecode/cloud/base.py +20 -0
- package/onecode/codebase/__init__.py +69 -0
- package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/tools.cpython-314.pyc +0 -0
- package/onecode/codebase/chunker.py +130 -0
- package/onecode/codebase/indexer.py +136 -0
- package/onecode/codebase/retriever.py +192 -0
- package/onecode/codebase/storage.py +130 -0
- package/onecode/codebase/tools.py +59 -0
- package/onecode/commands.py +276 -0
- package/onecode/config.py +285 -0
- package/onecode/config_screen.py +594 -0
- package/onecode/cron/__init__.py +3 -0
- package/onecode/lsp/__init__.py +3 -0
- package/onecode/mcp/__init__.py +4 -0
- package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
- package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/mcp/client.py +193 -0
- package/onecode/mcp/manager.py +191 -0
- package/onecode/memory/__init__.py +95 -0
- package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/symbolic.cpython-314.pyc +0 -0
- package/onecode/memory/backend.py +185 -0
- package/onecode/memory/offload.py +114 -0
- package/onecode/memory/pyramid.py +202 -0
- package/onecode/memory/recall.py +168 -0
- package/onecode/memory/symbolic.py +164 -0
- package/onecode/models/__init__.py +4 -0
- package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +94 -0
- package/onecode/models/messages.py +662 -0
- package/onecode/models/provider.py +450 -0
- package/onecode/models/providers/__init__.py +17 -0
- package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +224 -0
- package/onecode/models/providers/deepseek_provider.py +210 -0
- package/onecode/models/providers/glm_provider.py +154 -0
- package/onecode/models/providers/minimax_provider.py +154 -0
- package/onecode/models/providers/minimaxi_provider.py +363 -0
- package/onecode/models/providers/ollama_provider.py +96 -0
- package/onecode/models/providers/openai_provider.py +233 -0
- package/onecode/models/registry.py +212 -0
- package/onecode/server/__init__.py +3 -0
- package/onecode/server/app.py +224 -0
- package/onecode/skills/__init__.py +11 -0
- package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/frontmatter.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/model.cpython-314.pyc +0 -0
- package/onecode/skills/argument_substitution.py +39 -0
- package/onecode/skills/create.py +56 -0
- package/onecode/skills/frontmatter.py +33 -0
- package/onecode/skills/loader.py +157 -0
- package/onecode/skills/manager.py +75 -0
- package/onecode/skills/model.py +43 -0
- package/onecode/storage/__init__.py +0 -0
- package/onecode/storage/activity.py +64 -0
- package/onecode/storage/project.py +54 -0
- package/onecode/storage/session.py +113 -0
- package/onecode/tasks/__init__.py +26 -0
- package/onecode/tasks/manager.py +219 -0
- package/onecode/tasks/models.py +240 -0
- package/onecode/trace/__init__.py +0 -0
- package/onecode/trace/tracer.py +174 -0
- package/onecode/utils/__init__.py +0 -0
- package/onecode/utils/helpers.py +20 -0
- package/onecode-cli.js +2 -0
- package/package.json +32 -0
- package/pyproject.toml +60 -0
- package/run.js +86 -0
- package/tui/__init__.py +46 -0
- package/tui/__main__.py +4 -0
- package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
- package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
- package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
- package/tui/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
- package/tui/__pycache__/agents.cpython-314.pyc +0 -0
- package/tui/__pycache__/answer.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
- package/tui/__pycache__/complete.cpython-314.pyc +0 -0
- package/tui/__pycache__/constants.cpython-314.pyc +0 -0
- package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
- package/tui/__pycache__/danger.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/dec.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
- package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
- package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
- package/tui/__pycache__/history.cpython-314.pyc +0 -0
- package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
- package/tui/__pycache__/menus.cpython-314.pyc +0 -0
- package/tui/__pycache__/message_log.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
- package/tui/__pycache__/path_filter.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/__pycache__/pill.cpython-314.pyc +0 -0
- package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
- package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
- package/tui/__pycache__/shell.cpython-314.pyc +0 -0
- package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
- package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
- package/tui/__pycache__/version.cpython-314.pyc +0 -0
- package/tui/_loop.py +86 -0
- package/tui/_path_fuzzy_search.py +48 -0
- package/tui/_path_match.py +141 -0
- package/tui/about.py +95 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/encode_tool_call_id.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +886 -0
- package/tui/acp/api.py +55 -0
- package/tui/acp/encode_tool_call_id.py +12 -0
- package/tui/acp/messages.py +190 -0
- package/tui/acp/prompt.py +110 -0
- package/tui/acp/protocol.py +462 -0
- package/tui/agent.py +70 -0
- package/tui/agent_schema.py +76 -0
- package/tui/agents.py +45 -0
- package/tui/ansi/__init__.py +1 -0
- package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
- package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
- package/tui/ansi/_ansi.py +1689 -0
- package/tui/ansi/_ansi_colors.py +264 -0
- package/tui/ansi/_control_codes.py +37 -0
- package/tui/ansi/_keys.py +251 -0
- package/tui/ansi/_sgr_styles.py +64 -0
- package/tui/ansi/_stream_parser.py +418 -0
- package/tui/answer.py +12 -0
- package/tui/app.py +1236 -0
- package/tui/atomic.py +37 -0
- package/tui/cli.py +313 -0
- package/tui/code_analyze.py +27 -0
- package/tui/complete.py +38 -0
- package/tui/constants.py +61 -0
- package/tui/conversation_markdown.py +30 -0
- package/tui/danger.py +372 -0
- package/tui/data/agents/ampcode.com.toml +51 -0
- package/tui/data/agents/augmentcode.com.toml +40 -0
- package/tui/data/agents/cdh.cloud-dev-harness.toml +22 -0
- package/tui/data/agents/claude.com.toml +41 -0
- package/tui/data/agents/copilot.github.com.toml +29 -0
- package/tui/data/agents/crowai.dev.toml +35 -0
- package/tui/data/agents/cursor.com.toml +38 -0
- package/tui/data/agents/deepagents.langchain.com +33 -0
- package/tui/data/agents/docker.com.toml +59 -0
- package/tui/data/agents/geminicli.com.toml +28 -0
- package/tui/data/agents/goose.ai.toml +51 -0
- package/tui/data/agents/inference.huggingface.co.toml +41 -0
- package/tui/data/agents/kimi.com.toml +35 -0
- package/tui/data/agents/kiro.dev.toml +42 -0
- package/tui/data/agents/openai.com.toml +53 -0
- package/tui/data/agents/openclaw.ai.toml +37 -0
- package/tui/data/agents/opencode.ai.toml +39 -0
- package/tui/data/agents/openhands.dev.toml +45 -0
- package/tui/data/agents/stakpak.dev.toml +61 -0
- package/tui/data/agents/vibe.mistral.ai.toml +30 -0
- package/tui/data/agents/vtcode.dev.toml +62 -0
- package/tui/data/images/frog.png +0 -0
- package/tui/data/sounds/question.wav +0 -0
- package/tui/data/sounds/turn-over.wav +0 -0
- package/tui/db.py +190 -0
- package/tui/dec.py +332 -0
- package/tui/directory.py +234 -0
- package/tui/directory_suggester.py +93 -0
- package/tui/directory_watcher.py +197 -0
- package/tui/format_path.py +25 -0
- package/tui/fuzzy.py +140 -0
- package/tui/fuzzy_index.py +275 -0
- package/tui/gist.py +33 -0
- package/tui/history.py +138 -0
- package/tui/jsonrpc.py +578 -0
- package/tui/menus.py +14 -0
- package/tui/message_log.py +69 -0
- package/tui/messages.py +139 -0
- package/tui/option_content.py +51 -0
- package/tui/os.py +0 -0
- package/tui/path_complete.py +217 -0
- package/tui/path_filter.py +124 -0
- package/tui/paths.py +71 -0
- package/tui/pill.py +23 -0
- package/tui/platform_commands.py +52 -0
- package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
- package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
- package/tui/prompt/extract.py +19 -0
- package/tui/prompt/resource.py +68 -0
- package/tui/protocol.py +28 -0
- package/tui/screens/__init__.py +0 -0
- package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/action_modal.py +94 -0
- package/tui/screens/agent_modal.py +175 -0
- package/tui/screens/command_edit_modal.py +74 -0
- package/tui/screens/log.py +553 -0
- package/tui/screens/main.py +411 -0
- package/tui/screens/main.tcss +655 -0
- package/tui/screens/permissions.py +404 -0
- package/tui/screens/permissions.tcss +72 -0
- package/tui/screens/projects.tcss +66 -0
- package/tui/screens/projects_app.py +319 -0
- package/tui/screens/projects_screen.py +142 -0
- package/tui/screens/session_resume_modal.py +172 -0
- package/tui/screens/session_resume_modal.tcss +35 -0
- package/tui/screens/sessions.py +166 -0
- package/tui/screens/sessions.tcss +119 -0
- package/tui/screens/settings.py +254 -0
- package/tui/screens/settings.tcss +101 -0
- package/tui/screens/store.py +500 -0
- package/tui/screens/store.tcss +274 -0
- package/tui/screens/tool_content_screen.py +59 -0
- package/tui/session_tracker.py +120 -0
- package/tui/settings.py +353 -0
- package/tui/settings_schema.py +431 -0
- package/tui/shell.py +304 -0
- package/tui/shell_read.py +42 -0
- package/tui/slash_command.py +34 -0
- package/tui/tui.tcss +246 -0
- package/tui/twitter.py +38 -0
- package/tui/version.py +80 -0
- package/tui/visuals/__init__.py +0 -0
- package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
- package/tui/visuals/columns.py +273 -0
- package/tui/widgets/__init__.py +0 -0
- package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/flash.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/highlighted_textarea.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/menu.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_tabs.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
- package/tui/widgets/acp_content.py +70 -0
- package/tui/widgets/agent_response.py +77 -0
- package/tui/widgets/agent_thought.py +168 -0
- package/tui/widgets/ask_user.py +264 -0
- package/tui/widgets/command_pane.py +223 -0
- package/tui/widgets/condensed_path.py +115 -0
- package/tui/widgets/context_stats.py +66 -0
- package/tui/widgets/conversation.py +2441 -0
- package/tui/widgets/danger_warning.py +65 -0
- package/tui/widgets/diff_view.py +59 -0
- package/tui/widgets/directory_input.py +8 -0
- package/tui/widgets/flash.py +81 -0
- package/tui/widgets/future_text.py +126 -0
- package/tui/widgets/grid_select.py +234 -0
- package/tui/widgets/highlighted_textarea.py +180 -0
- package/tui/widgets/mandelbrot.py +294 -0
- package/tui/widgets/markdown_note.py +13 -0
- package/tui/widgets/menu.py +147 -0
- package/tui/widgets/modified_files.py +106 -0
- package/tui/widgets/non_selectable_label.py +5 -0
- package/tui/widgets/note.py +18 -0
- package/tui/widgets/path_search.py +492 -0
- package/tui/widgets/plan.py +263 -0
- package/tui/widgets/project_directory_tree.py +90 -0
- package/tui/widgets/project_grid_select.py +35 -0
- package/tui/widgets/project_summary.py +33 -0
- package/tui/widgets/prompt.py +795 -0
- package/tui/widgets/question.py +360 -0
- package/tui/widgets/session_grid_select.py +224 -0
- package/tui/widgets/session_summary.py +115 -0
- package/tui/widgets/session_tabs.py +188 -0
- package/tui/widgets/shell_result.py +35 -0
- package/tui/widgets/shell_terminal.py +40 -0
- package/tui/widgets/side_bar.py +114 -0
- package/tui/widgets/slash_complete.py +235 -0
- package/tui/widgets/strike_text.py +70 -0
- package/tui/widgets/subagent.py +87 -0
- package/tui/widgets/terminal.py +577 -0
- package/tui/widgets/terminal_tool.py +351 -0
- package/tui/widgets/throbber.py +100 -0
- package/tui/widgets/tool_call.py +368 -0
- package/tui/widgets/user_input.py +23 -0
- package/tui/widgets/version.py +5 -0
- package/tui/widgets/welcome.py +31 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import partial
|
|
3
|
+
from rich.style import Style as RichStyle
|
|
4
|
+
|
|
5
|
+
from textual import work
|
|
6
|
+
from textual.app import ComposeResult, RenderResult
|
|
7
|
+
|
|
8
|
+
from textual import events
|
|
9
|
+
from textual.content import Content
|
|
10
|
+
from textual.geometry import Offset
|
|
11
|
+
from textual.reactive import reactive
|
|
12
|
+
from textual.renderables.bar import Bar
|
|
13
|
+
from textual.widget import Widget
|
|
14
|
+
from textual import containers
|
|
15
|
+
from textual import widgets
|
|
16
|
+
from textual import getters
|
|
17
|
+
from textual.message import Message
|
|
18
|
+
|
|
19
|
+
from tui.app import A2TUIApp
|
|
20
|
+
from tui.session_tracker import SessionDetails
|
|
21
|
+
from tui import messages
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SessionLabel(widgets.Label):
|
|
25
|
+
ALLOW_SELECT = False
|
|
26
|
+
|
|
27
|
+
def on_click(self) -> None:
|
|
28
|
+
if self.id is not None:
|
|
29
|
+
self.post_message(messages.SessionSwitch(self.id))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Underline(Widget):
|
|
33
|
+
"""The animated underline beneath tabs."""
|
|
34
|
+
|
|
35
|
+
COMPONENT_CLASSES = {"underline--bar"}
|
|
36
|
+
"""
|
|
37
|
+
| Class | Description |
|
|
38
|
+
| :- | :- |
|
|
39
|
+
| `underline--bar` | Style of the bar (may be used to change the color). |
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
highlight_start = reactive(0)
|
|
43
|
+
"""First cell in highlight."""
|
|
44
|
+
highlight_end = reactive(0)
|
|
45
|
+
"""Last cell (inclusive) in highlight."""
|
|
46
|
+
show_highlight: reactive[bool] = reactive(True)
|
|
47
|
+
"""Flag to indicate if a highlight should be shown at all."""
|
|
48
|
+
|
|
49
|
+
class Clicked(Message):
|
|
50
|
+
"""Inform ancestors the underline was clicked."""
|
|
51
|
+
|
|
52
|
+
offset: Offset
|
|
53
|
+
"""The offset of the click, relative to the origin of the bar."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, offset: Offset) -> None:
|
|
56
|
+
self.offset = offset
|
|
57
|
+
super().__init__()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def _highlight_range(self) -> tuple[int, int]:
|
|
61
|
+
"""Highlighted range for underline bar."""
|
|
62
|
+
return (
|
|
63
|
+
(self.highlight_start, self.highlight_end)
|
|
64
|
+
if self.show_highlight
|
|
65
|
+
else (0, 0)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def render(self) -> RenderResult:
|
|
69
|
+
"""Render the bar."""
|
|
70
|
+
bar_style = self.get_component_rich_style("underline--bar")
|
|
71
|
+
return Bar(
|
|
72
|
+
highlight_range=self._highlight_range,
|
|
73
|
+
highlight_style=RichStyle.from_color(bar_style.color),
|
|
74
|
+
background_style=RichStyle.from_color(bar_style.bgcolor),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _on_click(self, event: events.Click):
|
|
78
|
+
"""Catch clicks, so that the underline can activate the tabs."""
|
|
79
|
+
event.stop()
|
|
80
|
+
self.post_message(self.Clicked(event.screen_offset))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SessionsTabs(Widget):
|
|
84
|
+
|
|
85
|
+
ALLOW_SELECT = False
|
|
86
|
+
app: getters.app[A2TUIApp] = getters.app(A2TUIApp)
|
|
87
|
+
|
|
88
|
+
title_container = getters.query_one("#title-container", Widget)
|
|
89
|
+
|
|
90
|
+
current_session = reactive("", init=False)
|
|
91
|
+
|
|
92
|
+
def on_mount(self) -> None:
|
|
93
|
+
self.current_session = self.app.current_mode
|
|
94
|
+
self.app.mode_change_signal.subscribe(self, self.handle_mode_change)
|
|
95
|
+
self.app.session_update_signal.subscribe(
|
|
96
|
+
self, self.handle_session_update_signal, immediate=True
|
|
97
|
+
)
|
|
98
|
+
self.update_underline(self.current_session, animate=False)
|
|
99
|
+
self.call_after_refresh(self.update_underline, self.current_session)
|
|
100
|
+
|
|
101
|
+
def handle_mode_change(self, mode: str) -> None:
|
|
102
|
+
self.current_session = mode
|
|
103
|
+
|
|
104
|
+
def watch_current_session(self, old_session: str, new_session: str) -> None:
|
|
105
|
+
self.query(".-current").remove_class("-current")
|
|
106
|
+
self.query(f"#{new_session}").add_class("-current")
|
|
107
|
+
if old_session:
|
|
108
|
+
self.update_underline(old_session, animate=False)
|
|
109
|
+
|
|
110
|
+
self.update_underline(new_session, animate=True)
|
|
111
|
+
|
|
112
|
+
def update_underline(self, session: str | None = None, animate: bool = True):
|
|
113
|
+
if not self.is_mounted or not self.is_attached:
|
|
114
|
+
return
|
|
115
|
+
if session is None:
|
|
116
|
+
session = self.current_session
|
|
117
|
+
if not session:
|
|
118
|
+
return
|
|
119
|
+
if current_label := self.query_one_optional(f"#{session}", SessionLabel):
|
|
120
|
+
tab_region = current_label.virtual_region.shrink((0, 1, 0, 1))
|
|
121
|
+
if not tab_region:
|
|
122
|
+
return
|
|
123
|
+
start, end = tab_region.column_span
|
|
124
|
+
underline = self.query_one(Underline)
|
|
125
|
+
if animate:
|
|
126
|
+
underline.animate("highlight_start", start, duration=0.3)
|
|
127
|
+
underline.animate(
|
|
128
|
+
"highlight_end",
|
|
129
|
+
end,
|
|
130
|
+
duration=0.3,
|
|
131
|
+
on_complete=partial(self.scroll_to_center, current_label),
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
underline.highlight_start = start
|
|
135
|
+
underline.highlight_end = end
|
|
136
|
+
self.scroll_to_center(current_label, animate=False)
|
|
137
|
+
|
|
138
|
+
def render_session_label(self, session: SessionDetails) -> Content:
|
|
139
|
+
match session.state:
|
|
140
|
+
case "asking":
|
|
141
|
+
return Content.assemble(
|
|
142
|
+
("❯ ", "not dim $text-secondary"), session.title
|
|
143
|
+
)
|
|
144
|
+
case "busy":
|
|
145
|
+
return Content(f"⌛ {session.title}")
|
|
146
|
+
return Content(session.title)
|
|
147
|
+
|
|
148
|
+
def compose(self) -> ComposeResult:
|
|
149
|
+
with containers.HorizontalGroup(id="title-container"):
|
|
150
|
+
for session in self.app.session_tracker.ordered_sessions:
|
|
151
|
+
yield SessionLabel(
|
|
152
|
+
self.render_session_label(session),
|
|
153
|
+
id=session.mode_name,
|
|
154
|
+
classes="-current" if session.mode_name == self.screen.id else "",
|
|
155
|
+
)
|
|
156
|
+
yield Underline()
|
|
157
|
+
|
|
158
|
+
@work(exit_on_error=False)
|
|
159
|
+
async def handle_session_update_signal(
|
|
160
|
+
self, update: tuple[str, SessionDetails | None]
|
|
161
|
+
) -> None:
|
|
162
|
+
mode, details = update
|
|
163
|
+
if details is None:
|
|
164
|
+
removed_mode = mode
|
|
165
|
+
await self.query(f"#{mode}").remove()
|
|
166
|
+
if self.current_session == removed_mode:
|
|
167
|
+
remaining = list(self.query(SessionLabel))
|
|
168
|
+
if remaining:
|
|
169
|
+
new_current = remaining[0].id or ""
|
|
170
|
+
else:
|
|
171
|
+
new_current = ""
|
|
172
|
+
self.current_session = new_current
|
|
173
|
+
else:
|
|
174
|
+
if tab_label := self.query_one_optional(f"#{mode}", SessionLabel):
|
|
175
|
+
tab_label.update(self.render_session_label(details))
|
|
176
|
+
else:
|
|
177
|
+
self.query(SessionLabel).remove_class("-current")
|
|
178
|
+
await self.title_container.mount(
|
|
179
|
+
SessionLabel(
|
|
180
|
+
self.render_session_label(details),
|
|
181
|
+
id=details.mode_name,
|
|
182
|
+
classes=(
|
|
183
|
+
"-current" if details.mode_name == self.screen.id else ""
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
await asyncio.sleep(0.05)
|
|
188
|
+
self.call_after_refresh(self.update_underline, self.current_session)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual import containers
|
|
6
|
+
from textual.highlight import highlight
|
|
7
|
+
from textual.widgets import Static
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from tui.menus import MenuItem
|
|
11
|
+
from tui.widgets.non_selectable_label import NonSelectableLabel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ShellResult(containers.HorizontalGroup):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
command: str,
|
|
18
|
+
*,
|
|
19
|
+
name: str | None = None,
|
|
20
|
+
id: str | None = None,
|
|
21
|
+
classes: str | None = None,
|
|
22
|
+
disabled: bool = False,
|
|
23
|
+
) -> None:
|
|
24
|
+
self._command = command
|
|
25
|
+
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
|
26
|
+
|
|
27
|
+
def compose(self) -> ComposeResult:
|
|
28
|
+
yield NonSelectableLabel("$", id="prompt", markup=False)
|
|
29
|
+
yield Static(highlight(self._command, language="sh"))
|
|
30
|
+
|
|
31
|
+
def get_block_menu(self) -> Iterable[MenuItem]:
|
|
32
|
+
yield from ()
|
|
33
|
+
|
|
34
|
+
def get_block_content(self, destination: str) -> str | None:
|
|
35
|
+
return self._command
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from textual.content import Content
|
|
5
|
+
from textual.dom import DOMNode
|
|
6
|
+
from textual.message import Message
|
|
7
|
+
|
|
8
|
+
from tui.menus import MenuItem
|
|
9
|
+
from tui.widgets.terminal import Terminal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ShellTerminal(Terminal):
|
|
13
|
+
"""Subclass of Terminal used in the Shell view."""
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Terminate(Message):
|
|
17
|
+
teminal: ShellTerminal
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def control(self) -> DOMNode:
|
|
21
|
+
return self.terminal
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Interrupt(Message):
|
|
25
|
+
teminal: ShellTerminal
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def control(self) -> DOMNode:
|
|
29
|
+
return self.terminal
|
|
30
|
+
|
|
31
|
+
def get_block_menu(self) -> Iterable[MenuItem]:
|
|
32
|
+
if not self.is_finalized:
|
|
33
|
+
yield MenuItem("Interrupt", "interrupt", "i")
|
|
34
|
+
yield MenuItem("Focus", f"focus_block({self.id!r})", "f")
|
|
35
|
+
|
|
36
|
+
def get_block_content(self, destination: str) -> str | None:
|
|
37
|
+
return "\n".join(line.content.plain for line in self.state.buffer.lines)
|
|
38
|
+
|
|
39
|
+
def on_mount(self) -> None:
|
|
40
|
+
self.border_title = Content(self.name)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.widget import Widget
|
|
5
|
+
from textual import containers
|
|
6
|
+
from textual import widgets
|
|
7
|
+
from textual.message import Message
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SideBarCollapsible(widgets.Collapsible):
|
|
11
|
+
BINDING_GROUP_TITLE = "Sidebar collapsible"
|
|
12
|
+
HELP = """\
|
|
13
|
+
## Sidebar
|
|
14
|
+
|
|
15
|
+
This is your sidebar.
|
|
16
|
+
|
|
17
|
+
The Sidebar contains additonal information associated with the conversation.
|
|
18
|
+
|
|
19
|
+
- **tab / shift+tab** Navigate sections
|
|
20
|
+
- **enter** expand or collapse secions
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SideBar(containers.Vertical):
|
|
25
|
+
BINDING_GROUP_TITLE = "Sidebar"
|
|
26
|
+
BINDINGS = [("escape", "dismiss", "Dismiss sidebar")]
|
|
27
|
+
|
|
28
|
+
class Dismiss(Message):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Panel:
|
|
33
|
+
title: str
|
|
34
|
+
widget: Widget
|
|
35
|
+
flex: bool = False
|
|
36
|
+
collapsed: bool = False
|
|
37
|
+
id: str | None = None
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*panels: Panel,
|
|
42
|
+
name: str | None = None,
|
|
43
|
+
id: str | None = None,
|
|
44
|
+
classes: str | None = None,
|
|
45
|
+
disabled: bool = False,
|
|
46
|
+
hide: bool = False,
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
|
49
|
+
self.panels: list[SideBar.Panel] = [*panels]
|
|
50
|
+
self.hide = hide
|
|
51
|
+
|
|
52
|
+
def on_mount(self) -> None:
|
|
53
|
+
self.trap_focus()
|
|
54
|
+
|
|
55
|
+
def compose(self) -> ComposeResult:
|
|
56
|
+
for panel in self.panels:
|
|
57
|
+
yield SideBarCollapsible(
|
|
58
|
+
panel.widget,
|
|
59
|
+
title=panel.title,
|
|
60
|
+
collapsed=panel.collapsed,
|
|
61
|
+
classes="-flex" if panel.flex else "-fixed",
|
|
62
|
+
id=panel.id,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def action_dismiss(self) -> None:
|
|
66
|
+
self.post_message(self.Dismiss())
|
|
67
|
+
|
|
68
|
+
def has_panel(self, panel_id: str) -> bool:
|
|
69
|
+
return self.query_one_optional(f"#{panel_id}") is not None
|
|
70
|
+
|
|
71
|
+
async def add_panel(self, panel: Panel) -> SideBarCollapsible:
|
|
72
|
+
collapsible = SideBarCollapsible(
|
|
73
|
+
panel.widget,
|
|
74
|
+
title=panel.title,
|
|
75
|
+
collapsed=panel.collapsed,
|
|
76
|
+
classes="-flex" if panel.flex else "-fixed",
|
|
77
|
+
id=panel.id,
|
|
78
|
+
)
|
|
79
|
+
await self.mount(collapsible)
|
|
80
|
+
self.panels.append(panel)
|
|
81
|
+
return collapsible
|
|
82
|
+
|
|
83
|
+
def remove_panel(self, panel_id: str) -> bool:
|
|
84
|
+
for i, p in enumerate(self.panels):
|
|
85
|
+
if p.id == panel_id:
|
|
86
|
+
widget = self.query_one_optional(f"#{panel_id}")
|
|
87
|
+
if widget is not None:
|
|
88
|
+
widget.remove()
|
|
89
|
+
self.panels.pop(i)
|
|
90
|
+
return True
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
from textual.app import App, ComposeResult
|
|
96
|
+
|
|
97
|
+
class SApp(App):
|
|
98
|
+
def compose(self) -> ComposeResult:
|
|
99
|
+
yield SideBar(
|
|
100
|
+
SideBar.Panel("Hello", widgets.Label("Hello, World!")),
|
|
101
|
+
SideBar.Panel(
|
|
102
|
+
"Files",
|
|
103
|
+
widgets.DirectoryTree(
|
|
104
|
+
"~/",
|
|
105
|
+
),
|
|
106
|
+
flex=True,
|
|
107
|
+
),
|
|
108
|
+
SideBar.Panel(
|
|
109
|
+
"Hello",
|
|
110
|
+
widgets.Static("Where there is a Will! " * 10),
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
SApp().run()
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from operator import itemgetter
|
|
3
|
+
from typing import Iterable, Self, Sequence
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.content import Content, Span
|
|
9
|
+
|
|
10
|
+
from textual import getters
|
|
11
|
+
from textual.message import Message
|
|
12
|
+
from textual.reactive import var
|
|
13
|
+
from textual import containers
|
|
14
|
+
from textual import widgets
|
|
15
|
+
from textual.widgets.option_list import Option
|
|
16
|
+
|
|
17
|
+
from tui.fuzzy import FuzzySearch
|
|
18
|
+
from tui.messages import Dismiss
|
|
19
|
+
from tui.slash_command import SlashCommand
|
|
20
|
+
from tui.visuals.columns import Columns
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SlashCompleteInput(widgets.Input):
|
|
24
|
+
BINDING_GROUP_TITLE = "Fuzzy search slash commands"
|
|
25
|
+
HELP = """\
|
|
26
|
+
## Slash command fuzzy search
|
|
27
|
+
|
|
28
|
+
Search for slash commands by typing a few characters from the command.
|
|
29
|
+
|
|
30
|
+
- **cursor keys** Navigate list
|
|
31
|
+
- **enter** Add command to prompt
|
|
32
|
+
- **escape** Dismiss fuzzy search
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SlashComplete(containers.VerticalGroup):
|
|
37
|
+
"""A widget to auto-complete slash commands."""
|
|
38
|
+
|
|
39
|
+
CURSOR_BINDING_GROUP = Binding.Group(description="Select")
|
|
40
|
+
BINDINGS = [
|
|
41
|
+
Binding(
|
|
42
|
+
"up",
|
|
43
|
+
"cursor_up",
|
|
44
|
+
"Cursor up",
|
|
45
|
+
group=CURSOR_BINDING_GROUP,
|
|
46
|
+
priority=True,
|
|
47
|
+
),
|
|
48
|
+
Binding(
|
|
49
|
+
"down",
|
|
50
|
+
"cursor_down",
|
|
51
|
+
"Cursor down",
|
|
52
|
+
group=CURSOR_BINDING_GROUP,
|
|
53
|
+
priority=True,
|
|
54
|
+
),
|
|
55
|
+
Binding("enter", "submit", "Insert /command", priority=True),
|
|
56
|
+
Binding("escape", "dismiss", "Dismiss", priority=True),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
DEFAULT_CSS = """
|
|
60
|
+
SlashComplete {
|
|
61
|
+
OptionList {
|
|
62
|
+
height: auto;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
input = getters.query_one(widgets.Input)
|
|
68
|
+
option_list = getters.query_one(widgets.OptionList)
|
|
69
|
+
|
|
70
|
+
slash_commands: var[list[SlashCommand]] = var(list)
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Completed(Message):
|
|
74
|
+
command: str
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
slash_commands: Iterable[SlashCommand] | None = None,
|
|
79
|
+
id: str | None = None,
|
|
80
|
+
classes: str | None = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
super().__init__(id=id, classes=classes)
|
|
83
|
+
self.slash_commands = list(slash_commands) if slash_commands else []
|
|
84
|
+
self.hints = {
|
|
85
|
+
slash_command.command: slash_command.hint
|
|
86
|
+
for slash_command in self.slash_commands
|
|
87
|
+
if slash_command.hint
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
self.fuzzy_search = FuzzySearch(case_sensitive=False)
|
|
91
|
+
|
|
92
|
+
def compose(self) -> ComposeResult:
|
|
93
|
+
yield SlashCompleteInput(compact=True, placeholder="fuzzy search")
|
|
94
|
+
yield widgets.OptionList()
|
|
95
|
+
|
|
96
|
+
def focus(self, scroll_visible: bool = False) -> Self:
|
|
97
|
+
self.filter_slash_commands("")
|
|
98
|
+
self.input.focus(scroll_visible)
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def on_mount(self) -> None:
|
|
102
|
+
self.filter_slash_commands("")
|
|
103
|
+
|
|
104
|
+
def on_descendant_blur(self) -> None:
|
|
105
|
+
self.post_message(Dismiss(self))
|
|
106
|
+
|
|
107
|
+
@on(widgets.Input.Changed)
|
|
108
|
+
def on_input_changed(self, event: widgets.Input.Changed) -> None:
|
|
109
|
+
event.stop()
|
|
110
|
+
self.filter_slash_commands(event.value)
|
|
111
|
+
|
|
112
|
+
async def watch_slash_commands(self, slash_commands: list[SlashCommand]) -> None:
|
|
113
|
+
self.hints = {
|
|
114
|
+
slash_command.command: slash_command.hint
|
|
115
|
+
for slash_command in slash_commands
|
|
116
|
+
if slash_command.hint
|
|
117
|
+
}
|
|
118
|
+
self.filter_slash_commands(self.input.value)
|
|
119
|
+
|
|
120
|
+
def filter_slash_commands(self, prompt: str) -> None:
|
|
121
|
+
"""Filter slash commands by the given prompt.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
prompt: Text prompt.
|
|
125
|
+
"""
|
|
126
|
+
prompt = prompt.lstrip("/").casefold().rstrip()
|
|
127
|
+
columns = self.columns = Columns("auto", "flex")
|
|
128
|
+
|
|
129
|
+
slash_commands = sorted(
|
|
130
|
+
self.slash_commands,
|
|
131
|
+
key=lambda slash_command: slash_command.command.casefold(),
|
|
132
|
+
)
|
|
133
|
+
deduplicated_slash_commands = {
|
|
134
|
+
slash_command.command: slash_command for slash_command in slash_commands
|
|
135
|
+
}
|
|
136
|
+
self.fuzzy_search.cache.grow(len(deduplicated_slash_commands))
|
|
137
|
+
|
|
138
|
+
if prompt:
|
|
139
|
+
slash_prompt = f"/{prompt}"
|
|
140
|
+
scores: list[tuple[float, Sequence[int], SlashCommand]] = [
|
|
141
|
+
(
|
|
142
|
+
*self.fuzzy_search.match(prompt, slash_command.command[1:]),
|
|
143
|
+
slash_command,
|
|
144
|
+
)
|
|
145
|
+
for slash_command in slash_commands
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
scores = sorted(
|
|
149
|
+
[
|
|
150
|
+
(
|
|
151
|
+
(
|
|
152
|
+
score * 2
|
|
153
|
+
if slash_command.command.casefold().startswith(slash_prompt)
|
|
154
|
+
else score
|
|
155
|
+
),
|
|
156
|
+
highlights,
|
|
157
|
+
slash_command,
|
|
158
|
+
)
|
|
159
|
+
for score, highlights, slash_command in scores
|
|
160
|
+
if score
|
|
161
|
+
],
|
|
162
|
+
key=itemgetter(0),
|
|
163
|
+
reverse=True,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
scores = [(1.0, [], slash_command) for slash_command in slash_commands]
|
|
167
|
+
|
|
168
|
+
def make_row(
|
|
169
|
+
slash_command: SlashCommand, indices: Iterable[int]
|
|
170
|
+
) -> tuple[Content, ...]:
|
|
171
|
+
"""Make a row for the Columns display.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
slash_command: The slash command instance.
|
|
175
|
+
indices: Indices of matching characters.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
A tuple of `Content` instances for use as a column row.
|
|
179
|
+
"""
|
|
180
|
+
command = Content.styled(slash_command.command, "$text-success")
|
|
181
|
+
command = command.add_spans(
|
|
182
|
+
[Span(index + 1, index + 2, "underline not dim") for index in indices]
|
|
183
|
+
)
|
|
184
|
+
return (command, Content.styled(slash_command.help, "dim"))
|
|
185
|
+
|
|
186
|
+
rows = [
|
|
187
|
+
(
|
|
188
|
+
columns.add_row(
|
|
189
|
+
*make_row(slash_command, indices),
|
|
190
|
+
),
|
|
191
|
+
slash_command.command,
|
|
192
|
+
)
|
|
193
|
+
for _, indices, slash_command in scores
|
|
194
|
+
]
|
|
195
|
+
self.option_list.set_options(
|
|
196
|
+
Option(row, id=command_name) for row, command_name in rows
|
|
197
|
+
)
|
|
198
|
+
if self.display:
|
|
199
|
+
self.option_list.highlighted = 0
|
|
200
|
+
else:
|
|
201
|
+
with self.option_list.prevent(widgets.OptionList.OptionHighlighted):
|
|
202
|
+
self.option_list.highlighted = 0
|
|
203
|
+
|
|
204
|
+
def action_cursor_down(self) -> None:
|
|
205
|
+
self.option_list.action_cursor_down()
|
|
206
|
+
|
|
207
|
+
def action_cursor_up(self) -> None:
|
|
208
|
+
self.option_list.action_cursor_up()
|
|
209
|
+
|
|
210
|
+
def action_dismiss(self) -> None:
|
|
211
|
+
self.post_message(Dismiss(self))
|
|
212
|
+
|
|
213
|
+
def action_submit(self) -> None:
|
|
214
|
+
option_list = self.option_list
|
|
215
|
+
if (option := option_list.highlighted_option) is not None:
|
|
216
|
+
with self.input.prevent(widgets.Input.Changed):
|
|
217
|
+
self.input.clear()
|
|
218
|
+
self.post_message(self.Completed(option.id or ""))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
from textual.app import App, ComposeResult
|
|
223
|
+
|
|
224
|
+
COMMANDS = [
|
|
225
|
+
SlashCommand("/help", "Help with slash commands"),
|
|
226
|
+
SlashCommand("/foo", "This is FOO"),
|
|
227
|
+
SlashCommand("/bar", "This is BAR"),
|
|
228
|
+
SlashCommand("/baz", "This is BAZ"),
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
class SlashApp(App):
|
|
232
|
+
def compose(self) -> ComposeResult:
|
|
233
|
+
yield SlashComplete(COMMANDS)
|
|
234
|
+
|
|
235
|
+
SlashApp().run()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from time import monotonic
|
|
2
|
+
|
|
3
|
+
from textual.widget import Widget
|
|
4
|
+
|
|
5
|
+
from textual.content import Content
|
|
6
|
+
from textual.reactive import reactive
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StrikeText(Widget):
|
|
10
|
+
DEFAULT_CSS = """
|
|
11
|
+
StrikeText {
|
|
12
|
+
height: auto;
|
|
13
|
+
&.-complete {
|
|
14
|
+
text-style: strike;
|
|
15
|
+
color: $text-primary;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
strike_time: reactive[float | None] = reactive(None)
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
content: Content,
|
|
25
|
+
name: str | None = None,
|
|
26
|
+
id: str | None = None,
|
|
27
|
+
classes: str | None = None,
|
|
28
|
+
):
|
|
29
|
+
self.content = content
|
|
30
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
31
|
+
|
|
32
|
+
def strike(self) -> None:
|
|
33
|
+
self.strike_time = monotonic()
|
|
34
|
+
self.auto_refresh = 1 / 30
|
|
35
|
+
|
|
36
|
+
def render(self) -> Content:
|
|
37
|
+
content = self.content
|
|
38
|
+
if self.strike_time is not None:
|
|
39
|
+
position = int((monotonic() - self.strike_time) * 70)
|
|
40
|
+
content = content.stylize("strike $text-primary", 0, position)
|
|
41
|
+
if position > len(content):
|
|
42
|
+
self.auto_refresh = None
|
|
43
|
+
return content
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
from textual.app import App, ComposeResult
|
|
48
|
+
from textual.widgets import Static
|
|
49
|
+
|
|
50
|
+
class StrikeApp(App):
|
|
51
|
+
CSS = """
|
|
52
|
+
Screen {
|
|
53
|
+
overflow: auto;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
BINDINGS = [("space", "strike", "Strike")]
|
|
58
|
+
|
|
59
|
+
def compose(self) -> ComposeResult:
|
|
60
|
+
for n in range(20):
|
|
61
|
+
yield Static("HELLO")
|
|
62
|
+
yield StrikeText(Content("Where there is a Will, there is a way"))
|
|
63
|
+
for n in range(200):
|
|
64
|
+
yield Static("World")
|
|
65
|
+
|
|
66
|
+
def action_strike(self):
|
|
67
|
+
self.query_one(StrikeText).strike()
|
|
68
|
+
|
|
69
|
+
app = StrikeApp()
|
|
70
|
+
app.run()
|