browsercli 1.0.2

A browser visual workspace for AI agents
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# Plugin Development Guide

This guide covers how to create, install, and use plugins for browsercli.

## Overview

Plugins extend browsercli with three capabilities:

1. **Templates** — HTML/CSS/JS scaffolds copied to the serve directory at startup
2. **Custom RPC endpoints** — Scripts invoked via HTTP at `/x/<plugin>/<action>` paths
3. **Lifecycle hooks** — Fire-and-forget scripts triggered by daemon events

Plugins are directories containing a `plugin.json` manifest and executable scripts. No compilation, WASM, or dynamic libraries required — just scripts that read JSON from stdin and write JSON to stdout.

## Quick Start

```bash
# Scaffold a new plugin
browsercli plugin init my-plugin

# The scaffold is created at:
#   macOS/Linux: ~/.browsercli/plugins/my-plugin/
#   Windows:     %LOCALAPPDATA%\browsercli\plugins\my-plugin\

# List installed plugins
browsercli plugin list

# Start with a plugin template
browsercli start --template my-template
```

## Plugin Directory Structure

```
~/.browsercli/plugins/my-plugin/
├── plugin.json              # Manifest (required)
├── templates/
│   └── dashboard/           # Template source directory
│       ├── index.html       # Entrypoint
│       ├── style.css
│       └── app.js
├── handlers/
│   └── action.sh            # RPC handler script
└── hooks/
    ├── on_start.sh          # Lifecycle hook
    └── on_navigate.sh
```

## Manifest (`plugin.json`)

Every plugin must have a `plugin.json` file in its root directory.

```json
{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "A brief description of the plugin",
  "author": "Your Name",
  "templates": {
    "dashboard": {
      "description": "Analytics dashboard template",
      "source": "templates/dashboard/",
      "entrypoint": "index.html"
    }
  },
  "hooks": {
    "on_daemon_start": "hooks/on_start.sh",
    "on_navigate": "hooks/on_navigate.sh"
  },
  "rpc": {
    "endpoints": [
      {
        "path": "/x/my-plugin/refresh",
        "handler": "handlers/refresh.sh",
        "method": "POST",
        "description": "Refresh data"
      }
    ]
  }
}
```

### Manifest Fields

| Field | Required | Description |
| --- | --- | --- |
| `name` | Yes | Plugin name. 1-64 characters, alphanumeric, hyphens, and underscores only. |
| `version` | Yes | Semantic version (`X.Y.Z`). |
| `description` | No | Brief description shown in `plugin list`. |
| `author` | No | Author name or contact. |
| `templates` | No | Map of template name to template entry. |
| `hooks` | No | Map of event name to script path. |
| `rpc` | No | RPC configuration with endpoint definitions. |

### Validation Rules

- **Name**: must match `^[a-zA-Z0-9_-]{1,64}$`
- **Version**: must be `X.Y.Z` where X, Y, Z are non-negative integers
- **Script paths**: must be relative (no leading `/`) and must not contain `..`
- **RPC paths**: must start with `/x/`
- **Template sources**: must be relative paths without `..`

## Templates

Templates are directories of static files (HTML, CSS, JS, images, etc.) that get copied to the serve directory when browsercli starts with `--template <name>`.

### Template Entry

```json
{
  "description": "A dashboard template",
  "source": "templates/dashboard/",
  "entrypoint": "index.html"
}
```

| Field | Default | Description |
| --- | --- | --- |
| `description` | `""` | Shown in `plugin list` output |
| `source` | *(required)* | Relative path to the template directory within the plugin |
| `entrypoint` | `"index.html"` | File that must exist in the source directory |

### Copy Behavior

When `--template dashboard` is passed:

1. The template source directory is located via the plugin registry
2. All files are recursively copied to the serve directory
3. Hidden files (starting with `.`), `node_modules/`, `.git/`, `target/`, and `__pycache__/` are skipped
4. Symbolic links are skipped for security
5. The entrypoint file must exist in the source or the copy fails

### Usage

```bash
# Start with a template
browsercli start --template dashboard

# Start with a template and a specific directory
browsercli start --dir ./my-project --template dashboard
```

