lazyopencode 0.1.0__tar.gz → 0.2.0__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.1.0 → lazyopencode-0.2.0}/AGENTS.md +3 -12
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/PKG-INFO +28 -25
- lazyopencode-0.2.0/README.md +88 -0
- lazyopencode-0.2.0/artifacts/demo.png +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/pyproject.toml +2 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/__init__.py +12 -1
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/_version.py +2 -2
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/app.py +152 -23
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/bindings.py +2 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/mixins/help.py +2 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/mixins/navigation.py +21 -26
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/models/customization.py +35 -4
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/__init__.py +9 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/discovery.py +158 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/parsers/__init__.py +7 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/parsers/agent.py +58 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/parsers/command.py +75 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/parsers/skill.py +130 -0
- lazyopencode-0.2.0/src/lazyopencode/services/claude_code/plugin_loader.py +164 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/discovery.py +25 -4
- lazyopencode-0.2.0/src/lazyopencode/services/writer.py +119 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/app_footer.py +1 -1
- lazyopencode-0.2.0/src/lazyopencode/widgets/level_selector.py +130 -0
- lazyopencode-0.2.0/tests/spec/SPEC.md +129 -0
- lazyopencode-0.2.0/tests/spec/__init__.py +1 -0
- lazyopencode-0.2.0/tests/spec/conftest.py +208 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/agent/reviewer.md +14 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/command/verify.md +16 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/plugin/metrics.ts +18 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/skill/deploy-helper/SKILL.md +19 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/skill/deploy-helper/scripts/deploy.sh +6 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/.opencode/tool/search.ts +16 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/AGENTS.md +14 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/README.md +51 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/docs/guidelines.md +12 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/opencode.json +39 -0
- lazyopencode-0.2.0/tests/spec/scenarios/complex/prompts/auditor.txt +7 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_commands/.opencode/command/deploy.md +11 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_commands/.opencode/command/greet.md +10 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_commands/README.md +13 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_references/README.md +14 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_references/opencode.json +16 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_references/prompts/agent.txt +8 -0
- lazyopencode-0.2.0/tests/spec/scenarios/file_references/templates/cmd.txt +7 -0
- lazyopencode-0.2.0/tests/spec/scenarios/inline_commands/README.md +14 -0
- lazyopencode-0.2.0/tests/spec/scenarios/inline_commands/opencode.json +14 -0
- lazyopencode-0.2.0/tests/spec/scenarios/minimal/AGENTS.md +7 -0
- lazyopencode-0.2.0/tests/spec/scenarios/minimal/README.md +11 -0
- lazyopencode-0.2.0/tests/spec/scenarios/mixed_config/.opencode/agent/file-agent.md +7 -0
- lazyopencode-0.2.0/tests/spec/scenarios/mixed_config/.opencode/command/file-cmd.md +6 -0
- lazyopencode-0.2.0/tests/spec/scenarios/mixed_config/AGENTS.md +7 -0
- lazyopencode-0.2.0/tests/spec/scenarios/mixed_config/README.md +18 -0
- lazyopencode-0.2.0/tests/spec/scenarios/mixed_config/opencode.json +22 -0
- lazyopencode-0.2.0/tests/spec/scenarios/rich_opencode_json/README.md +46 -0
- lazyopencode-0.2.0/tests/spec/scenarios/rich_opencode_json/docs/api-standards.md +17 -0
- {lazyopencode-0.1.0/reference-customizations → lazyopencode-0.2.0/tests/spec/scenarios/rich_opencode_json}/opencode.json +2 -4
- {lazyopencode-0.1.0/reference-customizations → lazyopencode-0.2.0/tests/spec/scenarios/rich_opencode_json}/prompts/security-audit.txt +1 -1
- {lazyopencode-0.1.0/reference-customizations → lazyopencode-0.2.0/tests/spec/scenarios/rich_opencode_json}/scripts/fake-lint.sh +0 -1
- lazyopencode-0.2.0/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/SKILL.md +15 -0
- lazyopencode-0.2.0/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/docs/guide.md +11 -0
- lazyopencode-0.2.0/tests/spec/scenarios/skills_with_tree/.opencode/skill/my-skill/scripts/run.sh +5 -0
- lazyopencode-0.2.0/tests/spec/scenarios/skills_with_tree/README.md +14 -0
- lazyopencode-0.2.0/tests/spec/test_scenarios.py +709 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/uv.lock +22 -0
- lazyopencode-0.1.0/.opencode/command/commit.md +0 -36
- lazyopencode-0.1.0/.opencode/plugin/compaction.ts +0 -21
- lazyopencode-0.1.0/.opencode/plugin/custom-tools.ts +0 -31
- lazyopencode-0.1.0/.opencode/plugin/env-protection.ts +0 -21
- lazyopencode-0.1.0/.opencode/plugin/notification.ts +0 -24
- lazyopencode-0.1.0/.opencode/tool/database.ts +0 -12
- lazyopencode-0.1.0/.opencode/tool/math.ts +0 -39
- lazyopencode-0.1.0/.opencode/tool/project-info.ts +0 -11
- lazyopencode-0.1.0/.opencode/tool/python-runner.ts +0 -17
- lazyopencode-0.1.0/README.md +0 -86
- lazyopencode-0.1.0/reference-customizations/.opencode/agent/researcher.md +0 -12
- lazyopencode-0.1.0/reference-customizations/.opencode/command/verify.md +0 -11
- lazyopencode-0.1.0/reference-customizations/.opencode/plugin/compaction.ts +0 -21
- lazyopencode-0.1.0/reference-customizations/.opencode/plugin/custom-tools.ts +0 -31
- lazyopencode-0.1.0/reference-customizations/.opencode/plugin/env-protection.ts +0 -21
- lazyopencode-0.1.0/reference-customizations/.opencode/plugin/notification.ts +0 -24
- lazyopencode-0.1.0/reference-customizations/.opencode/skill/architecture-audit/SKILL.md +0 -15
- lazyopencode-0.1.0/reference-customizations/.opencode/skill/architecture-audit/references/patterns.md +0 -10
- lazyopencode-0.1.0/reference-customizations/.opencode/skill/simple-formatter/SKILL.md +0 -10
- lazyopencode-0.1.0/reference-customizations/.opencode/tool/database.ts +0 -12
- lazyopencode-0.1.0/reference-customizations/.opencode/tool/math.ts +0 -39
- lazyopencode-0.1.0/reference-customizations/.opencode/tool/project-info.ts +0 -11
- lazyopencode-0.1.0/reference-customizations/.opencode/tool/python-runner.ts +0 -17
- lazyopencode-0.1.0/reference-customizations/AGENTS.md +0 -12
- lazyopencode-0.1.0/reference-customizations/TODO.md +0 -10
- lazyopencode-0.1.0/reference-customizations/docs/api-standards.md +0 -82
- lazyopencode-0.1.0/reference-customizations/docs/testing-guidelines.md +0 -70
- lazyopencode-0.1.0/reference-customizations/docs/typescript-guidelines.md +0 -56
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.github/release-drafter.yml +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.github/workflows/ci.yml +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.github/workflows/publish.yml +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.github/workflows/release-drafter.yml +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.gitignore +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.opencode/command/run-quality-gates.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.opencode/skill/quality-gates/SKILL.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/.pre-commit-config.yaml +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/LICENSE +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/artifacts/lazyclaude-reference.png +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/opencode.json +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/scripts/check_quality.sh +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/__main__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/mixins/filtering.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/models/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/gitignore_filter.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/agent.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/command.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/mcp.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/plugin.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/rules.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/skill.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/services/parsers/tool.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/styles/app.tcss +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/themes.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/combined_panel.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/detail_pane.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/filter_input.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/helpers/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/helpers/rendering.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/status_panel.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/src/lazyopencode/widgets/type_panel.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/AGENTS.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/conftest.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/__init__.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_agents.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_commands.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_full_discovery.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_mcps.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_rules.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/discovery/test_skills.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/agent/explorer.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/command/greet.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/mcp/project-opencode.json +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/mcp/user-opencode.json +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/memory/AGENTS.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/AGENTS.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/agent/reviewer.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/command/project-cmd.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/docs/guidelines.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/skill/project-skill/SKILL.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/project/skill/project-skill/src/helper.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/skill/task-tracker/SKILL.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/skill/task-tracker/reference.md +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/integration/fixtures/skill/task-tracker/scripts/run.sh +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/test_inline.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/test_version.py +0 -0
- {lazyopencode-0.1.0 → lazyopencode-0.2.0}/tests/unit/__init__.py +0 -0
|
@@ -50,7 +50,7 @@ src/lazyopencode/
|
|
|
50
50
|
├── models/ # Data models (Customization, ConfigLevel, etc.)
|
|
51
51
|
├── services/ # Business logic
|
|
52
52
|
│ ├── discovery.py # Finds customizations on disk
|
|
53
|
-
│ ├──
|
|
53
|
+
│ ├── gitignore_filter.py # Gitignore-aware filtering
|
|
54
54
|
│ └── parsers/ # Type-specific parsers
|
|
55
55
|
├── widgets/ # Textual UI components
|
|
56
56
|
├── mixins/ # App functionality mixins
|
|
@@ -99,7 +99,7 @@ The application discovers customizations from these locations:
|
|
|
99
99
|
|
|
100
100
|
### Services
|
|
101
101
|
- `ConfigDiscoveryService` - Scans filesystem, uses parsers
|
|
102
|
-
- `
|
|
102
|
+
- `GitignoreFilter` - Filters paths using gitignore rules
|
|
103
103
|
- `ICustomizationParser` - Protocol for type-specific parsers
|
|
104
104
|
|
|
105
105
|
### Widgets
|
|
@@ -111,7 +111,7 @@ The application discovers customizations from these locations:
|
|
|
111
111
|
|
|
112
112
|
### Mixins
|
|
113
113
|
- `NavigationMixin` - Panel focus, cursor movement
|
|
114
|
-
- `
|
|
114
|
+
- `FilteringMixin` - Level filters, search
|
|
115
115
|
- `HelpMixin` - Help overlay
|
|
116
116
|
|
|
117
117
|
## Testing
|
|
@@ -162,12 +162,3 @@ uv run pytest tests/unit/test_parsers.py
|
|
|
162
162
|
2. Add styles to `styles/app.tcss`
|
|
163
163
|
3. Compose in `app.py`
|
|
164
164
|
|
|
165
|
-
## Planning Documents
|
|
166
|
-
|
|
167
|
-
See `_plans/` directory for detailed specifications:
|
|
168
|
-
- `00-overview.md` - Project overview
|
|
169
|
-
- `01-architecture.md` - Architecture decisions
|
|
170
|
-
- `02-customization-types.md` - OpenCode customization mapping
|
|
171
|
-
- `03-implementation-phases.md` - Implementation plan
|
|
172
|
-
- `04-file-structure.md` - File structure
|
|
173
|
-
- `05-agents-md-template.md` - This template
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lazyopencode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
21
|
Requires-Dist: pathspec>=0.12.0
|
|
22
|
+
Requires-Dist: pyperclip>=1.9.0
|
|
22
23
|
Requires-Dist: pyyaml>=6.0
|
|
23
24
|
Requires-Dist: rich>=13.0.0
|
|
24
25
|
Requires-Dist: textual>=0.89.0
|
|
@@ -34,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
34
35
|
|
|
35
36
|
A keyboard-driven TUI for managing OpenCode customizations.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+

