# SafeCoBrowser CLI — Usage

`safecobrowser` is a thin command-line client for a **running SafeCoBrowser session**. It talks to the
same broker-gated control surface as the MCP endpoint, so anything an AI agent can do over MCP,
you can do (and test) from the shell.

Like every SafeCoBrowser surface, the CLI **cannot change the permission mode** — that stays with the
human in the SafeCoBrowser UI. The CLI can only call tools *within* whatever mode the active tab is in.

---

## 1. Running it

Until a `bin` is installed, run it via Node:

```bash
node dist/cli/safecobrowser.js <command> [args]
```

Optional convenience alias:

```bash
alias safecobrowser='node /Volumes/MacData/macOS/JoinTab/dist/cli/safecobrowser.js'
```

(The rest of this doc writes `safecobrowser …` for brevity.)

### Prerequisites
- **SafeCoBrowser must be running** for `status`, `tools`, and `invoke`. On launch it writes
  `~/.safecobrowser/endpoint.json` (`{ "url": "http://127.0.0.1:8676", "token": "<bearer>" }`), which
  the CLI auto-discovers. No flags, no config.
- **`audit`** reads the local log directly and works **even when SafeCoBrowser is not running**.
- The control server binds to `127.0.0.1` only and requires the bearer token (handled for you).

---

## 2. Commands

### `safecobrowser status`
Show session status, including the **active tab id** (the tab your tool calls hit).
```bash
$ safecobrowser status
{ "ok": true, "tab": "t2" }
```
> Note: `status` does **not** report the permission mode — the control API doesn't expose it. You
> learn the mode from results: a `permission_denied` means the tab is below that tool's minimum mode.

### `safecobrowser tools`
List the tools the broker exposes, with each tool's minimum mode, risk, and whether it needs approval.
```bash
$ safecobrowser tools
{
  "tools": [
    { "name": "read_page",       "minMode": "read",    "risk": "low",    "requiresApproval": false },
    { "name": "screenshot",      "minMode": "read",    "risk": "low",    "requiresApproval": false },
    { "name": "list_recipes",    "minMode": "read",    "risk": "low",    "requiresApproval": false },
    { "name": "get_recipe",      "minMode": "read",    "risk": "low",    "requiresApproval": false },
    { "name": "inspect_element", "minMode": "inspect", "risk": "low",    "requiresApproval": false },
    { "name": "read_console",    "minMode": "inspect", "risk": "low",    "requiresApproval": false },
    { "name": "read_network",    "minMode": "inspect", "risk": "low",    "requiresApproval": false },
    { "name": "click",           "minMode": "act",     "risk": "medium", "requiresApproval": true  },
    { "name": "fill",            "minMode": "act",     "risk": "medium", "requiresApproval": true  },
    { "name": "run_js",          "minMode": "develop", "risk": "high",   "requiresApproval": true  }
  ]
}
```

### `safecobrowser invoke <tool> [json-input]`
Invoke a tool through the broker. `[json-input]` is a single JSON argument (omit for no-arg tools).
```bash
$ safecobrowser invoke read_page
{ "ok": true, "output": { "url": "https://example.com/", "title": "Example", "text": "…", "links": [ … ] } }
```
- On success: prints `{ "ok": true, "output": … }` and exits **0**.
- On denial/failure: prints `{ "ok": false, "reason": "…", "message": "…" }` and exits **2**.
- Approval-gated tools (`click`, `fill`, `run_js`) **block** until the human Approves/Rejects in the
  UI (or auto-approve is on). Reject → `approval_rejected`; ~120s no answer → `approval_required`.

### `safecobrowser audit [n]`
Show the last **n** audit records (default **20**). Works offline.
```bash
$ safecobrowser audit 5
#41 2026-06-20T10:39:12.004Z ALLOWED <t1> read_page [read]
#42 2026-06-20T10:40:01.882Z ALLOWED <t1> click [act] — a[href*="/contact"]
#43 2026-06-20T10:41:55.310Z ALLOWED <t1> fill [act] — #email (value hidden)
#44 2026-06-20T10:42:08.991Z DENIED  <t1> click [act] approval_rejected
#45 2026-06-20T10:43:20.140Z ALLOWED <t1> click [act] — button[type="submit"]
```
Format: `#<seq> <ISO time> <OUTCOME> <tabId> <toolName> [<mode>] <reason?> — <detail?>`.
`fill` shows the **selector** but never the value; `click`/`inspect_element` show the selector;
`run_js` shows the full script.

### `safecobrowser audit verify`
Re-walk the audit log's tamper-evident hash chain.
```bash
$ safecobrowser audit verify
audit OK — 45 record(s) verified            # exit 0
# or, if tampered:
audit TAMPERED — chain broken at record #12 (11 ok before it)   # exit 2
```

### `safecobrowser help`
Print usage. (`help`, `--help`, `-h`, or no command.)

---

## 3. Tool invocation reference

Each call requires the active tab to be at least the listed mode (set by the human in the UI).