## Custom RPC Endpoints

Plugins can define HTTP endpoints under the `/x/` namespace. These are handled by executing a script that receives JSON on stdin and writes JSON to stdout.

### Endpoint Definition

```json
{
  "path": "/x/my-plugin/refresh",
  "handler": "handlers/refresh.sh",
  "method": "POST",
  "description": "Refresh dashboard data"
}
```

| Field | Default | Description |
| --- | --- | --- |
| `path` | *(required)* | HTTP path, must start with `/x/` |
| `handler` | *(required)* | Relative path to the handler script |
| `method` | `"POST"` | HTTP method (currently all endpoints accept POST) |
| `description` | `""` | Shown in `plugin list` output |

### Handler Protocol

The handler script:

1. Receives the HTTP request body as JSON on **stdin** (may be empty)
2. Must write a JSON response to **stdout**
3. Has a **5-second timeout** by default (configurable)
4. Receives context via environment variables (see below)

#### Example Handler (Shell)

```bash
#!/bin/sh
# handlers/refresh.sh — /x/my-plugin/refresh

INPUT=$(cat)
TIMESTAMP=$(date +%s)

cat <<EOF
{
  "ok": true,
  "timestamp": ${TIMESTAMP},
  "plugin": "${BROWSERCLI_PLUGIN_NAME}"
}
EOF
```

#### Example Handler (Python)

```python
#!/usr/bin/env python3
import json
import os
import sys

request = json.loads(sys.stdin.read() or "{}")

response = {
    "ok": True,
    "plugin": os.environ.get("BROWSERCLI_PLUGIN_NAME", "unknown"),
    "echo": request,
}

print(json.dumps(response))
```

#### Example Handler (Node.js)

```javascript
#!/usr/bin/env node
const chunks = [];
process.stdin.on("data", (c) => chunks.push(c));
process.stdin.on("end", () => {
  const request = JSON.parse(Buffer.concat(chunks).toString() || "{}");
  const response = {
    ok: true,
    plugin: process.env.BROWSERCLI_PLUGIN_NAME || "unknown",
    echo: request,
  };
  console.log(JSON.stringify(response));
});
```

### Calling Plugin Endpoints

From the CLI:

```bash
# Using curl (find the HTTP port from `browsercli status`)
curl -X POST http://127.0.0.1:<port>/x/my-plugin/refresh \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"key": "value"}'
```

From Node.js:

```typescript
const ac = BrowserCLI.connect();
const result = await ac.pluginRpc("/x/my-plugin/refresh", { key: "value" });
```

From Python:

```python
ac = BrowserCLI.connect()
result = ac.plugin_rpc("/x/my-plugin/refresh", {"key": "value"})
```

## Lifecycle Hooks

Hooks are scripts that run in response to daemon events. They are fire-and-forget: errors are logged but never crash the daemon.

### Supported Events

| Event | Trigger | Extra Environment Variables |
| --- | --- | --- |
| `on_daemon_start` | Daemon starts and browser is ready | *(none)* |
| `on_daemon_stop` | Daemon is shutting down | *(none)* |
| `on_file_change` | File changed in serve directory | `BROWSERCLI_FILE_PATH` |
| `on_navigate` | Browser navigates to a new URL | `BROWSERCLI_URL` |
| `on_console` | Console message emitted | JSON on stdin |
| `on_network` | Network request completed | JSON on stdin |

### Hook Script

Hook scripts:

- Run asynchronously (daemon does not wait for completion)
- Have a **5-second timeout**
- Receive context via environment variables
- For `on_console` and `on_network`, receive event data as JSON on stdin
- Multiple plugins can register hooks for the same event (all run)

#### Example Hook

```bash
#!/bin/sh
# hooks/on_start.sh — runs when daemon starts

echo "[my-plugin] Daemon started on port ${BROWSERCLI_HTTP_PORT}"
echo "[my-plugin] Serving from ${BROWSERCLI_DIR}"
```

## Environment Variables

All plugin scripts (hooks, handlers) receive these environment variables:

