# Use the computer

Call `devices` first. Pass the selected UUID as `deviceId` on every hosted operation call. For MCP, discover current inputs with `tools/list`.

## Read or change a file

Use `files_read` with `path`, `files_write` with `path` and `content`, or `files_edit` with `path`, `oldText`, and `newText`. These tools accept UTF-8 text up to 1 MB. Paths must be absolute or start with `~/`.

`files_write` replaces the file. `files_edit` replaces text that must match exactly once. Read the file before editing and check the destination before overwriting it.

## Run a command

Call `shell_exec` with `command` and, when needed, `cwd` and `timeoutMs`. The timeout is at most 55,000 milliseconds. It runs as the computer user who installed the helper. Shell access includes files outside any file-tool folder limits.

Check `stdout`, `stderr`, and `code`. A nonzero exit code means the command finished with an error.

If the result is `outcome_unknown`, the command may have run. Inspect `operations` or the resulting files. Never automatically repeat it. Reusing a request ID rejects duplicate execution; it does not replay the earlier result.

## Transfer a file

For files too large for text tools, use a transfer. The agent host must save downloaded bytes itself; hosted MCP cannot write to its filesystem.

For large files:

- Download: call `bulk_export`, poll `bulk_status` until ready, then fetch `downloadUrl` with your own download tool.
- Upload: follow the part-upload steps below, then poll `bulk_status` until `completed` or `failed`.
- Stop or clean up: call `bulk_cancel`. It deletes temporary cloud data, not a file already delivered to the computer.

Keep the same agent token for the whole job. Download links are private and short-lived. Jobs last up to one hour and support files up to 10 GiB. Temporary cloud storage is not a backup.

For small binary files, call `transfer_start`, send zero-based chunks with `transfer_chunk` using `transferId`, `index`, and `base64`, then call `transfer_finish`. Each chunk is at most 192 KB before base64 encoding. To download, call `transfer_pull` and set `offset` to the returned `nextOffset` until `done` is true.

### Upload parts for a large file

1. Call `bulk_upload` with `deviceId` and the exact byte `size`.
2. Use the returned `partSize`. Add a one-based `partNumber` query parameter to `uploadUrl`, preserving its existing query parameters. PUT each binary part with `Content-Length` and `X-Content-SHA256`, the lowercase hex SHA-256 of that part. For an empty file, send one empty part.
3. POST to `completeUrl` after all parts succeed.
4. Call `bulk_import` with `deviceId`, `transferId`, and the destination `path`.

For REST bulk calls, supply a UUID `requestId`. Export and upload use it as the transfer ID, so inspect that ID with `bulk.status` if the start response is lost. Never automatically start another job for an unknown outcome.

## Use the browser

The person must approve browser access separately. Approval covers every open tab in the selected Chrome or Helium profile, including signed-in pages.

Call `browser_call` with `name: "tools/list"`, then use a returned tool name and its inputs. The helper starts Chrome DevTools MCP when needed. For a separate browser with copied logins, follow [browser sessions](browser-sessions.md). The person imports locally; call `profiles/list` and pass the chosen `profile` UUID on each browser call.

If `browser_installing` is returned, no browser action ran. Wait a few seconds and retry the call while the helper installs Chromium in the background. Do not automatically retry `outcome_unknown`. Live attach to daily Chrome/Helium is removed; both legacy browser values select the same managed profile.

## Use local MCP servers

Call `mcp.servers` to list servers the person already configured for Claude, Claude Code, Codex, Cursor or Windsurf on that computer. It returns ids, names and sources, never commands or secrets. Call `mcp.tools` with `{ server }` to see a server's tools, then `mcp.call` with `{ server, tool, arguments }`. Stdio servers start for the call and stop afterwards. Calls run with the server's own permissions; the Terminal sandbox does not apply.

## Read a Terminal result

Terminal commands run in a sandbox by default. Writes outside approved folders and the temp folder, reads of credentials or browser data, and connections to local network addresses fail. The result's `sandbox.blocked` is true and `stderr` contains a `<sandbox_violations>` block saying what was refused. Tell the person; do not try to work around it. `update_helper_for_terminal_sandbox` means the computer's helper is too old to sandbox: ask the person to rerun setup, or to allow full Terminal access. Temporary connections never include Terminal.

