lazyopencode 0.1.1__py3-none-any.whl → 0.2.1__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.
@@ -0,0 +1,115 @@
1
+ """Confirmation widget for delete 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 Customization
10
+
11
+
12
+ class DeleteConfirm(Widget):
13
+ """Bottom bar for confirming delete operations."""
14
+
15
+ BINDINGS = [
16
+ Binding("y", "confirm", "Yes", show=False),
17
+ Binding("n", "deny", "No", show=False),
18
+ Binding("escape", "cancel", "Cancel", show=False),
19
+ ]
20
+
21
+ DEFAULT_CSS = """
22
+ DeleteConfirm {
23
+ dock: bottom;
24
+ height: 4;
25
+ border: solid $error;
26
+ padding: 0 1;
27
+ margin-bottom: 1;
28
+ display: none;
29
+ background: $surface;
30
+ }
31
+
32
+ DeleteConfirm.visible {
33
+ display: block;
34
+ }
35
+
36
+ DeleteConfirm:focus {
37
+ border: double $error;
38
+ }
39
+
40
+ DeleteConfirm #prompt {
41
+ width: 100%;
42
+ text-align: center;
43
+ }
44
+ """
45
+
46
+ can_focus = True
47
+
48
+ class DeleteConfirmed(Message):
49
+ """Emitted when delete is confirmed."""
50
+
51
+ def __init__(self, customization: Customization) -> None:
52
+ self.customization = customization
53
+ super().__init__()
54
+
55
+ class DeleteCancelled(Message):
56
+ """Emitted when delete is cancelled."""
57
+
58
+ pass
59
+
60
+ def __init__(
61
+ self,
62
+ name: str | None = None,
63
+ id: str | None = None,
64
+ classes: str | None = None,
65
+ ) -> None:
66
+ """Initialize DeleteConfirm."""
67
+ super().__init__(name=name, id=id, classes=classes)
68
+ self._customization: Customization | None = None
69
+
70
+ def compose(self) -> ComposeResult:
71
+ """Compose the confirmation bar."""
72
+ yield Static("", id="prompt")
73
+
74
+ def show(self, customization: Customization) -> None:
75
+ """Show the confirmation bar and focus it."""
76
+ self._customization = customization
77
+ self._update_prompt(customization)
78
+ self.add_class("visible")
79
+ self.focus()
80
+
81
+ def hide(self) -> None:
82
+ """Hide the confirmation bar."""
83
+ self.remove_class("visible")
84
+ self._customization = None
85
+
86
+ def _update_prompt(self, customization: Customization) -> None:
87
+ """Update the prompt text."""
88
+ prompt_widget = self.query_one("#prompt", Static)
89
+ error_color = self.app.get_css_variables().get("error", "red")
90
+ prompt_widget.update(
91
+ f'Delete [{error_color}]"{customization.name}"[/] ({customization.type_label})?\n'
92
+ "\\[y] Yes \\[n] No \\[Esc] Cancel"
93
+ )
94
+
95
+ def action_confirm(self) -> None:
96
+ """Confirm the delete."""
97
+ if self._customization:
98
+ customization = self._customization
99
+ self.hide()
100
+ self.post_message(self.DeleteConfirmed(customization))
101
+
102
+ def action_deny(self) -> None:
103
+ """Deny the delete."""
104
+ self.hide()
105
+ self.post_message(self.DeleteCancelled())
106
+
107
+ def action_cancel(self) -> None:
108
+ """Cancel the delete."""
109
+ self.hide()
110
+ self.post_message(self.DeleteCancelled())
111
+
112
+ @property
113
+ def is_visible(self) -> bool:
114
+ """Check if the confirmation bar is visible."""
115
+ return self.has_class("visible")
@@ -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.1.1
3
+ Version: 0.2.1
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
@@ -43,6 +44,7 @@ A keyboard-driven TUI for managing OpenCode customizations.
43
44
  - View commands, agents, skills, rules, MCPs, and plugins
44
45
  - Filter by configuration level (global/project)
45
46
  - Search within customizations
47
+ - Claude Code compatibility mode (`--claude-code`)
46
48
 
47
49
  ## Installation
48
50
 
@@ -70,6 +72,8 @@ pip install lazyopencode
70
72
  | `p` | Project filter |
71
73
  | `/` | Search |
72
74
  | `e` | Edit selected |
75
+ | `c` | Copy to level |
76
+ | `C` | Copy path |
73
77
  | `r` | Refresh |
74
78
  | `ctrl+u` | User Config |
75
79
  | `?` | Help |
@@ -89,6 +93,24 @@ LazyOpenCode discovers customizations from:
89
93
  | Tools | `~/.config/opencode/tool/` | `.opencode/tool/` |
90
94
  | Plugins | `~/.config/opencode/plugin/` | `.opencode/plugin/` |
91
95
 
96
+ ## Claude Code Mode
97
+
98
+ Enable Claude Code compatibility to also discover customizations from `~/.claude/`:
99
+
100
+ ```bash
101
+ lazyopencode --claude-code
102
+ ```
103
+
104
+ This discovers commands, agents, and skills from:
105
+
106
+ | Scope | Path |
107
+ | ------- | ----------------------------------------- |
108
+ | User | `~/.claude/commands/`, `~/.claude/agents/` |
109
+ | Project | `.claude/commands/`, `.claude/agents/` |
110
+ | Plugins | Installed plugins from registry |
111
+
112
+ Claude Code items are marked with 👾 and can be copied to OpenCode paths using `c`.
113
+
92
114
  ## Inspired By
93
115
 
94
116
  - [LazyClaude](https://github.com/NikiforovAll/lazyclaude) - Similar TUI for Claude Code
@@ -1,17 +1,25 @@
1
- lazyopencode/__init__.py,sha256=p0PzeS7KA58kGpxWMzOVMHIPAXDXOPg-bMiF9Jxtp2c,1303
1
+ lazyopencode/__init__.py,sha256=roprUA5NQ0OsqJrG-XYGgCJYST5_M3oQNKSVrpDZM1E,1558
2
2
  lazyopencode/__main__.py,sha256=8sGwTbHrRZyACIYWqpMRP76sq7fTBeJSj-VOAd6hUKE,120
3
- lazyopencode/_version.py,sha256=m8HxkqoKGw_wAJtc4ZokpJKNLXqp4zwnNhbnfDtro7w,704
4
- lazyopencode/app.py,sha256=s5lPLNCuBD8C31tNL3z9lBmnswOSCbq9SxyKkNltqfM,11358
5
- lazyopencode/bindings.py,sha256=N6e-U23aFuMDlOjqtsGvoLl4_qGq-loafgKZ2US7A7M,1214
3
+ lazyopencode/_version.py,sha256=vYqoJTG51NOUmYyL0xt8asRK8vUT4lGAdal_EZ59mvw,704
4
+ lazyopencode/app.py,sha256=L7fpKK8VO7NduLvdgGQlROAfxo7O7D_RpVtxCXgICq8,18137
5
+ lazyopencode/bindings.py,sha256=nIcMU77LxjQQ2YsitSdmcODP9e3UGHqZyc8NCnTFQ4s,1394
6
6
  lazyopencode/themes.py,sha256=9KsyWlk9XeAupAdUTj785riWuDlQMnERjD8DUzogCPc,713
7
7
  lazyopencode/mixins/filtering.py,sha256=m7G5NN-dvphPCnnNc8GyJY3p6ijmFxnr4GrzukYMoII,988
8
- lazyopencode/mixins/help.py,sha256=sZdoa0kmdfGE-81hNQAQKPY-ycH2f-2widAiWvH5iiM,1980
8
+ lazyopencode/mixins/help.py,sha256=4OnRoUlaVPM_81IDyG04YgJquZwv_wGAkmom-5ebyUk,2059
9
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=Tom492ns5ZqGFGURMKNRugqwt43GZrGBuHfa5BUx_sw,2910
11
+ lazyopencode/models/customization.py,sha256=4Vu2lCaRT5icdzx01A83camsdOSVlu0t-q3CF9Tkt2A,4453
12
12
  lazyopencode/services/__init__.py,sha256=aCVZJecnMeupVU-T6EhR34dFu295XwJP9dqZ8MKWOJE,146
13
- lazyopencode/services/discovery.py,sha256=5helVx3gye6axuVClKpRIGMMLOjTA9Je7oSkQDx0aOk,12608
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=B9XZor9VH28P3Fo6sQ2HhT1Tj1eDq7Pcc8i-YpkYoGg,5328
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,18 @@ 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=kapIzxk2F5h6NgoDLYtkbFSTU8le5YtvDQcJ_Mribsw,2200
33
+ lazyopencode/widgets/app_footer.py,sha256=K5qf_KhY6njSh8UXTtn7LahnACzMuNnhRlfzGBu2E7U,2478
26
34
  lazyopencode/widgets/combined_panel.py,sha256=vEopdSAmThctwAlawwY88u5Bmz4XNMnDY9u2YnCLco8,12113
35
+ lazyopencode/widgets/delete_confirm.py,sha256=cUEPj3wIPgk5GnWBsa-Yc-QX2BeS12Z7WRwTh7XYFeQ,3260
27
36
  lazyopencode/widgets/detail_pane.py,sha256=fFTGZhaJXP4PGAUItv4rYMiDkTbdqRXG0NTI6ooS_kc,11296
28
37
  lazyopencode/widgets/filter_input.py,sha256=rJp7Y0NVJSj7c-wIS8ZPXgkxTtYU4Gp-DXJOFYOhV5U,2518
38
+ lazyopencode/widgets/level_selector.py,sha256=rO3Ik87rDLmlsw4eXkUE5ps7rrrv7v_KO1_HwlfMTHA,3733
29
39
  lazyopencode/widgets/status_panel.py,sha256=k4lx4GrHlVUvt6t32_bz2WOWvZdPNawdSMhmXUZYpc8,2127
30
40
  lazyopencode/widgets/type_panel.py,sha256=wg2D5diKvdrwco7pvcUnF9WPOgTveiH1qt0O82gkqa4,19313
31
41
  lazyopencode/widgets/helpers/__init__.py,sha256=-jGrYtTMl9rmX8Q5Vod_oAuYaMl6Yh5JRwv8AFKQcwk,135
32
42
  lazyopencode/widgets/helpers/rendering.py,sha256=lgxzVsnH6fgTZss2x4n8hKYYtDN35YmFMD83impkpH8,576
33
- lazyopencode-0.1.1.dist-info/METADATA,sha256=1J3ghVNB1JGRtuBzIu3IZ94638X2tQi__RnaW8EamrU,3526
34
- lazyopencode-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
- lazyopencode-0.1.1.dist-info/entry_points.txt,sha256=PPVT4NhHce2hFuMj-NQTSN9xQIM2jwb0_ojMEcOpPJ0,60
36
- lazyopencode-0.1.1.dist-info/licenses/LICENSE,sha256=KY9Pw3pDaLTe2eiMvUoj09VHhE3i8N1Le0d01JrG_3Q,1069
37
- lazyopencode-0.1.1.dist-info/RECORD,,
43
+ lazyopencode-0.2.1.dist-info/METADATA,sha256=n_xtbwjR_Xdtg0HovrurySD2iP3x43uQlL8dpM3kPp4,4243
44
+ lazyopencode-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
45
+ lazyopencode-0.2.1.dist-info/entry_points.txt,sha256=PPVT4NhHce2hFuMj-NQTSN9xQIM2jwb0_ojMEcOpPJ0,60
46
+ lazyopencode-0.2.1.dist-info/licenses/LICENSE,sha256=KY9Pw3pDaLTe2eiMvUoj09VHhE3i8N1Le0d01JrG_3Q,1069
47
+ lazyopencode-0.2.1.dist-info/RECORD,,