Skip to content

Commit 4f9f778

Browse files
feat: scheduled boards — define & schedule auto-refreshing boards (#221)
* docs: spec for scheduled boards (agent-refreshed, host-agnostic) * feat(scheduled-boards): define & schedule auto-refreshing boards A scheduled board is a normal project/agent board that an agent re-refreshes on a schedule. termchart owns two portable pieces — a committed YAML definition (.termchart/boards/<id>.yaml) and a host-agnostic `termchart run <id>` primitive that hands the board's assembled prompt to an agent which gathers data and pushes. - core: validateBoardDef() — pure, dependency-free structural validator - cli: `termchart board create|list|show|prompt|scaffold-workflow|delete` `termchart run <id>` — assemble prompt + spawn agentCommand (prompt on stdin to avoid E2BIG/shell-quoting), exit-code propagation, disabled skip - cli: scaffold-workflow generates a GitHub Actions workflow; tz->UTC cron conversion for the single-hour case with DST/day-cross warnings - plugin: /termchart:schedule-board command + scheduled-boards skill (session-cron v1, GH Actions turnkey, system cron compatible) Build: esbuild ESM bundle now injects a createRequire banner so bundled CJS deps (yaml) resolve node builtins instead of throwing "Dynamic require". Version bumps: cli 0.5.0->0.6.0, plugin 0.12.0->0.13.0, marketplace ->0.13.0. Tests: core validator (10), board CRUD, prompt assembly, run primitive, scaffold-workflow + tz conversion, and a built-bundle regression guard. * fix(scheduled-boards): address new-user testing — lifecycle, GH Actions, UX Agent-tester dogfooding (4 fresh "new user" agents) surfaced real gaps; fixes: Correctness / honesty: - Lifecycle is now actually enforced by `termchart run`: skip past `until`; persistent `lifecycle.runs` counter enforces `maxRuns` (written back to YAML). assembleBoardPrompt now always includes a self-unschedule clause for bounded OR frequent (sub-daily) boards — the job-tracker case the docs promised. - `run` pre-flights the viewer env and exits 4 (was: swallowed the failure in the agent subprocess and returned 0). Rejects unknown flags; one-line verbosity. - Version: bump 0.6.0 -> 0.7.0 (0.6.0 was already published to npm without this feature); scaffolded workflow pins `@ivanmkc/termchart@0.7.0`. GitHub Actions turnkey + DST-correct: - DST handled via two seasonal UTC crons + a local-time guard step (GH cron is UTC-only) so the board fires at the intended local time year-round. - Day-of-week is correctly shifted on UTC midnight crossing (was: emitted a knowingly-wrong cron with a warning); restricted day-of-month is warned, not mis-shifted. - Workflow now installs the agent CLI, sets permissions + GH_TOKEN, timeout- minutes, and a concurrency guard. Non-claude agentCommand omits the Anthropic secret + claude install (gets a TODO note). CLI/UX: - Per-subcommand `--help`; `--enabled/--until/--max-runs` flags; create always writes a visible lifecycle block. `board list` warns about skipped invalid files instead of hiding them. Unknown top-level command says so (instead of trying to open it as a file); unknown flag names the flag, not its value; missing-required-flag names which; `board delete` shows the expected path; YAML parse errors carry the board id. Docs: skill + command truthed-up (lifecycle enforcement + GH-Actions maxRuns caveat, turnkey workflow, component payload pointer, run exit-4). Tests: core 11, cli 228 (incl. built-bundle smoke); re-verified live against the remote viewer (pre-flight exit 4, maxRuns stop, DST workflow, full push loop). * fix(scheduled-boards): code-review fixes — DST guard, dow wrap, hardening Senior-review pass on the branch found 2 critical defects in the GH Actions scaffolding plus hardening gaps; all fixed with tests. Critical: - DST guard was exact-minute wall-clock equality — GitHub's routinely-late cron starts (3-20+ min) would skip nearly every run, and workflow_dispatch was swallowed too. Now keyed on WHICH cron fired (github.event.schedule via an EVENT_SCHEDULE env) vs the zone's CURRENT UTC offset (date +%z): delay-immune, fail-open, dispatch always runs. Behavioral test executes the generated shell. - shiftDow emitted an invalid descending range when a day-of-week shift wrapped past Saturday ("4-6"+1 -> "5-0", which GitHub rejects — silent dead schedule). Wrapping ranges now split ("5-6,0"). Hardening: - Board ids are validated (BOARD_ID_RE, exported from core) before any path join — no traversal via `board delete ../../x` etc. - agentCommand containing quotes is rejected at validation (whitespace-split, no-shell contract documented in the skill); filename stem must match id (loadBoard error + scanBoards issue); saveBoard writes tmp+rename (atomic). - Cron field values validated in core (charset, per-field bounds, step>0, numeric-only) so typos fail at create, not on GitHub. - scaffold-workflow warns when lifecycle.maxRuns can't persist on an ephemeral runner (suggests until) and when the board's host isn't github-actions; the bounded-lifecycle prompt clause no longer overclaims enforcement. - --enabled accepts only true|false; removed unused cronToUtc export. Also: regenerate package-lock for 0.7.0; spec truth-ups (drop the never-built {prompt} placeholder, record run-enforced lifecycle, note next-run deferral); sessionCronId write-back instruction in the schedule-board command. Tests: core 13, cli 234, viewer 492 — all green; live run->push->viewer loop re-verified against the remote viewer. * docs: verify skill — runtime-verification recipe for CLI changes * refactor(scheduled-boards): coding-standards audit — single-source, predicate narrowing Applied the transferable /dev-coding-standards rules to the branch: - Single source of truth: "claude -p" default was duplicated in run.ts, board.ts (show), and scaffold-workflow.ts — now one DEFAULT_AGENT_COMMAND exported from core alongside the BoardDef domain. - Correct-by-construction: added isBoardDef() type predicate in core; the six `as BoardDef` casts in board-store.ts are gone — narrowing is structural. - Version dual-source (version.ts vs package.json is forced by the standalone bundle): added a tripwire test asserting they match, since scaffolded workflows pin @ivanmkc/termchart@VERSION. - Dead code: removed unused BoardDeps.env field and the unconsumed BOARDS_SUBDIR export. - No function-scoped imports: hoisted a require() in scaffold-workflow.test.ts to a top-level import. Behavior unchanged: core 14, cli 235 green (viewer-stub files re-run serially under load-114 box contention); built binary spot-checked. * docs: verify skill — default-agent resolution probe recipe * refactor(scheduled-boards): standards pass 2 — remove type lie in --max-runs parsing Deep second audit against /dev-coding-standards. One real violation found: create() smuggled a non-numeric --max-runs value through `as unknown as number` so the core validator would report it — a deliberate type-system lie (hidden from grep by its own trailing comment). Now validated at the flag edge with an error that names --max-runs (test added, RED→GREEN). Also: JSDoc on BOARD_ID_RE. Everything else audited clean: catch-(e as Error) is idiomatic TS, cron "*" literals are domain vocabulary, host comparison is compile-checked via the BoardHost literal type, planSchedule's early returns are graceful degradation (one strategy), spawn injection is DI not patching. * fix(scheduled-boards): run --timeout (kill hung agents) + --force dropped-field warning Closes the two findings left open by runtime verification: - `termchart run` now kills a hung agent: --timeout <secs> (default 600), SIGTERM then SIGKILL after 5s, exit 124 (GNU timeout convention). A stuck default `claude -p` previously wedged a session-cron/local-cron scheduler indefinitely (only GH Actions had a timeout-minutes backstop). Verified against the built bundle: `sleep 60` agent killed at 1s → exit 124. - `board create --force` REPLACES the definition; it now warns on stderr with the exact previously-set fields that were dropped because they weren't re-passed (description, tz, host, agentCommand, sessionCronId, lifecycle.until/maxRuns). This bit live testing when a --force re-create silently lost --tz. Docs updated (skill CLI reference, top-level usage, verify-skill gotchas). Tests: run 18, board 24, bundle incl. real-process timeout kill — green. --------- Co-authored-by: Ivan Cheung <ivanmkc@google.com>
1 parent 5a671f5 commit 4f9f778

26 files changed

Lines changed: 2321 additions & 10 deletions

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"name": "termchart",
1010
"source": "./plugin",
1111
"description": "A live canvas your AI draws on — instead of walls of text, your agent pushes rich, native visuals to a browser / iPad / second screen in real time: React Flow graphs (architecture, sequence, call-graph, ER/class, state machine, PR-review, journeys, recursion trees), Vega-Lite charts (incl. scientific + Big-O), Mantine dashboards & product comparisons, deep-code walkthroughs, markdown, and split panes, with animated edges and live status overlays (toasts, progress bars, loaders). Deterministic Mermaid → ASCII/Unicode is the terminal fallback. Persona recipes, a showcase gallery, and fullscreen panes.",
12-
"version": "0.12.3",
12+
"version": "0.14.0",
1313
"license": "MIT",
1414
"keywords": [
1515
"mermaid",

.claude/skills/verify/SKILL.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
name: verify
3+
description: How to runtime-verify termchart CLI changes (esp. scheduled boards) — build, drive the real binary against the live viewer, execute generated workflow guards. Use when verifying a termchart diff at its surface instead of re-running CI.
4+
---
5+
6+
# Verifying termchart CLI changes at the surface
7+
8+
## Handle
9+
10+
```bash
11+
cd packages/cli && npm run build # tsc + esbuild → dist/cli.js (single bundle)
12+
node packages/cli/dist/cli.js --version # sanity: version matches src/version.ts
13+
```
14+
15+
Drive `node <repo>/packages/cli/dist/cli.js` as `termchart` — never `import ./src/...`.
16+
The **built bundle** is the surface: esbuild CJS→ESM shims have broken it before while
17+
unit tests stayed green (yaml dep dynamic-require).
18+
19+
## Live viewer
20+
21+
`TERMCHART_VIEWER_URL`/`TERMCHART_VIEWER_TOKEN` live in `~/.profile`. The Bash tool shell
22+
is initialized from the profile, so they are often ALREADY SET — to test the "no viewer"
23+
path use `env -u TERMCHART_VIEWER_URL -u TERMCHART_VIEWER_TOKEN …`, don't assume unset.
24+
Verify pushes landed with `termchart list --project <p>` and round-trip with `pull`.
25+
Always `termchart clear --project <p> --agent <a>` when done; use a unique throwaway scope.
26+
27+
## Scheduled boards flow
28+
29+
Work in `mktemp -d` (definitions are cwd-relative: `.termchart/boards/<id>.yaml`).
30+
31+
```bash
32+
termchart board create --id x --schedule "0 8 * * 1-5" --project <p> --agent <a> \
33+
--prompt "" [--tz Zone] [--max-runs N] [--agent-command <cmd>]
34+
termchart run x # spawns agentCommand, prompt on stdin
35+
```
36+
37+
- For a deterministic run→push→viewer proof, point `--agent-command` at a stub script that
38+
`cat`s stdin then calls the real binary's `push`. Don't use the real `claude -p` in probes —
39+
it can sit silently until `run`'s timeout (default 600s; override with `--timeout <secs>`,
40+
hung agents are killed → exit 124).
41+
- To exercise DEFAULT agent resolution safely (without a hanging real `claude`), run with a
42+
PATH that has node but not claude: `mkdir nodeonly && ln -s "$(which node)" nodeonly/node;
43+
PATH="$PWD/nodeonly:/usr/bin:/bin" node $BIN run <id>` → expect `cannot launch agent
44+
"claude"` + exit 127. (Plain `PATH=/usr/bin:/bin` loses node itself — fnm installs it
45+
elsewhere.)
46+
- lifecycle checks: `--max-runs 2` → run 3× (third skips, `runs:` persisted in YAML);
47+
`--until <past>` and `--enabled false` → skip lines, exit 0.
48+
49+
## Generated GH workflow
50+
51+
`termchart board scaffold-workflow <id>``.github/workflows/termchart-<id>.yml`.
52+
Execute the DST guard locally (don't just read it):
53+
54+
```bash
55+
sed -n '/run: |/,/env:/p' .github/workflows/termchart-<id>.yml \
56+
| sed '1d;$d;s/^ //' | sed 's/^npx .*/echo RAN/' > guard.sh
57+
EVENT_SCHEDULE="<in-season cron>" bash guard.sh # → RAN
58+
EVENT_SCHEDULE="<off-season cron>" bash guard.sh # → skip line
59+
EVENT_SCHEDULE="" bash guard.sh # dispatch → RAN
60+
```
61+
62+
In-season = the cron whose offset matches `TZ=<zone> date +%z` today.
63+
64+
## Gotchas
65+
66+
- System awk is mawk: `{10}` regex intervals silently don't match — use sed.
67+
- Piping the CLI into `head` can produce bogus exit codes (SIGPIPE) — redirect to a file.
68+
- The full vitest suite is load-flaky (viewer-stub `fetch failed`) under a parallel
69+
process; that's contention, not a regression — but /verify shouldn't run tests anyway.
70+
- `board create --force` REPLACES the definition; fields not re-passed (e.g. --tz) are lost —
71+
a stderr warning now lists exactly which were dropped.
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Scheduled Boards — Design
2+
3+
**Date:** 2026-06-24
4+
**Status:** Approved (brainstorm) — pending spec review
5+
**Branch:** worktree-scheduled-boards
6+
7+
## Problem
8+
9+
termchart is a push-based system: an *agent* (Claude, `agy`, any CLI caller) gathers
10+
data, decides a layout, and pushes static JSON to a board (a `project/agent` scope on
11+
the viewer). The viewer is a dumb display that live-updates over SSE. Today a board only
12+
changes when a human triggers an agent to push.
13+
14+
We want **boards that refresh themselves on a schedule** — generically enough to cover:
15+
16+
- A **daily morning board** ("today's tasks, calendar, open PRs") that rebuilds at 8am.
17+
- A **fast job-tracker** that refreshes every couple of minutes while a job runs, then
18+
stops when the job finishes.
19+
20+
These differ in cadence and lifetime but share one shape: *on a schedule, an agent
21+
re-runs a saved intent and pushes the result to a fixed board.*
22+
23+
## Decisions (from brainstorming)
24+
25+
| Question | Decision |
26+
|---|---|
27+
| What produces refreshed content? | **An agent re-runs** a saved prompt (not a fixed data pull). Most flexible; covers all cases. |
28+
| What drives the schedule? | **Host-agnostic.** termchart owns a board *definition* + a `run` primitive; any scheduler calls it. |
29+
| First-class host in v1 | **Claude Code session cron** (`CronCreate`), wired by the skill. |
30+
| Where do definitions live? | **Committed repo files** — portable, diffable, a GH Actions runner gets them on checkout. |
31+
| v1 scope | **Core + GitHub Actions scaffolding.** (Viewer badge deferred.) |
32+
| Definition file format | **YAML** (`.termchart/boards/<id>.yaml`). |
33+
34+
### Non-goals
35+
36+
- No cron engine inside the viewer server (it runs on Cloud Run — scales to zero,
37+
ephemeral; a server-side scheduler there is unreliable and contradicts repo-file
38+
definitions).
39+
- No fixed/no-LLM data-pull mode in v1 (the refresh engine is always an agent; a board's
40+
prompt can *tell* the agent to run a command, which covers the deterministic case).
41+
- No viewer "scheduled" badge in v1 (passive label is a clean follow-up).
42+
43+
## Architecture
44+
45+
Three cooperating pieces, each in its existing termchart home:
46+
47+
```
48+
.termchart/boards/<id>.yaml ── board definition (committed, portable)
49+
50+
├── packages/cli ── `termchart board` CRUD + `termchart run <id>` (host-agnostic primitive)
51+
│ + `termchart board scaffold-workflow <id>` (GH Actions YAML)
52+
53+
└── plugin skill ── `/termchart:schedule-board` guides the agent to author a
54+
definition and register it with a scheduler (CronCreate v1)
55+
```
56+
57+
The unifying primitive is the **assembled prompt**: for any board, `termchart board prompt
58+
<id>` prints the exact text handed to an agent. Every host path uses that same text, so an
59+
in-session cron and a headless GH Actions run produce identical behaviour.
60+
61+
### Data flow per refresh
62+
63+
1. A scheduler fires (session cron in-process, or GH Actions / system cron calling
64+
`termchart run <id>`).
65+
2. The board's assembled prompt is handed to an agent (`agentCommand`).
66+
3. The agent gathers data with its own tools and `termchart push`es to the board's
67+
`target` (`project/agent`) on the viewer.
68+
4. The viewer broadcasts the update over SSE; connected browsers refresh instantly —
69+
exactly the existing push path. **No new viewer code is required for v1.**
70+
71+
## Component 1 — Board definition
72+
73+
One YAML file per board at `.termchart/boards/<id>.yaml`:
74+
75+
```yaml
76+
id: daily-standup # also the filename stem; [a-z0-9-]
77+
description: "Morning board: today's tasks, calendar, open PRs"
78+
schedule: "0 8 * * 1-5" # 5-field cron
79+
tz: "America/Los_Angeles" # optional; default = system tz. Used for cron interpretation.
80+
target:
81+
project: me
82+
agent: daily # the board scope the push updates
83+
prompt: |
84+
Gather my open tasks, today's calendar, and assigned PRs.
85+
Build a component board grouped by priority and push it to project=me agent=daily.
86+
agentCommand: "claude -p" # agent-agnostic; prompt delivered on stdin (see below)
87+
host: claude-session # claude-session | github-actions | cron
88+
lifecycle:
89+
enabled: true
90+
until: null # ISO-8601; stop scheduling after this instant
91+
maxRuns: null # stop after N successful runs
92+
```
93+
94+
- `prompt` is the saved intent. It is self-contained: it names the `target` so a headless
95+
agent with no plugin context still knows where to push.
96+
- `target` reuses the existing `project/agent` scope model — **a scheduled board is just a
97+
normal board an agent keeps refreshing.**
98+
- `agentCommand` keeps the system agent-agnostic (Claude, `agy --print`, or a test stub),
99+
honouring the project's cross-tool integration stance.
100+
- `lifecycle` gives the job-tracker case a natural end (`until` / `maxRuns`), and lets a
101+
board be paused (`enabled: false`).
102+
103+
### Validation
104+
105+
A `validateBoardDef()` in `packages/core` (sibling to the existing content validators):
106+
checks `id` shape, required fields, a 5-field cron, valid `tz`, `target.project/agent`
107+
non-empty, `host` enum. Surfaces precise errors used by every CLI subcommand.
108+
109+
## Component 2 — CLI surface (`packages/cli`)
110+
111+
Mirrors the existing `termchart template <cmd>` sub-dispatch pattern.
112+
113+
| Command | Behaviour |
114+
|---|---|
115+
| `termchart board create` | Scaffold a `.termchart/boards/<id>.yaml` from flags (`--id --schedule --project --agent --prompt --host --tz`). No LLM. |
116+
| `termchart board list` | List defined boards: id · schedule · target · host · enabled. `--json`. |
117+
| `termchart board show <id>` | Print the parsed/validated definition + effective defaults. `--json`. (Computed next-run display deferred — needs a cron evaluator; not in v1.) |
118+
| `termchart board delete <id>` | Remove the file. |
119+
| `termchart board prompt <id>` | Print the fully-assembled agent prompt (the canonical text every host hands to the agent). |
120+
| `termchart run <id>` | **Host-agnostic run primitive.** Load + validate def, assemble prompt, execute `agentCommand` with the prompt on **stdin**, return its exit code. The agent does the push. |
121+
| `termchart board scaffold-workflow <id>` | Emit `.github/workflows/termchart-<id>.yml` (`schedule:` cron + `workflow_dispatch`) that runs `termchart run <id>`. |
122+
123+
### Prompt delivery (robustness)
124+
125+
`termchart run` delivers the assembled prompt to `agentCommand` via **stdin**, not as an
126+
argv string. This avoids the `E2BIG` argv-overflow class of failure and all shell-quoting
127+
hazards. `agentCommand` is tokenised on whitespace (not run through a shell; quotes are rejected
128+
at validation) and the prompt is piped in; `claude -p` and `agy --print` both accept a prompt on
129+
stdin. Stdin is the only delivery path — an inline `{prompt}` placeholder was considered and
130+
dropped (YAGNI; it reintroduces the quoting/E2BIG hazards).
131+
132+
### Assembled prompt
133+
134+
`termchart board prompt <id>` composes:
135+
136+
1. The board's `prompt` (the saved intent).
137+
2. An explicit **push instruction**: the exact `target` (`project`/`agent`) and a reminder
138+
to push with the termchart CLI, so a context-free headless agent still succeeds.
139+
3. A **lifecycle instruction** when relevant: e.g. "If the tracked job has finished,
140+
unschedule this board (`CronDelete` in-session, or disable the workflow)." This is how
141+
the job-tracker self-terminates.
142+
143+
## Component 3 — Scheduling / host integration
144+
145+
### v1 first-class — Claude Code session cron
146+
147+
The `/termchart:schedule-board` skill, after authoring the definition, registers a
148+
`CronCreate` whose prompt is the output of `termchart board prompt <id>`. It fires
149+
**in-session** (no subprocess) — ideal for "watch this running job while I work." The skill
150+
records the cron id in the board file (`sessionCronId`, written back) so it can be updated
151+
or `CronDelete`d. Session crons inherit the harness's 7-day auto-expiry. `lifecycle.until`/
152+
`maxRuns` are enforced deterministically by `termchart run` (skip past `until`; a persisted
153+
`lifecycle.runs` counter stops at `maxRuns` — counter-based stops need the YAML to persist, so on
154+
ephemeral GitHub runners use `until`), with the assembled prompt additionally instructing the
155+
agent to self-unschedule when the tracked work completes.
156+
157+
### v1 turnkey — GitHub Actions
158+
159+
`termchart board scaffold-workflow <id>` generates:
160+
161+
```yaml
162+
name: termchart board <id>
163+
on:
164+
schedule:
165+
- cron: "<schedule, converted to UTC>" # comment notes the source tz
166+
workflow_dispatch: {}
167+
jobs:
168+
refresh:
169+
runs-on: ubuntu-latest
170+
steps:
171+
- uses: actions/checkout@v4
172+
- uses: actions/setup-node@v4
173+
with: { node-version: 20 }
174+
- run: npx -y @ivanmkc/termchart run <id>
175+
env:
176+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
177+
TERMCHART_VIEWER_URL: ${{ secrets.TERMCHART_VIEWER_URL }}
178+
TERMCHART_VIEWER_TOKEN: ${{ secrets.TERMCHART_VIEWER_TOKEN }}
179+
```
180+
181+
Caveats the scaffolder prints:
182+
- **GH Actions cron is UTC.** The scaffolder converts `schedule` from `tz` to a UTC cron
183+
and annotates it with a comment; it warns that DST shifts the wall-clock time (a fixed
184+
offset is used — exact DST-aware scheduling is out of scope).
185+
- **A cloud runner can't see local files.** Boards whose data is local-only must use
186+
`host: claude-session` or a local `cron` host. The scaffolder refuses (with a clear
187+
message) only if it can detect an obviously local-only intent; otherwise it warns.
188+
- The agent in the runner needs the termchart CLI (via `npx`) and an API key; the assembled
189+
prompt is self-contained re: where to push.
190+
191+
### Compatible — system cron
192+
193+
Documented, not scaffolded in v1 beyond the `run` primitive: a crontab line runs
194+
`termchart run <id>`. Same primitive, same prompt.
195+
196+
## Lifecycle & stop conditions
197+
198+
- `enabled: false` → schedulers skip the board; `run` exits 0 with a notice.
199+
- `until` / `maxRuns` → enforced by the assembled prompt (agent self-unschedules) and
200+
surfaced by `board show` (computed next-run / remaining runs).
201+
- Job-tracker pattern: a short-cadence session-cron board whose prompt ends with "if the
202+
job is done, unschedule" — it cleans itself up.
203+
204+
## Viewer surfacing (deferred)
205+
206+
Out of scope for v1. Follow-up: scheduled pushes set a `source: "schedule:<id>"` marker so
207+
the viewer can badge "auto-updated 08:03". Passive label only — still no server scheduler.
208+
209+
## Testing strategy
210+
211+
Pure, deterministic units (no LLM, no network) cover the core:
212+
213+
- `validateBoardDef()` — happy path + each failure (bad id, bad cron, bad tz, missing
214+
target, bad host enum).
215+
- `board create/list/show/delete` — round-trip a YAML file in a temp dir.
216+
- `board prompt` — golden assembled-prompt output (intent + push instruction + lifecycle).
217+
- `run` with a **stub `agentCommand`** (e.g. a script that records its stdin) — asserts the
218+
prompt is delivered on stdin, exit code propagates, `enabled:false` short-circuits.
219+
- `scaffold-workflow` — golden YAML; tz→UTC conversion; local-data warning.
220+
221+
End-to-end (live verification surface): `termchart run <id>` with a real `agentCommand`
222+
against the live viewer → confirm the target board updates over SSE.
223+
224+
## Release channels
225+
226+
Per termchart's independent channels:
227+
228+
- **CLI** (`@ivanmkc/termchart`) — new `board` + `run` commands, YAML dep, version bump,
229+
publish.
230+
- **Core** (`@ivanmkc/termchart-core`) — `validateBoardDef()`.
231+
- **Plugin** (marketplace) — new `/termchart:schedule-board` skill/command, version bump.
232+
- **Viewer** — unchanged in v1.
233+
234+
## Open implementation notes
235+
236+
- Add a YAML parser to the CLI bundle (`yaml`); esbuild bundles it like existing deps.
237+
- `agentCommand` is tokenised and spawned without a shell (`spawn`, not `exec`) — no shell
238+
injection, prompt on stdin.
239+
- `tz`→UTC cron conversion uses a fixed current-offset translation with an explicit DST
240+
caveat in v1.

package-lock.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ivanmkc/termchart",
3-
"version": "0.5.0",
3+
"version": "0.7.0",
44
"description": "Push native diagrams, charts, dashboards & live status to the termchart viewer (browser/iPad) in real time — the CLI for the live canvas your AI draws on; also renders deterministic Mermaid → ASCII/Unicode in the terminal.",
55
"type": "module",
66
"bin": {
@@ -17,12 +17,14 @@
1717
},
1818
"scripts": {
1919
"build:tsc": "tsc -p tsconfig.json",
20-
"build": "npm run build:tsc && esbuild build/cli.js --bundle --platform=node --format=esm --target=node18 --outfile=dist/cli.js",
20+
"build": "npm run build:tsc && esbuild build/cli.js --bundle --platform=node --format=esm --target=node18 --outfile=dist/cli.js --banner:js=\"import { createRequire as __tcCreateRequire } from 'module'; const require = __tcCreateRequire(import.meta.url);\"",
2121
"prepublishOnly": "npm run build",
2222
"test": "vitest run",
2323
"test:watch": "vitest"
2424
},
25-
"dependencies": {},
25+
"dependencies": {
26+
"yaml": "^2.9.0"
27+
},
2628
"devDependencies": {
2729
"@types/node": "^20.0.0",
2830
"beautiful-mermaid": "^1.1.3",

0 commit comments

Comments
 (0)