lazyopencode 0.1.0__py3-none-any.whl → 0.2.0__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.
- lazyopencode/__init__.py +12 -1
- lazyopencode/_version.py +2 -2
- lazyopencode/app.py +152 -23
- lazyopencode/bindings.py +2 -0
- lazyopencode/mixins/help.py +2 -0
- lazyopencode/mixins/navigation.py +21 -26
- lazyopencode/models/customization.py +35 -4
- lazyopencode/services/claude_code/__init__.py +9 -0
- lazyopencode/services/claude_code/discovery.py +158 -0
- lazyopencode/services/claude_code/parsers/__init__.py +7 -0
- lazyopencode/services/claude_code/parsers/agent.py +58 -0
- lazyopencode/services/claude_code/parsers/command.py +75 -0
- lazyopencode/services/claude_code/parsers/skill.py +130 -0
- lazyopencode/services/claude_code/plugin_loader.py +164 -0
- lazyopencode/services/discovery.py +25 -4
- lazyopencode/services/writer.py +119 -0
- lazyopencode/widgets/app_footer.py +1 -1
- lazyopencode/widgets/level_selector.py +130 -0
- {lazyopencode-0.1.0.dist-info → lazyopencode-0.2.0.dist-info}/METADATA +28 -25
- {lazyopencode-0.1.0.dist-info → lazyopencode-0.2.0.dist-info}/RECORD +23 -14
- {lazyopencode-0.1.0.dist-info → lazyopencode-0.2.0.dist-info}/WHEEL +0 -0
- {lazyopencode-0.1.0.dist-info → lazyopencode-0.2.0.dist-info}/entry_points.txt +0 -0
- {lazyopencode-0.1.0.dist-info → lazyopencode-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Level selector bar for copy operations."""
|
|
2
|
+
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.binding import Binding
|
|
5
|
+
from textual.message import Message
|
|
6
|
+
from textual.widget import Widget
|
|
7
|
+
from textual.widgets import Static
|
|
8
|
+
|
|
9
|
+
from lazyopencode.models.customization import ConfigLevel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LevelSelector(Widget):
|
|
13
|
+
"""Bottom bar for selecting target configuration level."""
|
|
14
|
+
|
|
15
|
+
BINDINGS = [
|
|
16
|
+
Binding("1", "select_global", "Global", show=False),
|
|
17
|
+
Binding("2", "select_project", "Project", show=False),
|
|
18
|
+
Binding("escape", "cancel", "Cancel", show=False),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
DEFAULT_CSS = """
|
|
22
|
+
LevelSelector {
|
|
23
|
+
dock: bottom;
|
|
24
|
+
height: 3;
|
|
25
|
+
border: solid $accent;
|
|
26
|
+
padding: 0 1;
|
|
27
|
+
margin-bottom: 1;
|
|
28
|
+
display: none;
|
|
29
|
+
background: $surface;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
LevelSelector.visible {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
LevelSelector:focus {
|
|
37
|
+
border: double $accent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
LevelSelector #prompt {
|
|
41
|
+
width: 100%;
|
|
42
|
+
text-align: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
LevelSelector .key {
|
|
46
|
+
color: $accent;
|
|
47
|
+
text-style: bold;
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
can_focus = True
|
|
52
|
+
|
|
53
|
+
class LevelSelected(Message):
|
|
54
|
+
"""Emitted when a level is selected."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, level: ConfigLevel) -> None:
|
|
57
|
+
self.level = level
|
|
58
|
+
super().__init__()
|
|
59
|
+
|
|
60
|
+
class SelectionCancelled(Message):
|
|
61
|
+
"""Emitted when selection is cancelled."""
|
|
62
|
+
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
name: str | None = None,
|
|
68
|
+
id: str | None = None,
|
|
69
|
+
classes: str | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize LevelSelector."""
|
|
72
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
73
|
+
self._available_levels: list[ConfigLevel] = []
|
|
74
|
+
self._customization_name: str = ""
|
|
75
|
+
|
|
76
|
+
def compose(self) -> ComposeResult:
|
|
77
|
+
"""Compose the level selector bar."""
|
|
78
|
+
yield Static("", id="prompt")
|
|
79
|
+
|
|
80
|
+
def show(
|
|
81
|
+
self,
|
|
82
|
+
available_levels: list[ConfigLevel],
|
|
83
|
+
customization_name: str = "",
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Show the level selector and focus it."""
|
|
86
|
+
self._available_levels = available_levels
|
|
87
|
+
self._customization_name = customization_name
|
|
88
|
+
self._update_prompt()
|
|
89
|
+
self.add_class("visible")
|
|
90
|
+
self.focus()
|
|
91
|
+
|
|
92
|
+
def hide(self) -> None:
|
|
93
|
+
"""Hide the level selector."""
|
|
94
|
+
self.remove_class("visible")
|
|
95
|
+
|
|
96
|
+
def _update_prompt(self) -> None:
|
|
97
|
+
"""Update the prompt text based on available levels."""
|
|
98
|
+
name_part = f'"{self._customization_name}" ' if self._customization_name else ""
|
|
99
|
+
|
|
100
|
+
options = []
|
|
101
|
+
if ConfigLevel.GLOBAL in self._available_levels:
|
|
102
|
+
options.append("\\[1] Global")
|
|
103
|
+
if ConfigLevel.PROJECT in self._available_levels:
|
|
104
|
+
options.append("\\[2] Project")
|
|
105
|
+
|
|
106
|
+
options_text = " ".join(options)
|
|
107
|
+
prompt_widget = self.query_one("#prompt", Static)
|
|
108
|
+
prompt_widget.update(f"Copy {name_part}to: {options_text} \\[Esc] Cancel")
|
|
109
|
+
|
|
110
|
+
def action_select_global(self) -> None:
|
|
111
|
+
"""Select global level."""
|
|
112
|
+
if ConfigLevel.GLOBAL in self._available_levels:
|
|
113
|
+
self.hide()
|
|
114
|
+
self.post_message(self.LevelSelected(ConfigLevel.GLOBAL))
|
|
115
|
+
|
|
116
|
+
def action_select_project(self) -> None:
|
|
117
|
+
"""Select project level."""
|
|
118
|
+
if ConfigLevel.PROJECT in self._available_levels:
|
|
119
|
+
self.hide()
|
|
120
|
+
self.post_message(self.LevelSelected(ConfigLevel.PROJECT))
|
|
121
|
+
|
|
122
|
+
def action_cancel(self) -> None:
|
|
123
|
+
"""Cancel selection."""
|
|
124
|
+
self.hide()
|
|
125
|
+
self.post_message(self.SelectionCancelled())
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_visible(self) -> bool:
|
|
129
|
+
"""Check if the level selector is visible."""
|
|
130
|
+
return self.has_class("visible")
|
|
@@ -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
|
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
lazyopencode/__init__.py,sha256=
|
|
1
|
+
lazyopencode/__init__.py,sha256=roprUA5NQ0OsqJrG-XYGgCJYST5_M3oQNKSVrpDZM1E,1558
|
|
2
2
|
lazyopencode/__main__.py,sha256=8sGwTbHrRZyACIYWqpMRP76sq7fTBeJSj-VOAd6hUKE,120
|
|
3
|
-
lazyopencode/_version.py,sha256=
|
|
4
|
-
lazyopencode/app.py,sha256=
|
|
5
|
-
lazyopencode/bindings.py,sha256=
|
|
3
|
+
lazyopencode/_version.py,sha256=Dg8AmJomLVpjKL6prJylOONZAPRtB86LOce7dorQS_A,704
|
|
4
|
+
lazyopencode/app.py,sha256=PmVBhvRbggITuC2KNKP3DiOcaYiThQXmD3Dlc8UYODg,15886
|
|
5
|
+
lazyopencode/bindings.py,sha256=QOPYO3BnxuriNd4lqHaCl1v_-CjogcL_o9Ibpa1eqDs,1342
|
|
6
6
|
lazyopencode/themes.py,sha256=9KsyWlk9XeAupAdUTj785riWuDlQMnERjD8DUzogCPc,713
|
|
7
7
|
lazyopencode/mixins/filtering.py,sha256=m7G5NN-dvphPCnnNc8GyJY3p6ijmFxnr4GrzukYMoII,988
|
|
8
|
-
lazyopencode/mixins/help.py,sha256=
|
|
9
|
-
lazyopencode/mixins/navigation.py,sha256=
|
|
8
|
+
lazyopencode/mixins/help.py,sha256=4OnRoUlaVPM_81IDyG04YgJquZwv_wGAkmom-5ebyUk,2059
|
|
9
|
+
lazyopencode/mixins/navigation.py,sha256=RHA5J9Wn9nYjDmRglMagvn94OFTti8q3yuYI6bNAgcM,6600
|
|
10
10
|
lazyopencode/models/__init__.py,sha256=X2Wlkc6IS1nQF0JNTvqZNNLh6YI1SH-D5YP4xpWL-rc,298
|
|
11
|
-
lazyopencode/models/customization.py,sha256=
|
|
11
|
+
lazyopencode/models/customization.py,sha256=INO9NkEu5vBKYZ8vxdWB5QuQ6QqKVSzuT4jNjwmkx8g,4089
|
|
12
12
|
lazyopencode/services/__init__.py,sha256=aCVZJecnMeupVU-T6EhR34dFu295XwJP9dqZ8MKWOJE,146
|
|
13
|
-
lazyopencode/services/discovery.py,sha256=
|
|
13
|
+
lazyopencode/services/discovery.py,sha256=w7qv_Sk83zpmEZJnP1HCM8JxaJDHqyQE59zQP3prrJc,13510
|
|
14
14
|
lazyopencode/services/gitignore_filter.py,sha256=9QFKrUsSm0kSSYXzJ3_Zx440Pk1T6CSo6eaoPP5Pthk,3155
|
|
15
|
+
lazyopencode/services/writer.py,sha256=eRDCUTzmoQ92cD5gB4nGEWE9l03SqRY53UFKm4dyGFc,4443
|
|
16
|
+
lazyopencode/services/claude_code/__init__.py,sha256=lMcoJR-x2eHR6ddvL3YbYT4xonGjKKFUdHwjBwEcaNg,310
|
|
17
|
+
lazyopencode/services/claude_code/discovery.py,sha256=gRzX_HUWPI5tdkhV4JmIeh539QkhpEeSXM3a4fI2rT0,5780
|
|
18
|
+
lazyopencode/services/claude_code/plugin_loader.py,sha256=KriqpCeYVR_8NExdgAKtMASz2r2krYNiKNxEYwtKhnE,5572
|
|
19
|
+
lazyopencode/services/claude_code/parsers/__init__.py,sha256=L0SleU_DoacF-rN-crQZ6Rkf5BWvnEhpSGhVHg504ic,326
|
|
20
|
+
lazyopencode/services/claude_code/parsers/agent.py,sha256=YeqmBinlqViAWb2FGxnsAnfQOFoTGXLVJ_pUWvnvqBQ,1754
|
|
21
|
+
lazyopencode/services/claude_code/parsers/command.py,sha256=E8H0X0lMaEzT2PIpNQKhtydf3chw7NKzNh-F9B121Cw,2429
|
|
22
|
+
lazyopencode/services/claude_code/parsers/skill.py,sha256=nD7JxkJW_hHrvA0Nfb5t7OKx4p1TBIdlfn90-KfGVak,3805
|
|
15
23
|
lazyopencode/services/parsers/__init__.py,sha256=zav6eIpqF5TGFp2jlGSOFZtCicHyltA--cVMycox9M0,4344
|
|
16
24
|
lazyopencode/services/parsers/agent.py,sha256=zaw8y3RZsPFOu8DsoM3b2XPGzp0BnQJcbx86mEIYUxM,2885
|
|
17
25
|
lazyopencode/services/parsers/command.py,sha256=-Mg3sq_4TLkIvXKzIX4lB7KvU0e4T0TA-5lpwhcUgBE,2977
|
|
@@ -22,16 +30,17 @@ lazyopencode/services/parsers/skill.py,sha256=WMKIv1HJSdx0vt0KJ9YtQ0oB1G5GOOC_sm
|
|
|
22
30
|
lazyopencode/services/parsers/tool.py,sha256=sL3N63cim68R_wo5s0WONnnnvYxrK-tyiGCIc7guMV0,1800
|
|
23
31
|
lazyopencode/styles/app.tcss,sha256=fAAWxQ7ePJ7oYfMUoU3ffC3PfJ9hGocHhPF7nNMf-qU,2658
|
|
24
32
|
lazyopencode/widgets/__init__.py,sha256=syhZGjXKhKjg75iSU1xgRqiRWV-S-Jh8ipd0g1-qkMs,497
|
|
25
|
-
lazyopencode/widgets/app_footer.py,sha256=
|
|
33
|
+
lazyopencode/widgets/app_footer.py,sha256=blVdZobd4XXJ4qxQDsXyP6tm52gSvloImqFa_x5L8Bo,2217
|
|
26
34
|
lazyopencode/widgets/combined_panel.py,sha256=vEopdSAmThctwAlawwY88u5Bmz4XNMnDY9u2YnCLco8,12113
|
|
27
35
|
lazyopencode/widgets/detail_pane.py,sha256=fFTGZhaJXP4PGAUItv4rYMiDkTbdqRXG0NTI6ooS_kc,11296
|
|
28
36
|
lazyopencode/widgets/filter_input.py,sha256=rJp7Y0NVJSj7c-wIS8ZPXgkxTtYU4Gp-DXJOFYOhV5U,2518
|
|
37
|
+
lazyopencode/widgets/level_selector.py,sha256=rO3Ik87rDLmlsw4eXkUE5ps7rrrv7v_KO1_HwlfMTHA,3733
|
|
29
38
|
lazyopencode/widgets/status_panel.py,sha256=k4lx4GrHlVUvt6t32_bz2WOWvZdPNawdSMhmXUZYpc8,2127
|
|
30
39
|
lazyopencode/widgets/type_panel.py,sha256=wg2D5diKvdrwco7pvcUnF9WPOgTveiH1qt0O82gkqa4,19313
|
|
31
40
|
lazyopencode/widgets/helpers/__init__.py,sha256=-jGrYtTMl9rmX8Q5Vod_oAuYaMl6Yh5JRwv8AFKQcwk,135
|
|
32
41
|
lazyopencode/widgets/helpers/rendering.py,sha256=lgxzVsnH6fgTZss2x4n8hKYYtDN35YmFMD83impkpH8,576
|
|
33
|
-
lazyopencode-0.
|
|
34
|
-
lazyopencode-0.
|
|
35
|
-
lazyopencode-0.
|
|
36
|
-
lazyopencode-0.
|
|
37
|
-
lazyopencode-0.
|
|
42
|
+
lazyopencode-0.2.0.dist-info/METADATA,sha256=YVZPl7bgvnynIOWAJyctsF-xzXiZfxwzfWrOZ11ja4s,3558
|
|
43
|
+
lazyopencode-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
44
|
+
lazyopencode-0.2.0.dist-info/entry_points.txt,sha256=PPVT4NhHce2hFuMj-NQTSN9xQIM2jwb0_ojMEcOpPJ0,60
|
|
45
|
+
lazyopencode-0.2.0.dist-info/licenses/LICENSE,sha256=KY9Pw3pDaLTe2eiMvUoj09VHhE3i8N1Le0d01JrG_3Q,1069
|
|
46
|
+
lazyopencode-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|