|
|
38
39
|
|
|
39
40
|
## Features
|
|
40
41
|
|
|
@@ -58,34 +59,36 @@ pip install lazyopencode
|
|
|
58
59
|
|
|
59
60
|
## Keyboard Shortcuts
|
|
60
61
|
|
|
61
|
-
| Key
|
|
62
|
-
|
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
|
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `ctrl
|
|
75
|
-
| `?`
|
|
62
|
+
| Key | Action |
|
|
63
|
+
| ---------- | ---------------- |
|
|
64
|
+
| `j` / `↓` | Move down |
|
|
65
|
+
| `k` / `↑` | Move up |
|
|
66
|
+
| `Tab` | Next panel |
|
|
67
|
+
| `[` / `]` | Prev/Next view |
|
|
68
|
+
| `1`-`7` | Jump to panel |
|
|
69
|
+
| `a` | All filter |
|
|
70
|
+
| `g` | Global filter |
|
|
71
|
+
| `p` | Project filter |
|
|
72
|
+
| `/` | Search |
|
|
73
|
+
| `e` | Edit selected |
|
|
74
|
+
| `r` | Refresh |
|
|
75
|
+
| `ctrl+u` | User Config |
|
|
76
|
+
| `?` | Help |
|
|
77
|
+
| `q` | Quit |
|
|
76
78
|
|
|
77
79
|
## Configuration Paths
|
|
78
80
|
|
|
79
81
|
LazyOpenCode discovers customizations from:
|
|
80
82
|
|
|
81
|
-
| Type | Global
|
|
82
|
-
| -------- |
|
|
83
|
-
| Commands | `~/.config/opencode/command/`
|
|
84
|
-
| Agents | `~/.config/opencode/agent/`
|
|
85
|
-
| Skills | `~/.config/opencode/skill/`
|
|
86
|
-
| Rules | `~/.config/opencode/AGENTS.md`
|
|
87
|
-
| MCPs |
|
|
88
|
-
|
|
|
83
|
+
| Type | Global | Project |
|
|
84
|
+
| -------- | ---------------------------------- | -------------------- |
|
|
85
|
+
| Commands | `~/.config/opencode/command/` | `.opencode/command/` |
|
|
86
|
+
| Agents | `~/.config/opencode/agent/` | `.opencode/agent/` |
|
|
87
|
+
| Skills | `~/.config/opencode/skill/` | `.opencode/skill/` |
|
|
88
|
+
| Rules | `~/.config/opencode/AGENTS.md` | `AGENTS.md` |
|
|
89
|
+
| MCPs | `~/.config/opencode/opencode.json` | `opencode.json` |
|
|
90
|
+
| Tools | `~/.config/opencode/tool/` | `.opencode/tool/` |
|
|
91
|
+
| Plugins | `~/.config/opencode/plugin/` | `.opencode/plugin/` |
|
|
89
92
|
|
|
90
93
|
## Inspired By
|
|
91
94
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# LazyOpenCode
|
|
2
|
+
|
|
3
|
+
A keyboard-driven TUI for managing OpenCode customizations.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Visual discovery of all OpenCode customizations
|
|
10
|
+
- Keyboard-driven navigation (lazygit-inspired)
|
|
11
|
+
- View commands, agents, skills, rules, MCPs, and plugins
|
|
12
|
+
- Filter by configuration level (global/project)
|
|
13
|
+
- Search within customizations
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uvx lazyopencode
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install with pip:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install lazyopencode
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Keyboard Shortcuts
|
|
28
|
+
|
|
29
|
+
| Key | Action |
|
|
30
|
+
| ---------- | ---------------- |
|
|
31
|
+
| `j` / `↓` | Move down |
|
|
32
|
+
| `k` / `↑` | Move up |
|
|
33
|
+
| `Tab` | Next panel |
|
|
34
|
+
| `[` / `]` | Prev/Next view |
|
|
35
|
+
| `1`-`7` | Jump to panel |
|
|
36
|
+
| `a` | All filter |
|
|
37
|
+
| `g` | Global filter |
|
|
38
|
+
| `p` | Project filter |
|
|
39
|
+
| `/` | Search |
|
|
40
|
+
| `e` | Edit selected |
|
|
41
|
+
| `r` | Refresh |
|
|
42
|
+
| `ctrl+u` | User Config |
|
|
43
|
+
| `?` | Help |
|
|
44
|
+
| `q` | Quit |
|
|
45
|
+
|
|
46
|
+
## Configuration Paths
|
|
47
|
+
|
|
48
|
+
LazyOpenCode discovers customizations from:
|
|
49
|
+
|
|
50
|
+
| Type | Global | Project |
|
|
51
|
+
| -------- | ---------------------------------- | -------------------- |
|
|
52
|
+
| Commands | `~/.config/opencode/command/` | `.opencode/command/` |
|
|
53
|
+
| Agents | `~/.config/opencode/agent/` | `.opencode/agent/` |
|
|
54
|
+
| Skills | `~/.config/opencode/skill/` | `.opencode/skill/` |
|
|
55
|
+
| Rules | `~/.config/opencode/AGENTS.md` | `AGENTS.md` |
|
|
56
|
+
| MCPs | `~/.config/opencode/opencode.json` | `opencode.json` |
|
|
57
|
+
| Tools | `~/.config/opencode/tool/` | `.opencode/tool/` |
|
|
58
|
+
| Plugins | `~/.config/opencode/plugin/` | `.opencode/plugin/` |
|
|
59
|
+
|
|
60
|
+
## Inspired By
|
|
61
|
+
|
|
62
|
+
- [LazyClaude](https://github.com/NikiforovAll/lazyclaude) - Similar TUI for Claude Code
|
|
63
|
+
- [Lazygit](https://github.com/jesseduffield/lazygit) - Keyboard-driven Git TUI
|
|
64
|
+
- [OpenCode](https://opencode.ai) - AI coding agent
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Clone and install
|
|
71
|
+
git clone https://github.com/yourusername/lazyopencode
|
|
72
|
+
cd lazyopencode
|
|
73
|
+
uv sync
|
|
74
|
+
|
|
75
|
+
# Run
|
|
76
|
+
uv run lazyopencode
|
|
77
|
+
|
|
78
|
+
# Run tests
|
|
79
|
+
uv run pytest
|
|
80
|
+
|
|
81
|
+
# Lint and format
|
|
82
|
+
uv run ruff check .
|
|
83
|
+
uv run ruff format .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
Binary file
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"rich>=13.0.0",
|
|
27
27
|
"pyyaml>=6.0",
|
|
28
28
|
"pathspec>=0.12.0",
|
|
29
|
+
"pyperclip>=1.9.0",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.urls]
|
|
@@ -108,5 +109,6 @@ dev = [
|
|
|
108
109
|
"pytest>=9.0.1",
|
|
109
110
|
"pytest-asyncio>=1.3.0",
|
|
110
111
|
"ruff>=0.14.8",
|
|
112
|
+
"types-pyperclip>=1.9.0",
|
|
111
113
|
"types-pyyaml>=6.0.12.20250915",
|
|
112
114
|
]
|
|
@@ -38,11 +38,22 @@ def main() -> None:
|
|
|
38
38
|
help="Override user config path (default: ~/.config/opencode)",
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--claude-code",
|
|
43
|
+
action="store_true",
|
|
44
|
+
default=False,
|
|
45
|
+
help="Enable Claude Code customizations discovery (from ~/.claude/)",
|
|
46
|
+
)
|
|
47
|
+
|
|
41
48
|
args = parser.parse_args()
|
|
42
49
|
|
|
43
50
|
# Handle directory argument - resolve to absolute path
|
|
44
51
|
project_root = args.directory.resolve() if args.directory else None
|
|
45
52
|
user_config = args.user_config.resolve() if args.user_config else None
|
|
46
53
|
|
|
47
|
-
app = create_app(
|
|
54
|
+
app = create_app(
|
|
55
|
+
project_root=project_root,
|
|
56
|
+
global_config_path=user_config,
|
|
57
|
+
enable_claude_code=args.claude_code,
|
|
58
|
+
)
|
|
48
59
|
app.run()
|
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.2.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -16,6 +16,7 @@ from lazyopencode.mixins.help import HelpMixin
|
|
|
16
16
|
from lazyopencode.mixins.navigation import NavigationMixin
|
|
17
17
|
from lazyopencode.models.customization import (
|
|
18
18
|
ConfigLevel,
|
|
19
|
+
ConfigSource,
|
|
19
20
|
Customization,
|
|
20
21
|
CustomizationType,
|
|
21
22
|
)
|
|
@@ -25,6 +26,7 @@ from lazyopencode.widgets.app_footer import AppFooter
|
|
|
25
26
|
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
26
27
|
from lazyopencode.widgets.detail_pane import MainPane
|
|
27
28
|
from lazyopencode.widgets.filter_input import FilterInput
|
|
29
|
+
from lazyopencode.widgets.level_selector import LevelSelector
|
|
28
30
|
from lazyopencode.widgets.status_panel import StatusPanel
|
|
29
31
|
from lazyopencode.widgets.type_panel import TypePanel
|
|
30
32
|
|
|
@@ -44,6 +46,7 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
44
46
|
discovery_service: ConfigDiscoveryService | None = None,
|
|
45
47
|
project_root: Path | None = None,
|
|
46
48
|
global_config_path: Path | None = None,
|
|
49
|
+
enable_claude_code: bool = False,
|
|
47
50
|
) -> None:
|
|
48
51
|
"""Initialize LazyOpenCode application."""
|
|
49
52
|
super().__init__()
|
|
@@ -51,6 +54,7 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
51
54
|
self._discovery_service = discovery_service or ConfigDiscoveryService(
|
|
52
55
|
project_root=project_root,
|
|
53
56
|
global_config_path=global_config_path,
|
|
57
|
+
enable_claude_code=enable_claude_code,
|
|
54
58
|
)
|
|
55
59
|
self._customizations: list[Customization] = []
|
|
56
60
|
self._level_filter: ConfigLevel | None = None
|
|
@@ -60,7 +64,9 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
60
64
|
self._main_pane: MainPane | None = None
|
|
61
65
|
self._filter_input: FilterInput | None = None
|
|
62
66
|
self._app_footer: AppFooter | None = None
|
|
67
|
+
self._level_selector: LevelSelector | None = None
|
|
63
68
|
self._last_focused_panel: TypePanel | CombinedPanel | None = None
|
|
69
|
+
self._pending_customization: Customization | None = None
|
|
64
70
|
|
|
65
71
|
def compose(self) -> ComposeResult:
|
|
66
72
|
"""Compose the application layout."""
|
|
@@ -68,16 +74,17 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
68
74
|
self._status_panel = StatusPanel(id="status-panel")
|
|
69
75
|
yield self._status_panel
|
|
70
76
|
|
|
71
|
-
# [1]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
# [1] Type Panel: Commands
|
|
78
|
+
tp_cmd = TypePanel(CustomizationType.COMMAND, id="panel-command")
|
|
79
|
+
tp_cmd.panel_number = 1
|
|
80
|
+
self._panels.append(tp_cmd)
|
|
81
|
+
yield tp_cmd
|
|
82
|
+
|
|
83
|
+
# [2] Type Panel: Agents
|
|
84
|
+
tp_agent = TypePanel(CustomizationType.AGENT, id="panel-agent")
|
|
85
|
+
tp_agent.panel_number = 2
|
|
86
|
+
self._panels.append(tp_agent)
|
|
87
|
+
yield tp_agent
|
|
81
88
|
|
|
82
89
|
# [3] Type Panel: Skills
|
|
83
90
|
tp_skills = TypePanel(CustomizationType.SKILL, id="panel-skill")
|
|
@@ -85,23 +92,18 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
85
92
|
self._panels.append(tp_skills)
|
|
86
93
|
yield tp_skills
|
|
87
94
|
|
|
88
|
-
# [4]
|
|
89
|
-
|
|
90
|
-
tp_rules.panel_number = 4
|
|
91
|
-
self._panels.append(tp_rules)
|
|
92
|
-
yield tp_rules
|
|
93
|
-
|
|
94
|
-
# [5]+[6]+[7] Combined Panel: MCPs, Tools, Plugins
|
|
95
|
-
cp2 = CombinedPanel(
|
|
95
|
+
# [4]+[5]+[6]+[7] Combined Panel: Memory, MCPs, Tools, Plugins
|
|
96
|
+
cp = CombinedPanel(
|
|
96
97
|
tabs=[
|
|
98
|
+
(CustomizationType.RULES, 4, "Memory"),
|
|
97
99
|
(CustomizationType.MCP, 5, "MCPs"),
|
|
98
100
|
(CustomizationType.TOOL, 6, "Tools"),
|
|
99
101
|
(CustomizationType.PLUGIN, 7, "Plugins"),
|
|
100
102
|
],
|
|
101
|
-
id="panel-combined
|
|
103
|
+
id="panel-combined",
|
|
102
104
|
)
|
|
103
|
-
self._panels.append(
|
|
104
|
-
yield
|
|
105
|
+
self._panels.append(cp)
|
|
106
|
+
yield cp
|
|
105
107
|
|
|
106
108
|
self._main_pane = MainPane(id="main-pane")
|
|
107
109
|
yield self._main_pane
|
|
@@ -109,6 +111,9 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
109
111
|
self._filter_input = FilterInput(id="filter-input")
|
|
110
112
|
yield self._filter_input
|
|
111
113
|
|
|
114
|
+
self._level_selector = LevelSelector(id="level-selector")
|
|
115
|
+
yield self._level_selector
|
|
116
|
+
|
|
112
117
|
self._app_footer = AppFooter(id="app-footer")
|
|
113
118
|
yield self._app_footer
|
|
114
119
|
|
|
@@ -121,6 +126,9 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
121
126
|
self._update_status_panel()
|
|
122
127
|
project_name = self._discovery_service.project_root.name
|
|
123
128
|
self.title = f"{project_name} - LazyOpenCode"
|
|
129
|
+
self.console.set_window_title(self.title)
|
|
130
|
+
if os.name == "nt":
|
|
131
|
+
os.system(f"title {self.title}")
|
|
124
132
|
# Focus first non-empty panel or first panel
|
|
125
133
|
if self._panels:
|
|
126
134
|
self._panels[0].focus()
|
|
@@ -154,7 +162,12 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
154
162
|
"""Get customizations filtered by current level and search query."""
|
|
155
163
|
result = self._customizations
|
|
156
164
|
if self._level_filter:
|
|
157
|
-
|
|
165
|
+
# When filtering by level, only show OpenCode items (exclude Claude Code)
|
|
166
|
+
result = [
|
|
167
|
+
c
|
|
168
|
+
for c in result
|
|
169
|
+
if c.level == self._level_filter and c.source == ConfigSource.OPENCODE
|
|
170
|
+
]
|
|
158
171
|
if self._search_query:
|
|
159
172
|
query = self._search_query.lower()
|
|
160
173
|
result = [c for c in result if query in c.name.lower()]
|
|
@@ -297,14 +310,130 @@ class LazyOpenCode(App, NavigationMixin, FilteringMixin, HelpMixin):
|
|
|
297
310
|
except Exception as e:
|
|
298
311
|
self.notify(f"Error opening editor: {e}", severity="error")
|
|
299
312
|
|
|
313
|
+
# Copy actions
|
|
314
|
+
|
|
315
|
+
def action_copy_customization(self) -> None:
|
|
316
|
+
"""Copy selected customization to another level."""
|
|
317
|
+
panel = self._get_focused_panel()
|
|
318
|
+
customization = None
|
|
319
|
+
|
|
320
|
+
if panel:
|
|
321
|
+
customization = panel.selected_customization
|
|
322
|
+
|
|
323
|
+
if not customization:
|
|
324
|
+
self.notify("No customization selected", severity="warning")
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
# Only allow copying commands, agents, and skills
|
|
328
|
+
copyable_types = (
|
|
329
|
+
CustomizationType.COMMAND,
|
|
330
|
+
CustomizationType.AGENT,
|
|
331
|
+
CustomizationType.SKILL,
|
|
332
|
+
)
|
|
333
|
+
if customization.type not in copyable_types:
|
|
334
|
+
self.notify(
|
|
335
|
+
f"Cannot copy {customization.type_label} customizations",
|
|
336
|
+
severity="warning",
|
|
337
|
+
)
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
available = customization.get_copy_targets()
|
|
341
|
+
if not available:
|
|
342
|
+
self.notify("No available target levels", severity="warning")
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
self._pending_customization = customization
|
|
346
|
+
self._last_focused_panel = panel
|
|
347
|
+
if self._level_selector:
|
|
348
|
+
self._level_selector.show(available, customization.name)
|
|
349
|
+
|
|
350
|
+
def action_copy_path_to_clipboard(self) -> None:
|
|
351
|
+
"""Copy path of selected customization to clipboard."""
|
|
352
|
+
panel = self._get_focused_panel()
|
|
353
|
+
customization = None
|
|
354
|
+
|
|
355
|
+
if panel:
|
|
356
|
+
customization = panel.selected_customization
|
|
357
|
+
|
|
358
|
+
if not customization:
|
|
359
|
+
self.notify("No customization selected", severity="warning")
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
file_path = customization.path
|
|
363
|
+
if customization.type == CustomizationType.SKILL:
|
|
364
|
+
file_path = customization.path.parent
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
import pyperclip
|
|
368
|
+
|
|
369
|
+
pyperclip.copy(str(file_path))
|
|
370
|
+
self.notify(f"Copied: {file_path}", severity="information")
|
|
371
|
+
except ImportError:
|
|
372
|
+
self.notify(
|
|
373
|
+
"pyperclip not installed. Run: pip install pyperclip",
|
|
374
|
+
severity="error",
|
|
375
|
+
)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self.notify(f"Failed to copy to clipboard: {e}", severity="error")
|
|
378
|
+
|
|
379
|
+
# Level selector message handlers
|
|
380
|
+
|
|
381
|
+
def on_level_selector_level_selected(
|
|
382
|
+
self, message: LevelSelector.LevelSelected
|
|
383
|
+
) -> None:
|
|
384
|
+
"""Handle level selection from the level selector."""
|
|
385
|
+
if self._pending_customization:
|
|
386
|
+
self._handle_copy(self._pending_customization, message.level)
|
|
387
|
+
self._pending_customization = None
|
|
388
|
+
self._restore_focus_after_selector()
|
|
389
|
+
|
|
390
|
+
def on_level_selector_selection_cancelled(
|
|
391
|
+
self,
|
|
392
|
+
message: LevelSelector.SelectionCancelled, # noqa: ARG002
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Handle level selector cancellation."""
|
|
395
|
+
self._pending_customization = None
|
|
396
|
+
self._restore_focus_after_selector()
|
|
397
|
+
|
|
398
|
+
def _handle_copy(
|
|
399
|
+
self, customization: Customization, target_level: ConfigLevel
|
|
400
|
+
) -> None:
|
|
401
|
+
"""Handle copy operation."""
|
|
402
|
+
from lazyopencode.services.writer import CustomizationWriter
|
|
403
|
+
|
|
404
|
+
writer = CustomizationWriter(
|
|
405
|
+
global_config_path=self._discovery_service.global_config_path,
|
|
406
|
+
project_config_path=self._discovery_service.project_config_path,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
success, msg = writer.copy_customization(customization, target_level)
|
|
410
|
+
|
|
411
|
+
if success:
|
|
412
|
+
self.notify(msg, severity="information")
|
|
413
|
+
self.action_refresh()
|
|
414
|
+
else:
|
|
415
|
+
self.notify(msg, severity="error")
|
|
416
|
+
|
|
417
|
+
def _restore_focus_after_selector(self) -> None:
|
|
418
|
+
"""Restore focus to the previously focused panel."""
|
|
419
|
+
if self._last_focused_panel:
|
|
420
|
+
self._last_focused_panel.focus()
|
|
421
|
+
elif self._panels:
|
|
422
|
+
self._panels[0].focus()
|
|
423
|
+
|
|
300
424
|
|
|
301
425
|
def create_app(
|
|
302
426
|
project_root: Path | None = None,
|
|
303
427
|
global_config_path: Path | None = None,
|
|
428
|
+
enable_claude_code: bool = False,
|
|
304
429
|
) -> LazyOpenCode:
|
|
305
430
|
"""Create application with all dependencies wired."""
|
|
306
431
|
discovery_service = ConfigDiscoveryService(
|
|
307
432
|
project_root=project_root,
|
|
308
433
|
global_config_path=global_config_path,
|
|
434
|
+
enable_claude_code=enable_claude_code,
|
|
435
|
+
)
|
|
436
|
+
return LazyOpenCode(
|
|
437
|
+
discovery_service=discovery_service,
|
|
438
|
+
enable_claude_code=enable_claude_code,
|
|
309
439
|
)
|
|
310
|
-
return LazyOpenCode(discovery_service=discovery_service)
|
|
@@ -7,6 +7,8 @@ APP_BINDINGS: list[BindingType] = [
|
|
|
7
7
|
Binding("?", "toggle_help", "Help"),
|
|
8
8
|
Binding("r", "refresh", "Refresh"),
|
|
9
9
|
Binding("e", "open_in_editor", "Edit"),
|
|
10
|
+
Binding("c", "copy_customization", "Copy"),
|
|
11
|
+
Binding("C", "copy_path_to_clipboard", "Copy Path", key_display="shift+c"),
|
|
10
12
|
Binding("ctrl+u", "open_user_config", "User Config"),
|
|
11
13
|
Binding("tab", "focus_next_panel", "Next Panel", show=False),
|
|
12
14
|
Binding("shift+tab", "focus_previous_panel", "Prev Panel", show=False),
|
|
@@ -80,70 +80,65 @@ class NavigationMixin:
|
|
|
80
80
|
prev_panel.focus()
|
|
81
81
|
|
|
82
82
|
def action_focus_panel_1(self) -> None:
|
|
83
|
-
"""Focus Commands
|
|
83
|
+
"""Focus Commands panel."""
|
|
84
84
|
app = cast("LazyOpenCode", self)
|
|
85
|
-
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
86
|
-
|
|
87
85
|
if len(app._panels) > 0:
|
|
88
|
-
|
|
89
|
-
if isinstance(panel, CombinedPanel):
|
|
90
|
-
panel.switch_to_tab(0) # Commands tab
|
|
91
|
-
panel.focus()
|
|
86
|
+
app._panels[0].focus()
|
|
92
87
|
|
|
93
88
|
def action_focus_panel_2(self) -> None:
|
|
94
|
-
"""Focus Agents
|
|
89
|
+
"""Focus Agents panel."""
|
|
95
90
|
app = cast("LazyOpenCode", self)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if len(app._panels) > 0:
|
|
99
|
-
panel = app._panels[0]
|
|
100
|
-
if isinstance(panel, CombinedPanel):
|
|
101
|
-
panel.switch_to_tab(1) # Agents tab
|
|
102
|
-
panel.focus()
|
|
91
|
+
if len(app._panels) > 1:
|
|
92
|
+
app._panels[1].focus()
|
|
103
93
|
|
|
104
94
|
def action_focus_panel_3(self) -> None:
|
|
105
95
|
"""Focus Skills panel."""
|
|
106
96
|
app = cast("LazyOpenCode", self)
|
|
107
|
-
if len(app._panels) >
|
|
108
|
-
app._panels[
|
|
97
|
+
if len(app._panels) > 2:
|
|
98
|
+
app._panels[2].focus()
|
|
109
99
|
|
|
110
100
|
def action_focus_panel_4(self) -> None:
|
|
111
|
-
"""Focus Agent Memory (Rules) panel."""
|
|
101
|
+
"""Focus Agent Memory (Rules) tab in combined panel."""
|
|
112
102
|
app = cast("LazyOpenCode", self)
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
104
|
+
|
|
105
|
+
if len(app._panels) > 3:
|
|
106
|
+
panel = app._panels[3]
|
|
107
|
+
if isinstance(panel, CombinedPanel):
|
|
108
|
+
panel.switch_to_tab(0) # Memory tab
|
|
109
|
+
panel.focus()
|
|
115
110
|
|
|
116
111
|
def action_focus_panel_5(self) -> None:
|
|
117
|
-
"""Focus MCPs tab in
|
|
112
|
+
"""Focus MCPs tab in combined panel."""
|
|
118
113
|
app = cast("LazyOpenCode", self)
|
|
119
114
|
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
120
115
|
|
|
121
116
|
if len(app._panels) > 3:
|
|
122
117
|
panel = app._panels[3]
|
|
123
118
|
if isinstance(panel, CombinedPanel):
|
|
124
|
-
panel.switch_to_tab(
|
|
119
|
+
panel.switch_to_tab(1) # MCPs tab
|
|
125
120
|
panel.focus()
|
|
126
121
|
|
|
127
122
|
def action_focus_panel_6(self) -> None:
|
|
128
|
-
"""Focus Tools tab in
|
|
123
|
+
"""Focus Tools tab in combined panel."""
|
|
129
124
|
app = cast("LazyOpenCode", self)
|
|
130
125
|
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
131
126
|
|
|
132
127
|
if len(app._panels) > 3:
|
|
133
128
|
panel = app._panels[3]
|
|
134
129
|
if isinstance(panel, CombinedPanel):
|
|
135
|
-
panel.switch_to_tab(
|
|
130
|
+
panel.switch_to_tab(2) # Tools tab
|
|
136
131
|
panel.focus()
|
|
137
132
|
|
|
138
133
|
def action_focus_panel_7(self) -> None:
|
|
139
|
-
"""Focus Plugins tab in
|
|
134
|
+
"""Focus Plugins tab in combined panel."""
|
|
140
135
|
app = cast("LazyOpenCode", self)
|
|
141
136
|
from lazyopencode.widgets.combined_panel import CombinedPanel
|
|
142
137
|
|
|
143
138
|
if len(app._panels) > 3:
|
|
144
139
|
panel = app._panels[3]
|
|
145
140
|
if isinstance(panel, CombinedPanel):
|
|
146
|
-
panel.switch_to_tab(
|
|
141
|
+
panel.switch_to_tab(3) # Plugins tab
|
|
147
142
|
panel.focus()
|
|
148
143
|
|
|
149
144
|
def action_focus_main_pane(self) -> None:
|