Skip to content

Commit 13b427b

Browse files
backnotpropclaude
andauthored
feat: use BROWSER env var as fallback for opening UI (#153)
* feat: use BROWSER env var as fallback for opening UI Closes #119 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing reference docs (were gitignored by reference/ pattern) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6f3bfde commit 13b427b

6 files changed

Lines changed: 235 additions & 4 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ apps/opencode-plugin/review-editor.html
3333
opencode.json
3434
plannotator-local
3535
# Local research/reference docs (not for repo)
36-
reference/
36+
/reference/

apps/marketing/src/content/docs/guides/remote-and-devcontainers.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ Plannotator also detects `SSH_TTY` and `SSH_CONNECTION` environment variables fo
2828

2929
## VS Code Remote / devcontainers
3030

31-
VS Code automatically forwards ports from remote hosts. When Plannotator starts on a fixed port, VS Code detects it and makes it accessible on your local machine.
31+
VS Code sets the `BROWSER` environment variable in devcontainers to a helper script that opens URLs on your local machine. Plannotator respects this — in most cases, the browser opens automatically with no extra configuration.
32+
33+
If the automatic `BROWSER` detection doesn't work for your setup, you can fall back to manual remote mode:
3234

3335
1. Set the environment variables in your devcontainer config:
3436

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
title: "API Endpoints"
3+
description: "Server API reference for plan review, code review, and annotation servers."
4+
sidebar:
5+
order: 32
6+
section: "Reference"
7+
---
8+
9+
Plannotator runs a local Bun HTTP server for each session. The server serves the UI and exposes a REST API for communication between the browser and the CLI.
10+
11+
All servers use random ports locally or a fixed port (`19432` by default) in remote mode.
12+
13+
## Plan server
14+
15+
Used during plan review (`ExitPlanMode` hook).
16+
17+
| Endpoint | Method | Purpose |
18+
|----------|--------|---------|
19+
| `/api/plan` | GET | Returns the plan and session info |
20+
| `/api/approve` | POST | Approve the plan |
21+
| `/api/deny` | POST | Deny the plan with feedback |
22+
| `/api/image` | GET | Serve a local image by path query param |
23+
| `/api/upload` | POST | Upload an image, returns `{ path, originalName }` |
24+
| `/api/obsidian/vaults` | GET | Detect available Obsidian vaults |
25+
| `/api/save-notes` | POST | Save plan to Obsidian/Bear on demand |
26+
27+
### GET `/api/plan`
28+
29+
Returns:
30+
31+
```json
32+
{
33+
"plan": "# Implementation Plan...",
34+
"origin": "claude-code",
35+
"sharingEnabled": true,
36+
"shareBaseUrl": "https://share.plannotator.ai",
37+
"repoInfo": { "display": "my-project", "branch": "main" }
38+
}
39+
```
40+
41+
### POST `/api/approve`
42+
43+
Body:
44+
45+
```json
46+
{
47+
"permissionMode": "bypassPermissions",
48+
"agentSwitch": "claude-3-5-sonnet",
49+
"planSave": { "enabled": true, "customPath": null },
50+
"obsidian": { "vaultPath": "/path/to/vault", "folder": "plannotator", "plan": "..." },
51+
"bear": { "plan": "..." },
52+
"feedback": "optional annotations if present"
53+
}
54+
```
55+
56+
### POST `/api/deny`
57+
58+
Body:
59+
60+
```json
61+
{
62+
"feedback": "# Plan Feedback\n\nI've reviewed this plan...",
63+
"planSave": { "enabled": true }
64+
}
65+
```
66+
67+
## Review server
68+
69+
Used during code review (`/plannotator-review`).
70+
71+
| Endpoint | Method | Purpose |
72+
|----------|--------|---------|
73+
| `/api/diff` | GET | Returns the diff and session info |
74+
| `/api/feedback` | POST | Submit review feedback |
75+
| `/api/image` | GET | Serve a local image by path |
76+
| `/api/upload` | POST | Upload an image attachment |
77+
78+
### GET `/api/diff`
79+
80+
Returns:
81+
82+
```json
83+
{
84+
"rawPatch": "diff --git a/file.ts...",
85+
"gitRef": "abc1234",
86+
"origin": "claude-code"
87+
}
88+
```
89+
90+
### POST `/api/feedback`
91+
92+
Body:
93+
94+
```json
95+
{
96+
"feedback": "formatted review feedback",
97+
"annotations": [],
98+
"agentSwitch": "optional-agent-name"
99+
}
100+
```
101+
102+
## Annotate server
103+
104+
Used during file annotation (`/plannotator-annotate`).
105+
106+
| Endpoint | Method | Purpose |
107+
|----------|--------|---------|
108+
| `/api/plan` | GET | Returns the file content in annotate mode |
109+
| `/api/feedback` | POST | Submit annotation feedback |
110+
| `/api/image` | GET | Serve a local image by path |
111+
| `/api/upload` | POST | Upload an image attachment |
112+
113+
### GET `/api/plan`
114+
115+
Returns:
116+
117+
```json
118+
{
119+
"plan": "# File contents...",
120+
"origin": "claude-code",
121+
"mode": "annotate",
122+
"filePath": "/absolute/path/to/file.md"
123+
}
124+
```
125+
126+
### POST `/api/feedback`
127+
128+
Body:
129+
130+
```json
131+
{
132+
"feedback": "formatted annotation feedback",
133+
"annotations": []
134+
}
135+
```
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: "Environment Variables"
3+
description: "Complete reference for all Plannotator environment variables."
4+
sidebar:
5+
order: 30
6+
section: "Reference"
7+
---
8+
9+
All Plannotator environment variables and their defaults.
10+
11+
## Core variables
12+
13+
| Variable | Default | Description |
14+
|----------|---------|-------------|
15+
| `PLANNOTATOR_REMOTE` | auto-detect | Set to `1` or `true` to force remote mode. Uses fixed port and skips browser auto-open. |
16+
| `PLANNOTATOR_PORT` | random (local) / `19432` (remote) | Fixed server port. When not set, local sessions use a random port; remote sessions default to `19432`. |
17+
| `PLANNOTATOR_BROWSER` | system default | Custom browser to open the UI in. macOS: app name or path. Linux/Windows: executable path. Can also be a script. Takes priority over `BROWSER`. |
18+
| `BROWSER` | (none) | Standard env var for specifying a browser. VS Code sets this automatically in devcontainers. Used as fallback when `PLANNOTATOR_BROWSER` is not set. |
19+
| `PLANNOTATOR_SHARE` | enabled | Set to `disabled` to turn off sharing. Hides share UI and import options. |
20+
| `PLANNOTATOR_SHARE_URL` | `https://share.plannotator.ai` | Base URL for share links. Set this when self-hosting the share portal. |
21+
22+
## Install script variables
23+
24+
| Variable | Default | Description |
25+
|----------|---------|-------------|
26+
| `CLAUDE_CONFIG_DIR` | `~/.claude` | Custom Claude Code config directory. The install script places hooks here instead of the default location. |
27+
28+
## Remote mode behavior
29+
30+
When `PLANNOTATOR_REMOTE=1` or SSH is detected:
31+
32+
- Server binds to `PLANNOTATOR_PORT` (default `19432`) instead of a random port
33+
- Browser auto-open is skipped
34+
- The URL is printed to stderr for manual access
35+
36+
### Legacy SSH detection
37+
38+
These environment variables are still detected for backwards compatibility:
39+
40+
| Variable | Description |
41+
|----------|-------------|
42+
| `SSH_TTY` | Set by SSH when a TTY is allocated |
43+
| `SSH_CONNECTION` | Set by SSH with connection details |
44+
45+
If either is present, Plannotator enables remote mode automatically. Prefer `PLANNOTATOR_REMOTE=1` for explicit control.
46+
47+
## Port resolution order
48+
49+
1. `PLANNOTATOR_PORT` environment variable (if valid integer 1-65535)
50+
2. `19432` if in remote mode
51+
3. `0` (random) if in local mode
52+
53+
## Custom browser examples
54+
55+
```bash
56+
# macOS: open in Chrome
57+
export PLANNOTATOR_BROWSER="Google Chrome"
58+
59+
# macOS: open in specific app
60+
export PLANNOTATOR_BROWSER="/Applications/Firefox.app"
61+
62+
# Linux: open in Firefox
63+
export PLANNOTATOR_BROWSER="/usr/bin/firefox"
64+
65+
# Custom script for remote URL handling
66+
export PLANNOTATOR_BROWSER="/path/to/my-open-script.sh"
67+
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: "Keyboard Shortcuts"
3+
description: "All keyboard shortcuts available in the Plannotator UI."
4+
sidebar:
5+
order: 31
6+
section: "Reference"
7+
---
8+
9+
Keyboard shortcuts available in the Plannotator plan review, code review, and annotation UIs.
10+
11+
## Global shortcuts
12+
13+
| Shortcut | Context | Action |
14+
|----------|---------|--------|
15+
| `Cmd/Ctrl+Enter` | Plan review (no annotations) | Approve plan |
16+
| `Cmd/Ctrl+Enter` | Plan review (with annotations) | Send feedback |
17+
| `Cmd/Ctrl+Enter` | Code review | Send feedback / Approve |
18+
| `Cmd/Ctrl+Enter` | Annotate mode | Send annotations |
19+
| `Cmd/Ctrl+S` | Any mode (with API) | Quick save to default notes app |
20+
| `Escape` | Annotation toolbar | Close toolbar |
21+
22+
## Notes
23+
24+
- `Cmd/Ctrl+Enter` is blocked when a modal or dialog is open (export, import, confirm dialogs, image annotator)
25+
- `Cmd/Ctrl+Enter` is blocked when typing in an input or textarea
26+
- `Cmd/Ctrl+S` opens the Export modal if no default notes app is configured
27+
- `Escape` in the annotation toolbar closes it without creating an annotation

packages/server/browser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ async function isWSL(): Promise<boolean> {
4444
*/
4545
export async function openBrowser(url: string): Promise<boolean> {
4646
try {
47-
const browser = process.env.PLANNOTATOR_BROWSER;
47+
const browser = process.env.PLANNOTATOR_BROWSER || process.env.BROWSER;
4848
const platform = process.platform;
4949
const wsl = await isWSL();
5050

5151
if (browser) {
52-
// Custom browser specified
52+
// Custom browser specified (PLANNOTATOR_BROWSER takes priority over BROWSER)
5353
if (platform === "darwin") {
5454
await $`open -a ${browser} ${url}`.quiet();
5555
} else if (platform === "win32" || wsl) {

0 commit comments

Comments
 (0)