lazyopencode 0.2.1__tar.gz → 0.2.3__tar.gz
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.
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/AGENTS.md +2 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/PKG-INFO +1 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/_version.py +2 -2
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/discovery.py +84 -50
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/agent.py +5 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/command.py +5 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/plugin.py +1 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/skill.py +2 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/tool.py +1 -1
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/conftest.py +30 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_skills.py +60 -0
- lazyopencode-0.2.3/tests/integration/fixtures/agents-skill/agents-compat-skill/SKILL.md +7 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.github/release-drafter.yml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.github/workflows/ci.yml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.github/workflows/publish.yml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.github/workflows/release-drafter.yml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.gitignore +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.opencode/command/run-quality-gates.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.opencode/skill/quality-gates/SKILL.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/.pre-commit-config.yaml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/LICENSE +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/artifacts/demo.png +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/artifacts/lazyclaude-reference.png +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/pyproject.toml +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/scripts/check_quality.sh +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/__main__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/app.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/bindings.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/mixins/filtering.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/mixins/help.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/mixins/navigation.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/models/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/models/customization.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/discovery.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/agent.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/command.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/skill.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/plugin_loader.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/gitignore_filter.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/mcp.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/parsers/rules.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/writer.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/styles/app.tcss +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/themes.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/app_footer.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/combined_panel.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/delete_confirm.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/detail_pane.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/filter_input.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/helpers/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/helpers/rendering.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/level_selector.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/status_panel.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/widgets/type_panel.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_agents.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_commands.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_full_discovery.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_mcps.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_rules.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/agent/explorer.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/command/greet.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/mcp/project-opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/mcp/user-opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/memory/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/agent/reviewer.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/command/project-cmd.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/docs/guidelines.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/skill/project-skill/SKILL.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/skill/project-skill/src/helper.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/skill/task-tracker/SKILL.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/skill/task-tracker/reference.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/skill/task-tracker/scripts/run.sh +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/SPEC.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/conftest.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/agent/reviewer.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/command/verify.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/plugin/metrics.ts +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/skill/deploy-helper/SKILL.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/skill/deploy-helper/scripts/deploy.sh +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/tool/search.ts +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/docs/guidelines.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/prompts/auditor.txt +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_commands/.opencode/command/deploy.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_commands/.opencode/command/greet.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_commands/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/prompts/agent.txt +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/templates/cmd.txt +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/inline_commands/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/inline_commands/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/minimal/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/minimal/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/mixed_config/.opencode/agent/file-agent.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/mixed_config/.opencode/command/file-cmd.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/mixed_config/AGENTS.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/mixed_config/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/mixed_config/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/docs/api-standards.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/opencode.json +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/prompts/security-audit.txt +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/scripts/fake-lint.sh +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/SKILL.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/docs/guide.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/scripts/run.sh +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/skills_with_tree/README.md +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/test_scenarios.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/test_inline.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/test_version.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/unit/__init__.py +0 -0
- {lazyopencode-0.2.1 → lazyopencode-0.2.3}/uv.lock +0 -0
|
@@ -85,6 +85,8 @@ The application discovers customizations from these locations:
|
|
|
85
85
|
| Commands | `~/.config/opencode/command/*.md` | `.opencode/command/*.md` |
|
|
86
86
|
| Agents | `~/.config/opencode/agent/*.md` | `.opencode/agent/*.md` |
|
|
87
87
|
| Skills | `~/.config/opencode/skill/*/SKILL.md` | `.opencode/skill/*/SKILL.md` |
|
|
88
|
+
| Skills | `~/.agents/skill[s]/*/SKILL.md` | `.agents/skill[s]/*/SKILL.md` |
|
|
89
|
+
| Skills | (via Claude Code) `~/.claude/skills/*/SKILL.md` | `.claude/skill[s]/*/SKILL.md` |
|
|
88
90
|
| Rules | `~/.config/opencode/AGENTS.md` | `AGENTS.md` |
|
|
89
91
|
| MCPs | `~/.config/opencode/opencode.json` | `opencode.json` |
|
|
90
92
|
| Tools | `~/.config/opencode/tool/*.ts` | `.opencode/tool/*.ts` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lazyopencode
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A lazygit-style TUI for visualizing OpenCode customizations
|
|
5
5
|
Project-URL: Homepage, https://github.com/nikiforovall/lazyopencode
|
|
6
6
|
Project-URL: Repository, https://github.com/nikiforovall/lazyopencode
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.2.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
31
|
+
__version__ = version = '0.2.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 3)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -73,6 +73,19 @@ class ConfigDiscoveryService:
|
|
|
73
73
|
"""Path to project's .opencode directory."""
|
|
74
74
|
return self.project_root / ".opencode"
|
|
75
75
|
|
|
76
|
+
def _get_path_variants(self, base_path: Path, singular: str) -> list[Path]:
|
|
77
|
+
"""Return both singular and plural path variants that exist.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
base_path: Base directory to check
|
|
81
|
+
singular: Singular form of folder name (e.g., 'command')
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of existing paths (both singular and plural if they exist)
|
|
85
|
+
"""
|
|
86
|
+
variants = [base_path / singular, base_path / f"{singular}s"]
|
|
87
|
+
return [p for p in variants if p.exists()]
|
|
88
|
+
|
|
76
89
|
def discover_all(self) -> list[Customization]:
|
|
77
90
|
"""
|
|
78
91
|
Discover all customizations from global and project levels.
|
|
@@ -152,16 +165,13 @@ class ConfigDiscoveryService:
|
|
|
152
165
|
self, base_path: Path, level: ConfigLevel
|
|
153
166
|
) -> list[Customization]:
|
|
154
167
|
"""Discover command customizations."""
|
|
155
|
-
commands_path = base_path / "command"
|
|
156
|
-
if not commands_path.exists():
|
|
157
|
-
return []
|
|
158
|
-
|
|
159
168
|
customizations = []
|
|
160
169
|
parser = self._parsers[CustomizationType.COMMAND]
|
|
161
170
|
|
|
162
|
-
for
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
for commands_path in self._get_path_variants(base_path, "command"):
|
|
172
|
+
for md_file in commands_path.glob("*.md"):
|
|
173
|
+
if parser.can_parse(md_file):
|
|
174
|
+
customizations.append(parser.parse(md_file, level))
|
|
165
175
|
|
|
166
176
|
return customizations
|
|
167
177
|
|
|
@@ -182,16 +192,13 @@ class ConfigDiscoveryService:
|
|
|
182
192
|
self, base_path: Path, level: ConfigLevel
|
|
183
193
|
) -> list[Customization]:
|
|
184
194
|
"""Discover agent customizations."""
|
|
185
|
-
agents_path = base_path / "agent"
|
|
186
|
-
if not agents_path.exists():
|
|
187
|
-
return []
|
|
188
|
-
|
|
189
195
|
customizations = []
|
|
190
196
|
parser = self._parsers[CustomizationType.AGENT]
|
|
191
197
|
|
|
192
|
-
for
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
for agents_path in self._get_path_variants(base_path, "agent"):
|
|
199
|
+
for md_file in agents_path.glob("*.md"):
|
|
200
|
+
if parser.can_parse(md_file):
|
|
201
|
+
customizations.append(parser.parse(md_file, level))
|
|
195
202
|
|
|
196
203
|
return customizations
|
|
197
204
|
|
|
@@ -211,39 +218,72 @@ class ConfigDiscoveryService:
|
|
|
211
218
|
def _discover_skills(
|
|
212
219
|
self, base_path: Path, level: ConfigLevel
|
|
213
220
|
) -> list[Customization]:
|
|
214
|
-
"""Discover skill customizations from .opencode/skill/.
|
|
221
|
+
"""Discover skill customizations from .opencode/skill/ or skills/.
|
|
222
|
+
|
|
223
|
+
Also discovers from agent-compatible paths (.agents/skill[s]/)
|
|
224
|
+
and Claude-compatible paths (.claude/skill[s]/) per OpenCode docs:
|
|
225
|
+
https://opencode.ai/docs/skills/#place-files
|
|
226
|
+
"""
|
|
215
227
|
customizations = []
|
|
228
|
+
parser = self._parsers[CustomizationType.SKILL]
|
|
216
229
|
|
|
217
|
-
#
|
|
218
|
-
skills_path
|
|
219
|
-
|
|
230
|
+
# Check both singular and plural variants
|
|
231
|
+
for skills_path in self._get_path_variants(base_path, "skill"):
|
|
232
|
+
for skill_dir in skills_path.iterdir():
|
|
233
|
+
if skill_dir.is_dir():
|
|
234
|
+
skill_file = skill_dir / "SKILL.md"
|
|
235
|
+
if parser.can_parse(skill_file):
|
|
236
|
+
customizations.append(parser.parse(skill_file, level))
|
|
220
237
|
|
|
221
238
|
# Also discover from Claude-compatible path at project level
|
|
222
239
|
if level == ConfigLevel.PROJECT:
|
|
223
|
-
claude_skills_path
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
for claude_skills_path in self._get_path_variants(
|
|
241
|
+
self.project_root / ".claude", "skill"
|
|
242
|
+
):
|
|
243
|
+
for skill_dir in claude_skills_path.iterdir():
|
|
244
|
+
if skill_dir.is_dir():
|
|
245
|
+
skill_file = skill_dir / "SKILL.md"
|
|
246
|
+
if parser.can_parse(skill_file):
|
|
247
|
+
customizations.append(parser.parse(skill_file, level))
|
|
248
|
+
|
|
249
|
+
# Discover from agent-compatible paths (.agents/skill[s]/)
|
|
250
|
+
self._discover_skills_from_compat_roots(
|
|
251
|
+
customizations, parser, level, ".agents"
|
|
252
|
+
)
|
|
227
253
|
|
|
228
254
|
return customizations
|
|
229
255
|
|
|
230
|
-
def
|
|
231
|
-
self,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
256
|
+
def _discover_skills_from_compat_roots(
|
|
257
|
+
self,
|
|
258
|
+
customizations: list[Customization],
|
|
259
|
+
parser: object,
|
|
260
|
+
level: ConfigLevel,
|
|
261
|
+
compat_dir: str,
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Discover skills from a compatibility root directory.
|
|
236
264
|
|
|
237
|
-
|
|
238
|
-
|
|
265
|
+
At PROJECT level: scans <project_root>/<compat_dir>/skill[s]/*/SKILL.md
|
|
266
|
+
At GLOBAL level: scans ~/<compat_dir>/skill[s]/*/SKILL.md
|
|
239
267
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
Args:
|
|
269
|
+
customizations: List to append discovered skills to
|
|
270
|
+
parser: Skill parser instance
|
|
271
|
+
level: Configuration level (GLOBAL or PROJECT)
|
|
272
|
+
compat_dir: Compatibility directory name (e.g., ".agents")
|
|
273
|
+
"""
|
|
274
|
+
if level == ConfigLevel.PROJECT:
|
|
275
|
+
compat_base = self.project_root / compat_dir
|
|
276
|
+
else:
|
|
277
|
+
compat_base = Path.home() / compat_dir
|
|
245
278
|
|
|
246
|
-
|
|
279
|
+
for skills_path in self._get_path_variants(compat_base, "skill"):
|
|
280
|
+
for skill_dir in skills_path.iterdir():
|
|
281
|
+
if skill_dir.is_dir():
|
|
282
|
+
skill_file = skill_dir / "SKILL.md"
|
|
283
|
+
if parser.can_parse(skill_file): # type: ignore[attr-defined]
|
|
284
|
+
customizations.append(
|
|
285
|
+
parser.parse(skill_file, level) # type: ignore[attr-defined]
|
|
286
|
+
)
|
|
247
287
|
|
|
248
288
|
def _discover_rules(self, level: ConfigLevel) -> list[Customization]:
|
|
249
289
|
"""Discover AGENTS.md rules files."""
|
|
@@ -278,16 +318,13 @@ class ConfigDiscoveryService:
|
|
|
278
318
|
self, base_path: Path, level: ConfigLevel
|
|
279
319
|
) -> list[Customization]:
|
|
280
320
|
"""Discover tool customizations from .opencode/tool/."""
|
|
281
|
-
tools_path = base_path / "tool"
|
|
282
|
-
if not tools_path.exists():
|
|
283
|
-
return []
|
|
284
|
-
|
|
285
321
|
customizations = []
|
|
286
322
|
parser = self._parsers[CustomizationType.TOOL]
|
|
287
323
|
|
|
288
|
-
for
|
|
289
|
-
|
|
290
|
-
|
|
324
|
+
for tools_path in self._get_path_variants(base_path, "tool"):
|
|
325
|
+
for tool_file in tools_path.iterdir():
|
|
326
|
+
if tool_file.is_file() and parser.can_parse(tool_file):
|
|
327
|
+
customizations.append(parser.parse(tool_file, level))
|
|
291
328
|
|
|
292
329
|
return customizations
|
|
293
330
|
|
|
@@ -295,16 +332,13 @@ class ConfigDiscoveryService:
|
|
|
295
332
|
self, base_path: Path, level: ConfigLevel
|
|
296
333
|
) -> list[Customization]:
|
|
297
334
|
"""Discover plugin customizations from .opencode/plugin/."""
|
|
298
|
-
plugins_path = base_path / "plugin"
|
|
299
|
-
if not plugins_path.exists():
|
|
300
|
-
return []
|
|
301
|
-
|
|
302
335
|
customizations = []
|
|
303
336
|
parser = self._parsers[CustomizationType.PLUGIN]
|
|
304
337
|
|
|
305
|
-
for
|
|
306
|
-
|
|
307
|
-
|
|
338
|
+
for plugins_path in self._get_path_variants(base_path, "plugin"):
|
|
339
|
+
for plugin_file in plugins_path.iterdir():
|
|
340
|
+
if plugin_file.is_file() and parser.can_parse(plugin_file):
|
|
341
|
+
customizations.append(parser.parse(plugin_file, level))
|
|
308
342
|
|
|
309
343
|
return customizations
|
|
310
344
|
|
|
@@ -23,7 +23,11 @@ class AgentParser(ICustomizationParser):
|
|
|
23
23
|
|
|
24
24
|
def can_parse(self, path: Path) -> bool:
|
|
25
25
|
"""Check if path is an agent markdown file."""
|
|
26
|
-
return
|
|
26
|
+
return (
|
|
27
|
+
path.is_file()
|
|
28
|
+
and path.suffix == ".md"
|
|
29
|
+
and path.parent.name in ("agent", "agents")
|
|
30
|
+
)
|
|
27
31
|
|
|
28
32
|
def parse(self, path: Path, level: ConfigLevel) -> Customization:
|
|
29
33
|
"""Parse agent file."""
|
|
@@ -23,7 +23,11 @@ class CommandParser(ICustomizationParser):
|
|
|
23
23
|
|
|
24
24
|
def can_parse(self, path: Path) -> bool:
|
|
25
25
|
"""Check if path is a command markdown file."""
|
|
26
|
-
return
|
|
26
|
+
return (
|
|
27
|
+
path.is_file()
|
|
28
|
+
and path.suffix == ".md"
|
|
29
|
+
and path.parent.name in ("command", "commands")
|
|
30
|
+
)
|
|
27
31
|
|
|
28
32
|
def parse(self, path: Path, level: ConfigLevel) -> Customization:
|
|
29
33
|
"""Parse command file."""
|
|
@@ -32,7 +32,7 @@ class PluginParser(ICustomizationParser):
|
|
|
32
32
|
return (
|
|
33
33
|
path.is_file()
|
|
34
34
|
and path.suffix in self.VALID_EXTENSIONS
|
|
35
|
-
and path.parent.name
|
|
35
|
+
and path.parent.name in ("plugin", "plugins")
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
def parse(self, path: Path, level: ConfigLevel) -> Customization:
|
|
@@ -92,10 +92,11 @@ class SkillParser(ICustomizationParser):
|
|
|
92
92
|
|
|
93
93
|
def can_parse(self, path: Path) -> bool:
|
|
94
94
|
"""Check if path is a SKILL.md file in a skill directory."""
|
|
95
|
+
parent_dir_name = path.parent.parent.name
|
|
95
96
|
return (
|
|
96
97
|
path.is_file()
|
|
97
98
|
and path.name == "SKILL.md"
|
|
98
|
-
and
|
|
99
|
+
and parent_dir_name in ("skill", "skills")
|
|
99
100
|
)
|
|
100
101
|
|
|
101
102
|
def parse(self, path: Path, level: ConfigLevel) -> Customization:
|
|
@@ -24,7 +24,7 @@ class ToolParser(ICustomizationParser):
|
|
|
24
24
|
return (
|
|
25
25
|
path.is_file()
|
|
26
26
|
and path.suffix in self.VALID_EXTENSIONS
|
|
27
|
-
and path.parent.name
|
|
27
|
+
and path.parent.name in ("tool", "tools")
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
def parse(self, path: Path, level: ConfigLevel) -> Customization:
|
|
@@ -146,6 +146,36 @@ def project_config_path(fake_project_root: Path, fs: FakeFilesystem) -> Path:
|
|
|
146
146
|
return project_opencode
|
|
147
147
|
|
|
148
148
|
|
|
149
|
+
@pytest.fixture
|
|
150
|
+
def agents_project_skills(fake_project_root: Path, fs: FakeFilesystem) -> Path:
|
|
151
|
+
"""Create .agents/skills/ directory with fixture skill at project level."""
|
|
152
|
+
agents_skills = fake_project_root / ".agents" / "skills"
|
|
153
|
+
fs.create_dir(agents_skills)
|
|
154
|
+
|
|
155
|
+
fs.add_real_directory(
|
|
156
|
+
FIXTURES_DIR / "agents-skill" / "agents-compat-skill",
|
|
157
|
+
target_path=agents_skills / "agents-compat-skill",
|
|
158
|
+
read_only=False,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return agents_skills
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.fixture
|
|
165
|
+
def agents_global_skills(fake_home: Path, fs: FakeFilesystem) -> Path:
|
|
166
|
+
"""Create ~/.agents/skills/ directory with fixture skill at global level."""
|
|
167
|
+
agents_skills = fake_home / ".agents" / "skills"
|
|
168
|
+
fs.create_dir(agents_skills)
|
|
169
|
+
|
|
170
|
+
fs.add_real_directory(
|
|
171
|
+
FIXTURES_DIR / "agents-skill" / "agents-compat-skill",
|
|
172
|
+
target_path=agents_skills / "agents-compat-skill",
|
|
173
|
+
read_only=False,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return agents_skills
|
|
177
|
+
|
|
178
|
+
|
|
149
179
|
@pytest.fixture
|
|
150
180
|
def full_user_config(
|
|
151
181
|
user_config_path: Path,
|
|
@@ -146,6 +146,66 @@ class TestSkillDiscovery:
|
|
|
146
146
|
assert "reference.md" in file_names
|
|
147
147
|
assert "scripts" in file_names
|
|
148
148
|
|
|
149
|
+
def test_discovers_project_agents_compat_skills(
|
|
150
|
+
self,
|
|
151
|
+
agents_project_skills: Path, # noqa: ARG002
|
|
152
|
+
fake_project_root: Path,
|
|
153
|
+
fake_home: Path,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Test discovering skills from .agents/skills/ at project level."""
|
|
156
|
+
service = ConfigDiscoveryService(
|
|
157
|
+
project_root=fake_project_root,
|
|
158
|
+
global_config_path=fake_home / ".config" / "opencode",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
skills = service.by_type(CustomizationType.SKILL)
|
|
162
|
+
project_skills = [s for s in skills if s.level == ConfigLevel.PROJECT]
|
|
163
|
+
|
|
164
|
+
assert len(project_skills) == 1
|
|
165
|
+
assert project_skills[0].name == "agents-compat-skill"
|
|
166
|
+
assert project_skills[0].description == "Skill discovered from .agents/ path"
|
|
167
|
+
|
|
168
|
+
def test_discovers_global_agents_compat_skills(
|
|
169
|
+
self,
|
|
170
|
+
agents_global_skills: Path, # noqa: ARG002
|
|
171
|
+
fake_project_root: Path,
|
|
172
|
+
fake_home: Path,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Test discovering skills from ~/.agents/skills/ at global level."""
|
|
175
|
+
service = ConfigDiscoveryService(
|
|
176
|
+
project_root=fake_project_root,
|
|
177
|
+
global_config_path=fake_home / ".config" / "opencode",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
skills = service.by_type(CustomizationType.SKILL)
|
|
181
|
+
global_skills = [s for s in skills if s.level == ConfigLevel.GLOBAL]
|
|
182
|
+
|
|
183
|
+
assert len(global_skills) == 1
|
|
184
|
+
assert global_skills[0].name == "agents-compat-skill"
|
|
185
|
+
|
|
186
|
+
def test_agents_compat_skills_deduplicated(
|
|
187
|
+
self,
|
|
188
|
+
project_config_path: Path, # noqa: ARG002
|
|
189
|
+
agents_project_skills: Path, # noqa: ARG002
|
|
190
|
+
fake_project_root: Path,
|
|
191
|
+
fake_home: Path,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Test that same skill in .opencode/ and .agents/ is not duplicated."""
|
|
194
|
+
# The agents-compat-skill is only in .agents/, project-skill is only in .opencode/
|
|
195
|
+
# So total project skills should be 2, not 3
|
|
196
|
+
service = ConfigDiscoveryService(
|
|
197
|
+
project_root=fake_project_root,
|
|
198
|
+
global_config_path=fake_home / ".config" / "opencode",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
skills = service.by_type(CustomizationType.SKILL)
|
|
202
|
+
project_skills = [s for s in skills if s.level == ConfigLevel.PROJECT]
|
|
203
|
+
|
|
204
|
+
names = [s.name for s in project_skills]
|
|
205
|
+
assert "project-skill" in names
|
|
206
|
+
assert "agents-compat-skill" in names
|
|
207
|
+
assert len(project_skills) == 2
|
|
208
|
+
|
|
149
209
|
def test_skill_nested_directory_structure(
|
|
150
210
|
self,
|
|
151
211
|
project_config_path: Path, # noqa: ARG002
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/discovery.py
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/__init__.py
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/agent.py
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/command.py
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/parsers/skill.py
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/src/lazyopencode/services/claude_code/plugin_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/discovery/test_full_discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/mcp/project-opencode.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/agent/reviewer.md
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/command/project-cmd.md
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/project/docs/guidelines.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/skill/task-tracker/SKILL.md
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/integration/fixtures/skill/task-tracker/reference.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/agent/reviewer.md
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/command/verify.md
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/plugin/metrics.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/complex/.opencode/tool/search.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/opencode.json
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/prompts/agent.txt
RENAMED
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/file_references/templates/cmd.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/inline_commands/opencode.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazyopencode-0.2.1 → lazyopencode-0.2.3}/tests/spec/scenarios/rich_opencode_json/opencode.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|