Skip to content

Extract a reusable 'run a server task, follow progress, print' primitive to keep CLI handlers thin #43

Description

@kfastov

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 #41runBackfill, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions