cecli-dev 0.95.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cecli/__init__.py +20 -0
- cecli/__main__.py +4 -0
- cecli/_version.py +34 -0
- cecli/args.py +1092 -0
- cecli/args_formatter.py +228 -0
- cecli/change_tracker.py +133 -0
- cecli/coders/__init__.py +38 -0
- cecli/coders/agent_coder.py +1872 -0
- cecli/coders/architect_coder.py +63 -0
- cecli/coders/ask_coder.py +8 -0
- cecli/coders/base_coder.py +3993 -0
- cecli/coders/chat_chunks.py +116 -0
- cecli/coders/context_coder.py +52 -0
- cecli/coders/copypaste_coder.py +269 -0
- cecli/coders/editblock_coder.py +656 -0
- cecli/coders/editblock_fenced_coder.py +9 -0
- cecli/coders/editblock_func_coder.py +140 -0
- cecli/coders/editor_diff_fenced_coder.py +8 -0
- cecli/coders/editor_editblock_coder.py +8 -0
- cecli/coders/editor_whole_coder.py +8 -0
- cecli/coders/help_coder.py +15 -0
- cecli/coders/patch_coder.py +705 -0
- cecli/coders/search_replace.py +757 -0
- cecli/coders/shell.py +37 -0
- cecli/coders/single_wholefile_func_coder.py +101 -0
- cecli/coders/udiff_coder.py +428 -0
- cecli/coders/udiff_simple.py +12 -0
- cecli/coders/wholefile_coder.py +143 -0
- cecli/coders/wholefile_func_coder.py +133 -0
- cecli/commands/__init__.py +192 -0
- cecli/commands/add.py +226 -0
- cecli/commands/agent.py +51 -0
- cecli/commands/architect.py +46 -0
- cecli/commands/ask.py +44 -0
- cecli/commands/chat_mode.py +0 -0
- cecli/commands/clear.py +37 -0
- cecli/commands/code.py +46 -0
- cecli/commands/command_prefix.py +44 -0
- cecli/commands/commit.py +52 -0
- cecli/commands/context.py +47 -0
- cecli/commands/context_blocks.py +124 -0
- cecli/commands/context_management.py +51 -0
- cecli/commands/copy.py +62 -0
- cecli/commands/copy_context.py +81 -0
- cecli/commands/core.py +287 -0
- cecli/commands/diff.py +68 -0
- cecli/commands/drop.py +217 -0
- cecli/commands/editor.py +78 -0
- cecli/commands/exit.py +55 -0
- cecli/commands/git.py +57 -0
- cecli/commands/help.py +140 -0
- cecli/commands/history_search.py +40 -0
- cecli/commands/lint.py +109 -0
- cecli/commands/list_sessions.py +56 -0
- cecli/commands/load.py +85 -0
- cecli/commands/load_session.py +48 -0
- cecli/commands/load_skill.py +68 -0
- cecli/commands/ls.py +75 -0
- cecli/commands/map.py +37 -0
- cecli/commands/map_refresh.py +35 -0
- cecli/commands/model.py +118 -0
- cecli/commands/models.py +41 -0
- cecli/commands/multiline_mode.py +38 -0
- cecli/commands/paste.py +91 -0
- cecli/commands/quit.py +32 -0
- cecli/commands/read_only.py +267 -0
- cecli/commands/read_only_stub.py +270 -0
- cecli/commands/reasoning_effort.py +70 -0
- cecli/commands/remove_skill.py +68 -0
- cecli/commands/report.py +40 -0
- cecli/commands/reset.py +88 -0
- cecli/commands/run.py +99 -0
- cecli/commands/save.py +49 -0
- cecli/commands/save_session.py +43 -0
- cecli/commands/settings.py +69 -0
- cecli/commands/test.py +58 -0
- cecli/commands/think_tokens.py +74 -0
- cecli/commands/tokens.py +207 -0
- cecli/commands/undo.py +145 -0
- cecli/commands/utils/__init__.py +0 -0
- cecli/commands/utils/base_command.py +131 -0
- cecli/commands/utils/helpers.py +142 -0
- cecli/commands/utils/registry.py +53 -0
- cecli/commands/utils/save_load_manager.py +98 -0
- cecli/commands/voice.py +78 -0
- cecli/commands/weak_model.py +123 -0
- cecli/commands/web.py +87 -0
- cecli/deprecated_args.py +185 -0
- cecli/diffs.py +129 -0
- cecli/dump.py +29 -0
- cecli/editor.py +147 -0
- cecli/exceptions.py +115 -0
- cecli/format_settings.py +26 -0
- cecli/help.py +119 -0
- cecli/help_pats.py +19 -0
- cecli/helpers/__init__.py +9 -0
- cecli/helpers/copypaste.py +123 -0
- cecli/helpers/coroutines.py +8 -0
- cecli/helpers/file_searcher.py +142 -0
- cecli/helpers/model_providers.py +552 -0
- cecli/helpers/plugin_manager.py +81 -0
- cecli/helpers/profiler.py +162 -0
- cecli/helpers/requests.py +77 -0
- cecli/helpers/similarity.py +98 -0
- cecli/helpers/skills.py +577 -0
- cecli/history.py +186 -0
- cecli/io.py +1782 -0
- cecli/linter.py +304 -0
- cecli/llm.py +101 -0
- cecli/main.py +1280 -0
- cecli/mcp/__init__.py +154 -0
- cecli/mcp/oauth.py +250 -0
- cecli/mcp/server.py +278 -0
- cecli/mdstream.py +243 -0
- cecli/models.py +1255 -0
- cecli/onboarding.py +301 -0
- cecli/prompts/__init__.py +0 -0
- cecli/prompts/agent.yml +71 -0
- cecli/prompts/architect.yml +35 -0
- cecli/prompts/ask.yml +31 -0
- cecli/prompts/base.yml +99 -0
- cecli/prompts/context.yml +60 -0
- cecli/prompts/copypaste.yml +5 -0
- cecli/prompts/editblock.yml +143 -0
- cecli/prompts/editblock_fenced.yml +106 -0
- cecli/prompts/editblock_func.yml +25 -0
- cecli/prompts/editor_diff_fenced.yml +115 -0
- cecli/prompts/editor_editblock.yml +121 -0
- cecli/prompts/editor_whole.yml +46 -0
- cecli/prompts/help.yml +37 -0
- cecli/prompts/patch.yml +110 -0
- cecli/prompts/single_wholefile_func.yml +24 -0
- cecli/prompts/udiff.yml +106 -0
- cecli/prompts/udiff_simple.yml +13 -0
- cecli/prompts/utils/__init__.py +0 -0
- cecli/prompts/utils/prompt_registry.py +167 -0
- cecli/prompts/utils/system.py +56 -0
- cecli/prompts/wholefile.yml +50 -0
- cecli/prompts/wholefile_func.yml +24 -0
- cecli/queries/tree-sitter-language-pack/README.md +7 -0
- cecli/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/c-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- cecli/queries/tree-sitter-language-pack/clojure-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/commonlisp-tags.scm +127 -0
- cecli/queries/tree-sitter-language-pack/cpp-tags.scm +18 -0
- cecli/queries/tree-sitter-language-pack/csharp-tags.scm +32 -0
- cecli/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- cecli/queries/tree-sitter-language-pack/dart-tags.scm +97 -0
- cecli/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/elixir-tags.scm +59 -0
- cecli/queries/tree-sitter-language-pack/elm-tags.scm +22 -0
- cecli/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- cecli/queries/tree-sitter-language-pack/go-tags.scm +49 -0
- cecli/queries/tree-sitter-language-pack/java-tags.scm +26 -0
- cecli/queries/tree-sitter-language-pack/javascript-tags.scm +96 -0
- cecli/queries/tree-sitter-language-pack/lua-tags.scm +39 -0
- cecli/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- cecli/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- cecli/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +101 -0
- cecli/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- cecli/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- cecli/queries/tree-sitter-language-pack/python-tags.scm +24 -0
- cecli/queries/tree-sitter-language-pack/r-tags.scm +27 -0
- cecli/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- cecli/queries/tree-sitter-language-pack/ruby-tags.scm +69 -0
- cecli/queries/tree-sitter-language-pack/rust-tags.scm +63 -0
- cecli/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- cecli/queries/tree-sitter-language-pack/swift-tags.scm +54 -0
- cecli/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- cecli/queries/tree-sitter-languages/README.md +24 -0
- cecli/queries/tree-sitter-languages/c-tags.scm +12 -0
- cecli/queries/tree-sitter-languages/c_sharp-tags.scm +52 -0
- cecli/queries/tree-sitter-languages/cpp-tags.scm +18 -0
- cecli/queries/tree-sitter-languages/dart-tags.scm +92 -0
- cecli/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- cecli/queries/tree-sitter-languages/elixir-tags.scm +59 -0
- cecli/queries/tree-sitter-languages/elm-tags.scm +22 -0
- cecli/queries/tree-sitter-languages/fortran-tags.scm +18 -0
- cecli/queries/tree-sitter-languages/go-tags.scm +36 -0
- cecli/queries/tree-sitter-languages/haskell-tags.scm +5 -0
- cecli/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- cecli/queries/tree-sitter-languages/java-tags.scm +26 -0
- cecli/queries/tree-sitter-languages/javascript-tags.scm +96 -0
- cecli/queries/tree-sitter-languages/julia-tags.scm +60 -0
- cecli/queries/tree-sitter-languages/kotlin-tags.scm +30 -0
- cecli/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- cecli/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- cecli/queries/tree-sitter-languages/ocaml_interface-tags.scm +104 -0
- cecli/queries/tree-sitter-languages/php-tags.scm +32 -0
- cecli/queries/tree-sitter-languages/python-tags.scm +22 -0
- cecli/queries/tree-sitter-languages/ql-tags.scm +26 -0
- cecli/queries/tree-sitter-languages/ruby-tags.scm +69 -0
- cecli/queries/tree-sitter-languages/rust-tags.scm +63 -0
- cecli/queries/tree-sitter-languages/scala-tags.scm +64 -0
- cecli/queries/tree-sitter-languages/typescript-tags.scm +44 -0
- cecli/queries/tree-sitter-languages/zig-tags.scm +20 -0
- cecli/reasoning_tags.py +82 -0
- cecli/repo.py +626 -0
- cecli/repomap.py +1368 -0
- cecli/report.py +260 -0
- cecli/resources/__init__.py +3 -0
- cecli/resources/model-metadata.json +25751 -0
- cecli/resources/model-settings.yml +2394 -0
- cecli/resources/providers.json +67 -0
- cecli/run_cmd.py +143 -0
- cecli/scrape.py +295 -0
- cecli/sendchat.py +250 -0
- cecli/sessions.py +281 -0
- cecli/special.py +203 -0
- cecli/tools/__init__.py +72 -0
- cecli/tools/command.py +103 -0
- cecli/tools/command_interactive.py +113 -0
- cecli/tools/context_manager.py +175 -0
- cecli/tools/delete_block.py +154 -0
- cecli/tools/delete_line.py +120 -0
- cecli/tools/delete_lines.py +144 -0
- cecli/tools/extract_lines.py +281 -0
- cecli/tools/finished.py +35 -0
- cecli/tools/git_branch.py +132 -0
- cecli/tools/git_diff.py +49 -0
- cecli/tools/git_log.py +43 -0
- cecli/tools/git_remote.py +39 -0
- cecli/tools/git_show.py +37 -0
- cecli/tools/git_status.py +32 -0
- cecli/tools/grep.py +242 -0
- cecli/tools/indent_lines.py +195 -0
- cecli/tools/insert_block.py +263 -0
- cecli/tools/list_changes.py +71 -0
- cecli/tools/load_skill.py +51 -0
- cecli/tools/ls.py +77 -0
- cecli/tools/remove_skill.py +51 -0
- cecli/tools/replace_all.py +113 -0
- cecli/tools/replace_line.py +135 -0
- cecli/tools/replace_lines.py +180 -0
- cecli/tools/replace_text.py +186 -0
- cecli/tools/show_numbered_context.py +137 -0
- cecli/tools/thinking.py +52 -0
- cecli/tools/undo_change.py +82 -0
- cecli/tools/update_todo_list.py +148 -0
- cecli/tools/utils/base_tool.py +64 -0
- cecli/tools/utils/helpers.py +359 -0
- cecli/tools/utils/output.py +119 -0
- cecli/tools/utils/registry.py +145 -0
- cecli/tools/view_files_matching.py +138 -0
- cecli/tools/view_files_with_symbol.py +117 -0
- cecli/tui/__init__.py +83 -0
- cecli/tui/app.py +971 -0
- cecli/tui/io.py +566 -0
- cecli/tui/styles.tcss +117 -0
- cecli/tui/widgets/__init__.py +19 -0
- cecli/tui/widgets/completion_bar.py +331 -0
- cecli/tui/widgets/file_list.py +76 -0
- cecli/tui/widgets/footer.py +165 -0
- cecli/tui/widgets/input_area.py +320 -0
- cecli/tui/widgets/key_hints.py +16 -0
- cecli/tui/widgets/output.py +354 -0
- cecli/tui/widgets/status_bar.py +279 -0
- cecli/tui/worker.py +160 -0
- cecli/urls.py +16 -0
- cecli/utils.py +499 -0
- cecli/versioncheck.py +90 -0
- cecli/voice.py +90 -0
- cecli/waiting.py +38 -0
- cecli/watch.py +316 -0
- cecli/watch_prompts.py +12 -0
- cecli/website/Gemfile +8 -0
- cecli/website/_includes/blame.md +162 -0
- cecli/website/_includes/get-started.md +22 -0
- cecli/website/_includes/help-tip.md +5 -0
- cecli/website/_includes/help.md +24 -0
- cecli/website/_includes/install.md +5 -0
- cecli/website/_includes/keys.md +4 -0
- cecli/website/_includes/model-warnings.md +67 -0
- cecli/website/_includes/multi-line.md +22 -0
- cecli/website/_includes/python-m-aider.md +5 -0
- cecli/website/_includes/recording.css +228 -0
- cecli/website/_includes/recording.md +34 -0
- cecli/website/_includes/replit-pipx.md +9 -0
- cecli/website/_includes/works-best.md +1 -0
- cecli/website/_sass/custom/custom.scss +103 -0
- cecli/website/docs/config/adv-model-settings.md +2498 -0
- cecli/website/docs/config/agent-mode.md +320 -0
- cecli/website/docs/config/aider_conf.md +548 -0
- cecli/website/docs/config/api-keys.md +90 -0
- cecli/website/docs/config/custom-commands.md +187 -0
- cecli/website/docs/config/dotenv.md +493 -0
- cecli/website/docs/config/editor.md +127 -0
- cecli/website/docs/config/mcp.md +210 -0
- cecli/website/docs/config/model-aliases.md +173 -0
- cecli/website/docs/config/options.md +890 -0
- cecli/website/docs/config/reasoning.md +210 -0
- cecli/website/docs/config/skills.md +172 -0
- cecli/website/docs/config/tui.md +126 -0
- cecli/website/docs/config.md +44 -0
- cecli/website/docs/faq.md +379 -0
- cecli/website/docs/git.md +76 -0
- cecli/website/docs/index.md +47 -0
- cecli/website/docs/install/codespaces.md +39 -0
- cecli/website/docs/install/docker.md +48 -0
- cecli/website/docs/install/optional.md +100 -0
- cecli/website/docs/install/replit.md +8 -0
- cecli/website/docs/install.md +115 -0
- cecli/website/docs/languages.md +264 -0
- cecli/website/docs/legal/contributor-agreement.md +111 -0
- cecli/website/docs/legal/privacy.md +104 -0
- cecli/website/docs/llms/anthropic.md +77 -0
- cecli/website/docs/llms/azure.md +48 -0
- cecli/website/docs/llms/bedrock.md +132 -0
- cecli/website/docs/llms/cohere.md +34 -0
- cecli/website/docs/llms/deepseek.md +32 -0
- cecli/website/docs/llms/gemini.md +49 -0
- cecli/website/docs/llms/github.md +111 -0
- cecli/website/docs/llms/groq.md +36 -0
- cecli/website/docs/llms/lm-studio.md +39 -0
- cecli/website/docs/llms/ollama.md +75 -0
- cecli/website/docs/llms/openai-compat.md +39 -0
- cecli/website/docs/llms/openai.md +58 -0
- cecli/website/docs/llms/openrouter.md +78 -0
- cecli/website/docs/llms/other.md +117 -0
- cecli/website/docs/llms/vertex.md +50 -0
- cecli/website/docs/llms/warnings.md +10 -0
- cecli/website/docs/llms/xai.md +53 -0
- cecli/website/docs/llms.md +54 -0
- cecli/website/docs/more/analytics.md +127 -0
- cecli/website/docs/more/edit-formats.md +116 -0
- cecli/website/docs/more/infinite-output.md +192 -0
- cecli/website/docs/more-info.md +8 -0
- cecli/website/docs/recordings/auto-accept-architect.md +31 -0
- cecli/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- cecli/website/docs/recordings/index.md +21 -0
- cecli/website/docs/recordings/model-accepts-settings.md +69 -0
- cecli/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- cecli/website/docs/repomap.md +112 -0
- cecli/website/docs/scripting.md +100 -0
- cecli/website/docs/sessions.md +213 -0
- cecli/website/docs/troubleshooting/aider-not-found.md +24 -0
- cecli/website/docs/troubleshooting/edit-errors.md +76 -0
- cecli/website/docs/troubleshooting/imports.md +62 -0
- cecli/website/docs/troubleshooting/models-and-keys.md +54 -0
- cecli/website/docs/troubleshooting/support.md +79 -0
- cecli/website/docs/troubleshooting/token-limits.md +96 -0
- cecli/website/docs/troubleshooting/warnings.md +12 -0
- cecli/website/docs/troubleshooting.md +11 -0
- cecli/website/docs/usage/browser.md +57 -0
- cecli/website/docs/usage/caching.md +49 -0
- cecli/website/docs/usage/commands.md +133 -0
- cecli/website/docs/usage/conventions.md +119 -0
- cecli/website/docs/usage/copypaste.md +136 -0
- cecli/website/docs/usage/images-urls.md +48 -0
- cecli/website/docs/usage/lint-test.md +118 -0
- cecli/website/docs/usage/modes.md +211 -0
- cecli/website/docs/usage/not-code.md +179 -0
- cecli/website/docs/usage/notifications.md +87 -0
- cecli/website/docs/usage/tips.md +79 -0
- cecli/website/docs/usage/tutorials.md +30 -0
- cecli/website/docs/usage/voice.md +121 -0
- cecli/website/docs/usage/watch.md +294 -0
- cecli/website/docs/usage.md +102 -0
- cecli/website/share/index.md +101 -0
- cecli_dev-0.95.5.dist-info/METADATA +549 -0
- cecli_dev-0.95.5.dist-info/RECORD +366 -0
- cecli_dev-0.95.5.dist-info/WHEEL +5 -0
- cecli_dev-0.95.5.dist-info/entry_points.txt +4 -0
- cecli_dev-0.95.5.dist-info/licenses/LICENSE.txt +202 -0
- cecli_dev-0.95.5.dist-info/top_level.txt +1 -0
cecli/helpers/skills.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills helper for cecli.
|
|
3
|
+
|
|
4
|
+
This module provides functions for loading, parsing, and managing skills
|
|
5
|
+
according to the Skills specification.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SkillMetadata:
|
|
18
|
+
"""Metadata for an skill."""
|
|
19
|
+
|
|
20
|
+
name: str
|
|
21
|
+
description: str
|
|
22
|
+
path: Path
|
|
23
|
+
license: Optional[str] = None
|
|
24
|
+
allowed_tools: List[str] = field(default_factory=list)
|
|
25
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SkillContent:
|
|
30
|
+
"""Complete skill content including metadata and instructions."""
|
|
31
|
+
|
|
32
|
+
metadata: SkillMetadata
|
|
33
|
+
frontmatter: Dict[str, Any]
|
|
34
|
+
instructions: str
|
|
35
|
+
references: Dict[str, Path] = field(default_factory=dict)
|
|
36
|
+
scripts: Dict[str, Path] = field(default_factory=dict)
|
|
37
|
+
assets: Dict[str, Path] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SkillsManager:
|
|
41
|
+
"""Manager for loading and managing skills."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
directory_paths: List[str],
|
|
46
|
+
include_list: Optional[List[str]] = None,
|
|
47
|
+
exclude_list: Optional[List[str]] = None,
|
|
48
|
+
git_root: Optional[str] = None,
|
|
49
|
+
coder=None,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Initialize the skills manager.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
directory_paths: List of directory paths to search for skills
|
|
56
|
+
include_list: Optional list of skill names to include (whitelist)
|
|
57
|
+
exclude_list: Optional list of skill names to exclude (blacklist)
|
|
58
|
+
git_root: Optional git root directory for relative path resolution
|
|
59
|
+
coder: Optional reference to the coder instance (weak reference)
|
|
60
|
+
"""
|
|
61
|
+
self.directory_paths = [Path(p).expanduser().resolve() for p in directory_paths]
|
|
62
|
+
self.include_list = set(include_list) if include_list else None
|
|
63
|
+
self.exclude_list = set(exclude_list) if exclude_list else set()
|
|
64
|
+
self.git_root = Path(git_root).expanduser().resolve() if git_root else None
|
|
65
|
+
self.coder = coder # Weak reference to coder instance
|
|
66
|
+
|
|
67
|
+
# Cache for loaded skills
|
|
68
|
+
self._skills_cache: Dict[str, SkillContent] = {}
|
|
69
|
+
self._skill_metadata_cache: Dict[str, SkillMetadata] = {}
|
|
70
|
+
self._skills_find_cache: Optional[List[SkillMetadata]] = None
|
|
71
|
+
|
|
72
|
+
# Track which skills have been loaded via load_skill()
|
|
73
|
+
self._loaded_skills: set[str] = set()
|
|
74
|
+
|
|
75
|
+
def find_skills(self, reload: bool = False) -> List[SkillMetadata]:
|
|
76
|
+
"""
|
|
77
|
+
Find all skills in the configured directory paths.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
reload: If True, force reload from disk instead of using cache
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of skill metadata objects
|
|
84
|
+
"""
|
|
85
|
+
# Return cached results if available and not forced to reload
|
|
86
|
+
if not reload and self._skills_find_cache is not None:
|
|
87
|
+
return self._skills_find_cache
|
|
88
|
+
|
|
89
|
+
skills = []
|
|
90
|
+
|
|
91
|
+
for directory_path in self.directory_paths:
|
|
92
|
+
if not directory_path.exists():
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# Look for directories containing SKILL.md files
|
|
96
|
+
for skill_dir in directory_path.iterdir():
|
|
97
|
+
if not skill_dir.is_dir():
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
skill_md_path = skill_dir / "SKILL.md"
|
|
101
|
+
if skill_md_path.exists():
|
|
102
|
+
try:
|
|
103
|
+
metadata = self._parse_skill_metadata(skill_md_path)
|
|
104
|
+
skill_name = metadata.name
|
|
105
|
+
|
|
106
|
+
# Apply include/exclude filters
|
|
107
|
+
if self.include_list and skill_name not in self.include_list:
|
|
108
|
+
continue
|
|
109
|
+
if skill_name in self.exclude_list:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
skills.append(metadata)
|
|
113
|
+
self._skill_metadata_cache[skill_name] = metadata
|
|
114
|
+
except Exception:
|
|
115
|
+
# Skip skills that can't be parsed
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
# Cache the results
|
|
119
|
+
self._skills_find_cache = skills
|
|
120
|
+
return skills
|
|
121
|
+
|
|
122
|
+
def _parse_skill_metadata(self, skill_md_path: Path) -> SkillMetadata:
|
|
123
|
+
"""
|
|
124
|
+
Parse the metadata from a SKILL.md file.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
skill_md_path: Path to the SKILL.md file
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
SkillMetadata object
|
|
131
|
+
"""
|
|
132
|
+
content = skill_md_path.read_text(encoding="utf-8")
|
|
133
|
+
|
|
134
|
+
# Parse YAML frontmatter (between --- markers)
|
|
135
|
+
frontmatter_match = re.search(
|
|
136
|
+
r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL | re.MULTILINE
|
|
137
|
+
)
|
|
138
|
+
if not frontmatter_match:
|
|
139
|
+
raise ValueError(f"No YAML frontmatter found in {skill_md_path}")
|
|
140
|
+
|
|
141
|
+
frontmatter = yaml.safe_load(frontmatter_match.group(1))
|
|
142
|
+
|
|
143
|
+
# Extract required fields
|
|
144
|
+
name = frontmatter.get("name")
|
|
145
|
+
description = frontmatter.get("description")
|
|
146
|
+
|
|
147
|
+
if not name or not description:
|
|
148
|
+
raise ValueError(f"Missing required fields (name or description) in {skill_md_path}")
|
|
149
|
+
|
|
150
|
+
return SkillMetadata(
|
|
151
|
+
name=name,
|
|
152
|
+
description=description,
|
|
153
|
+
path=skill_md_path.parent,
|
|
154
|
+
license=frontmatter.get("license"),
|
|
155
|
+
allowed_tools=frontmatter.get("allowed-tools", []),
|
|
156
|
+
metadata=frontmatter.get("metadata", {}),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def get_skill_content(self, skill_name: str) -> Optional[SkillContent]:
|
|
160
|
+
"""
|
|
161
|
+
Get skill content by name (loads and caches if not already loaded).
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
skill_name: Name of the skill to get
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
SkillContent object or None if not found
|
|
168
|
+
"""
|
|
169
|
+
# Check cache first
|
|
170
|
+
if skill_name in self._skills_cache:
|
|
171
|
+
return self._skills_cache[skill_name]
|
|
172
|
+
|
|
173
|
+
# Find the skill metadata
|
|
174
|
+
if skill_name not in self._skill_metadata_cache:
|
|
175
|
+
# Try to find it
|
|
176
|
+
skills = self.find_skills()
|
|
177
|
+
skill_metadata = next((s for s in skills if s.name == skill_name), None)
|
|
178
|
+
if not skill_metadata:
|
|
179
|
+
return None
|
|
180
|
+
self._skill_metadata_cache[skill_name] = skill_metadata
|
|
181
|
+
else:
|
|
182
|
+
skill_metadata = self._skill_metadata_cache[skill_name]
|
|
183
|
+
|
|
184
|
+
# Load the complete skill
|
|
185
|
+
skill_content = self._load_complete_skill(skill_metadata)
|
|
186
|
+
self._skills_cache[skill_name] = skill_content
|
|
187
|
+
|
|
188
|
+
return skill_content
|
|
189
|
+
|
|
190
|
+
def _load_complete_skill(self, metadata: SkillMetadata) -> SkillContent:
|
|
191
|
+
"""
|
|
192
|
+
Load a complete skill including all components.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
metadata: SkillMetadata object
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
SkillContent object
|
|
199
|
+
"""
|
|
200
|
+
skill_dir = metadata.path
|
|
201
|
+
|
|
202
|
+
# Load SKILL.md content
|
|
203
|
+
skill_md_path = skill_dir / "SKILL.md"
|
|
204
|
+
content = skill_md_path.read_text(encoding="utf-8")
|
|
205
|
+
|
|
206
|
+
# Parse frontmatter and instructions
|
|
207
|
+
frontmatter_match = re.search(
|
|
208
|
+
r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL | re.MULTILINE
|
|
209
|
+
)
|
|
210
|
+
if not frontmatter_match:
|
|
211
|
+
raise ValueError(f"No YAML frontmatter found in {skill_md_path}")
|
|
212
|
+
|
|
213
|
+
frontmatter = yaml.safe_load(frontmatter_match.group(1))
|
|
214
|
+
instructions = content[frontmatter_match.end() :].strip()
|
|
215
|
+
|
|
216
|
+
# Load references
|
|
217
|
+
references = self._load_references(skill_dir)
|
|
218
|
+
|
|
219
|
+
# Load scripts
|
|
220
|
+
scripts = self._load_scripts(skill_dir)
|
|
221
|
+
|
|
222
|
+
# Load assets
|
|
223
|
+
assets = self._load_assets(skill_dir)
|
|
224
|
+
|
|
225
|
+
return SkillContent(
|
|
226
|
+
metadata=metadata,
|
|
227
|
+
frontmatter=frontmatter,
|
|
228
|
+
instructions=instructions,
|
|
229
|
+
references=references,
|
|
230
|
+
scripts=scripts,
|
|
231
|
+
assets=assets,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def _load_references(self, skill_dir: Path) -> Dict[str, Path]:
|
|
235
|
+
"""Load reference files from the references/ directory."""
|
|
236
|
+
references = {}
|
|
237
|
+
references_dir = skill_dir / "references"
|
|
238
|
+
|
|
239
|
+
if references_dir.exists():
|
|
240
|
+
for ref_file in references_dir.glob("**/*.md"):
|
|
241
|
+
try:
|
|
242
|
+
# Use relative path as key, store the Path object
|
|
243
|
+
rel_path = ref_file.relative_to(references_dir)
|
|
244
|
+
references[str(rel_path)] = ref_file
|
|
245
|
+
except Exception:
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
return references
|
|
249
|
+
|
|
250
|
+
def _load_scripts(self, skill_dir: Path) -> Dict[str, Path]:
|
|
251
|
+
"""Load script files from the scripts/ directory."""
|
|
252
|
+
scripts = {}
|
|
253
|
+
scripts_dir = skill_dir / "scripts"
|
|
254
|
+
|
|
255
|
+
if scripts_dir.exists():
|
|
256
|
+
for script_file in scripts_dir.glob("**/*"):
|
|
257
|
+
if script_file.is_file():
|
|
258
|
+
try:
|
|
259
|
+
# Use relative path as key, store the Path object
|
|
260
|
+
rel_path = script_file.relative_to(scripts_dir)
|
|
261
|
+
scripts[str(rel_path)] = script_file
|
|
262
|
+
except Exception:
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
return scripts
|
|
266
|
+
|
|
267
|
+
def _load_assets(self, skill_dir: Path) -> Dict[str, Path]:
|
|
268
|
+
"""Load asset files from the assets/ directory."""
|
|
269
|
+
assets = {}
|
|
270
|
+
assets_dir = skill_dir / "assets"
|
|
271
|
+
|
|
272
|
+
if assets_dir.exists():
|
|
273
|
+
for asset_file in assets_dir.glob("**/*"):
|
|
274
|
+
if asset_file.is_file():
|
|
275
|
+
try:
|
|
276
|
+
# Use relative path as key, store the Path object
|
|
277
|
+
rel_path = asset_file.relative_to(assets_dir)
|
|
278
|
+
assets[str(rel_path)] = asset_file
|
|
279
|
+
except Exception:
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
return assets
|
|
283
|
+
|
|
284
|
+
def get_skill_summary(self, skill_name: str) -> Optional[str]:
|
|
285
|
+
"""
|
|
286
|
+
Get a summary of a skill for display purposes.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
skill_name: Name of the skill
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Summary string or None if skill not found
|
|
293
|
+
"""
|
|
294
|
+
skill = self.get_skill_content(skill_name)
|
|
295
|
+
if not skill:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
summary = f"Skill: {skill.metadata.name}\n"
|
|
299
|
+
summary += f"Description: {skill.metadata.description}\n"
|
|
300
|
+
|
|
301
|
+
if skill.metadata.license:
|
|
302
|
+
summary += f"License: {skill.metadata.license}\n"
|
|
303
|
+
|
|
304
|
+
if skill.metadata.allowed_tools:
|
|
305
|
+
summary += f"Allowed tools: {', '.join(skill.metadata.allowed_tools)}\n"
|
|
306
|
+
|
|
307
|
+
summary += f"Path: {skill.metadata.path}\n"
|
|
308
|
+
|
|
309
|
+
# Count resources
|
|
310
|
+
ref_count = len(skill.references)
|
|
311
|
+
script_count = len(skill.scripts)
|
|
312
|
+
asset_count = len(skill.assets)
|
|
313
|
+
|
|
314
|
+
summary += (
|
|
315
|
+
f"Resources: {ref_count} references, {script_count} scripts, {asset_count} assets\n"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
return summary
|
|
319
|
+
|
|
320
|
+
def get_all_skill_summaries(self) -> Dict[str, str]:
|
|
321
|
+
"""
|
|
322
|
+
Get summaries for all available skills.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Dictionary mapping skill names to summary strings
|
|
326
|
+
"""
|
|
327
|
+
skills = self.find_skills()
|
|
328
|
+
summaries = {}
|
|
329
|
+
|
|
330
|
+
for skill_metadata in skills:
|
|
331
|
+
summary = self.get_skill_summary(skill_metadata.name)
|
|
332
|
+
if summary:
|
|
333
|
+
summaries[skill_metadata.name] = summary
|
|
334
|
+
|
|
335
|
+
return summaries
|
|
336
|
+
|
|
337
|
+
def load_skill(self, skill_name: str) -> str:
|
|
338
|
+
"""
|
|
339
|
+
Add a skill to the loaded skills set for inclusion in context.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Success or error message
|
|
343
|
+
"""
|
|
344
|
+
if not skill_name:
|
|
345
|
+
return "Error: Skill name is required."
|
|
346
|
+
|
|
347
|
+
# Check if coder is available
|
|
348
|
+
if not self.coder:
|
|
349
|
+
return "Error: Skills manager not connected to a coder instance."
|
|
350
|
+
|
|
351
|
+
# Check if we're in agent mode
|
|
352
|
+
if not hasattr(self.coder, "edit_format") or self.coder.edit_format != "agent":
|
|
353
|
+
return "Error: Skill loading is only available in agent mode."
|
|
354
|
+
|
|
355
|
+
# Check if skill is already loaded
|
|
356
|
+
if skill_name in self._loaded_skills:
|
|
357
|
+
return f"Skill '{skill_name}' is already loaded."
|
|
358
|
+
|
|
359
|
+
# Find the skill to verify it exists
|
|
360
|
+
skills = self.find_skills()
|
|
361
|
+
skill_found = any(skill.name == skill_name for skill in skills)
|
|
362
|
+
|
|
363
|
+
if skill_found:
|
|
364
|
+
# Load the skill content
|
|
365
|
+
skill_content = self.get_skill_content(skill_name)
|
|
366
|
+
|
|
367
|
+
if skill_content:
|
|
368
|
+
# Add to loaded skills set
|
|
369
|
+
self._loaded_skills.add(skill_name)
|
|
370
|
+
|
|
371
|
+
result = f"Skill '{skill_name}' loaded successfully."
|
|
372
|
+
|
|
373
|
+
# Show skill summary
|
|
374
|
+
summary = self.get_skill_summary(skill_name)
|
|
375
|
+
if summary:
|
|
376
|
+
result += f"\n\n{summary}"
|
|
377
|
+
return result
|
|
378
|
+
else:
|
|
379
|
+
return f"Error: Skill '{skill_name}' found but could not be loaded."
|
|
380
|
+
else:
|
|
381
|
+
return f"Error: Skill '{skill_name}' not found in configured directories."
|
|
382
|
+
|
|
383
|
+
def remove_skill(self, skill_name: str) -> str:
|
|
384
|
+
"""
|
|
385
|
+
Remove a skill from the loaded skills set.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Success or error message
|
|
389
|
+
"""
|
|
390
|
+
if not skill_name:
|
|
391
|
+
return "Error: Skill name is required."
|
|
392
|
+
|
|
393
|
+
# Check if coder is available
|
|
394
|
+
if not self.coder:
|
|
395
|
+
return "Error: Skills manager not connected to a coder instance."
|
|
396
|
+
|
|
397
|
+
# Check if we're in agent mode
|
|
398
|
+
if not hasattr(self.coder, "edit_format") or self.coder.edit_format != "agent":
|
|
399
|
+
return "Error: Skill removal is only available in agent mode."
|
|
400
|
+
|
|
401
|
+
# Check if skill is already removed
|
|
402
|
+
if skill_name not in self._loaded_skills:
|
|
403
|
+
return f"Skill '{skill_name}' is not loaded."
|
|
404
|
+
|
|
405
|
+
# Remove from loaded skills set
|
|
406
|
+
self._loaded_skills.remove(skill_name)
|
|
407
|
+
|
|
408
|
+
return f"Skill '{skill_name}' removed successfully."
|
|
409
|
+
|
|
410
|
+
@classmethod
|
|
411
|
+
def skill_summary_loader(
|
|
412
|
+
cls,
|
|
413
|
+
directory_paths: List[str],
|
|
414
|
+
include_list: Optional[List[str]] = None,
|
|
415
|
+
exclude_list: Optional[List[str]] = None,
|
|
416
|
+
git_root: Optional[str] = None,
|
|
417
|
+
) -> str:
|
|
418
|
+
"""
|
|
419
|
+
High-level function to load and summarize all available skills.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
directory_paths: List of directory paths to search for skills
|
|
423
|
+
include_list: Optional list of skill names to include (whitelist)
|
|
424
|
+
exclude_list: Optional list of skill names to exclude (blacklist)
|
|
425
|
+
git_root: Optional git root directory for relative path resolution
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Formatted summary of all available skills
|
|
429
|
+
"""
|
|
430
|
+
manager = cls(directory_paths, include_list, exclude_list, git_root)
|
|
431
|
+
summaries = manager.get_all_skill_summaries()
|
|
432
|
+
|
|
433
|
+
if not summaries:
|
|
434
|
+
return "No skills found in the specified directories."
|
|
435
|
+
|
|
436
|
+
result = f"Found {len(summaries)} skill(s):\n\n"
|
|
437
|
+
|
|
438
|
+
for i, (skill_name, summary) in enumerate(summaries.items(), 1):
|
|
439
|
+
result += f"{i}. {summary}\n"
|
|
440
|
+
|
|
441
|
+
return result
|
|
442
|
+
|
|
443
|
+
@staticmethod
|
|
444
|
+
def resolve_skill_directories(
|
|
445
|
+
base_paths: List[str], git_root: Optional[str] = None
|
|
446
|
+
) -> List[Path]:
|
|
447
|
+
"""
|
|
448
|
+
Resolve skill directory paths relative to various locations.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
base_paths: List of base directory paths
|
|
452
|
+
git_root: Optional git root directory
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
List of resolved Path objects
|
|
456
|
+
"""
|
|
457
|
+
resolved_paths = []
|
|
458
|
+
|
|
459
|
+
for base_path in base_paths:
|
|
460
|
+
# Try to resolve relative to git root first
|
|
461
|
+
if git_root and not Path(base_path).is_absolute():
|
|
462
|
+
git_path = Path(git_root) / base_path
|
|
463
|
+
if git_path.exists():
|
|
464
|
+
resolved_paths.append(git_path.resolve())
|
|
465
|
+
continue
|
|
466
|
+
|
|
467
|
+
# Try as absolute or relative to current directory
|
|
468
|
+
try:
|
|
469
|
+
path = Path(base_path).expanduser().resolve()
|
|
470
|
+
if path.exists():
|
|
471
|
+
resolved_paths.append(path)
|
|
472
|
+
except Exception:
|
|
473
|
+
continue
|
|
474
|
+
|
|
475
|
+
return resolved_paths
|
|
476
|
+
|
|
477
|
+
def get_skills_content(self) -> Optional[str]:
|
|
478
|
+
"""
|
|
479
|
+
Generate a context block with skill metadata and file paths for references, scripts, and assets.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Formatted context block string with skill metadata and file paths or None if no skills available
|
|
483
|
+
"""
|
|
484
|
+
try:
|
|
485
|
+
# Only return skills that have been explicitly loaded via load_skill()
|
|
486
|
+
if not self._loaded_skills:
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
result = '<context name="loaded_skills">\n'
|
|
490
|
+
result += "## Loaded Skills Content\n\n"
|
|
491
|
+
result += f"Found {len(self._loaded_skills)} skill(s) in configured directories:\n\n"
|
|
492
|
+
|
|
493
|
+
for i, skill_name in enumerate(sorted(self._loaded_skills)):
|
|
494
|
+
# Load the complete skill (should be cached)
|
|
495
|
+
skill_content = self.get_skill_content(skill_name)
|
|
496
|
+
if not skill_content:
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
result += f"### Skill {i}: {skill_content.metadata.name}\n\n"
|
|
500
|
+
result += f"**Description**: {skill_content.metadata.description}\n\n"
|
|
501
|
+
|
|
502
|
+
if skill_content.metadata.license:
|
|
503
|
+
result += f"**License**: {skill_content.metadata.license}\n\n"
|
|
504
|
+
|
|
505
|
+
if skill_content.metadata.allowed_tools:
|
|
506
|
+
result += (
|
|
507
|
+
f"**Allowed Tools**: {', '.join(skill_content.metadata.allowed_tools)}\n\n"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Add instructions
|
|
511
|
+
result += "#### Instructions\n\n"
|
|
512
|
+
result += f"{skill_content.instructions}\n\n"
|
|
513
|
+
|
|
514
|
+
# Add references file paths
|
|
515
|
+
if skill_content.references:
|
|
516
|
+
result += "#### References\n\n"
|
|
517
|
+
result += f"Available reference files ({len(skill_content.references)}):\n\n"
|
|
518
|
+
for ref_name, ref_path in skill_content.references.items():
|
|
519
|
+
result += f"- **{ref_name}**: `{ref_path}`\n"
|
|
520
|
+
result += "\n"
|
|
521
|
+
|
|
522
|
+
# Add scripts file paths
|
|
523
|
+
if skill_content.scripts:
|
|
524
|
+
result += "#### Scripts\n\n"
|
|
525
|
+
result += f"Available script files ({len(skill_content.scripts)}):\n\n"
|
|
526
|
+
for script_name, script_path in skill_content.scripts.items():
|
|
527
|
+
result += f"- **{script_name}**: `{script_path}`\n"
|
|
528
|
+
result += "\n"
|
|
529
|
+
|
|
530
|
+
# Add assets file paths
|
|
531
|
+
if skill_content.assets:
|
|
532
|
+
result += f"#### Assets ({len(skill_content.assets)} file(s))\n\n"
|
|
533
|
+
result += "Available asset files:\n\n"
|
|
534
|
+
for asset_name, asset_path in skill_content.assets.items():
|
|
535
|
+
result += f"- **{asset_name}**: `{asset_path}`\n"
|
|
536
|
+
result += "\n"
|
|
537
|
+
|
|
538
|
+
result += "---\n\n"
|
|
539
|
+
|
|
540
|
+
result += "</context>"
|
|
541
|
+
return result
|
|
542
|
+
except Exception:
|
|
543
|
+
# We can't use io.tool_error here since we don't have access to io
|
|
544
|
+
# The caller should handle the exception
|
|
545
|
+
raise
|
|
546
|
+
|
|
547
|
+
def get_skills_context(self) -> Optional[str]:
|
|
548
|
+
"""
|
|
549
|
+
Generate a context block for available skills.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Formatted context block string or None if no skills available
|
|
553
|
+
"""
|
|
554
|
+
try:
|
|
555
|
+
# Get skill summaries
|
|
556
|
+
summaries = self.get_all_skill_summaries()
|
|
557
|
+
if not summaries:
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
result = '<context name="skills">\n'
|
|
561
|
+
result += "## Available Skills\n\n"
|
|
562
|
+
result += f"Found {len(summaries)} skill(s) in configured directories:\n\n"
|
|
563
|
+
|
|
564
|
+
for i, (skill_name, summary) in enumerate(summaries.items(), 1):
|
|
565
|
+
result += f"### Skill {i}: {skill_name}\n\n"
|
|
566
|
+
result += f"{summary}\n"
|
|
567
|
+
|
|
568
|
+
result += (
|
|
569
|
+
"Use the `LoadSkill` tool with the skill name if "
|
|
570
|
+
"the skill is relevant to the current task."
|
|
571
|
+
)
|
|
572
|
+
result += "</context>"
|
|
573
|
+
return result
|
|
574
|
+
except Exception:
|
|
575
|
+
# We can't use io.tool_error here since we don't have access to io
|
|
576
|
+
# The caller should handle the exception
|
|
577
|
+
raise
|