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.
Files changed (105) hide show
  1. package/README.md +127 -21
  2. package/dist/board/App.js +1 -1
  3. package/dist/board/components/ActionButtons.js +1 -1
  4. package/dist/board/components/BoardButton.js +1 -1
  5. package/dist/board/components/ConfirmModal.js +1 -1
  6. package/dist/board/components/ContextHelpModal.js +1 -1
  7. package/dist/board/components/CreateForm.js +1 -1
  8. package/dist/board/components/CreateProjectModal.js +1 -1
  9. package/dist/board/components/DeleteProjectConfirm.js +1 -1
  10. package/dist/board/components/DetailView.js +1 -1
  11. package/dist/board/components/EditProjectModal.js +1 -1
  12. package/dist/board/components/FilterBar.js +1 -1
  13. package/dist/board/components/Footer.js +1 -1
  14. package/dist/board/components/Header.js +1 -1
  15. package/dist/board/components/HelpModal.js +1 -1
  16. package/dist/board/components/Inspector.js +1 -1
  17. package/dist/board/components/LogModal.js +1 -1
  18. package/dist/board/components/Navigator.js +1 -1
  19. package/dist/board/components/ProjectsModal.js +1 -1
  20. package/dist/board/components/ProjectsPage.js +1 -1
  21. package/dist/board/components/RunHistory.js +1 -1
  22. package/dist/board/components/SearchBox.js +1 -1
  23. package/dist/board/components/SearchSelect.js +1 -1
  24. package/dist/board/components/TaskBrowser.js +1 -1
  25. package/dist/board/components/TaskFilterBar.js +1 -1
  26. package/dist/board/components/TaskForm.js +1 -1
  27. package/dist/board/components/Timeline.js +1 -1
  28. package/dist/board/focus-context.js +1 -1
  29. package/dist/board/format.js +15 -6
  30. package/dist/board/index.js +1 -1
  31. package/dist/board/toast.js +1 -1
  32. package/dist/cli/import-validator.js +151 -0
  33. package/dist/cli/import-writer.js +77 -0
  34. package/dist/cli.js +111 -17
  35. package/dist/config/constants.js +52 -0
  36. package/dist/daemon/file-watcher.js +171 -0
  37. package/dist/daemon/http-server.js +605 -0
  38. package/dist/daemon/index.js +36 -6
  39. package/dist/daemon/manager.js +94 -13
  40. package/dist/daemon/projects.js +10 -0
  41. package/dist/daemon/server.js +21 -0
  42. package/dist/daemon/task-manager.js +7 -0
  43. package/dist/esm-loader.js +4 -1
  44. package/dist/hooks/useLoopFormValidation.js +140 -0
  45. package/dist/i18n/en.json +237 -17
  46. package/dist/shared/fs-utils.js +34 -1
  47. package/dist/tui/app.js +675 -151
  48. package/dist/tui/commands.js +114 -0
  49. package/dist/tui/components/ActionButtons.js +49 -0
  50. package/dist/tui/components/Button.js +18 -0
  51. package/dist/tui/components/ChainEditor.js +112 -0
  52. package/dist/tui/components/CommandBuilderField.js +143 -0
  53. package/dist/tui/components/CommandEditorModal.js +159 -0
  54. package/dist/tui/components/CommandInput.js +303 -0
  55. package/dist/tui/components/CommandsBrowserModal.js +77 -0
  56. package/dist/tui/components/ConfirmModal.js +9 -0
  57. package/dist/tui/components/ContextHelpModal.js +8 -0
  58. package/dist/tui/components/CreateForm.js +223 -0
  59. package/dist/tui/components/DebugPanel.js +8 -0
  60. package/dist/tui/components/ExportModal.js +37 -0
  61. package/dist/tui/components/FilterBar.js +13 -0
  62. package/dist/tui/components/FocusableButton.js +16 -0
  63. package/dist/tui/components/FocusableInput.js +69 -0
  64. package/dist/tui/components/FocusableList.js +46 -0
  65. package/dist/tui/components/FocusableSearchSelect.js +82 -0
  66. package/dist/tui/components/Footer.js +42 -0
  67. package/dist/tui/components/Header.js +40 -0
  68. package/dist/tui/components/HelpGuideModal.js +21 -0
  69. package/dist/tui/components/HelpModal.js +59 -0
  70. package/dist/tui/components/InlineCommandEditor.js +143 -0
  71. package/dist/tui/components/Inspector.js +31 -0
  72. package/dist/tui/components/LeftPanel.js +33 -0
  73. package/dist/tui/components/LogModal.js +121 -0
  74. package/dist/tui/components/Modal.js +11 -0
  75. package/dist/tui/components/Navigator.js +68 -0
  76. package/dist/tui/components/PatchEditForm.js +78 -0
  77. package/dist/tui/components/ProjectForm.js +64 -0
  78. package/dist/tui/components/ProjectsModal.js +40 -0
  79. package/dist/tui/components/ProjectsPage.js +99 -0
  80. package/dist/tui/components/RightPanel.js +34 -0
  81. package/dist/tui/components/RunHistory.js +108 -0
  82. package/dist/tui/components/SearchSelect.js +67 -0
  83. package/dist/tui/components/SelectModal.js +76 -0
  84. package/dist/tui/components/TabBar.js +20 -0
  85. package/dist/tui/components/TaskBrowser.js +96 -0
  86. package/dist/tui/components/TaskFilterBar.js +9 -0
  87. package/dist/tui/components/TaskForm.js +134 -0
  88. package/dist/tui/components/TaskPickerModal.js +60 -0
  89. package/dist/tui/components/Toast.js +37 -0
  90. package/dist/tui/components/WelcomeScreen.js +70 -0
  91. package/dist/tui/components/WizardForm.js +178 -0
  92. package/dist/tui/daemon.js +142 -0
  93. package/dist/tui/format.js +111 -0
  94. package/dist/tui/hooks/useBreakpoint.js +7 -0
  95. package/dist/tui/hooks/useHoverState.js +11 -0
  96. package/dist/tui/hooks/useLogStream.js +32 -0
  97. package/dist/tui/hooks/useLoopPolling.js +28 -0
  98. package/dist/tui/index.js +24 -0
  99. package/dist/tui/router.js +16 -0
  100. package/dist/tui/state.js +175 -0
  101. package/dist/tui/theme.js +96 -0
  102. package/dist/tui/types.js +1 -0
  103. package/dist/tui/utils/paste.js +10 -0
  104. package/dist/tui/utils/validation.js +56 -0
  105. 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
  [![npm version](https://img.shields.io/npm/v/loop-task?style=flat-square&color=black)](https://www.npmjs.com/package/loop-task)
11
13
  [![npm downloads](https://img.shields.io/npm/dm/loop-task?style=flat-square&color=black)](https://www.npmjs.com/package/loop-task)
12
14
  [![license](https://img.shields.io/npm/l/loop-task?style=flat-square&color=black)](./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-cli/refs/heads/main/demo.gif" alt="opencode-task demo" width="700" />
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 (requires Bun)
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 bun
72
+ npm install -g loop-task
73
73
  ```
74
74
 
75
- `start`, `new`, and `run` work with Node alone. The board auto-delegates to Bun when needed.
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 (requires Bun) |
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) ──IPC──► daemon ──► loop 1 ──► task (command)
221
- ├──► loop 2 ──► task ──► on-success task
222
- └──► loop 3 ──► task ──► on-failure task
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" --limit 1 --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
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" --limit 1 --json number --jq 'length == 0' | grep -q true && gh issue list --label "to implement" --limit 1 --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
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 [Bun](https://bun.sh) >= 1.2 for package management and the board, and [Node.js](https://nodejs.org) >= 20 for the CLI and daemon.
458
+ Requires [Node.js](https://nodejs.org) >= 20. Uses [pnpm](https://pnpm.io) for package management.
373
459
 
374
460
  ```bash
375
- bun install
461
+ pnpm install
376
462
  npm run build
377
463
  ```
378
464
 
379
465
  Run locally:
380
466
 
381
467
  ```bash
382
- bun run dev # board
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
- bun run typecheck
391
- bun run lint
392
- bun run test
393
- npm run build
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 "@opentui/react/jsx-runtime";
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 } from "@opentui/react/jsx-runtime";
1
+ import { jsx as _jsx } 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 } from "@opentui/react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useHoverState } from "../hooks/useHoverState.js";
3
3
  const PRIMARY_REST = "#1e3a5f";
4
4
  const PRIMARY_HOVER = "#2a4f7a";
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 } from "@opentui/react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRef } from "react";
3
3
  import { t } from "../../i18n/index.js";
4
4
  import { useInputShortcuts } from "../hooks/useInputShortcuts.js";
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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 "@opentui/react/jsx-runtime";
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);
@@ -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: "#38bdf8",
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
- const d = new Date(ts);
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
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "@opentui/react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createCliRenderer } from "@opentui/core";
3
3
  import { createRoot } from "@opentui/react";
4
4
  import { App } from "./App.js";
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
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() {