| Tool | Needs mode | Example |
|------|-----------|---------|
| `read_page` | Read | `safecobrowser invoke read_page` |
| `screenshot` | Read | `safecobrowser invoke screenshot` |
| `list_recipes` | Read | `safecobrowser invoke list_recipes` |
| `get_recipe` | Read | `safecobrowser invoke get_recipe '{"name":"Search AskMingLi"}'` |
| `inspect_element` | Inspect | `safecobrowser invoke inspect_element '{"selector":"form button"}'` |
| `read_console` | Inspect | `safecobrowser invoke read_console '{"limit":50}'` |
| `read_network` | Inspect | `safecobrowser invoke read_network '{"limit":50}'` |
| `click` | Act | `safecobrowser invoke click '{"selector":"a[href*=\"/contact\"]"}'` |
| `fill` | Act | `safecobrowser invoke fill '{"selector":"#email","value":"hi@example.com"}'` |
| `run_js` | Develop | `safecobrowser invoke run_js '{"script":"document.title"}'` |

### Inputs & outputs
- **`read_page`** → `{ url, title, text, links:[{href,text}] }`
- **`screenshot`** → `{ mimeType:"image/png", base64 }` (large — pipe to a file, see §5)
- **`inspect_element`** `{ selector }` → `{ matched, tagName?, attributes?, text?, outerHTML?, rect? }`
- **`read_console`** `{ limit? }` (default 100, max 1000) → `[{ level, text, ts }]`
- **`read_network`** `{ limit? }` (default 100, max 1000) → `[{ method, url, status?, ts }]`
- **`click`** `{ selector }` → `{ clicked, matched }`
- **`fill`** `{ selector, value }` → `{ filled, matched }` (value never logged; fill ≠ submit)
- **`run_js`** `{ script }` (≤100,000 chars) → the script's return value

> **Quoting tip:** wrap the JSON in single quotes and escape inner double quotes:
> `'{"selector":"input[name=\"email\"]"}'`.

---

## 4. Result shape & exit codes

Every `invoke` prints one JSON object:
```ts
{ ok: true,  output: <tool output> }            // success
{ ok: false, reason: <code>, message?: string } // denial / failure
```

| Command | Exit 0 | Exit 2 | Exit 1 |
|---------|--------|--------|--------|
| `invoke` | `ok: true` | `ok: false` (any deny reason) | bad usage / not running |
| `audit verify` | chain OK | chain tampered | — |
| `status`/`tools` | success | — | not running / error |

### Deny `reason` values
`permission_denied` (mode too low / AI off) · `invalid_input` (bad JSON shape) ·
`approval_required` (no approval, incl. timeout) · `approval_rejected` (human said no) ·
`revoked` (Stop AI / mode change mid-call) · `timeout` (handler too slow) ·
`handler_error` (tool threw) · `unknown_tool`.

---

## 5. Recipes

```bash
# Save a screenshot to a PNG
safecobrowser invoke screenshot \
  | node -e 'const r=JSON.parse(require("fs").readFileSync(0));require("fs").writeFileSync("/tmp/shot.png",Buffer.from(r.output.base64,"base64"))'

# Pretty-print just the page text
safecobrowser invoke read_page | node -pe 'JSON.parse(require("fs").readFileSync(0)).output.text'

# Find failed requests (non-2xx) for debugging
safecobrowser invoke read_network '{"limit":100}' \
  | node -pe 'JSON.parse(require("fs").readFileSync(0)).output.filter(e=>e.status&&e.status>=400)'

# Fill a form, then submit as a separate (approved) step
safecobrowser invoke fill '{"selector":"#email","value":"hi@example.com"}'
safecobrowser invoke click '{"selector":"button[type=\"submit\"]"}'
```

---

## 6. Troubleshooting

| Symptom | Cause / fix |
|---------|-------------|
| `SafeCoBrowser is not running (no ~/.safecobrowser/endpoint.json)` | Launch SafeCoBrowser first. |
| `invoke` returns `permission_denied` | The active tab's mode is below the tool's minimum. Ask the human to raise the mode (Read/Inspect/Assist/Developer) in the toolbar. The CLI cannot raise it. |
| `invoke` **hangs** on `click`/`fill`/`run_js` | It's waiting for the human's approval card. Approve/Reject in the UI, or turn on the tab's auto-approve. |
| `approval_required` after a pause | The card timed out (~120s) with no answer. |
| `revoked` | The human hit **Stop AI**, changed the mode, or closed the tab mid-call. Re-confirm before continuing. |
| `invalid_input` | The JSON shape is wrong for that tool (see §3). |
| `audit` prints `(no audit log yet)` | Nothing has been logged on this machine yet. |
| Calls hit the wrong window | Only one SafeCoBrowser should run; the CLI targets whichever instance wrote `endpoint.json`. Quit stray instances. |

---

## 7. Relationship to MCP

The CLI and the MCP endpoint (`POST {url}/mcp`) are **two front-ends over one broker**. Same tools,
same permission checks, same audit log, same inability to change the mode. Use the CLI for
scripting/manual testing; wire MCP for Claude Code / Codex. For the agent-facing version of all
this, see **`safecobrowser_ai_agent_guide.md`**.