| Variable | Description |
| --- | --- |
| `BROWSERCLI_TOKEN` | Bearer token for RPC authentication |
| `BROWSERCLI_HTTP_PORT` | HTTP server port |
| `BROWSERCLI_DIR` | Path to the serve directory |
| `BROWSERCLI_BASE_URL` | Full base URL (e.g., `http://127.0.0.1:8080`) |
| `BROWSERCLI_STATE_DIR` | Path to the state directory (`~/.browsercli/`) |
| `BROWSERCLI_PLUGIN_NAME` | Name of the current plugin |

Additional event-specific variables are listed in the hooks table above.

## Cross-Platform Notes

### Script Interpreters

On **Unix** (macOS/Linux), scripts use their shebang line (`#!/bin/sh`, `#!/usr/bin/env python3`, etc.).

On **Windows**, the executor maps file extensions to interpreters:

| Extension | Interpreter |
| --- | --- |
| `.sh` | `bash` (Git Bash, WSL, or MSYS2) |
| `.py` | `python` |
| `.js` | `node` |
| `.ps1` | `powershell` |
| `.bat`, `.cmd` | `cmd /C` |

### Recommendations

- Use `#!/bin/sh` for maximum portability
- For cross-platform plugins, consider Python or Node.js scripts
- Mark scripts as executable on Unix: `chmod +x handlers/*.sh hooks/*.sh`
- Test on all target platforms

## Security Model

Plugins run with the same permissions as the browsercli daemon process. The following security measures are in place:

- **Path traversal prevention**: script paths cannot contain `..` or be absolute
- **Namespace isolation**: all plugin RPC endpoints must use the `/x/` prefix
- **Timeout enforcement**: scripts are killed after the timeout expires
- **Template source validation**: template directories must be relative to the plugin directory
- **No network access control**: scripts can make network requests — only install trusted plugins

### Best Practices

- Only install plugins from trusted sources
- Review `plugin.json` and handler scripts before installation
- Keep plugin scripts simple and focused
- Use the principle of least privilege in handler scripts
- Do not store secrets in `plugin.json`

## Testing Plugins

### Manual Testing

```bash
# Install your plugin
cp -r my-plugin ~/.browsercli/plugins/

# Verify it loads
browsercli plugin list

# Test a template
browsercli start --template my-template

# Test an RPC endpoint (find port from status)
browsercli status --json | jq .http_port
curl -X POST http://127.0.0.1:<port>/x/my-plugin/action \
  -H "Authorization: Bearer $(cat ~/.browsercli/session.json | jq -r .token)"
```

### Testing Handler Scripts Directly

You can test handler scripts without running browsercli:

```bash
# Test a handler script
echo '{"key":"value"}' | BROWSERCLI_PLUGIN_NAME=test ./handlers/refresh.sh

# Test a hook script
BROWSERCLI_HTTP_PORT=8080 BROWSERCLI_DIR=/tmp/test ./hooks/on_start.sh
```

## FAQ

**Q: Can I use any programming language for plugin scripts?**

A: Yes. Any executable script works. The daemon spawns a child process and communicates via stdin/stdout. Use the shebang line (Unix) or appropriate file extension (Windows).

**Q: What happens if a hook script fails?**

A: Hook failures are logged but never crash the daemon. The hook event is fire-and-forget.

**Q: What happens if an RPC handler script fails?**

A: The daemon returns an HTTP 500 error with the script's stderr content.

**Q: Can multiple plugins define the same template name?**

A: Yes, but the last plugin loaded wins. A warning is logged for conflicts.

**Q: Can multiple plugins register hooks for the same event?**

A: Yes. All registered hooks run (in parallel via `tokio::spawn`).

**Q: What is the script execution timeout?**

A: 5 seconds by default. Scripts exceeding this are killed.

**Q: Where do plugins store state?**

A: Plugins can use the serve directory (`BROWSERCLI_DIR`), the state directory (`BROWSERCLI_STATE_DIR`), or any location accessible to the user. There is no dedicated per-plugin state directory.

## Example

See [`examples/plugins/dashboard/`](examples/plugins/dashboard/) for a complete example plugin with a template, RPC handlers, and lifecycle hooks.