## Handle an unavailable computer

- Offline: Choose queue or discard based on the task. Use the queue flow below for authorized work that should run when the computer returns.
- Expired or revoked: Request a new connection and local approval.

The `operations` tool returns recent operation metadata. The helper also keeps a bounded log at `~/.ezremote/activity.jsonl`. Revocation and cancellation block new work but cannot guarantee stopping an operation already sent.

## Queue work explicitly

A normal operation reports `device_offline` without scheduling work. Choose queue
or discard based on the task, including authorized proactive work. Queueing is an
agent decision; it does not need another human confirmation for already authorized
work. Expired and revoked access still blocks work.

1. Call `queue_operation` with `{ deviceId, operation, args, requestId }`. Every
   operation name from `tools/list` can queue, using its dotted API name such as
   `files.read` or `bulk.export`. Generate and save a fresh UUID first. Do not reuse
   the ID of the earlier offline call.
2. The default deadline is 30 minutes and the maximum is 12 hours. Set
   `expiresInSeconds` (60 to 43200), or `waitUntil` as a duration such as
   `"3 hours"`, a clock time such as `"3:30pm"` with an IANA `timeZone`, or an ISO
   time. The job runs as soon as the computer is online and is discarded at the
   deadline. The reply's `waitUntil` states the interpreted deadline. Computer access
   and direct-token expiry can shorten it. Queueing never extends computer access.
3. The immediate reply includes `status: "queued"`, `computerState`, `jobId` and
   `expiresAt`. This confirms acceptance. Inform the person that the work is waiting.
4. Call `job_wait` once with `{ jobId }`. The service waits and returns the final
   result. Use `reply.result` on completion and inspect shell exit codes. Stop on
   `completed`, `failed`, `cancelled`, `expired` or `outcome_unknown`. Never repeatedly
   call `job_status` to keep a model busy while waiting.
5. Use `job_cancel` to stop pending work. `cancelled: true` prevents dispatch.
   `cancelled: false` means it was dispatched or terminal and cannot be stopped by
   cancellation. Aborting a wait leaves the job queued.

`job_wait` supports MCP task augmentation. A task-capable host can receive a task
ID immediately, manage the wait outside model turns, and retrieve the result
through `tasks/result`. Task status and cancellation are authorized by the same
job ownership checks. Without task support, the tool waits in its current HTTP
call. Host timeouts can close that call; resume waiting with the same job ID rather
than submitting again. Waking a closed agent thread requires integration in the
host app. A hosted MCP server alone cannot start a new model turn in another app.

If a submission reply is lost, inspect the saved request ID with `job_status`.
Duplicate IDs reject execution without replaying a result. For `outcome_unknown`,
inspect activity or resulting state before deciding what to do.

The helper reconnect wakes the service's queue. Dispatch rechecks permission,
folder policy, revocation and the original computer-access deadline. OAuth
jobs follow the saved MCP connection across credential renewal; hourly credential
expiry does not discard accepted work. Direct-token jobs require that individual
token to remain valid. A reconnect never extends a job's deadline. Known
undelivered calls can wait again; uncertain sends never retry automatically.

Queued transfer calls still depend on their transfer's existing state and
expiry. Queueing a chunk, import or status call cannot recreate expired transfer
state. Bulk work uses the public origin saved at submission and the ordinary
bulk authorization path, rather than forwarding public bulk arguments directly
to the helper.

Arguments and results are capped at 1.5 MB of encoded JSON each. Oversized results report
`job_result_too_large` after the operation may have run. Results remain until one
hour after the job deadline. Queued and dispatched jobs count toward 32 jobs per computer
and 1,000 globally. Retained completed, cancelled, failed and expired jobs do not
consume active queue capacity. A separate storage budget of 64 MiB per computer
and 256 MiB globally includes reserved result space; `job_storage_full` means
retained results must expire before more work can be accepted. D1 stores these arguments and results.

REST uses `POST https://tunnel.masti.lol/api/jobs` with the queue body and an agent bearer token.
`/api/jobs/wait` waits up to 55 seconds for a result; a client library can repeat
that wait without involving a model. `/api/jobs/status` inspects once, and
`/api/jobs/cancel` cancels, each accepting `{ "jobId": "..." }`.

