# Tools By default a panel cannot reach the network, so live data comes through tools: commands declared in `panel.json`. ## Approve the commands Before a panel with tools loads, Offsider shows a native permission window with its folder, working directory, and every tool's command and resolved executable. Choose **Allow All Capabilities** or **Cancel**. There are no per-tool switches in this version. Panels without tools open directly. Approval covers all declared tools and the JSON arguments the panel sends them. Commands run with your user permissions and terminal credentials, so approve only tools you trust. Signed, provisioned builds remember approvals in Offsider's app-scoped macOS Keychain group. Development builds show **Allow All for This Session** and ask again after Offsider quits. Changing the tool definitions, panel folder, working directory, resolved program, or an executable file requires approval again. Explicit file arguments (such as `python3 tools/query.py`) are also fingerprinted. The CLI checks these before each call and refuses changed tools until you reopen the panel. Moving a panel or updating a command-line program can therefore trigger another prompt. Use **Forget Panel Approvals…** in Offsider's menu bar to revoke all approvals and close current panels. Running jobs are stopped; ordinary tool calls already in progress may finish. ## Run a tool - **Where they run.** In Offsider.app, with the directory and environment captured from `offsider open`. They continue working after the opening CLI detaches. - **The command.** An array, never passed through a shell. A first element starting `./` is relative to the panel's folder. Absolute paths run directly; other paths containing `/` are relative to the opening terminal's directory. Bare command names are resolved on `PATH` when opening and pinned to the reviewed executable. - **Input.** `offsider.call(name, args)` sends `args` as JSON on stdin. `OFFSIDER_PANEL_DIR` is set to the panel's folder. - **Output.** A JSON object on stdout is what `call` resolves with. Other JSON comes back as `{ "value": … }`, and anything that isn't JSON comes back as the text. - **Failures.** A non-zero exit, or running past `timeout` (60 seconds by default), rejects the call with the command's stderr. ```ruby #!/usr/bin/env ruby # tools/prs: {"numbers": [377, 380]} in, {"prs": [...]} out require "json" numbers = JSON.parse($stdin.read).fetch("numbers") prs = numbers.map { |n| JSON.parse(`gh pr view #{Integer(n)} --json number,state,title`) } puts JSON.generate(prs:) ``` For long-running commands, use `offsider.run(name, args)` instead. It starts a declared tool and returns a job whose `events` async iterable emits labelled `stdout` and `stderr` chunks followed by an `exit` event. `job.cancel()` terminates it. Jobs stop when the panel closes. Tools run with your user permissions, so inspect the folder before opening it. ```js const job = await offsider.run("prepare", { pr: 377 }) for await (const event of job.events) { if (event.type === "stdout" || event.type === "stderr") appendLog(event.type, event.text) if (event.type === "exit") showExit(event.code) } ``` The job receives its arguments as one JSON document on stdin. A job with no declared timeout may keep running until it exits, the panel closes, or the panel calls `job.cancel()`. Keep `job.id` if the page may reload; `offsider.attachJob(id)` replays recent output and then continues with live events. Offsider keeps the most recent 256 events for each of up to 20 jobs per panel.