@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
package/tui/messages.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from textual.content import Content
|
|
6
|
+
from textual.widget import Widget
|
|
7
|
+
from textual.message import Message
|
|
8
|
+
|
|
9
|
+
from tui.session_tracker import SessionState
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkStarted(Message):
|
|
13
|
+
"""Work has started."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorkFinished(Message):
|
|
17
|
+
"""Work has finished."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class HistoryMove(Message):
|
|
22
|
+
"""Getting a new item form history."""
|
|
23
|
+
|
|
24
|
+
direction: Literal[-1, +1]
|
|
25
|
+
shell: bool
|
|
26
|
+
body: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class UserInputSubmitted(Message):
|
|
31
|
+
body: str
|
|
32
|
+
shell: bool = False
|
|
33
|
+
auto_complete: bool = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class PromptSuggestion(Message):
|
|
38
|
+
suggestion: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Dismiss(Message):
|
|
43
|
+
widget: Widget
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def control(self) -> Widget:
|
|
47
|
+
return self.widget
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class InsertPath(Message):
|
|
52
|
+
path: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ChangeMode(Message):
|
|
57
|
+
mode_id: str | None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Flash(Message):
|
|
62
|
+
"""Request a message flash.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
Message: Content of flash.
|
|
66
|
+
style: Semantic style.
|
|
67
|
+
duration: Duration in seconds or `None` for default.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
content: str | Content
|
|
71
|
+
style: Literal["default", "warning", "success", "error"]
|
|
72
|
+
duration: float | None = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ProjectDirectoryUpdated(Message):
|
|
76
|
+
"""The project directory may may changed."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class SessionNavigate(Message):
|
|
81
|
+
"""Request to switch session."""
|
|
82
|
+
|
|
83
|
+
mode_name: str
|
|
84
|
+
direction: Literal[-1, +1]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class SessionSwitch(Message):
|
|
89
|
+
"""Switch to specified session."""
|
|
90
|
+
|
|
91
|
+
mode_name: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class SessionNew(Message):
|
|
96
|
+
"""Open a new session."""
|
|
97
|
+
|
|
98
|
+
path: str
|
|
99
|
+
"""project directory path."""
|
|
100
|
+
agent: str
|
|
101
|
+
"""Agent identity."""
|
|
102
|
+
prompt: str
|
|
103
|
+
"""Initial prompt or command"""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class SessionUpdate(Message):
|
|
108
|
+
name: str | None = None
|
|
109
|
+
"""Name of the session, or `None` for no change."""
|
|
110
|
+
subtitle: str | None = None
|
|
111
|
+
"""Session subtitle (name of agent)."""
|
|
112
|
+
path: str | None = None
|
|
113
|
+
"""Project directory path."""
|
|
114
|
+
state: SessionState | None = None
|
|
115
|
+
"""New session state."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class SessionClose(Message):
|
|
120
|
+
name: str
|
|
121
|
+
"""Name of the session."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class SessionLoad(Message):
|
|
126
|
+
"""Load a session by ID."""
|
|
127
|
+
|
|
128
|
+
session_pk: int
|
|
129
|
+
"""Primary key of the session to load."""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class LaunchAgent(Message):
|
|
134
|
+
"""Inform app to launch agent."""
|
|
135
|
+
|
|
136
|
+
identity: str
|
|
137
|
+
session_id: str | None = None
|
|
138
|
+
pk: int | None = None
|
|
139
|
+
prompt: str | None = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from textual.content import Content
|
|
2
|
+
from textual.css.styles import RulesMap
|
|
3
|
+
from textual.style import Style
|
|
4
|
+
from textual.visual import Visual, RenderOptions
|
|
5
|
+
from textual.strip import Strip
|
|
6
|
+
|
|
7
|
+
from itertools import zip_longest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OptionContent(Visual):
|
|
11
|
+
def __init__(self, option: str | Content, help: str | Content) -> None:
|
|
12
|
+
self.option = Content(option) if isinstance(option, str) else option
|
|
13
|
+
self.help = Content(help) if isinstance(help, str) else help
|
|
14
|
+
self._label = Content(f"{option} {help}")
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return str(self.option)
|
|
18
|
+
|
|
19
|
+
def render_strips(
|
|
20
|
+
self, width: int, height: int | None, style: Style, options: RenderOptions
|
|
21
|
+
) -> list[Strip]:
|
|
22
|
+
option_strips = [
|
|
23
|
+
Strip(
|
|
24
|
+
self.option.render_segments(style), cell_length=self.option.cell_length
|
|
25
|
+
)
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
option_width = self.option.cell_length
|
|
29
|
+
remaining_width = width - self.option.cell_length
|
|
30
|
+
|
|
31
|
+
help_strips = self.help.render_strips(
|
|
32
|
+
remaining_width, None, style, options=options
|
|
33
|
+
)
|
|
34
|
+
help_width = max(strip.cell_length for strip in help_strips)
|
|
35
|
+
help_width = [strip.extend_cell_length(help_width) for strip in help_strips]
|
|
36
|
+
|
|
37
|
+
strips: list[Strip] = []
|
|
38
|
+
for option_strip, help_strip in zip_longest(option_strips, help_strips):
|
|
39
|
+
if option_strip is None:
|
|
40
|
+
option_strip = Strip.blank(option_width)
|
|
41
|
+
assert isinstance(help_strip, Strip)
|
|
42
|
+
strips.append(Strip.join([option_strip, help_strip]))
|
|
43
|
+
return strips
|
|
44
|
+
|
|
45
|
+
def get_optimal_width(self, rules: RulesMap, container_width: int) -> int:
|
|
46
|
+
return self._label.get_optimal_width(rules, container_width)
|
|
47
|
+
|
|
48
|
+
def get_height(self, rules: RulesMap, width: int) -> int:
|
|
49
|
+
label_width = self.option.cell_length + 1
|
|
50
|
+
height = self.help.get_height(rules, width - label_width)
|
|
51
|
+
return height
|
package/tui/os.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from itertools import islice
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Iterable, Literal, Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def longest_common_prefix(strings: list[str]) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Find the longest common prefix among a list of strings.
|
|
11
|
+
|
|
12
|
+
Arguments:
|
|
13
|
+
strings: List of strings
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The longest common prefix string
|
|
17
|
+
"""
|
|
18
|
+
if not strings:
|
|
19
|
+
return ""
|
|
20
|
+
|
|
21
|
+
# Start with the first string as reference
|
|
22
|
+
prefix: str = strings[0]
|
|
23
|
+
|
|
24
|
+
# Compare with each subsequent string
|
|
25
|
+
for current_string in strings[1:]:
|
|
26
|
+
# Reduce prefix until it matches the start of current string
|
|
27
|
+
while not current_string.startswith(prefix):
|
|
28
|
+
prefix = prefix[:-1]
|
|
29
|
+
if not prefix:
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
return prefix
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DirectoryReadTask:
|
|
36
|
+
"""A task to read a directory."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, path: Path) -> None:
|
|
39
|
+
self.path = path
|
|
40
|
+
self.done_event = asyncio.Event()
|
|
41
|
+
self.directory_listing: list[Path] = []
|
|
42
|
+
self._task: asyncio.Task | None = None
|
|
43
|
+
|
|
44
|
+
def read(self) -> None:
|
|
45
|
+
"""Read the directory contents."""
|
|
46
|
+
# TODO: Arbitary limit, to avoid stalling on very large directories
|
|
47
|
+
# Should this be configurable?
|
|
48
|
+
try:
|
|
49
|
+
for path in islice(self.path.iterdir(), None, 10_000):
|
|
50
|
+
self.directory_listing.append(path)
|
|
51
|
+
except OSError:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
def start(self) -> None:
|
|
55
|
+
self._task = asyncio.create_task(
|
|
56
|
+
self.run(), name=f"DirectoryReadTask({str(self.path)!r})"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
async def run(self):
|
|
60
|
+
try:
|
|
61
|
+
await asyncio.to_thread(self.read)
|
|
62
|
+
finally:
|
|
63
|
+
self.done_event.set()
|
|
64
|
+
|
|
65
|
+
async def wait(self) -> list[Path]:
|
|
66
|
+
await self.done_event.wait()
|
|
67
|
+
return self.directory_listing
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PathComplete:
|
|
71
|
+
"""Auto completes paths."""
|
|
72
|
+
|
|
73
|
+
def __init__(self) -> None:
|
|
74
|
+
self.read_tasks: dict[Path, DirectoryReadTask] = {}
|
|
75
|
+
self.directory_listings: dict[Path, list[Path]] = {}
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def decorate_listing(cls, paths: Iterable[Path]) -> list[str]:
|
|
79
|
+
"""Add trailing slash to directories.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
paths: A sequence of paths.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
A list of directory names.
|
|
86
|
+
"""
|
|
87
|
+
return [(path.name + "/" if path.is_dir() else path.name) for path in paths]
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def decorate_path(cls, path: Path) -> str:
|
|
91
|
+
"""Add trailing slash to a path if it is a directory.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
path: Path.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The path name, potentially with a trailing slash.
|
|
98
|
+
"""
|
|
99
|
+
return path.name + "/" if path.is_dir() else path.name
|
|
100
|
+
|
|
101
|
+
async def __call__(
|
|
102
|
+
self,
|
|
103
|
+
current_working_directory: Path,
|
|
104
|
+
path: str,
|
|
105
|
+
*,
|
|
106
|
+
exclude_type: Literal["file"] | Literal["dir"] | None = None,
|
|
107
|
+
) -> tuple[str | None, list[str] | None]:
|
|
108
|
+
"""Attempt to auto-complete a path.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
current_working_directory: Current working directory (to resolve relative paths).
|
|
112
|
+
path: String potentially containing path to auto-complete.
|
|
113
|
+
exclude_type: May be `"file"` to exclude files, `"dir"` to exclude directories, or `None` not to exclude anything.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A pair of the recommend auto complete (or `None`), and a list of options (or `None`).
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
current_working_directory = (
|
|
120
|
+
current_working_directory.expanduser().resolve().absolute()
|
|
121
|
+
)
|
|
122
|
+
directory_path = (current_working_directory / Path(path).expanduser()).resolve()
|
|
123
|
+
|
|
124
|
+
node: str = path
|
|
125
|
+
if not directory_path.is_dir():
|
|
126
|
+
node = directory_path.name
|
|
127
|
+
directory_path = directory_path.parent
|
|
128
|
+
|
|
129
|
+
if (listing := self.directory_listings.get(directory_path)) is None:
|
|
130
|
+
read_task = DirectoryReadTask(directory_path)
|
|
131
|
+
self.read_tasks[directory_path] = read_task
|
|
132
|
+
read_task.start()
|
|
133
|
+
listing = await read_task.wait()
|
|
134
|
+
|
|
135
|
+
def check_completions(
|
|
136
|
+
listing: list[Path],
|
|
137
|
+
) -> tuple[str | None, list[str] | None]:
|
|
138
|
+
"""Check completions from a listing (run in a thread)
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
listing: List of paths.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Completions.
|
|
145
|
+
"""
|
|
146
|
+
if exclude_type is not None:
|
|
147
|
+
if exclude_type == "dir":
|
|
148
|
+
listing = [
|
|
149
|
+
listing_path
|
|
150
|
+
for listing_path in listing
|
|
151
|
+
if not listing_path.is_dir()
|
|
152
|
+
]
|
|
153
|
+
else:
|
|
154
|
+
listing = [
|
|
155
|
+
listing_path
|
|
156
|
+
for listing_path in listing
|
|
157
|
+
if listing_path.is_dir()
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
if not node:
|
|
161
|
+
return None, self.decorate_listing(listing)
|
|
162
|
+
|
|
163
|
+
node_name = Path(node).name
|
|
164
|
+
|
|
165
|
+
if not node_name or path.endswith("/"):
|
|
166
|
+
return None, self.decorate_listing(listing)
|
|
167
|
+
matching_nodes = [
|
|
168
|
+
listing_path
|
|
169
|
+
for listing_path in listing
|
|
170
|
+
if listing_path.name.startswith(node_name)
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
if not (matching_nodes):
|
|
174
|
+
# Nothing matches
|
|
175
|
+
return None, None
|
|
176
|
+
|
|
177
|
+
if not (
|
|
178
|
+
prefix := longest_common_prefix(
|
|
179
|
+
[node_path.name for node_path in matching_nodes]
|
|
180
|
+
)
|
|
181
|
+
):
|
|
182
|
+
return None, None
|
|
183
|
+
|
|
184
|
+
picked_path = directory_path / prefix
|
|
185
|
+
|
|
186
|
+
complete_name = Path(path).name
|
|
187
|
+
completed_prefix = prefix[len(complete_name) :]
|
|
188
|
+
path_options = [
|
|
189
|
+
(
|
|
190
|
+
matched_path.name[len(complete_name) :] + "/"
|
|
191
|
+
if matched_path.is_dir()
|
|
192
|
+
else matched_path.name[len(complete_name) :]
|
|
193
|
+
)
|
|
194
|
+
for matched_path in matching_nodes
|
|
195
|
+
if matched_path.name.startswith(complete_name)
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
if len(path_options) > 1:
|
|
199
|
+
return None, path_options
|
|
200
|
+
|
|
201
|
+
if picked_path.is_dir() and len(path_options) <= 1:
|
|
202
|
+
completed_prefix += os.sep
|
|
203
|
+
|
|
204
|
+
return completed_prefix or None, path_options
|
|
205
|
+
|
|
206
|
+
return await asyncio.to_thread(check_completions, listing)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
|
|
211
|
+
async def run():
|
|
212
|
+
path_complete = PathComplete()
|
|
213
|
+
cwd = Path("~/sandbox")
|
|
214
|
+
|
|
215
|
+
print(await path_complete(cwd, "~/p"))
|
|
216
|
+
|
|
217
|
+
asyncio.run(run())
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from itertools import chain
|
|
2
|
+
from typing import Iterable, Sequence
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import pathspec
|
|
5
|
+
import pathspec.patterns
|
|
6
|
+
from pathspec import GitIgnoreSpec
|
|
7
|
+
|
|
8
|
+
import rich.repr
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_path_spec(git_ignore_path: Path) -> GitIgnoreSpec | None:
|
|
12
|
+
"""Get a path spec instance if there is a .gitignore file present.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
git_ignore_path): Path to .gitignore.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A `PathSpec` instance.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
if git_ignore_path.is_file():
|
|
22
|
+
try:
|
|
23
|
+
spec_text = git_ignore_path.read_text(encoding="utf-8")
|
|
24
|
+
except Exception:
|
|
25
|
+
# Permissions, encoding issue?
|
|
26
|
+
return None
|
|
27
|
+
try:
|
|
28
|
+
spec = GitIgnoreSpec.from_lines(
|
|
29
|
+
pathspec.patterns.GitWildMatchPattern, spec_text.splitlines()
|
|
30
|
+
)
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
return spec
|
|
34
|
+
except OSError:
|
|
35
|
+
return None
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@rich.repr.auto
|
|
40
|
+
class PathFilter:
|
|
41
|
+
"""Filter paths according to .gitignore files."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self, root: Path, path_specs: Iterable[GitIgnoreSpec] | None = None
|
|
45
|
+
) -> None:
|
|
46
|
+
self._root = root
|
|
47
|
+
self._default_specs = [] if path_specs is None else list(path_specs)
|
|
48
|
+
self._path_specs: dict[Path, Sequence[GitIgnoreSpec]] = {}
|
|
49
|
+
|
|
50
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
51
|
+
yield (str(self._root),)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_git_root(cls, path: Path) -> PathFilter:
|
|
55
|
+
"""Load all path specs from parent directories up to the most recent directory with .git
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
path: A directory path.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
PathFilter instance.
|
|
62
|
+
"""
|
|
63
|
+
filter_root = path
|
|
64
|
+
path_specs: list[GitIgnoreSpec] = []
|
|
65
|
+
try:
|
|
66
|
+
while (parent := path.parent) != parent:
|
|
67
|
+
if (path_spec := load_path_spec(path / ".gitignore")) is not None:
|
|
68
|
+
path_specs.append(path_spec)
|
|
69
|
+
if (path / ".git").is_dir():
|
|
70
|
+
break
|
|
71
|
+
path = parent
|
|
72
|
+
else:
|
|
73
|
+
del path_specs[:]
|
|
74
|
+
except OSError:
|
|
75
|
+
pass
|
|
76
|
+
return PathFilter(filter_root, reversed(path_specs))
|
|
77
|
+
|
|
78
|
+
def get_path_specs(self, path: Path) -> Sequence[GitIgnoreSpec]:
|
|
79
|
+
"""Get a sequence of path specs applicable to the give path.
|
|
80
|
+
|
|
81
|
+
This will inherit path specs up to the root path of the filtr.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
path: A directory path
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
A sequence of path specs.
|
|
88
|
+
"""
|
|
89
|
+
if (cached_path_specs := self._path_specs.get(path)) is not None:
|
|
90
|
+
return cached_path_specs
|
|
91
|
+
path_spec = load_path_spec(path / ".gitignore")
|
|
92
|
+
if path == self._root:
|
|
93
|
+
path_specs = [path_spec] if path_spec is not None else []
|
|
94
|
+
else:
|
|
95
|
+
parent_path_specs = self.get_path_specs(path.parent)
|
|
96
|
+
path_specs = (
|
|
97
|
+
parent_path_specs
|
|
98
|
+
if path_spec is None
|
|
99
|
+
else [*parent_path_specs, path_spec]
|
|
100
|
+
)
|
|
101
|
+
self._path_specs[path] = path_specs
|
|
102
|
+
return path_specs
|
|
103
|
+
|
|
104
|
+
def match(self, path: Path) -> bool:
|
|
105
|
+
"""Match a path againt the path filter.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
`True` if the path should be removed, `False` if it should be included.
|
|
109
|
+
"""
|
|
110
|
+
if path.name == ".git":
|
|
111
|
+
return True
|
|
112
|
+
path_specs = self.get_path_specs(path.parent)
|
|
113
|
+
for path_spec in chain(self._default_specs, path_specs):
|
|
114
|
+
if path_spec.match_file(path):
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
path_filter = PathFilter.from_git_root(Path("."))
|
|
121
|
+
|
|
122
|
+
for path in Path(".").iterdir():
|
|
123
|
+
print(path_filter.match(path), path)
|
|
124
|
+
print(path_filter)
|
package/tui/paths.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
from xdg_base_dirs import xdg_config_home, xdg_data_home, xdg_state_home
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
APP_NAME: Final[str] = "tui"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def path_to_name(path: Path) -> str:
|
|
12
|
+
"""Converts a path to a name (suitable as a path component).
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
path: A path.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A stringified version of the path.
|
|
19
|
+
"""
|
|
20
|
+
name = str(path.resolve()).lstrip("/").replace("/", "-")
|
|
21
|
+
return name
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_data() -> Path:
|
|
25
|
+
"""Return (possibly creating) the application data directory."""
|
|
26
|
+
path = xdg_data_home() / APP_NAME
|
|
27
|
+
with suppress(OSError):
|
|
28
|
+
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
29
|
+
return path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_config() -> Path:
|
|
33
|
+
"""Return (possibly creating) the application config directory."""
|
|
34
|
+
path = xdg_config_home() / APP_NAME
|
|
35
|
+
with suppress(OSError):
|
|
36
|
+
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
37
|
+
return path
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_state() -> Path:
|
|
41
|
+
"""Return (possibly creating) the application state directory."""
|
|
42
|
+
path = xdg_state_home() / APP_NAME
|
|
43
|
+
with suppress(OSError):
|
|
44
|
+
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
45
|
+
return path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_project_data(project_path: Path) -> Path:
|
|
49
|
+
"""Get a directory for per-project data.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
project_path: Path of project.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
project_data_path = get_data() / path_to_name(project_path)
|
|
56
|
+
with suppress(OSError):
|
|
57
|
+
project_data_path.mkdir(0o700, exist_ok=True, parents=True)
|
|
58
|
+
return project_data_path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_log() -> Path:
|
|
62
|
+
"""Get a directory for logs.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Path to log directory.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
path = Path.home() / ".onecode" / "logs"
|
|
69
|
+
with suppress(OSError):
|
|
70
|
+
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
71
|
+
return path
|
package/tui/pill.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from textual.content import Content
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def pill(text: Content | str, background: str, foreground: str) -> Content:
|
|
5
|
+
"""Format text as a pill (half block ends).
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
text: Pill contents as Content object or text.
|
|
9
|
+
background: Background color.
|
|
10
|
+
foreground: Foreground color.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Pill content.
|
|
14
|
+
"""
|
|
15
|
+
content = Content(text) if isinstance(text, str) else text
|
|
16
|
+
main_style = f"{foreground} on {background}"
|
|
17
|
+
end_style = f"{background} on transparent r"
|
|
18
|
+
pill_content = Content.assemble(
|
|
19
|
+
("▌", end_style),
|
|
20
|
+
content.stylize(main_style),
|
|
21
|
+
("▐", end_style),
|
|
22
|
+
)
|
|
23
|
+
return pill_content
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
4
|
+
|
|
5
|
+
from tui.slash_command import SlashCommand
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from tui.widgets.conversation import Conversation
|
|
9
|
+
|
|
10
|
+
Handler = Callable[["Conversation", str], Awaitable[None]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _CommandEntry:
|
|
14
|
+
def __init__(self, command: str, help: str, handler: Handler, hint: str | None = None):
|
|
15
|
+
self.slash_command = SlashCommand(f"/{command}", help, hint)
|
|
16
|
+
self.handler = handler
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_registry: dict[str, _CommandEntry] = {}
|
|
20
|
+
_registry_prefix: str = ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def set_prefix(prefix: str) -> None:
|
|
24
|
+
global _registry_prefix
|
|
25
|
+
_registry_prefix = prefix
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register(command: str, help: str, handler: Handler, hint: str | None = None) -> None:
|
|
29
|
+
prefixed = f"{_registry_prefix}{command}"
|
|
30
|
+
_registry[prefixed] = _CommandEntry(prefixed, help, handler, hint)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def clear() -> None:
|
|
34
|
+
global _registry_prefix
|
|
35
|
+
_registry.clear()
|
|
36
|
+
_registry_prefix = ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_commands() -> dict[str, _CommandEntry]:
|
|
40
|
+
return _registry
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_slash_commands() -> list[SlashCommand]:
|
|
44
|
+
return [entry.slash_command for entry in _registry.values()]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def dispatch(conversation: Conversation, command: str, parameters: str) -> bool:
|
|
48
|
+
entry = _registry.get(command)
|
|
49
|
+
if entry is None:
|
|
50
|
+
return False
|
|
51
|
+
await entry.handler(conversation, parameters)
|
|
52
|
+
return True
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
RE_MATCH_FILE_PROMPT = re.compile(r"@(\S+)|@\"(.*)\"")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def extract_paths_from_prompt(prompt: str) -> Iterable[tuple[str, int, int]]:
|
|
9
|
+
"""Find file syntax in prompts.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
prompt: A line of prompt.
|
|
13
|
+
|
|
14
|
+
Yields:
|
|
15
|
+
A tuple of (PATH, START, END).
|
|
16
|
+
"""
|
|
17
|
+
for match in RE_MATCH_FILE_PROMPT.finditer(prompt):
|
|
18
|
+
path, quoted_path = match.groups()
|
|
19
|
+
yield (path or quoted_path, match.start(0), match.end(0))
|