The SDK exposes `remote.queue(deviceId, operation, args, { requestId,
expiresInSeconds })`, `remote.waitJob(jobId, { signal, onUpdate })`, `remote.job`
and `remote.cancelJob`. `waitJob` uses the server wait endpoint. Pi exposes
`ezremote_queue` with the same optional deadline and `ezremote_job`, which waits
by default. Set `wait: false` to inspect once. Native Pi tools still run on the Pi
host when `device` is omitted. Configure a scoped agent token through isolated
Pi settings; store only the activated agent token.

## Keep a Terminal session

Use `shell.start` for commands that may exceed 30 seconds, servers, prompts and
REPLs. Save its `sessionId`. Call `shell.read` with that ID, a byte `cursor`
starting at 0 and optional `waitMs` up to 20000. Save the returned cursor for the
next read unchanged. `bytesDropped > 0` means output was lost from the buffer. `maxBytes` defaults to 65536 and accepts 4–524288 bytes. The cursor advances only over returned bytes; `more: true` means output remains. Reads preserve UTF-8 character boundaries. `running` and `code`
say whether the process has exited. Send a line with `shell.write`, including its
newline. Use `shell.status`, `shell.list` and `shell.kill` to inspect or stop work.

Each connection has eight sessions, each with a 1 MB output buffer. Sessions end
at connection expiry or after two hours without an agent read, write or status
call. Set `idleMinutes` on `shell.start` to 1–1440 minutes; the default is 120. Connection expiry always wins. Exited output is retained for five minutes.
Background jobs (`cmd &`) end when the session's shell exits; keep the shell in
the foreground, for example with `wait`, to keep them running. Sessions run through the same
Terminal sandbox as `shell.exec`. Pipes support interactive line input, but
programs requiring a TTY are unsupported. The person can inspect and kill sessions
from the helper tray or `ezremote sessions list` and `ezremote sessions kill ID`.

## Read, list and search with coverage

`files.read` without `limit` returns the whole file up to 1 MB. Before writing a
file back, check that its read `status` is `complete`. For large files, page with a
zero-based line `offset` and a `limit` up to 1000, or `tail: true`. Pass the opaque
`cursor` to continue. `files.list` accepts `depth` up to 20
and `limit` up to 1000. `files.stat` returns size, type and modification time.
`files.search` accepts a literal `query`, `mode: "names"` or `"content"`, depth,
limit, `timeoutMs` up to 3000 and `maxBytes` up to 16000000. Content search returns
the first matching line per file. Continue with `cursor` and the same query options.

All these results include `status`, `skipped` and `scannedBytes`. `complete` covers
the requested range; `partial` has another page or skipped files; `truncated` hit
a size or entry budget; `timed_out` hit the time budget. Follow a cursor when one
is returned and retain skipped-file reports across pages. Empty results establish
absence only when status is `complete`. Binary files, files over the search byte
budget, inaccessible paths and symlink directories are reported in `skipped`.
Search scans at most 1 MB per file. Raise the budget or use paged reads to inspect
an oversized file. Filesystem edits during pagination can change results.

## Resolve an uncertain outcome

Never retry `outcome_unknown` blindly. Save the request ID and call MCP
`resolve_operation` with `deviceId` and `requestId`, or POST those fields to
`https://tunnel.masti.lol/api/operations/resolve`. The SDK provides
`remote.resolve(deviceId, requestId)`. Resolution never runs the operation again.

`running` describes a mutating request the current helper has admitted. Read-only calls are not journaled and resolve to `unknown`; they are safe to retry.
`refused_expired` means it did not start because its deadline passed. `finished`
includes the saved reply. A `resultTruncated` reply retains the success or failure
but omits an oversized result. `unknown` means the helper is offline, has no
retained record, or restarted while the outcome was uncertain. Inspect activity
and resulting state before deciding what to do next. A fresh ID is new work, not
a safe retry. Results remain available for at most 24 hours, 1,024 requests or
16 MB, whichever limit is reached first, and each reply is capped at 64 KB.

Synchronous calls accept `deadlineInMs` from 1000 to 55000, default 30000. This is
an admission deadline, not cancellation of a running command. Use sessions for
long commands and explicit offline jobs when work should wait for a computer.
