taskmux 0.2.4__tar.gz → 0.2.5__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.
- {taskmux-0.2.4 → taskmux-0.2.5}/PKG-INFO +107 -9
- {taskmux-0.2.4 → taskmux-0.2.5}/README.md +106 -8
- {taskmux-0.2.4 → taskmux-0.2.5}/pyproject.toml +1 -1
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/tmux_manager.py +79 -6
- {taskmux-0.2.4 → taskmux-0.2.5}/.gitignore +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/LICENSE +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/__init__.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/agent.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/cli.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/config.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/daemon.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/hooks.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/init.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/main.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/models.py +0 -0
- {taskmux-0.2.4 → taskmux-0.2.5}/taskmux/templates/claude.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskmux
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Modern tmux-based task manager for LLM development tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/nc9/taskmux
|
|
6
6
|
Project-URL: Repository, https://github.com/nc9/taskmux
|
|
@@ -104,6 +104,67 @@ command = "docker compose up postgres"
|
|
|
104
104
|
auto_start = false
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
## Full Example
|
|
108
|
+
|
|
109
|
+
A full-stack app with a database, API server, and frontend — using health checks to ensure each service is ready before starting its dependents:
|
|
110
|
+
|
|
111
|
+
```toml
|
|
112
|
+
name = "fullstack-app"
|
|
113
|
+
|
|
114
|
+
[tasks.db]
|
|
115
|
+
command = "docker compose up postgres redis"
|
|
116
|
+
health_check = "pg_isready -h localhost -p 5432"
|
|
117
|
+
health_interval = 3
|
|
118
|
+
|
|
119
|
+
[tasks.migrate]
|
|
120
|
+
command = "python manage.py migrate && echo 'done' && sleep infinity"
|
|
121
|
+
cwd = "apps/api"
|
|
122
|
+
depends_on = ["db"]
|
|
123
|
+
health_check = "test -f .migrate-complete"
|
|
124
|
+
|
|
125
|
+
[tasks.api]
|
|
126
|
+
command = "python manage.py runserver 0.0.0.0:8000"
|
|
127
|
+
cwd = "apps/api"
|
|
128
|
+
depends_on = ["migrate"]
|
|
129
|
+
health_check = "curl -sf http://localhost:8000/health"
|
|
130
|
+
|
|
131
|
+
[tasks.worker]
|
|
132
|
+
command = "celery -A myapp worker -l info"
|
|
133
|
+
cwd = "apps/api"
|
|
134
|
+
depends_on = ["db"]
|
|
135
|
+
|
|
136
|
+
[tasks.web]
|
|
137
|
+
command = "bun dev"
|
|
138
|
+
cwd = "apps/web"
|
|
139
|
+
depends_on = ["api"]
|
|
140
|
+
health_check = "curl -sf http://localhost:3000"
|
|
141
|
+
|
|
142
|
+
[tasks.storybook]
|
|
143
|
+
command = "bun storybook"
|
|
144
|
+
cwd = "apps/web"
|
|
145
|
+
auto_start = false
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
What happens on `taskmux start`:
|
|
149
|
+
|
|
150
|
+
1. **db** starts first (no dependencies)
|
|
151
|
+
2. **migrate** and **worker** wait for db's health check (`pg_isready`) to pass
|
|
152
|
+
3. **api** waits for migrate's health check
|
|
153
|
+
4. **web** waits for api's health check (`curl localhost:8000/health`)
|
|
154
|
+
5. **storybook** is skipped (`auto_start = false`) — start it manually with `taskmux start storybook`
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
taskmux start # Starts everything in dependency order
|
|
158
|
+
taskmux logs # Interleaved logs from all tasks
|
|
159
|
+
taskmux logs -g "ERROR" # Grep all tasks for errors
|
|
160
|
+
taskmux logs api # Logs from just the API
|
|
161
|
+
taskmux logs -f api # Follow API logs live
|
|
162
|
+
taskmux health # Health check table
|
|
163
|
+
taskmux inspect api # JSON state for a single task
|
|
164
|
+
taskmux restart worker # Restart just the worker
|
|
165
|
+
taskmux start storybook # Start a manual task
|
|
166
|
+
```
|
|
167
|
+
|
|
107
168
|
## Commands
|
|
108
169
|
|
|
109
170
|
```bash
|
|
@@ -124,10 +185,13 @@ taskmux remove <task> # Remove task from config
|
|
|
124
185
|
taskmux inspect <task> # JSON task state (pid, command, health)
|
|
125
186
|
|
|
126
187
|
# Logs
|
|
127
|
-
taskmux logs
|
|
128
|
-
taskmux logs
|
|
129
|
-
taskmux logs -
|
|
130
|
-
taskmux logs <task>
|
|
188
|
+
taskmux logs # Interleaved logs from all tasks
|
|
189
|
+
taskmux logs <task> # Show recent logs for a task
|
|
190
|
+
taskmux logs -f # Attach to session (switch windows with tmux keybinds)
|
|
191
|
+
taskmux logs -f <task> # Follow a task's logs live
|
|
192
|
+
taskmux logs -n 200 <task> # Last N lines
|
|
193
|
+
taskmux logs -g "error" # Search all tasks
|
|
194
|
+
taskmux logs <task> -g "error" # Search one task
|
|
131
195
|
taskmux logs <task> -g "error" -C 5 # Grep with context lines
|
|
132
196
|
|
|
133
197
|
# Init
|
|
@@ -161,12 +225,20 @@ after_stop = "echo done"
|
|
|
161
225
|
|
|
162
226
|
[tasks.server]
|
|
163
227
|
command = "python manage.py runserver"
|
|
228
|
+
cwd = "apps/api"
|
|
229
|
+
health_check = "curl -sf http://localhost:8000/health"
|
|
230
|
+
depends_on = ["db"]
|
|
164
231
|
|
|
165
232
|
[tasks.server.hooks]
|
|
166
233
|
before_start = "python manage.py migrate"
|
|
167
234
|
|
|
235
|
+
[tasks.db]
|
|
236
|
+
command = "docker compose up postgres"
|
|
237
|
+
health_check = "pg_isready -h localhost"
|
|
238
|
+
|
|
168
239
|
[tasks.worker]
|
|
169
240
|
command = "celery worker -A myapp"
|
|
241
|
+
depends_on = ["db"]
|
|
170
242
|
|
|
171
243
|
[tasks.tailwind]
|
|
172
244
|
command = "npx tailwindcss -w"
|
|
@@ -185,8 +257,31 @@ auto_start = false
|
|
|
185
257
|
| `hooks.after_stop` | — | Run after stopping tasks |
|
|
186
258
|
| `tasks.<name>.command` | — | Shell command to run |
|
|
187
259
|
| `tasks.<name>.auto_start` | `true` | Start with `taskmux start` |
|
|
260
|
+
| `tasks.<name>.cwd` | — | Working directory for the task |
|
|
261
|
+
| `tasks.<name>.health_check` | — | Shell command to check health (exit 0 = healthy) |
|
|
262
|
+
| `tasks.<name>.health_interval` | `10` | Seconds between health checks |
|
|
263
|
+
| `tasks.<name>.health_timeout` | `5` | Seconds before health check times out |
|
|
264
|
+
| `tasks.<name>.health_retries` | `3` | Consecutive failures before "unhealthy" |
|
|
265
|
+
| `tasks.<name>.depends_on` | `[]` | Task names that must be healthy before this task starts |
|
|
188
266
|
| `tasks.<name>.hooks.*` | — | Per-task lifecycle hooks (same fields as global) |
|
|
189
267
|
|
|
268
|
+
### Dependency Ordering
|
|
269
|
+
|
|
270
|
+
Tasks with `depends_on` are started in topological order. Before starting a task, taskmux waits for each dependency's health check to pass (up to `health_retries * health_interval` seconds). If a dependency never becomes healthy, the dependent task is skipped with a warning.
|
|
271
|
+
|
|
272
|
+
Circular dependencies and references to nonexistent tasks are rejected at config load time.
|
|
273
|
+
|
|
274
|
+
When starting a single task with `taskmux start <task>`, dependencies are not auto-started — you get a warning if they aren't running.
|
|
275
|
+
|
|
276
|
+
### Health Checks
|
|
277
|
+
|
|
278
|
+
If `health_check` is set, taskmux runs it as a shell command. Exit code 0 means healthy. If not set, taskmux falls back to checking if the tmux pane has a running process (not just a shell prompt).
|
|
279
|
+
|
|
280
|
+
Health checks are used by:
|
|
281
|
+
- `taskmux health` — shows a table of all task health
|
|
282
|
+
- `taskmux start` — waits for dependencies to be healthy before starting dependents
|
|
283
|
+
- `taskmux daemon` — continuously monitors and auto-restarts unhealthy tasks
|
|
284
|
+
|
|
190
285
|
### Hook Cascade
|
|
191
286
|
|
|
192
287
|
Hooks fire in this order:
|
|
@@ -212,14 +307,17 @@ Use `--defaults` to skip prompts (CI/automation).
|
|
|
212
307
|
|
|
213
308
|
```json
|
|
214
309
|
{
|
|
215
|
-
"name": "
|
|
216
|
-
"command": "
|
|
310
|
+
"name": "api",
|
|
311
|
+
"command": "python manage.py runserver 0.0.0.0:8000",
|
|
217
312
|
"auto_start": true,
|
|
313
|
+
"cwd": "apps/api",
|
|
314
|
+
"health_check": "curl -sf http://localhost:8000/health",
|
|
315
|
+
"depends_on": ["db"],
|
|
218
316
|
"running": true,
|
|
219
317
|
"healthy": true,
|
|
220
318
|
"pid": "12345",
|
|
221
|
-
"pane_current_command": "
|
|
222
|
-
"pane_current_path": "/home/user/project",
|
|
319
|
+
"pane_current_command": "python",
|
|
320
|
+
"pane_current_path": "/home/user/project/apps/api",
|
|
223
321
|
"window_id": "@1",
|
|
224
322
|
"pane_id": "%1"
|
|
225
323
|
}
|
|
@@ -69,6 +69,67 @@ command = "docker compose up postgres"
|
|
|
69
69
|
auto_start = false
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Full Example
|
|
73
|
+
|
|
74
|
+
A full-stack app with a database, API server, and frontend — using health checks to ensure each service is ready before starting its dependents:
|
|
75
|
+
|
|
76
|
+
```toml
|
|
77
|
+
name = "fullstack-app"
|
|
78
|
+
|
|
79
|
+
[tasks.db]
|
|
80
|
+
command = "docker compose up postgres redis"
|
|
81
|
+
health_check = "pg_isready -h localhost -p 5432"
|
|
82
|
+
health_interval = 3
|
|
83
|
+
|
|
84
|
+
[tasks.migrate]
|
|
85
|
+
command = "python manage.py migrate && echo 'done' && sleep infinity"
|
|
86
|
+
cwd = "apps/api"
|
|
87
|
+
depends_on = ["db"]
|
|
88
|
+
health_check = "test -f .migrate-complete"
|
|
89
|
+
|
|
90
|
+
[tasks.api]
|
|
91
|
+
command = "python manage.py runserver 0.0.0.0:8000"
|
|
92
|
+
cwd = "apps/api"
|
|
93
|
+
depends_on = ["migrate"]
|
|
94
|
+
health_check = "curl -sf http://localhost:8000/health"
|
|
95
|
+
|
|
96
|
+
[tasks.worker]
|
|
97
|
+
command = "celery -A myapp worker -l info"
|
|
98
|
+
cwd = "apps/api"
|
|
99
|
+
depends_on = ["db"]
|
|
100
|
+
|
|
101
|
+
[tasks.web]
|
|
102
|
+
command = "bun dev"
|
|
103
|
+
cwd = "apps/web"
|
|
104
|
+
depends_on = ["api"]
|
|
105
|
+
health_check = "curl -sf http://localhost:3000"
|
|
106
|
+
|
|
107
|
+
[tasks.storybook]
|
|
108
|
+
command = "bun storybook"
|
|
109
|
+
cwd = "apps/web"
|
|
110
|
+
auto_start = false
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
What happens on `taskmux start`:
|
|
114
|
+
|
|
115
|
+
1. **db** starts first (no dependencies)
|
|
116
|
+
2. **migrate** and **worker** wait for db's health check (`pg_isready`) to pass
|
|
117
|
+
3. **api** waits for migrate's health check
|
|
118
|
+
4. **web** waits for api's health check (`curl localhost:8000/health`)
|
|
119
|
+
5. **storybook** is skipped (`auto_start = false`) — start it manually with `taskmux start storybook`
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
taskmux start # Starts everything in dependency order
|
|
123
|
+
taskmux logs # Interleaved logs from all tasks
|
|
124
|
+
taskmux logs -g "ERROR" # Grep all tasks for errors
|
|
125
|
+
taskmux logs api # Logs from just the API
|
|
126
|
+
taskmux logs -f api # Follow API logs live
|
|
127
|
+
taskmux health # Health check table
|
|
128
|
+
taskmux inspect api # JSON state for a single task
|
|
129
|
+
taskmux restart worker # Restart just the worker
|
|
130
|
+
taskmux start storybook # Start a manual task
|
|
131
|
+
```
|
|
132
|
+
|
|
72
133
|
## Commands
|
|
73
134
|
|
|
74
135
|
```bash
|
|
@@ -89,10 +150,13 @@ taskmux remove <task> # Remove task from config
|
|
|
89
150
|
taskmux inspect <task> # JSON task state (pid, command, health)
|
|
90
151
|
|
|
91
152
|
# Logs
|
|
92
|
-
taskmux logs
|
|
93
|
-
taskmux logs
|
|
94
|
-
taskmux logs -
|
|
95
|
-
taskmux logs <task>
|
|
153
|
+
taskmux logs # Interleaved logs from all tasks
|
|
154
|
+
taskmux logs <task> # Show recent logs for a task
|
|
155
|
+
taskmux logs -f # Attach to session (switch windows with tmux keybinds)
|
|
156
|
+
taskmux logs -f <task> # Follow a task's logs live
|
|
157
|
+
taskmux logs -n 200 <task> # Last N lines
|
|
158
|
+
taskmux logs -g "error" # Search all tasks
|
|
159
|
+
taskmux logs <task> -g "error" # Search one task
|
|
96
160
|
taskmux logs <task> -g "error" -C 5 # Grep with context lines
|
|
97
161
|
|
|
98
162
|
# Init
|
|
@@ -126,12 +190,20 @@ after_stop = "echo done"
|
|
|
126
190
|
|
|
127
191
|
[tasks.server]
|
|
128
192
|
command = "python manage.py runserver"
|
|
193
|
+
cwd = "apps/api"
|
|
194
|
+
health_check = "curl -sf http://localhost:8000/health"
|
|
195
|
+
depends_on = ["db"]
|
|
129
196
|
|
|
130
197
|
[tasks.server.hooks]
|
|
131
198
|
before_start = "python manage.py migrate"
|
|
132
199
|
|
|
200
|
+
[tasks.db]
|
|
201
|
+
command = "docker compose up postgres"
|
|
202
|
+
health_check = "pg_isready -h localhost"
|
|
203
|
+
|
|
133
204
|
[tasks.worker]
|
|
134
205
|
command = "celery worker -A myapp"
|
|
206
|
+
depends_on = ["db"]
|
|
135
207
|
|
|
136
208
|
[tasks.tailwind]
|
|
137
209
|
command = "npx tailwindcss -w"
|
|
@@ -150,8 +222,31 @@ auto_start = false
|
|
|
150
222
|
| `hooks.after_stop` | — | Run after stopping tasks |
|
|
151
223
|
| `tasks.<name>.command` | — | Shell command to run |
|
|
152
224
|
| `tasks.<name>.auto_start` | `true` | Start with `taskmux start` |
|
|
225
|
+
| `tasks.<name>.cwd` | — | Working directory for the task |
|
|
226
|
+
| `tasks.<name>.health_check` | — | Shell command to check health (exit 0 = healthy) |
|
|
227
|
+
| `tasks.<name>.health_interval` | `10` | Seconds between health checks |
|
|
228
|
+
| `tasks.<name>.health_timeout` | `5` | Seconds before health check times out |
|
|
229
|
+
| `tasks.<name>.health_retries` | `3` | Consecutive failures before "unhealthy" |
|
|
230
|
+
| `tasks.<name>.depends_on` | `[]` | Task names that must be healthy before this task starts |
|
|
153
231
|
| `tasks.<name>.hooks.*` | — | Per-task lifecycle hooks (same fields as global) |
|
|
154
232
|
|
|
233
|
+
### Dependency Ordering
|
|
234
|
+
|
|
235
|
+
Tasks with `depends_on` are started in topological order. Before starting a task, taskmux waits for each dependency's health check to pass (up to `health_retries * health_interval` seconds). If a dependency never becomes healthy, the dependent task is skipped with a warning.
|
|
236
|
+
|
|
237
|
+
Circular dependencies and references to nonexistent tasks are rejected at config load time.
|
|
238
|
+
|
|
239
|
+
When starting a single task with `taskmux start <task>`, dependencies are not auto-started — you get a warning if they aren't running.
|
|
240
|
+
|
|
241
|
+
### Health Checks
|
|
242
|
+
|
|
243
|
+
If `health_check` is set, taskmux runs it as a shell command. Exit code 0 means healthy. If not set, taskmux falls back to checking if the tmux pane has a running process (not just a shell prompt).
|
|
244
|
+
|
|
245
|
+
Health checks are used by:
|
|
246
|
+
- `taskmux health` — shows a table of all task health
|
|
247
|
+
- `taskmux start` — waits for dependencies to be healthy before starting dependents
|
|
248
|
+
- `taskmux daemon` — continuously monitors and auto-restarts unhealthy tasks
|
|
249
|
+
|
|
155
250
|
### Hook Cascade
|
|
156
251
|
|
|
157
252
|
Hooks fire in this order:
|
|
@@ -177,14 +272,17 @@ Use `--defaults` to skip prompts (CI/automation).
|
|
|
177
272
|
|
|
178
273
|
```json
|
|
179
274
|
{
|
|
180
|
-
"name": "
|
|
181
|
-
"command": "
|
|
275
|
+
"name": "api",
|
|
276
|
+
"command": "python manage.py runserver 0.0.0.0:8000",
|
|
182
277
|
"auto_start": true,
|
|
278
|
+
"cwd": "apps/api",
|
|
279
|
+
"health_check": "curl -sf http://localhost:8000/health",
|
|
280
|
+
"depends_on": ["db"],
|
|
183
281
|
"running": true,
|
|
184
282
|
"healthy": true,
|
|
185
283
|
"pid": "12345",
|
|
186
|
-
"pane_current_command": "
|
|
187
|
-
"pane_current_path": "/home/user/project",
|
|
284
|
+
"pane_current_command": "python",
|
|
285
|
+
"pane_current_path": "/home/user/project/apps/api",
|
|
188
286
|
"window_id": "@1",
|
|
189
287
|
"pane_id": "%1"
|
|
190
288
|
}
|
|
@@ -8,10 +8,27 @@ from collections import deque
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
|
|
10
10
|
import libtmux
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.markup import escape
|
|
11
13
|
|
|
12
14
|
from .hooks import runHook
|
|
13
15
|
from .models import TaskmuxConfig
|
|
14
16
|
|
|
17
|
+
TASK_COLORS = ["cyan", "green", "yellow", "magenta", "blue", "red"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _find_new_lines(current: list[str], prev_tail: list[str]) -> list[str]:
|
|
21
|
+
"""Return lines in current that are new since prev_tail."""
|
|
22
|
+
if not prev_tail:
|
|
23
|
+
return current
|
|
24
|
+
target = prev_tail[-1]
|
|
25
|
+
for i in range(len(current) - 1, -1, -1):
|
|
26
|
+
if current[i] == target:
|
|
27
|
+
ctx = min(len(prev_tail), i + 1)
|
|
28
|
+
if current[i - ctx + 1 : i + 1] == prev_tail[-ctx:]:
|
|
29
|
+
return current[i + 1 :]
|
|
30
|
+
return current # no match, prev scrolled away — return all
|
|
31
|
+
|
|
15
32
|
|
|
16
33
|
class TmuxManager:
|
|
17
34
|
"""Manages tmux sessions and tasks using libtmux API."""
|
|
@@ -433,6 +450,54 @@ class TmuxManager:
|
|
|
433
450
|
info["healthy"] = self.is_task_healthy(task_name)
|
|
434
451
|
return info
|
|
435
452
|
|
|
453
|
+
def _tail_panes(
|
|
454
|
+
self,
|
|
455
|
+
panes: list[tuple[str, libtmux.Pane, str]],
|
|
456
|
+
lines: int = 100,
|
|
457
|
+
grep: str | None = None,
|
|
458
|
+
) -> None:
|
|
459
|
+
"""Poll capture-pane and print new lines with colored task prefixes."""
|
|
460
|
+
console = Console()
|
|
461
|
+
state: dict[str, list[str]] = {}
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
while True:
|
|
465
|
+
for task_name, pane, color in panes:
|
|
466
|
+
output = pane.cmd("capture-pane", "-p", "-S", f"-{lines}").stdout
|
|
467
|
+
while output and not output[-1].strip():
|
|
468
|
+
output.pop()
|
|
469
|
+
|
|
470
|
+
prev = state.get(task_name, [])
|
|
471
|
+
new = _find_new_lines(output, prev)
|
|
472
|
+
|
|
473
|
+
if grep:
|
|
474
|
+
new = [ln for ln in new if grep.lower() in ln.lower()]
|
|
475
|
+
|
|
476
|
+
for line in new:
|
|
477
|
+
prefix = escape(f"[{task_name}]")
|
|
478
|
+
console.print(f"[{color}]{prefix}[/{color}] {escape(line)}")
|
|
479
|
+
|
|
480
|
+
if output:
|
|
481
|
+
state[task_name] = output[-50:]
|
|
482
|
+
|
|
483
|
+
time.sleep(0.5)
|
|
484
|
+
except KeyboardInterrupt:
|
|
485
|
+
console.print("\n[dim]Stopped following logs[/dim]")
|
|
486
|
+
|
|
487
|
+
def _collect_panes(self, task_names: list[str]) -> list[tuple[str, libtmux.Pane, str]]:
|
|
488
|
+
"""Collect (name, pane, color) tuples for running tasks."""
|
|
489
|
+
sess = self._get_session()
|
|
490
|
+
result: list[tuple[str, libtmux.Pane, str]] = []
|
|
491
|
+
for i, name in enumerate(task_names):
|
|
492
|
+
window = sess.windows.get(window_name=name, default=None)
|
|
493
|
+
if not window:
|
|
494
|
+
continue
|
|
495
|
+
pane = window.active_pane
|
|
496
|
+
if pane:
|
|
497
|
+
color = TASK_COLORS[i % len(TASK_COLORS)]
|
|
498
|
+
result.append((name, pane, color))
|
|
499
|
+
return result
|
|
500
|
+
|
|
436
501
|
def show_logs(
|
|
437
502
|
self,
|
|
438
503
|
task_name: str | None,
|
|
@@ -461,8 +526,9 @@ class TmuxManager:
|
|
|
461
526
|
return
|
|
462
527
|
|
|
463
528
|
if follow:
|
|
464
|
-
|
|
465
|
-
|
|
529
|
+
panes = self._collect_panes([task_name])
|
|
530
|
+
if panes:
|
|
531
|
+
self._tail_panes(panes, lines=lines, grep=grep)
|
|
466
532
|
else:
|
|
467
533
|
pane = window.active_pane
|
|
468
534
|
if pane:
|
|
@@ -482,26 +548,33 @@ class TmuxManager:
|
|
|
482
548
|
) -> None:
|
|
483
549
|
"""Show logs from all running tasks."""
|
|
484
550
|
sess = self._get_session()
|
|
551
|
+
console = Console()
|
|
552
|
+
task_names = list(self.config.tasks.keys())
|
|
485
553
|
|
|
486
554
|
if follow:
|
|
487
|
-
|
|
555
|
+
panes = self._collect_panes(task_names)
|
|
556
|
+
if panes:
|
|
557
|
+
self._tail_panes(panes, lines=lines, grep=grep)
|
|
488
558
|
return
|
|
489
559
|
|
|
490
|
-
for task_name in
|
|
560
|
+
for i, task_name in enumerate(task_names):
|
|
491
561
|
window = sess.windows.get(window_name=task_name, default=None)
|
|
492
562
|
if not window:
|
|
493
563
|
continue
|
|
494
564
|
pane = window.active_pane
|
|
495
565
|
if not pane:
|
|
496
566
|
continue
|
|
567
|
+
color = TASK_COLORS[i % len(TASK_COLORS)]
|
|
497
568
|
output = pane.cmd("capture-pane", "-p", "-S", f"-{lines}").stdout
|
|
498
569
|
if grep:
|
|
499
570
|
matching = [line for line in output if grep.lower() in line.lower()]
|
|
500
571
|
for line in matching:
|
|
501
|
-
|
|
572
|
+
prefix = escape(f"[{task_name}]")
|
|
573
|
+
console.print(f"[{color}]{prefix}[/{color}] {escape(line)}")
|
|
502
574
|
else:
|
|
503
575
|
for line in output:
|
|
504
|
-
|
|
576
|
+
prefix = escape(f"[{task_name}]")
|
|
577
|
+
console.print(f"[{color}]{prefix}[/{color}] {escape(line)}")
|
|
505
578
|
|
|
506
579
|
def list_tasks(self) -> None:
|
|
507
580
|
"""List all tasks and their status"""
|
|
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
|