Problem
runBackfill in cli.js (added in #41) carries a lot of orchestration inline:
ensureServer, enqueue via the control client, the foreground poll-to-completion
loop, progress rendering (respecting --quiet), the --background short-circuit,
the SIGINT = detach handling, and exit-code logic. It reads as though the backfill
logic lives in cli.js, even though the actual work runs on the server and this
handler is just a thin client. The command handler should be small.
Idea
There will be many "run X on the server, wait for it to finish, show progress"
commands over time (embedding / RAG indexing, batch media download, takeout sync,
metadata refresh, etc.). Extract the shared orchestration into one small primitive
(e.g. core/server-task.js) so each CLI command becomes: parse args → call the
runner with task-specific hooks.
Sketch — a followServerTask({ enqueue, poll, render, background, quiet }) that owns:
ensureServer (auto-start with idle-exit);
- enqueue → return id; if
background, print and exit;
- foreground: poll task/job state with a brief read snapshot each tick
(see withReadSnapshot in cli.js), render progress to stderr (respect quiet),
until a terminal state;
- final summary to stdout /
--json;
- no default timeout for long-running work; honor an explicit
--timeout;
Ctrl-C = detach (never cancel), with clean SIGINT listener teardown;
- consistent exit codes (success / task error / detached).
CLI handlers (runBackfill and future ones) then shrink to a few lines. Keep it a
minimal primitive — not a speculative framework.
When
Not now. Best done when the second server-executed long task appears, so the
abstraction is generalized from at least two real call sites (avoids a premature,
over-fit abstraction). Until then, runBackfill is the reference implementation.
Refs
Broader than progress-following (update)
This is not strictly the "long-running, follow progress to completion" case. The
sibling handlers added in #41 — runBackfill, runBackfillCount, runBackfillWait,
runBackfillCancel, and the enhanced status path — all share the same shape:
perform an operation on the server, optionally wait, and return a result, each
spelled out very verbosely. They repeat the same boilerplate:
- resolve the store dir;
ensureServer / pingServer (is a server reachable? should we auto-start one?);
- call the control client (
enqueueBackfill / cancelBackfill) or take a brief
read snapshot of job state;
- format output for
--json vs human;
- error handling and consistent exit codes.
So the extraction should cover this whole family, not just the foreground progress
runner: hoist the shared boilerplate so each handler reduces to its task-specific
part (one control call + one render). followServerTask (above) is then just the
"…and wait, showing progress" variant of that same primitive.
Problem
runBackfillincli.js(added in #41) carries a lot of orchestration inline:ensureServer, enqueue via the control client, the foreground poll-to-completionloop, progress rendering (respecting
--quiet), the--backgroundshort-circuit,the
SIGINT= detach handling, and exit-code logic. It reads as though the backfilllogic lives in
cli.js, even though the actual work runs on the server and thishandler is just a thin client. The command handler should be small.
Idea
There will be many "run X on the server, wait for it to finish, show progress"
commands over time (embedding / RAG indexing, batch media download, takeout sync,
metadata refresh, etc.). Extract the shared orchestration into one small primitive
(e.g.
core/server-task.js) so each CLI command becomes: parse args → call therunner with task-specific hooks.
Sketch — a
followServerTask({ enqueue, poll, render, background, quiet })that owns:ensureServer(auto-start with idle-exit);background, print and exit;(see
withReadSnapshotincli.js), render progress to stderr (respectquiet),until a terminal state;
--json;--timeout;Ctrl-C= detach (never cancel), with cleanSIGINTlistener teardown;CLI handlers (
runBackfilland future ones) then shrink to a few lines. Keep it aminimal primitive — not a speculative framework.
When
Not now. Best done when the second server-executed long task appears, so the
abstraction is generalized from at least two real call sites (avoids a premature,
over-fit abstraction). Until then,
runBackfillis the reference implementation.Refs
cli.js:runBackfill,withReadSnapshot(added in feat(cli): server-executed backfill client (#30, #26, PR-2b) #41)core/control-client.js(the control API client)Broader than progress-following (update)
This is not strictly the "long-running, follow progress to completion" case. The
sibling handlers added in #41 —
runBackfill,runBackfillCount,runBackfillWait,runBackfillCancel, and the enhancedstatuspath — all share the same shape:perform an operation on the server, optionally wait, and return a result, each
spelled out very verbosely. They repeat the same boilerplate:
ensureServer/pingServer(is a server reachable? should we auto-start one?);enqueueBackfill/cancelBackfill) or take a briefread snapshot of job state;
--jsonvs human;So the extraction should cover this whole family, not just the foreground progress
runner: hoist the shared boilerplate so each handler reduces to its task-specific
part (one control call + one render).
followServerTask(above) is then just the"…and wait, showing progress" variant of that same primitive.