Skip to content

feat(fleet): save a command with its settings, and manage them from the app - #23

Merged
ralyodio merged 1 commit into
mainfrom
feat/fleet-command-profiles
Aug 30, 2026
Merged

feat(fleet): save a command with its settings, and manage them from the app#23
ralyodio merged 1 commit into
mainfrom
feat/fleet-command-profiles

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Two gaps, both real.

A saved command didn't store its pacing. It kept the script, interpreter, sudo, working directory and timeout — but not how many servers at a time or whether a failure stops the rest. Those are the two settings that differ most between commands: "reload nginx" and "upgrade the database tier" want opposite answers, and re-choosing them on every run is how a saved command still gets run wrong.

The desktop couldn't save a command at all. The recipe strip was read-only; only the CLI could write one.

Now

diskpush fleet commands save careful "systemctl reload nginx" \
  --sudo --concurrency 2 --stop-on-error --timeout 45 --on tag:web

diskpush fleet run --command careful                    # 2 at a time, stops on failure
diskpush fleet run --command careful --concurrency 8    # a flag still wins

In the app, editing anything offers Save these settings. A saved command carries an × to delete it; a shipped recipe does not — it is copied, not edited, and the main process refuses a delete aimed at one rather than trusting the window to have hidden the button.

Migration 004 adds the two columns with the schema's own defaults, so every existing row reads back exactly as it behaved before.

Two bugs caught before they shipped

  • The type checker: copyRecipe didn't carry the new fields, so fleet commands copy would have produced a copy that ran the same script at a different speed — the one thing a copy must not do.
  • Running it: the built-in recipes are plain object literals typed as FleetCommand, and nothing parses them through the schema, so a field the schema merely defaults came back undefined. That reached the desktop as setConcurrency(undefined). They state every field now, with a test asserting it, because the next field added will have the same problem.

Verified

End to end against a real store: saved with --concurrency 2 --stop-on-error --timeout 45, read back intact, honoured on a run with no flags, overridden by a flag, and a pre-existing row migrating to the defaults.

The desktop side was screenshotted rather than reasoned about — picking a saved command restores sudo ✓, At once 2, Timeout 45, Stop after a failure ✓ together, not just the text. No CSP violations.

508 tests. Typecheck and pnpm smoke:desktop green.

Transfer profiles are the other half of this and are not in here: IPC.profilesSave is declared with no handler and there is no profiles UI, even though the table and the CLI have worked all along. That is a separate PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4

…he app

Two gaps, both real.

A saved command stored the script, the interpreter, sudo, the working
directory and the timeout — but not **how many servers at a time** or
**whether a failure stops the rest**. Those are the two settings that differ
most between commands: "reload nginx" and "upgrade the database tier" want
opposite answers, and re-choosing them on every run is how a saved command
still gets run wrong. They are part of the command now, stored and restored,
with a flag still winning at the point of use.

And the desktop could not save a command **at all**. The recipe strip was
read-only; only the CLI could write one. Editing anything now offers "Save
these settings", and a saved command carries an x to delete it. A shipped
recipe has no x — it is copied, not edited, so an upgrade of DiskPush never
silently changes a command someone relies on, and the main process refuses a
delete aimed at one rather than trusting the window to have hidden the button.

Migration 004 adds the two columns with the schema's own defaults, so every
existing row reads back exactly as it behaved before.

Caught by the type checker on the way through: `copyRecipe` did not carry the
new fields, so `fleet commands copy` would have produced a copy that ran the
same script at a different speed — the one thing a copy must not do.

Caught by running it: the built-in recipes are plain object literals typed as
FleetCommand, and nothing parses them through the schema, so a field the
schema merely defaults came back `undefined`. That reached the desktop as
`setConcurrency(undefined)`. They state every field now, with a test asserting
it, because the next field added will have the same problem.

Verified end to end against a real store: saved with `--concurrency 2
--stop-on-error --timeout 45`, read back intact, honoured on a run with no
flags, overridden by a flag, and a pre-existing row migrating to the defaults.
The desktop side was screenshotted: picking a saved command restores sudo, "At
once 2", "Timeout 45" and "Stop after a failure" together, not just the text.

508 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4
@ralyodio
ralyodio merged commit 9dd26fc into main Aug 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant