loop-task 1.5.5 → 2.0.1
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 +127 -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
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
`loop-task` is a cross-platform CLI that runs shell commands at human-readable intervals. Create loops in the background, manage them from an interactive TUI board, or run them in the foreground. It is the **heartbeat** primitive for [loop engineering](#loop-engineering): instead of running a task by hand every time, you schedule it once and let it run.
|
|
9
9
|
|
|
10
|
+
**[loop.ckgrafico.com](https://loop.ckgrafico.com)**
|
|
11
|
+
|
|
10
12
|
[](https://www.npmjs.com/package/loop-task)
|
|
11
13
|
[](https://www.npmjs.com/package/loop-task)
|
|
12
14
|
[](./LICENSE)
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
`loop-task` is that heartbeat as a tiny local primitive. Some examples:
|
|
22
24
|
|
|
23
25
|
<div align="center">
|
|
24
|
-
<img src="https://raw.githubusercontent.com/CKGrafico/loop-
|
|
26
|
+
<img src="https://raw.githubusercontent.com/CKGrafico/loop-task/refs/heads/main/demo.gif" alt="loop-task demo" width="700" />
|
|
25
27
|
</div>
|
|
26
28
|
|
|
27
29
|
```bash
|
|
@@ -46,12 +48,13 @@ No cron files to maintain and no daemon to babysit: loops persist across reboots
|
|
|
46
48
|
|
|
47
49
|
```bash
|
|
48
50
|
npm install -g loop-task
|
|
49
|
-
loop-task # open the board
|
|
51
|
+
loop-task # open the board
|
|
50
52
|
loop-task start # start the daemon, restore persisted loops
|
|
51
53
|
loop-task new 30m -- npm test # create a background loop
|
|
52
54
|
loop-task run --now 10s -- echo hi # run a loop in the foreground
|
|
53
55
|
loop-task stop <id> # stop a frozen loop and kill its child process
|
|
54
56
|
loop-task restart # kill daemon + all loops, restart fresh
|
|
57
|
+
loop-task api # show HTTP API endpoints (Swagger, OpenAPI)
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
Or run it directly:
|
|
@@ -63,16 +66,13 @@ npx loop-task new 30m -- npm test
|
|
|
63
66
|
|
|
64
67
|
## Requirements
|
|
65
68
|
|
|
66
|
-
- **Node.js >= 20** - required for all commands
|
|
67
|
-
- **Bun >= 1.2** - required for the interactive board only
|
|
68
|
-
|
|
69
|
-
Install Bun:
|
|
69
|
+
- **Node.js >= 20** - required for all commands including the board
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npm install -g
|
|
72
|
+
npm install -g loop-task
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
`start`, `new`,
|
|
75
|
+
All commands (`start`, `new`, `run`, `board`) work with Node alone.
|
|
76
76
|
|
|
77
77
|
## Concepts
|
|
78
78
|
|
|
@@ -134,13 +134,17 @@ Colors can be a name (`white`, `cyan`, `green`, `yellow`, `orange`, `pink`) or a
|
|
|
134
134
|
|
|
135
135
|
| Command | Description |
|
|
136
136
|
| ------- | ----------- |
|
|
137
|
-
| `loop-task` | Open the interactive board
|
|
137
|
+
| `loop-task` | Open the interactive board |
|
|
138
138
|
| `loop-task start` | Start the background daemon, restore persisted loops |
|
|
139
139
|
| `loop-task new <interval> -- <command>` | Create a background loop (creates an inline task) |
|
|
140
140
|
| `loop-task new <interval> --project <name> -- <command>` | Create a loop assigned to a project |
|
|
141
141
|
| `loop-task run <interval> -- <command>` | Run a loop in the foreground |
|
|
142
142
|
| `loop-task stop <id>` | Stop a loop and interrupt its running child process |
|
|
143
143
|
| `loop-task restart` | Kill the daemon and all running loops, then restart fresh |
|
|
144
|
+
| `loop-task status [--json]` | Show status of all loops (JSON optional for scripting) |
|
|
145
|
+
| `loop-task export [file]` | Export all configs to JSON file (or stdout) |
|
|
146
|
+
| `loop-task import <file>` | Import configs from file (triggers hot-reload) |
|
|
147
|
+
| `loop-task api` | Show HTTP API endpoints (base URL, Swagger UI, OpenAPI spec) |
|
|
144
148
|
| `loop-task project list` | List all projects |
|
|
145
149
|
| `loop-task project new <name> [--color <color>]` | Create a project |
|
|
146
150
|
| `loop-task project rename <id\|name> <new-name>` | Rename a project |
|
|
@@ -209,6 +213,15 @@ esc quit
|
|
|
209
213
|
|
|
210
214
|
Destructive actions (pause, force run, delete) prompt a confirmation before executing.
|
|
211
215
|
|
|
216
|
+
### Copy & paste in the command bar
|
|
217
|
+
|
|
218
|
+
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):
|
|
219
|
+
|
|
220
|
+
- **Paste** with **Ctrl+Shift+V** (Windows/Linux), **Cmd+V** (macOS), or **right-click**. Multi-line pastes collapse to a single line.
|
|
221
|
+
- **Ctrl+U** clears the command bar (select-all + delete).
|
|
222
|
+
- **`c`** copies the selected loop/task's command to the clipboard.
|
|
223
|
+
- To copy arbitrary text, drag-select in the terminal and use its native copy (Ctrl+Shift+C / right-click).
|
|
224
|
+
|
|
212
225
|
### Pause vs Stop
|
|
213
226
|
|
|
214
227
|
- **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 +230,9 @@ Destructive actions (pause, force run, delete) prompt a confirmation before exec
|
|
|
217
230
|
## How it works
|
|
218
231
|
|
|
219
232
|
```
|
|
220
|
-
loop-task (board)
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
loop-task (board) ──IPC──► daemon ──► loop 1 ──► task (command)
|
|
234
|
+
HTTP client (curl) ──HTTP──► ├──► loop 2 ──► task ──► on-success task
|
|
235
|
+
browser (Swagger) ──HTTP──► └──► loop 3 ──► task ──► on-failure task
|
|
223
236
|
```
|
|
224
237
|
|
|
225
238
|
- 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 +289,7 @@ A four-task chain that finds an issue, marks it in-progress, rewrites it with AI
|
|
|
276
289
|
**Task 1** (primary): Find an issue to refine
|
|
277
290
|
|
|
278
291
|
```bash
|
|
279
|
-
gh issue list --label "to refine" --
|
|
292
|
+
gh issue list --label "to refine" --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
|
|
280
293
|
```
|
|
281
294
|
|
|
282
295
|
stdout: `{"number":123,"title":"Fix login","body":"It doesn't work"}`
|
|
@@ -330,7 +343,7 @@ A four-task chain that finds an issue to implement, marks it in-progress, runs a
|
|
|
330
343
|
**Task 1** (primary): Find an issue to implement (or exit if one is already in progress)
|
|
331
344
|
|
|
332
345
|
```bash
|
|
333
|
-
gh issue list --label "implementing" --
|
|
346
|
+
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
347
|
```
|
|
335
348
|
|
|
336
349
|
stdout: `{"number":456,"title":"Add dark mode toggle","body":"Users want a dark theme"}`
|
|
@@ -367,19 +380,92 @@ interpolated: `git push && git fetch origin && [ "$(git rev-parse HEAD)" = "$(gi
|
|
|
367
380
|
|
|
368
381
|
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
382
|
|
|
383
|
+
## Docker
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# Run the board in a container
|
|
387
|
+
docker run -it -v ~/.loop-cli:/root/.loop-cli loop-task
|
|
388
|
+
|
|
389
|
+
# Check status
|
|
390
|
+
docker run -v ~/.loop-cli:/root/.loop-cli loop-task status --json
|
|
391
|
+
|
|
392
|
+
# Create a background loop
|
|
393
|
+
docker run -v ~/.loop-cli:/root/.loop-cli loop-task new 30m -- npm test
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## HTTP API
|
|
397
|
+
|
|
398
|
+
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.
|
|
399
|
+
|
|
400
|
+
### Quick reference
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# List all loops
|
|
404
|
+
curl http://127.0.0.1:8845/api/loops
|
|
405
|
+
|
|
406
|
+
# Create a loop
|
|
407
|
+
curl -X POST http://127.0.0.1:8845/api/loops \
|
|
408
|
+
-H "Content-Type: application/json" \
|
|
409
|
+
-d '{"command":"echo hi","intervalHuman":"5m","description":"test"}'
|
|
410
|
+
|
|
411
|
+
# Get a single loop
|
|
412
|
+
curl http://127.0.0.1:8845/api/loops/abc123
|
|
413
|
+
|
|
414
|
+
# Pause / resume / trigger / stop / delete
|
|
415
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/pause
|
|
416
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/resume
|
|
417
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/trigger
|
|
418
|
+
curl -X POST http://127.0.0.1:8845/api/loops/abc123/stop
|
|
419
|
+
curl -X DELETE http://127.0.0.1:8845/api/loops/abc123
|
|
420
|
+
|
|
421
|
+
# Fetch logs (last 50 lines)
|
|
422
|
+
curl http://127.0.0.1:8845/api/loops/abc123/logs?tail=50
|
|
423
|
+
|
|
424
|
+
# Stream logs in real-time (SSE)
|
|
425
|
+
curl -N http://127.0.0.1:8845/api/loops/abc123/logs/stream
|
|
426
|
+
|
|
427
|
+
# Subscribe to daemon events (SSE)
|
|
428
|
+
curl -N http://127.0.0.1:8845/api/events
|
|
429
|
+
|
|
430
|
+
# Tasks and projects
|
|
431
|
+
curl http://127.0.0.1:8845/api/tasks
|
|
432
|
+
curl http://127.0.0.1:8845/api/projects
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Interactive docs
|
|
436
|
+
|
|
437
|
+
- **Swagger UI**: `http://127.0.0.1:8845/api/docs`
|
|
438
|
+
- **OpenAPI 3.0 spec**: `http://127.0.0.1:8845/api/openapi.json`
|
|
439
|
+
|
|
440
|
+
### From the CLI/TUI
|
|
441
|
+
|
|
442
|
+
- `loop-task api` — prints all API endpoints to stdout
|
|
443
|
+
- Board: press **Ctrl+G** or type `api` — shows a toast with API info
|
|
444
|
+
|
|
445
|
+
### Response format
|
|
446
|
+
|
|
447
|
+
All responses use a consistent JSON envelope:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{"ok": true, "data": ...} // success (200/201)
|
|
451
|
+
{"ok": false, "error": {"message": "..."}} // error (400/404/405/500)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
|
|
370
456
|
## Development
|
|
371
457
|
|
|
372
|
-
Requires [
|
|
458
|
+
Requires [Node.js](https://nodejs.org) >= 20. Uses [pnpm](https://pnpm.io) for package management.
|
|
373
459
|
|
|
374
460
|
```bash
|
|
375
|
-
|
|
461
|
+
pnpm install
|
|
376
462
|
npm run build
|
|
377
463
|
```
|
|
378
464
|
|
|
379
465
|
Run locally:
|
|
380
466
|
|
|
381
467
|
```bash
|
|
382
|
-
|
|
468
|
+
tsx src/cli.ts # board
|
|
383
469
|
node dist/entry.js new --now 30m -- npm test # background loop
|
|
384
470
|
node dist/entry.js run --now --max-runs 1 10s -- echo hello # foreground
|
|
385
471
|
```
|
|
@@ -387,12 +473,32 @@ node dist/entry.js run --now --max-runs 1 10s -- echo hello # foreground
|
|
|
387
473
|
Quality gates:
|
|
388
474
|
|
|
389
475
|
```bash
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
476
|
+
pnpm run typecheck # tsc --noEmit
|
|
477
|
+
pnpm run lint # eslint src/ tests/
|
|
478
|
+
pnpm run test # vitest run
|
|
479
|
+
pnpm run build # tsc -p tsconfig.build.json
|
|
394
480
|
```
|
|
395
481
|
|
|
482
|
+
### Testing the board in a browser (ttyd)
|
|
483
|
+
|
|
484
|
+
> **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.
|
|
485
|
+
|
|
486
|
+
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.
|
|
487
|
+
|
|
488
|
+
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:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
# Point -w at the repo (absolute path) and run the dev board:
|
|
492
|
+
ttyd -W -w "C:\Projects\Personal\loop-cli" -p 7681 pnpm run dev
|
|
493
|
+
|
|
494
|
+
# The built entry works too (after `npm run build`), with the same -w:
|
|
495
|
+
ttyd -W -w "C:\Projects\Personal\loop-cli" -p 7681 node dist/entry.js
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
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).
|
|
499
|
+
|
|
500
|
+
> **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.
|
|
501
|
+
|
|
396
502
|
## License
|
|
397
503
|
|
|
398
504
|
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() {
|