asher-cli 0.0.1__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.
@@ -0,0 +1,448 @@
1
+ ---
2
+ name: textual
3
+ description: Expert guidance for building TUI (Text User Interface) applications with the Textual framework. Invoke when user asks about Textual development, TUI apps, widgets, screens, CSS styling, reactive programming, or testing Textual applications.
4
+ source: https://github.com/KyleKing/vcr-tui/tree/main/.claude/skills/textual
5
+ ---
6
+
7
+ # Textual - Python TUI Framework Expert
8
+
9
+ You are an expert in building Text User Interface (TUI) applications using **Textual**, a modern Python framework for creating sophisticated terminal applications. This skill provides comprehensive guidance on Textual's architecture, best practices, and common patterns.
10
+
11
+ ## What is Textual?
12
+
13
+ Textual is a TUI framework by Textualize.io that enables developers to build:
14
+
15
+ - Beautiful, responsive terminal applications
16
+ - Rich, interactive command-line tools
17
+ - Cross-platform TUIs with modern UX patterns
18
+ - Applications with CSS-like styling and reactive programming
19
+
20
+ ## When to Use This Skill
21
+
22
+ Invoke this skill when the user:
23
+
24
+ - Wants to build or modify a TUI application
25
+ - Asks about Textual framework features
26
+ - Needs help with widgets, screens, or layouts
27
+ - Has questions about CSS styling in Textual
28
+ - Wants to implement reactive programming patterns
29
+ - Needs testing guidance for Textual apps
30
+ - Encounters errors or issues with Textual code
31
+ - Asks about TUI design patterns or best practices
32
+
33
+ ## Core Concepts
34
+
35
+ ### Application Architecture
36
+
37
+ Textual applications follow an **event-driven architecture**:
38
+
39
+ - The `App` class is the entry point and foundation
40
+ - **Screens** contain widgets and occupy the full terminal
41
+ - **Widgets** are reusable UI components managing rectangular regions
42
+ - **Messages** enable communication between components
43
+ - **CSS (TCSS)** provides styling separate from logic
44
+
45
+ ### Key Components
46
+
47
+ **App Class:**
48
+ - Entry point via `app.run()`
49
+ - Manages screens, modes, and global state
50
+ - Handles key bindings and actions
51
+ - Configures CSS via `CSS_PATH` or inline `CSS`
52
+
53
+ **Screens:**
54
+ - Full-terminal containers for widgets
55
+ - Support push/pop navigation stack
56
+ - Can be modal for dialogs
57
+ - Define their own key bindings and CSS
58
+
59
+ **Widgets:**
60
+ - Rectangular UI components
61
+ - Support composition via `compose()`
62
+ - Handle events via `on_*` methods
63
+ - Can be focused and styled with CSS
64
+
65
+ ### Reactive Programming
66
+
67
+ Textual's reactive system automatically updates the UI when data changes:
68
+
69
+ ```python
70
+ from textual.reactive import reactive
71
+
72
+ class Counter(Widget):
73
+ count = reactive(0) # Auto-refreshes on change
74
+
75
+ def render(self) -> str:
76
+ return f"Count: {self.count}"
77
+ ```
78
+
79
+ Features:
80
+ - **Validation**: `validate_<attr>()` methods constrain values
81
+ - **Watchers**: `watch_<attr>()` methods react to changes
82
+ - **Computed properties**: `compute_<attr>()` for derived values
83
+ - **Recompose**: Rebuild widget tree when data changes
84
+
85
+ ### CSS Styling (TCSS)
86
+
87
+ Textual uses CSS-like syntax for styling:
88
+
89
+ ```css
90
+ Button {
91
+ background: $primary;
92
+ margin: 1;
93
+ }
94
+
95
+ #submit-button {
96
+ background: $success;
97
+ }
98
+
99
+ .danger {
100
+ background: $error;
101
+ }
102
+ ```
103
+
104
+ Benefits:
105
+ - Separation of concerns (style vs logic)
106
+ - Live reload during development
107
+ - Theme system with semantic colors
108
+ - Responsive layout with FR units
109
+
110
+ ## Common Patterns
111
+
112
+ ### Basic App Template
113
+
114
+ ```python
115
+ from textual.app import App, ComposeResult
116
+ from textual.widgets import Header, Footer, Static
117
+
118
+ class MyApp(App):
119
+ CSS_PATH = "app.tcss"
120
+
121
+ def compose(self) -> ComposeResult:
122
+ yield Header()
123
+ yield Static("Hello, Textual!")
124
+ yield Footer()
125
+
126
+ def on_mount(self) -> None:
127
+ """Called after app starts."""
128
+ pass
129
+
130
+ if __name__ == "__main__":
131
+ MyApp().run()
132
+ ```
133
+
134
+ ### Widget Communication
135
+
136
+ Follow **"Attributes down, messages up"**:
137
+
138
+ ```python
139
+ # Parent sets child attributes (down)
140
+ child.value = 10
141
+
142
+ # Child posts messages to parent (up)
143
+ class ChildWidget(Widget):
144
+ class Updated(Message):
145
+ def __init__(self, value: int) -> None:
146
+ super().__init__()
147
+ self.value = value
148
+
149
+ def update_value(self) -> None:
150
+ self.post_message(self.Updated(self.value))
151
+
152
+ # Parent handles child messages
153
+ class ParentWidget(Widget):
154
+ def on_child_widget_updated(self, message: ChildWidget.Updated) -> None:
155
+ self.log(f"Child updated: {message.value}")
156
+ ```
157
+
158
+ ### Testing Pattern
159
+
160
+ ```python
161
+ import pytest
162
+ from my_app import MyApp
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_button_click():
166
+ app = MyApp()
167
+ async with app.run_test() as pilot:
168
+ # Simulate user interaction
169
+ await pilot.click("#submit-button")
170
+
171
+ # CRITICAL: Wait for message processing
172
+ await pilot.pause()
173
+
174
+ # Assert state changed
175
+ result = app.query_one("#status")
176
+ assert "Success" in str(result.renderable)
177
+ ```
178
+
179
+ ## Best Practices
180
+
181
+ ### Design Process
182
+
183
+ 1. **Sketch First**: Draw UI layout on paper before coding
184
+ 2. **Work Outside-In**: Implement fixed elements (header/footer) first, then flexible content
185
+ 3. **Use Docking**: Fix elements with `dock: top/bottom/left/right`
186
+ 4. **FR Units**: Use `1fr` for flexible sizing that fills available space
187
+ 5. **Container Widgets**: Leverage `Vertical`, `Horizontal`, `Grid` for layouts
188
+
189
+ ### Code Organization
190
+
191
+ **Prefer composition over inheritance:**
192
+
193
+ ```python
194
+ # Good: Compose from smaller widgets
195
+ class UserCard(Widget):
196
+ def compose(self) -> ComposeResult:
197
+ with Vertical():
198
+ yield Avatar()
199
+ yield UserName()
200
+ yield UserEmail()
201
+ ```
202
+
203
+ **Separate concerns:**
204
+
205
+ ```python
206
+ # UI in widgets/
207
+ class UserPanel(Widget):
208
+ def __init__(self) -> None:
209
+ super().__init__()
210
+ self.service = UserService() # Business logic
211
+
212
+ # Business logic in business_logic/
213
+ class UserService:
214
+ async def fetch_user(self, user_id: int) -> User:
215
+ # API calls, data processing
216
+ pass
217
+ ```
218
+
219
+ **External CSS for apps:**
220
+
221
+ ```python
222
+ class MyApp(App):
223
+ CSS_PATH = "app.tcss" # Enables live reload
224
+ ```
225
+
226
+ ### Performance
227
+
228
+ 1. **Target 60fps** for smooth terminal rendering
229
+ 2. **Use `Static` widget** for cached rendering
230
+ 3. **Cache expensive operations** with `@lru_cache`
231
+ 4. **Use immutable objects** for data structures
232
+ 5. **Workers for async operations** to avoid blocking UI
233
+
234
+ ### Accessibility
235
+
236
+ - Full keyboard navigation support
237
+ - Set `can_focus = True` on interactive widgets
238
+ - Provide meaningful key bindings
239
+ - Use semantic color variables (`$primary`, `$error`)
240
+ - Test with different terminal sizes
241
+
242
+ ## Common Errors & Solutions
243
+
244
+ ### 1. Forgetting async/await
245
+
246
+ ```python
247
+ # WRONG
248
+ def on_button_pressed(self):
249
+ self.mount(Widget())
250
+
251
+ # RIGHT
252
+ async def on_button_pressed(self):
253
+ await self.mount(Widget())
254
+ ```
255
+
256
+ ### 2. Missing pilot.pause() in tests
257
+
258
+ ```python
259
+ # WRONG - race condition
260
+ async def test_feature():
261
+ await pilot.click("#button")
262
+ assert app.query_one("#status").text == "Done"
263
+
264
+ # RIGHT
265
+ async def test_feature():
266
+ await pilot.click("#button")
267
+ await pilot.pause() # Wait for processing
268
+ assert app.query_one("#status").text == "Done"
269
+ ```
270
+
271
+ ### 3. Modifying reactives in __init__
272
+
273
+ ```python
274
+ # WRONG - triggers watchers too early
275
+ def __init__(self):
276
+ super().__init__()
277
+ self.count = 10
278
+
279
+ # RIGHT - use set_reactive or on_mount
280
+ def __init__(self):
281
+ super().__init__()
282
+ self.set_reactive(MyWidget.count, 10)
283
+ ```
284
+
285
+ ### 4. Blocking the event loop
286
+
287
+ ```python
288
+ # WRONG
289
+ def on_button_pressed(self):
290
+ response = requests.get("https://api.example.com") # Blocks UI!
291
+
292
+ # RIGHT - use workers
293
+ from textual.worker import work
294
+
295
+ @work(exclusive=True)
296
+ async def on_button_pressed(self):
297
+ response = await httpx.get("https://api.example.com")
298
+ ```
299
+
300
+ ## Development Tools
301
+
302
+ ### Development Console
303
+
304
+ Terminal 1:
305
+ ```bash
306
+ textual console
307
+ ```
308
+
309
+ Terminal 2:
310
+ ```bash
311
+ textual run --dev my_app.py
312
+ ```
313
+
314
+ In code:
315
+ ```python
316
+ from textual import log
317
+ log("Debug message", locals())
318
+ ```
319
+
320
+ ### Screenshots & Live Editing
321
+
322
+ ```bash
323
+ # Screenshot after 5 seconds
324
+ textual run --screenshot 5 my_app.py
325
+
326
+ # Dev mode with live CSS reload
327
+ textual run --dev my_app.py
328
+ ```
329
+
330
+ ## Project Structure
331
+
332
+ **Medium/Large Apps:**
333
+
334
+ ```
335
+ project/
336
+ ├── src/
337
+ │ ├── app.py # Main App class
338
+ │ ├── screens/
339
+ │ │ ├── main_screen.py
340
+ │ │ └── settings_screen.py
341
+ │ ├── widgets/
342
+ │ │ ├── status_bar.py
343
+ │ │ └── data_grid.py
344
+ │ └── business_logic/
345
+ │ ├── models.py
346
+ │ └── services.py
347
+ ├── static/
348
+ │ └── app.tcss # External CSS
349
+ ├── tests/
350
+ │ ├── test_app.py
351
+ │ └── test_widgets/
352
+ └── pyproject.toml
353
+ ```
354
+
355
+ ## Instructions for Assistance
356
+
357
+ When helping users with Textual:
358
+
359
+ 1. **Assess Context**: Understand their app structure and goals
360
+ 2. **Check Basics**: Verify imports, async/await, and lifecycle methods
361
+ 3. **Provide Examples**: Show concrete, runnable code
362
+ 4. **Explain Patterns**: Describe why a pattern is recommended
363
+ 5. **Test Guidance**: Include testing code when implementing features
364
+ 6. **Debug Support**: Use console logging and visual debugging tips
365
+ 7. **Best Practices**: Suggest improvements for maintainability
366
+
367
+ Always consider:
368
+ - App complexity (simple vs multi-screen)
369
+ - State management needs (local vs global)
370
+ - Performance requirements
371
+ - Testing strategy
372
+ - Code organization and maintainability
373
+
374
+ ## Additional Resources
375
+
376
+ For detailed reference information:
377
+
378
+ - **[quick-reference.md](quick-reference.md)**: Concise templates, patterns, and cheat sheets
379
+ - **[guide.md](guide.md)**: Comprehensive architecture, design principles, and best practices
380
+ - **Official Documentation**: https://textual.textualize.io
381
+
382
+ ## Quick Reference Highlights
383
+
384
+ ### Useful Built-in Widgets
385
+
386
+ **Input & Selection:**
387
+ - `Button`, `Checkbox`, `Input`, `RadioButton`, `Select`, `Switch`, `TextArea`
388
+
389
+ **Display:**
390
+ - `Label`, `Static`, `Pretty`, `Markdown`, `MarkdownViewer`
391
+
392
+ **Data:**
393
+ - `DataTable`, `ListView`, `Tree`, `DirectoryTree`
394
+
395
+ **Containers:**
396
+ - `Header`, `Footer`, `Tabs`, `TabbedContent`, `Vertical`, `Horizontal`, `Grid`
397
+
398
+ ### Key Lifecycle Methods
399
+
400
+ ```python
401
+ def __init__(self) -> None:
402
+ """Widget created - don't modify reactives here."""
403
+ super().__init__()
404
+
405
+ def compose(self) -> ComposeResult:
406
+ """Build child widgets."""
407
+ yield ChildWidget()
408
+
409
+ def on_mount(self) -> None:
410
+ """After mounted - safe to modify reactives."""
411
+ self.set_interval(1, self.update)
412
+
413
+ def on_unmount(self) -> None:
414
+ """Before removal - cleanup resources."""
415
+ pass
416
+ ```
417
+
418
+ ### Common CSS Patterns
419
+
420
+ ```css
421
+ /* Docking */
422
+ #header { dock: top; height: 3; }
423
+ #sidebar { dock: left; width: 30; }
424
+
425
+ /* Flexible sizing */
426
+ #content { width: 1fr; height: 1fr; }
427
+
428
+ /* Grid layout */
429
+ #container {
430
+ layout: grid;
431
+ grid-size: 3 2;
432
+ grid-columns: 1fr 2fr 1fr;
433
+ }
434
+
435
+ /* Theme colors */
436
+ Button {
437
+ background: $primary;
438
+ color: $text;
439
+ }
440
+
441
+ Button:hover {
442
+ background: $primary-lighten-1;
443
+ }
444
+ ```
445
+
446
+ ## Summary
447
+
448
+ This skill provides expert-level guidance for building Textual applications. Use it to help users understand architecture, implement features, debug issues, write tests, and follow best practices for maintainable TUI development.
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ echo "► ruff check"
5
+ uv run ruff check .
6
+
7
+ echo "► ruff format"
8
+ uv run ruff format --check .
9
+
10
+ echo "► mypy"
11
+ uv run mypy asher/ --ignore-missing-imports
12
+
13
+ echo "► pytest"
14
+ uv run pytest tests/ -v --tb=short
15
+
16
+ echo "✓ all checks passed"
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ lint:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v3
17
+ - run: uv sync --dev
18
+ - run: uv run ruff check .
19
+ - run: uv run ruff format --check .
20
+ - run: uv run mypy asher/ --ignore-missing-imports
21
+
22
+ test:
23
+ needs: lint
24
+ strategy:
25
+ matrix:
26
+ python-version: ["3.10", "3.11", "3.12"]
27
+ os: [ubuntu-latest, windows-latest, macos-latest]
28
+ runs-on: ${{ matrix.os }}
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: astral-sh/setup-uv@v3
32
+ - run: uv sync --dev
33
+ - run: uv run pytest tests/ -v --tb=short
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "release/*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v3
18
+ - run: uv build
19
+ - uses: actions/upload-artifact@v4
20
+ with:
21
+ name: dist
22
+ path: dist/
23
+
24
+ publish:
25
+ name: Publish to PyPI
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write # required for OIDC trusted publisher
31
+ steps:
32
+ - uses: actions/download-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+ - uses: pypa/gh-action-pypi-publish@release/v1