loop-task 1.5.5 → 2.0.0
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.
- package/README.md +141 -21
- package/dist/board/App.js +1 -1
- package/dist/board/components/ActionButtons.js +1 -1
- package/dist/board/components/BoardButton.js +1 -1
- package/dist/board/components/ConfirmModal.js +1 -1
- package/dist/board/components/ContextHelpModal.js +1 -1
- package/dist/board/components/CreateForm.js +1 -1
- package/dist/board/components/CreateProjectModal.js +1 -1
- package/dist/board/components/DeleteProjectConfirm.js +1 -1
- package/dist/board/components/DetailView.js +1 -1
- package/dist/board/components/EditProjectModal.js +1 -1
- package/dist/board/components/FilterBar.js +1 -1
- package/dist/board/components/Footer.js +1 -1
- package/dist/board/components/Header.js +1 -1
- package/dist/board/components/HelpModal.js +1 -1
- package/dist/board/components/Inspector.js +1 -1
- package/dist/board/components/LogModal.js +1 -1
- package/dist/board/components/Navigator.js +1 -1
- package/dist/board/components/ProjectsModal.js +1 -1
- package/dist/board/components/ProjectsPage.js +1 -1
- package/dist/board/components/RunHistory.js +1 -1
- package/dist/board/components/SearchBox.js +1 -1
- package/dist/board/components/SearchSelect.js +1 -1
- package/dist/board/components/TaskBrowser.js +1 -1
- package/dist/board/components/TaskFilterBar.js +1 -1
- package/dist/board/components/TaskForm.js +1 -1
- package/dist/board/components/Timeline.js +1 -1
- package/dist/board/focus-context.js +1 -1
- package/dist/board/format.js +15 -6
- package/dist/board/index.js +1 -1
- package/dist/board/toast.js +1 -1
- package/dist/cli/import-validator.js +151 -0
- package/dist/cli/import-writer.js +77 -0
- package/dist/cli.js +111 -17
- package/dist/config/constants.js +52 -0
- package/dist/daemon/file-watcher.js +171 -0
- package/dist/daemon/http-server.js +605 -0
- package/dist/daemon/index.js +36 -6
- package/dist/daemon/manager.js +94 -13
- package/dist/daemon/projects.js +10 -0
- package/dist/daemon/server.js +21 -0
- package/dist/daemon/task-manager.js +7 -0
- package/dist/esm-loader.js +4 -1
- package/dist/hooks/useLoopFormValidation.js +140 -0
- package/dist/i18n/en.json +237 -17
- package/dist/shared/fs-utils.js +34 -1
- package/dist/tui/app.js +675 -151
- package/dist/tui/commands.js +114 -0
- package/dist/tui/components/ActionButtons.js +49 -0
- package/dist/tui/components/Button.js +18 -0
- package/dist/tui/components/ChainEditor.js +112 -0
- package/dist/tui/components/CommandBuilderField.js +143 -0
- package/dist/tui/components/CommandEditorModal.js +159 -0
- package/dist/tui/components/CommandInput.js +303 -0
- package/dist/tui/components/CommandsBrowserModal.js +77 -0
- package/dist/tui/components/ConfirmModal.js +9 -0
- package/dist/tui/components/ContextHelpModal.js +8 -0
- package/dist/tui/components/CreateForm.js +223 -0
- package/dist/tui/components/DebugPanel.js +8 -0
- package/dist/tui/components/ExportModal.js +37 -0
- package/dist/tui/components/FilterBar.js +13 -0
- package/dist/tui/components/FocusableButton.js +16 -0
- package/dist/tui/components/FocusableInput.js +69 -0
- package/dist/tui/components/FocusableList.js +46 -0
- package/dist/tui/components/FocusableSearchSelect.js +82 -0
- package/dist/tui/components/Footer.js +42 -0
- package/dist/tui/components/Header.js +40 -0
- package/dist/tui/components/HelpGuideModal.js +21 -0
- package/dist/tui/components/HelpModal.js +59 -0
- package/dist/tui/components/InlineCommandEditor.js +143 -0
- package/dist/tui/components/Inspector.js +31 -0
- package/dist/tui/components/LeftPanel.js +33 -0
- package/dist/tui/components/LogModal.js +121 -0
- package/dist/tui/components/Modal.js +11 -0
- package/dist/tui/components/Navigator.js +68 -0
- package/dist/tui/components/PatchEditForm.js +78 -0
- package/dist/tui/components/ProjectForm.js +64 -0
- package/dist/tui/components/ProjectsModal.js +40 -0
- package/dist/tui/components/ProjectsPage.js +99 -0
- package/dist/tui/components/RightPanel.js +34 -0
- package/dist/tui/components/RunHistory.js +108 -0
- package/dist/tui/components/SearchSelect.js +67 -0
- package/dist/tui/components/SelectModal.js +76 -0
- package/dist/tui/components/TabBar.js +20 -0
- package/dist/tui/components/TaskBrowser.js +96 -0
- package/dist/tui/components/TaskFilterBar.js +9 -0
- package/dist/tui/components/TaskForm.js +134 -0
- package/dist/tui/components/TaskPickerModal.js +60 -0
- package/dist/tui/components/Toast.js +37 -0
- package/dist/tui/components/WelcomeScreen.js +70 -0
- package/dist/tui/components/WizardForm.js +178 -0
- package/dist/tui/daemon.js +142 -0
- package/dist/tui/format.js +111 -0
- package/dist/tui/hooks/useBreakpoint.js +7 -0
- package/dist/tui/hooks/useHoverState.js +11 -0
- package/dist/tui/hooks/useLogStream.js +32 -0
- package/dist/tui/hooks/useLoopPolling.js +28 -0
- package/dist/tui/index.js +24 -0
- package/dist/tui/router.js +16 -0
- package/dist/tui/state.js +175 -0
- package/dist/tui/theme.js +96 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui/utils/paste.js +10 -0
- package/dist/tui/utils/validation.js +56 -0
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -12,6 +12,22 @@
|
|
|
12
12
|
[](./LICENSE)
|
|
13
13
|
[](https://nodejs.org)
|
|
14
14
|
|
|
15
|
+
## What's new in v2.0.0
|
|
16
|
+
|
|
17
|
+
- **Ink 7 + React 19 TUI**: Rebuilt the entire terminal UI on [Ink 7](https://github.com/vadimdemedes/ink) (React 19 for CLI). No more Bun dependency — runs on any Node >= 20.
|
|
18
|
+
- **HTTP API + Swagger**: The daemon now exposes a REST + SSE API on `localhost:8845`. Browse it at `http://127.0.0.1:8845/api/docs` (Swagger UI) or fetch the OpenAPI spec at `/api/openapi.json`. Manage loops, tasks, projects, and logs from any HTTP client.
|
|
19
|
+
- **Hot-reloading configs**: Edit `~/.loop-cli/loops.json`, `tasks.json`, or `projects.json` in any editor and the daemon auto-reloads in real-time.
|
|
20
|
+
- **Export/Import**: `loop-task export > backup.json` and `loop-task import backup.json` for backup and sharing.
|
|
21
|
+
- **CLI API**: `loop-task status --json` for scripting and automation. `loop-task api` prints HTTP API endpoints.
|
|
22
|
+
- **Polished loop forms**: Task mode toggle (inline command vs. existing task), per-field validation, smart CWD defaults, clipboard copy, and direct edit navigation — across both board and TUI.
|
|
23
|
+
- **Rich log viewer**: Search/filter log output, fold chain sections, scroll lock, copy to clipboard.
|
|
24
|
+
- **Run history trends**: Sparklines of durations, success/failure streaks, average duration.
|
|
25
|
+
- **Chain visual editor**: Tree view of task chains with success/failure branches.
|
|
26
|
+
- **First-run onboarding**: Welcome screen with example loops when no loops exist.
|
|
27
|
+
- **Daemon push notifications**: Subscribe to real-time events via IPC or SSE (`GET /api/events`).
|
|
28
|
+
- **Docker support**: `docker run -v ~/.loop-cli:/root/.loop-cli loop-task`.
|
|
29
|
+
- **GitHub Actions CI**: Runs typecheck, lint, test, build on ubuntu/macos/windows.
|
|
30
|
+
|
|
15
31
|
</div>
|
|
16
32
|
|
|
17
33
|
## Loop engineering
|
|
@@ -21,7 +37,7 @@
|
|
|
21
37
|
`loop-task` is that heartbeat as a tiny local primitive. Some examples:
|
|
22
38
|
|
|
23
39
|
<div align="center">
|
|
24
|
-
<img src="https://raw.githubusercontent.com/CKGrafico/loop-
|
|
40
|
+
<img src="https://raw.githubusercontent.com/CKGrafico/loop-task/refs/heads/main/demo.gif" alt="loop-task demo" width="700" />
|
|
25
41
|
</div>
|
|
26
42
|
|
|
27
43
|
```bash
|
|
@@ -46,12 +62,13 @@ No cron files to maintain and no daemon to babysit: loops persist across reboots
|
|
|
46
62
|
|
|
47
63
|
```bash
|
|
48
64
|
npm install -g loop-task
|
|
49
|
-
loop-task # open the board
|
|
65
|
+
loop-task # open the board
|
|
50
66
|
loop-task start # start the daemon, restore persisted loops
|
|
51
67
|
loop-task new 30m -- npm test # create a background loop
|
|
52
68
|
loop-task run --now 10s -- echo hi # run a loop in the foreground
|
|
53
69
|
loop-task stop <id> # stop a frozen loop and kill its child process
|
|
54
70
|
loop-task restart # kill daemon + all loops, restart fresh
|
|
71
|
+
loop-task api # show HTTP API endpoints (Swagger, OpenAPI)
|
|
55
72
|
```
|
|
56
73
|
|
|
57
74
|
Or run it directly:
|
|
@@ -63,16 +80,13 @@ npx loop-task new 30m -- npm test
|
|
|
63
80
|
|
|
64
81
|
## Requirements
|
|
65
82
|
|
|
66
|
-
- **Node.js >= 20** - required for all commands
|
|
67
|
-
- **Bun >= 1.2** - required for the interactive board only
|
|
68
|
-
|
|
69
|
-
Install Bun:
|
|
83
|
+
- **Node.js >= 20** - required for all commands including the board
|
|
70
84
|
|
|
71
85
|
```bash
|
|
72
|
-
npm install -g
|
|
86
|
+
npm install -g loop-task
|
|
73
87
|
```
|
|
74
88
|
|
|
75
|
-
`start`, `new`,
|
|
89
|
+
All commands (`start`, `new`, `run`, `board`) work with Node alone.
|
|
76
90
|
|
|
77
91
|
## Concepts
|
|
78
92
|
|
|
@@ -134,13 +148,17 @@ Colors can be a name (`white`, `cyan`, `green`, `yellow`, `orange`, `pink`) or a
|
|
|
134
148
|
|
|
135
149
|
| Command | Description |
|
|
136
150
|
| ------- | ----------- |
|
|
137
|
-
| `loop-task` | Open the interactive board
|
|
151
|
+
| `loop-task` | Open the interactive board |
|
|
138
152
|
| `loop-task start` | Start the background daemon, restore persisted loops |
|
|
139
153
|
| `loop-task new <interval> -- <command>` | Create a background loop (creates an inline task) |
|
|
140
154
|
| `loop-task new <interval> --project <name> -- <command>` | Create a loop assigned to a project |
|
|
141
155
|
| `loop-task run <interval> -- <command>` | Run a loop in the foreground |
|
|
142
156
|
| `loop-task stop <id>` | Stop a loop and interrupt its running child process |
|
|
143
157
|
| `loop-task restart` | Kill the daemon and all running loops, then restart fresh |
|
|
158
|
+
| `loop-task status [--json]` | Show status of all loops (JSON optional for scripting) |
|
|
159
|
+
| `loop-task export [file]` | Export all configs to JSON file (or stdout) |
|
|
160
|
+
| `loop-task import <file>` | Import configs from file (triggers hot-reload) |
|
|
161
|
+
| `loop-task api` | Show HTTP API endpoints (base URL, Swagger UI, OpenAPI spec) |
|
|
144
162
|
| `loop-task project list` | List all projects |
|
|
145
163
|
| `loop-task project new <name> [--color <color>]` | Create a project |
|
|
146
164
|
| `loop-task project rename <id\|name> <new-name>` | Rename a project |
|
|
@@ -209,6 +227,15 @@ esc quit
|
|
|
209
227
|
|
|
210
228
|
Destructive actions (pause, force run, delete) prompt a confirmation before executing.
|
|
211
229
|
|
|
230
|
+
### Copy & paste in the command bar
|
|
231
|
+
|
|
232
|
+
The bottom command bar is a normal terminal input, so use your terminal's own clipboard gestures — they work in every terminal (including the VS Code integrated terminal, where Ctrl+C/V are captured by the editor):
|
|
233
|
+
|
|
234
|
+
- **Paste** with **Ctrl+Shift+V** (Windows/Linux), **Cmd+V** (macOS), or **right-click**. Multi-line pastes collapse to a single line.
|
|
235
|
+
- **Ctrl+U** clears the command bar (select-all + delete).
|
|
236
|
+
- **`c`** copies the selected loop/task's command to the clipboard.
|
|
237
|
+
- To copy arbitrary text, drag-select in the terminal and use its native copy (Ctrl+Shift+C / right-click).
|
|
238
|
+
|
|
212
239
|
### Pause vs Stop
|
|
213
240
|
|
|
214
241
|
- **Pause** (`p`) - temporarily halts the loop. Resuming continues the original schedule (e.g., a loop that runs every 6h at :00 paused at 12:00 and resumed at 14:00 will still fire at 16:00).
|
|
@@ -217,9 +244,9 @@ Destructive actions (pause, force run, delete) prompt a confirmation before exec
|
|
|
217
244
|
## How it works
|
|
218
245
|
|
|
219
246
|
```
|
|
220
|
-
loop-task (board)
|
|
221
|
-
|
|
222
|
-
|
|
247
|
+
loop-task (board) ──IPC──► daemon ──► loop 1 ──► task (command)
|
|
248
|
+
HTTP client (curl) ──HTTP──► ├──► loop 2 ──► task ──► on-success task
|
|
249
|
+
browser (Swagger) ──HTTP──► └──► loop 3 ──► task ──► on-failure task
|
|
223
250
|
```
|
|
224
251
|
|
|
225
252
|
- The **daemon** is a background process that manages all loops and tasks. It starts automatically when you run `loop-task start` or any command that needs it.
|
|
@@ -276,7 +303,7 @@ A four-task chain that finds an issue, marks it in-progress, rewrites it with AI
|
|
|
276
303
|
**Task 1** (primary): Find an issue to refine
|
|
277
304
|
|
|
278
305
|
```bash
|
|
279
|
-
gh issue list --label "to refine" --
|
|
306
|
+
gh issue list --label "to refine" --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
|
|
280
307
|
```
|
|
281
308
|
|
|
282
309
|
stdout: `{"number":123,"title":"Fix login","body":"It doesn't work"}`
|
|
@@ -330,7 +357,7 @@ A four-task chain that finds an issue to implement, marks it in-progress, runs a
|
|
|
330
357
|
**Task 1** (primary): Find an issue to implement (or exit if one is already in progress)
|
|
331
358
|
|
|
332
359
|
```bash
|
|
333
|
-
gh issue list --label "implementing" --
|
|
360
|
+
gh issue list --label "implementing" --json number --jq 'length == 0' | grep -q true && gh issue list --label "to implement" --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
|
|
334
361
|
```
|
|
335
362
|
|
|
336
363
|
stdout: `{"number":456,"title":"Add dark mode toggle","body":"Users want a dark theme"}`
|
|
@@ -367,19 +394,92 @@ interpolated: `git push && git fetch origin && [ "$(git rev-parse HEAD)" = "$(gi
|
|
|
367
394
|
|
|
368
395
|
The `git rev-parse` check ensures local and remote are in sync before closing - if the push failed or remote is ahead, the command fails and the issue stays open.
|
|
369
396
|
|
|
397
|
+
## Docker
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Run the board in a container
|
|
401
|
+
docker run -it -v ~/.loop-cli:/root/.loop-cli loop-task
|
|
402
|
+
|
|
403
|
+
# Check status
|
|
404
|
+
docker run -v ~/.loop-cli:/root/.loop-cli loop-task status --json
|
|
405
|
+
|
|
406
|
+
# Create a background loop
|
|
407
|
+
docker run -v ~/.loop-cli:/root/.loop-cli loop-task new 30m -- npm test
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## HTTP API
|
|
411
|
+
|
|
412
|
+
The daemon exposes a REST + SSE API on `localhost:8845` (configurable via `LOOP_CLI_HTTP_PORT`). It starts automatically with the daemon — no extra flags needed.
|
|
413
|
+
|
|
414
|
+
### Quick reference
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# List all loops
|
|
418
|
+
curl http://127.0.0.1:8845/api/loops
|
|
419
|
+
|
|
420
|
+
# Create a loop
|
|
421
|
+
curl -X POST http://127.0.0.1:8845/api/loops \
|
|
422
|
+
-H "Content-Type: application/json" \
|
|
423
|
+
-d '{"command":"echo hi","intervalHuman":"5m","description":"test"}'
|
|
424
|
+
|
|
425
|
+
# Get a single loop
|
|
426
|
+
curl http://127.0.0.1:8845/api/loops/abc123
|
|
427
|
+
|
|
428
|
+
# Pause / resume / trigger / stop / delete
|
|
429
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/pause
|
|
430
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/resume
|
|
431
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/trigger
|
|
432
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/stop
|
|
433
|
+
curl -X DELETE http://127.0.0.1:8845/api/loops/abc123
|
|
434
|
+
|
|
435
|
+
# Fetch logs (last 50 lines)
|
|
436
|
+
curl http://127.0.0.1:8845/api/loops/abc123/logs?tail=50
|
|
437
|
+
|
|
438
|
+
# Stream logs in real-time (SSE)
|
|
439
|
+
curl -N http://127.0.0.1:8845/api/loops/abc123/logs/stream
|
|
440
|
+
|
|
441
|
+
# Subscribe to daemon events (SSE)
|
|
442
|
+
curl -N http://127.0.0.1:8845/api/events
|
|
443
|
+
|
|
444
|
+
# Tasks and projects
|
|
445
|
+
curl http://127.0.0.1:8845/api/tasks
|
|
446
|
+
curl http://127.0.0.1:8845/api/projects
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Interactive docs
|
|
450
|
+
|
|
451
|
+
- **Swagger UI**: `http://127.0.0.1:8845/api/docs`
|
|
452
|
+
- **OpenAPI 3.0 spec**: `http://127.0.0.1:8845/api/openapi.json`
|
|
453
|
+
|
|
454
|
+
### From the CLI/TUI
|
|
455
|
+
|
|
456
|
+
- `loop-task api` — prints all API endpoints to stdout
|
|
457
|
+
- Board: press **Ctrl+G** or type `api` — shows a toast with API info
|
|
458
|
+
|
|
459
|
+
### Response format
|
|
460
|
+
|
|
461
|
+
All responses use a consistent JSON envelope:
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{"ok": true, "data": ...} // success (200/201)
|
|
465
|
+
{"ok": false, "error": {"message": "..."}} // error (400/404/405/500)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
The API binds to `127.0.0.1` only — it is not reachable from the network. If the port is already in use, the daemon skips the HTTP server and continues with IPC only.
|
|
469
|
+
|
|
370
470
|
## Development
|
|
371
471
|
|
|
372
|
-
Requires [
|
|
472
|
+
Requires [Node.js](https://nodejs.org) >= 20. Uses [pnpm](https://pnpm.io) for package management.
|
|
373
473
|
|
|
374
474
|
```bash
|
|
375
|
-
|
|
475
|
+
pnpm install
|
|
376
476
|
npm run build
|
|
377
477
|
```
|
|
378
478
|
|
|
379
479
|
Run locally:
|
|
380
480
|
|
|
381
481
|
```bash
|
|
382
|
-
|
|
482
|
+
tsx src/cli.ts # board
|
|
383
483
|
node dist/entry.js new --now 30m -- npm test # background loop
|
|
384
484
|
node dist/entry.js run --now --max-runs 1 10s -- echo hello # foreground
|
|
385
485
|
```
|
|
@@ -387,12 +487,32 @@ node dist/entry.js run --now --max-runs 1 10s -- echo hello # foreground
|
|
|
387
487
|
Quality gates:
|
|
388
488
|
|
|
389
489
|
```bash
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
490
|
+
pnpm run typecheck # tsc --noEmit
|
|
491
|
+
pnpm run lint # eslint src/ tests/
|
|
492
|
+
pnpm run test # vitest run
|
|
493
|
+
pnpm run build # tsc -p tsconfig.build.json
|
|
394
494
|
```
|
|
395
495
|
|
|
496
|
+
### Testing the board in a browser (ttyd)
|
|
497
|
+
|
|
498
|
+
> **Agents: Do NOT use ttyd unless the user explicitly asks you to check the CLI in a browser.** It is never the default. Do not start a ttyd server on your own for "manual pass" tasks or visual QA — those are for the human. Reach for ttyd only when the user says "check the board in the browser", "use ttyd", or similar.
|
|
499
|
+
|
|
500
|
+
The board is an interactive TUI, so it needs a real terminal — you can't drive it from a piped/captured shell (and neither can an AI agent). [`ttyd`](https://github.com/tsl0922/ttyd) shares a terminal over HTTP, which makes the board reachable from a browser and scriptable by browser-automation agents — but only when explicitly requested.
|
|
501
|
+
|
|
502
|
+
Install ttyd (see the [ttyd README](https://github.com/tsl0922/ttyd#installation) — e.g. `winget install tsl0922.ttyd`, `brew install ttyd`, or `apt install ttyd`), then serve the board from an interactive terminal:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Point -w at the repo (absolute path) and run the dev board:
|
|
506
|
+
ttyd -W -w "C:\Projects\Personal\loop-cli" -p 7681 pnpm run dev
|
|
507
|
+
|
|
508
|
+
# The built entry works too (after `npm run build`), with the same -w:
|
|
509
|
+
ttyd -W -w "C:\Projects\Personal\loop-cli" -p 7681 node dist/entry.js
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Open `http://localhost:7681` in a browser and use the board as normal. `-W` makes it writable so keystrokes reach the TUI. Handy for demos, for testing on a machine without a good local terminal, and for letting an AI agent drive the board (navigate, send keys, screenshot; ttyd renders via xterm.js on a `<canvas>`, so read state from screenshots, not page text).
|
|
513
|
+
|
|
514
|
+
> **Windows note:** always pass `-w "<absolute repo path>"`. Without it, ttyd gives the spawned command no valid working directory and it fails with `CreateProcessW failed with error 267` — for *every* command (`pnpm`, `npx`, `node` all fail the same way; it is not a `.cmd`-shim issue). On macOS/Linux `-w` is optional but harmless. Start ttyd from a real interactive terminal; a detached/console-less launch can crash its ConPTY on Windows.
|
|
515
|
+
|
|
396
516
|
## License
|
|
397
517
|
|
|
398
518
|
MIT
|
package/dist/board/App.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { applyLoopFilters, cycleSortMode, cycleStatusFilter, defaultFilters, } from "./state.js";
|
|
4
4
|
import { ToastStack, useToasts } from "./toast.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
4
|
import { useHoverState } from "../hooks/useHoverState.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
4
|
export function ContextHelpModal(props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from "react";
|
|
3
3
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef } from "react";
|
|
3
3
|
import { useTabNav } from "../hooks/useTabNav.js";
|
|
4
4
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
4
|
import { commandLine, describeLoop, statusColor, statusLabel, timingLabel } from "../format.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef } from "react";
|
|
3
3
|
import { useTabNav } from "../hooks/useTabNav.js";
|
|
4
4
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { t } from "../../i18n/index.js";
|
|
3
3
|
import { useHoverState } from "../hooks/useHoverState.js";
|
|
4
4
|
import { HOVER_BG } from "../../config/constants.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { t } from "../../i18n/index.js";
|
|
3
3
|
import { ENTITY_COLORS } from "../../config/constants.js";
|
|
4
4
|
export function Footer(props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { HEADER_COMPACT_WIDTH, ENTITY_COLORS } from "../../config/constants.js";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { t } from "../../i18n/index.js";
|
|
4
4
|
export function HelpModal(props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { t } from "../../i18n/index.js";
|
|
3
3
|
import { commandLine, describeLoop, statusColor, statusLabel, timeAgo } from "../format.js";
|
|
4
4
|
export function Inspector(props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from "react";
|
|
3
3
|
import { useKeyboard } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useMemo } from "react";
|
|
3
3
|
import { useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState, useMemo, useEffect } from "react";
|
|
3
3
|
import { useKeyboard } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { useTerminalDimensions } from "@opentui/react";
|
|
4
4
|
import { t } from "../../i18n/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { SearchBox } from "./SearchBox.js";
|
|
3
3
|
export function TaskFilterBar(props) {
|
|
4
4
|
const { query, searchActive, focusedPanel, onQueryChange, onSearchActivate, onSearchDismiss } = props;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from "react";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { t } from "../../i18n/index.js";
|
|
3
3
|
export function Timeline(props) {
|
|
4
4
|
return (_jsx("scrollbox", { title: t("board.timelineTitle"), border: true, style: { flexGrow: 1, backgroundColor: "#0b0b0b" }, stickyScroll: true, stickyStart: "bottom", children: props.logLines.length === 0 ? (_jsx("text", { fg: "#9ca3af", children: t("board.timelineEmpty") })) : (props.logLines.map((line, index) => _jsx("text", { children: line }, index))) }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useState, useCallback, useRef, useEffect } from "react";
|
|
3
3
|
import { useKeyboard } from "@opentui/react";
|
|
4
4
|
const FocusContext = createContext(null);
|
package/dist/board/format.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { t } from "../i18n/index.js";
|
|
2
|
+
export function unescapeCommand(str) {
|
|
3
|
+
return str
|
|
4
|
+
.replace(/\\\\/g, "\x00")
|
|
5
|
+
.replace(/\\"/g, '"')
|
|
6
|
+
.replace(/\x00/g, "\\");
|
|
7
|
+
}
|
|
2
8
|
export function quoteArg(arg) {
|
|
3
9
|
return /[\s"]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
|
|
4
10
|
}
|
|
5
11
|
export function commandLine(command, args) {
|
|
6
|
-
return [command, ...args.map(quoteArg)].join(" ").trim();
|
|
12
|
+
return [unescapeCommand(command), ...args.map((a) => quoteArg(unescapeCommand(a)))].join(" ").trim();
|
|
7
13
|
}
|
|
8
14
|
export function formatCmd(command, args, max = 24) {
|
|
9
15
|
const full = commandLine(command, args);
|
|
@@ -51,7 +57,7 @@ export function timeUntil(iso) {
|
|
|
51
57
|
}
|
|
52
58
|
const STATUS_COLORS = {
|
|
53
59
|
running: "#4ade80",
|
|
54
|
-
waiting: "#
|
|
60
|
+
waiting: "#6b7280",
|
|
55
61
|
paused: "#facc15",
|
|
56
62
|
idle: "#fb923c",
|
|
57
63
|
stopped: "#f87171",
|
|
@@ -91,12 +97,15 @@ export function formatRunTime(iso) {
|
|
|
91
97
|
const d = new Date(iso);
|
|
92
98
|
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
93
99
|
}
|
|
100
|
+
export function formatDate(iso) {
|
|
101
|
+
const d = new Date(iso);
|
|
102
|
+
const date = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
103
|
+
const time = d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
104
|
+
return `${date} ${time}`;
|
|
105
|
+
}
|
|
94
106
|
export function sinceLabel(loop) {
|
|
95
107
|
const ts = loop.sessionStartedAt ?? loop.createdAt;
|
|
96
108
|
if (!ts)
|
|
97
109
|
return t("format.dash");
|
|
98
|
-
|
|
99
|
-
const date = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
100
|
-
const time = d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
101
|
-
return `${date} ${time}`;
|
|
110
|
+
return formatDate(ts);
|
|
102
111
|
}
|
package/dist/board/index.js
CHANGED
package/dist/board/toast.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useRef, useState } from "react";
|
|
3
3
|
import { TOAST_MAX, TOAST_TIMEOUT_MS } from "../config/constants.js";
|
|
4
4
|
export function useToasts() {
|