Background commands
For a long job or a server that never ends, run the command in the background. It keeps running after your terminal disconnects.
$ froog exec my-box --detach -- 'npm run build && npm test'
job_3f2a9c1d8e7b6a50
$ froog logs my-box job_3f2a9c1d8e7b6a50 -f # follow until it ends; exits with its exit code
$ froog jobs my-box # list: running, exited, killed
$ froog kill my-box job_3f2a9c1d8e7b6a50 # stop it and everything it started
$ froog exec my-box --timeout 0 -- ./long-script.sh # start it and stream it to the end
Output (stdout and stderr together) and the exit code are kept on the sandbox's own disk under ~/.froog/jobs/, so they survive a stop. Running processes don't: a job that was running when the sandbox stopped shows as lost (or paused during a warm resume window, after which it carries on). A running background command doesn't hold off auto-stop: give the sandbox a timer long enough for it (froog extend NAME --hours 8). Every background command also gets a record in the command history, which you can read while the sandbox is stopped.
One-off runs and pushing a folder
$ froog run -- npm test # new sandbox, this folder in ~/FOLDER, run, print the cost, delete
$ froog run --from web-stack --size large -- make e2e
$ froog run --keep -- ./build.sh # keep it afterwards
$ froog run --push ./api --timeout 1800 -e NODE_ENV=test -- bun test
$ froog push my-box # this folder to my-box:~/FOLDER
$ froog push my-box ../api --to src/api
froog run streams the command's output and exits with its exit code, so it works in CI and scripts. The last line, on stderr, sums it up: exit 0 · 4m 12s · large · $0.0048 · run-x7k2 deleted. The sandbox has a 15-minute auto-stop that froog run pushes back every 5 minutes while it runs, and snapshots off, so if froog run dies, the sandbox stops and is erased on its own within 15 minutes. Ctrl-C stops the command and deletes the sandbox (press it twice to exit at once). With --keep the sandbox stays, with snapshots on (your account's setting), until that auto-stop, which keeps its disk: froog extend NAME --hours 4 for longer, froog start NAME after it stopped. froog push uploads a folder as one archive through the API (no SSH needed): in a git repo, the tracked files and the untracked ones that aren't ignored. Elsewhere every file except .git, node_modules, target, .venv, dist, build and the like. Up to 100 MiB compressed. Existing files with the same names are overwritten. Others stay.
froog run flag | Default | What it does |
|---|---|---|
--push DIR | this folder | Push this folder to ~/DIR-NAME first and run the command there. |
--no-push | off | Push nothing. The command runs in ~. |
--cwd DIR | the pushed folder | Run in this directory, relative to ~ or absolute. |
--timeout SECS | no limit | Stop the command after this many seconds and exit 124. |
-e, --env KEY=VALUE | none | A variable for the command. KEY alone copies it from this shell. Repeat it. |
--from SAVED, --size | default | Start from a saved snapshot. small, default or large. |
--no-env | off | None of your account's environment: no variables, repos, secret files or setup script (also --no-environment). |
--keep | off | Keep the sandbox afterwards, with snapshots on. It stops at its 15-minute auto-stop. |
--json | off | One line {"type":"output","data":…} per chunk of output on stdout, then {"type":"summary",…} with name, exit_code, timed_out, interrupted, elapsed_secs, cost_usd (null when not billed), size, deleted and kept. |
froog exec --cwd DIR runs a command in a directory (relative to ~), and -e KEY=VALUE (repeat it) sets a variable for that command only. Both work with --detach and --timeout 0. The history keeps the command, not the values.
Standard input. Pipe or redirect into froog exec and the command reads it:
$ froog exec my-box -- python3 - < script.py
$ git diff | froog exec my-box --cwd app -- git apply
$ froog exec my-box --detach -- ./import.sh < rows.csv # a background job's stdin, given at start
Up to 1 MiB. For more, upload a file and read it from there. froog exec sends stdin only when it's a pipe or a file. A pipe that sends nothing for 3 seconds and stays open (as some agent harnesses leave it) is ignored, with a note on stderr. --no-stdin skips it (like ssh -n, e.g. inside a while read loop). The API takes stdin (text) or stdin_b64 (base64, for binary) on POST /exec, streamed or not, and on POST /jobs. The input ends after it, so a command reading to the end finishes. More than 1 MiB is 413 stdin_too_large. A background job's input is kept beside its output, in ~/.froog/jobs/<id>/stdin.
Writing to a running job. Start it with --stdin-open and its input stays open: write to it whenever you like, and close it to send end of file.
$ froog exec my-box --detach --stdin-open -- python3 -i
job_3f2a9c1d8e7b6a50
$ froog stdin my-box job_3f2a9c1d8e7b6a50 'print(6 * 7)' # a newline is added
$ cat more.py | froog stdin my-box job_3f2a9c1d8e7b6a50 # or pipe it in
$ froog stdin my-box job_3f2a9c1d8e7b6a50 --close # end of file: python3 exits
The API: stdin_open: true on POST /jobs (input given there with stdin comes first), then POST /jobs/{id}/stdin with {data?, data_b64?, close?}, up to 1 MiB per call. The input is a pipe (~/.froog/jobs/<id>/in): writes arrive in order, and a command that doesn't read its input fills it after 64 KiB, so a write that can't finish in 30 seconds fails with 409 stdin_blocked (part of it may have gone in). Writing to a job started without stdin_open is 409 stdin_not_open, after close 409 stdin_closed, and after the job ended 409 job_ended. Stopping the job closes its input too.