diff --git a/AGENTS.md b/AGENTS.md index b00d546e..09d35bc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ how it works, and why it exists. - **Repo:** `hydro13/tandem-browser` (GitHub: hydro13) - **Stack:** Electron 40 + TypeScript + Express.js API (`localhost:8765`) + - MCP server (253 tools) + MCP server (257 tools) - **Goal:** An agent-first browser where any AI (via MCP, HTTP API, or WebSocket) and a human browse together - **Philosophy:** Local-first, privacy-first, no cloud dependencies in the @@ -39,7 +39,7 @@ tandem-browser/ │ ├── snapshot/ # Accessibility tree with @refs │ ├── network/ # Inspector + mocking │ ├── sessions/ # Multi-session isolation -│ ├── mcp/ # MCP server (253 tools, full API parity) +│ ├── mcp/ # MCP server (257 tools, full API parity) │ │ ├── server.ts # MCP server entry point │ │ └── tools/ # Tool definitions (one file per domain) │ ├── agents/ # TaskManager, X-Scout, TabLockManager diff --git a/CHANGELOG.md b/CHANGELOG.md index 95bd95dc..6ad9578a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ All notable changes to Tandem Browser will be documented in this file. messages and reply into the panel without requiring OpenClaw on the host. OpenClaw remains available as a separate backend and the shell falls back to Tandem local chat when the gateway is not reachable. +- **Agent bootstrap contract** (`src/api/routes/bootstrap.ts`, + `src/api/agent-bootstrap.ts`) - adds authenticated `GET /agent/bootstrap` + with runtime workspace/tab context, a required startup sequence, operating + rules, toolbox guidance, and tool-selection hints so newly connected agents + understand how to use Tandem as a full browser layer. ### Changed @@ -20,6 +25,64 @@ All notable changes to Tandem Browser will be documented in this file. agents configured in Connected Agents, so a lone Codex binding no longer exposes stale OpenClaw/Claude tabs or lets the legacy Claude polling backend mirror the Codex conversation. +- **Pairing response** (`src/api/routes/pairing.ts`) - now returns bootstrap + next-read links, docs URLs, and snapshot-first workflow guidance alongside + the durable binding token without changing the existing token contract. +- **Paired-agent startup enforcement** (`src/api/server.ts`, + `src/pairing/manager.ts`) - new binding tokens must read `/skill`, + `/agent/manifest`, and `/agent/bootstrap` with the token before normal + API/MCP routes are unlocked; skipped startup now returns + `428 agent_startup_required` instead of silently letting the agent continue + uninformed. Legacy local `api-token` clients remain ungated. +- **Agent discovery docs** (`/agent`, `/skill`, `/agent/manifest`, + `skill/SKILL.md`) - now explicitly tell agents to read `/skill`, + `/agent/manifest`, `/agent/bootstrap`, `/status`, and `/workspaces` after + connecting instead of stopping at successful authentication. + +## [v1.11.0] - 2026-05-05 + +Configurable Agent API port release. Tandem now lets users change the Agent API +port from the default `8765` while preserving local MCP/HTTP clients and remote +Tailscale agent connectivity. + +### Added + +- **Agent API port setting** (`src/config/manager.ts`, `shell/settings.html`) - + stores `general.apiPort`, validates TCP port values, and shows local plus + remote endpoint previews in Settings > Connected Agents. +- **Endpoint bootstrap artifacts** (`~/.tandem/api-port`, + `~/.tandem/api-endpoints.json`) - publish the configured local loopback URL + and Tailscale/private-network metadata without replacing the readable + `api-token` compatibility contract. +- **Port helper tests** (`src/config/tests/api-endpoints.test.ts`, + `src/config/tests/config.test.ts`) - cover strict port validation, local + loopback URL construction, custom ports, and local-only remote warnings. + +### Changed + +- **API startup** (`src/main.ts`, `src/api/server.ts`, `scripts/start.js`) - + starts TandemAPI on the configured port, writes endpoint discovery metadata, + and reports configured-port conflicts without blindly terminating unrelated + processes. +- **Local clients** (`src/mcp/api-client.ts`, `src/mcp/server.ts`, + `src/agents/x-scout.ts`, `cli/client.ts`) - discover the configured port and + connect through `http://127.0.0.1:`. +- **Shell API helpers** (`src/preload/index.ts`, `shell/js/api-auth.js`, + shell settings and chat modules) - route internal calls through the configured + loopback endpoint while preserving compatibility with legacy default-port + fetches. +- **Remote agent instructions** (`src/api/routes/pairing.ts`, docs) - advertise + `http://:` only when remote listen + mode is enabled, and warn clearly for loopback-only mode. + +### Technical Details + +- `apiListenHost` remains separate from `apiPort`; supported remote mode still + binds to `0.0.0.0` for private overlay networks and is not documented as + public-WAN support. +- Invalid ports such as empty strings, text, decimals, negatives, `0`, and + values above `65535` are rejected with a clear API/UI error. +- No new dependency was added. ## [v1.10.0] - 2026-05-05 diff --git a/PROJECT.md b/PROJECT.md index 3d3037c5..3196bfa8 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -10,7 +10,7 @@ bicycle: two riders, one machine, each contributing what the other can't do alone. The browser runs two things in parallel. The human uses it like any other browser -while AI agents operate through a built-in **MCP server** (253 tools) or a +while AI agents operate through a built-in **MCP server** (257 tools) or a **300+ endpoint HTTP API** for navigation, interaction, data extraction, automation, sessions, sync, extensions, and developer tooling. Local agents can use MCP or HTTP. Remote agents on the same Tailscale network connect via HTTP @@ -31,7 +31,7 @@ The security layer exists because when an AI has access to your browser, your th Data stays local. Sessions are isolated. Nothing leaves the machine through Tandem Browser without going through a filter first. **GitHub:** `hydro13/tandem-browser` -**Current version:** `1.10.0` +**Current version:** `1.11.0` **Repository status:** Public developer preview **Started:** February 11, 2026 @@ -107,7 +107,7 @@ Within the product UI, the right-side assistant surface is called the Wingman pa │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Tandem HTTP API — localhost:8765 (Express) │ │ +│ │ Tandem HTTP API — configured port, default 8765 (Express) │ │ │ │ 300+ route handlers across 16 route modules │ │ │ │ │ │ │ │ Navigation, Content, Interaction, Tabs, Screenshots │ │ @@ -291,7 +291,8 @@ npm run compile npm start # API -curl http://127.0.0.1:8765/status +API_PORT="$(cat ~/.tandem/api-port 2>/dev/null || printf 8765)" +curl "http://127.0.0.1:${API_PORT}/status" ``` **macOS note:** `npm start` already clears Electron quarantine flags before launch. If Electron is re-downloaded or started outside the provided scripts, run `xattr -cr node_modules/electron/dist/Electron.app` first or macOS may terminate the process silently. diff --git a/README.md b/README.md index 10688733..f85e9654 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Want the fastest path in? | **System** | 6 | Browser status, headless mode, Google Photos, security overrides | | **Awareness** | 2 | Activity digest, real-time focus detection — the AI knows what you're doing | -**253 tools total** — full parity with the HTTP API. +**257 tools total** — full parity with the HTTP API. ## Why Not Just Use Playwright? @@ -158,7 +158,7 @@ That's it. Tandem publishes its own bootstrap surface — the agent reads `/agen **Windows 11 x64** — download the installer or portable build: -**[Download Tandem Browser v1.10.0 →](https://github.com/hydro13/tandem-browser/releases/tag/v1.10.0)** +**[Download Tandem Browser v1.11.0 →](https://github.com/hydro13/tandem-browser/releases/tag/v1.11.0)** Windows builds are official Tandem Browser downloads, but they are currently unsigned. Windows may show an unknown publisher or SmartScreen warning during @@ -181,7 +181,7 @@ macOS and Windows are supported platforms. Linux is best-effort. Depending on what you want to do: -- **Install Tandem** -> download [macOS v1.0.0](https://github.com/hydro13/tandem-browser/releases/tag/v1.0.0) or [Windows v1.10.0](https://github.com/hydro13/tandem-browser/releases/tag/v1.10.0), or follow Quick Start above +- **Install Tandem** -> download [macOS v1.0.0](https://github.com/hydro13/tandem-browser/releases/tag/v1.0.0) or [Windows v1.11.0](https://github.com/hydro13/tandem-browser/releases/tag/v1.11.0), or follow Quick Start above - **Connect an agent** -> see [Connect Your AI Agent](#connect-your-ai-agent) - **Explore the API and docs** -> browse [docs/](docs/) and [docs/INDEX.md](docs/INDEX.md) - **See the product story and website** -> visit [tandembrowser.org](https://tandembrowser.org) @@ -208,6 +208,14 @@ The primary onboarding flow is now inside Tandem itself: Tandem handles the setup-code flow and publishes its own bootstrap/discovery surface for the agent at `/agent`, `/agent/manifest`, `/agent/version`, and `/skill`. +New paired agents must read `/skill`, `/agent/manifest`, and +`/agent/bootstrap` with their binding token before normal API/MCP use; Tandem +returns `428 agent_startup_required` when that startup sequence is skipped. + +The Agent API port defaults to `8765` and can be changed in **Settings -> +Connected Agents -> Agent API settings**. Local clients should discover the +current port from `~/.tandem/api-port` or `~/.tandem/api-endpoints.json`. +`~/.tandem/api-token` remains the readable local token compatibility contract. ### On the same machine (MCP or HTTP) @@ -231,15 +239,17 @@ Cursor, Windsurf, or any MCP client): } ``` -Start Tandem, and 253 tools are available immediately. +Start Tandem, and 257 tools are available immediately. **HTTP API** — Use the local API token directly: ```bash +API_PORT="$(cat ~/.tandem/api-port 2>/dev/null || printf 8765)" +API="http://127.0.0.1:${API_PORT}" TOKEN="$(cat ~/.tandem/api-token)" -curl -sS http://127.0.0.1:8765/status -curl -sS http://127.0.0.1:8765/tabs/list \ +curl -sS "$API/status" +curl -sS "$API/tabs/list" \ -H "Authorization: Bearer $TOKEN" ``` @@ -266,7 +276,7 @@ Connected Agents UI. "mcpServers": { "tandem": { "type": "streamable-http", - "url": "http://:8765/mcp", + "url": "http://:/mcp", "headers": { "Authorization": "Bearer " } @@ -277,19 +287,19 @@ Connected Agents UI. **HTTP API** works the same way as local, using the binding token as Bearer auth. -Both transports give remote agents the same 253 tools and 300+ endpoints as local agents. +Both transports give remote agents the same 257 tools and 300+ endpoints as local agents.
Manual pairing (for scripts or custom tooling) ```bash # Exchange setup code for token -curl -X POST http://:8765/pairing/exchange \ +curl -X POST http://:/pairing/exchange \ -H "Content-Type: application/json" \ -d '{"code":"TDM-XXXX-XXXX","machineId":"...","machineName":"...","agentLabel":"...","agentType":"..."}' # Use the returned token -curl -sS http://:8765/status \ +curl -sS http://:/status \ -H "Authorization: Bearer " ``` @@ -304,7 +314,8 @@ A running Tandem instance publishes its own version-matched discovery surface: - `GET /skill` — version-matched usage guide These are public (no auth required) and use the request `Host` header, so they -return correct URLs whether accessed locally or over Tailscale. +return correct URLs whether accessed locally or over Tailscale on the +configured port. ## Security Model @@ -367,7 +378,7 @@ contributors, not yet a polished mass-user release. - Supported platform: Windows 11 x64 - Secondary platform: Linux - Binaries: signed and notarized macOS Apple Silicon builds plus unsigned Windows x64 installer/portable builds on [GitHub Releases](https://github.com/hydro13/tandem-browser/releases), starting with Windows in v1.10.0 -- Current version: `1.10.0` +- Current version: `1.11.0` - Package metadata: [package.json](package.json) ## Community diff --git a/TODO.md b/TODO.md index 330ded55..50f277fe 100644 --- a/TODO.md +++ b/TODO.md @@ -14,8 +14,8 @@ Last updated: May 5, 2026 ## Current Snapshot -- Current app version: `1.10.0` -- MCP server: 253 tools (full API parity + awareness) +- Current app version: `1.11.0` +- MCP server: 257 tools (full API parity + awareness) - The codebase scope is larger than this backlog summary and includes major subsystems such as `sidebar`, `workspaces`, `pinboards`, `sync`, `headless`, and `sessions`. - Scheduled browsing already exists in baseline form via `WatchManager` and the `/watch/*` API routes. - Session isolation already exists in baseline form via `SessionManager` and the `/sessions/*` API routes. @@ -98,6 +98,13 @@ Last updated: May 5, 2026 ## Recently Completed +- [x] Agent bootstrap contract: pairing now returns explicit next-read links, + `/agent/bootstrap` exposes runtime context plus the agent toolbox, and + `/agent`, `/skill`, and `/agent/manifest` now make the required startup + sequence clear for newly connected agents. +- [x] Configurable Agent API port settings: users can change the default + `8765` port, local clients discover the configured loopback endpoint through + bootstrap files, and remote Tailscale instructions use the configured port. - [x] Windows support Phase 12: centralized shortcut accelerator and label handling, switched Electron menus to `CommandOrControl` accelerator strings, preserved macOS shortcut labels, and rendered Windows shortcut labels as diff --git a/cli/client.ts b/cli/client.ts index 0b0bdebc..7f517c0e 100644 --- a/cli/client.ts +++ b/cli/client.ts @@ -1,7 +1,8 @@ import fs from 'fs'; +import { buildLocalApiBaseUrl, readApiPortFromBootstrap } from '../src/config/api-endpoints'; import { tandemDir } from '../src/utils/paths'; -const API_BASE = process.env.TANDEM_API || 'http://localhost:8765'; +const API_BASE = process.env.TANDEM_API || buildLocalApiBaseUrl(readApiPortFromBootstrap()); const TOKEN_PATH = tandemDir('api-token'); function getToken(): string { diff --git a/docs/api.html b/docs/api.html index 46578024..410a8287 100644 --- a/docs/api.html +++ b/docs/api.html @@ -113,11 +113,11 @@
HTTP API Reference

Full programmatic control over a real browser

-

Default port 8765. Accessible locally and remotely over Tailscale. Authenticate with Authorization: Bearer <token>.

+

Default port 8765, configurable in Tandem Settings. Accessible locally and remotely over Tailscale/private overlay networks. Authenticate with Authorization: Bearer <token>.

280+Endpoints
19Domains
-
253MCP tools
+
257MCP tools
@@ -590,7 +590,7 @@

Full programmatic control over a real browser

-

Base URL: http://localhost:8765 (local) or http://<tailscale-ip>:8765 (remote)

+

Base URL: http://127.0.0.1:<configured-port> (local) or http://<tailscale-ip>:<configured-port> (remote over Tailscale/private networks)

Auth: Authorization: Bearer <token> — local token from ~/.tandem/api-token or binding token from pairing.

Discovery: GET /agent/manifest returns the full endpoint list as structured JSON. GET /agent returns a human-readable bootstrap page.

Tab targeting: Use the X-Tab-Id header to target a specific background tab without focusing it.

diff --git a/docs/index.html b/docs/index.html index 56d0cbd9..cbc57d32 100644 --- a/docs/index.html +++ b/docs/index.html @@ -163,8 +163,8 @@ "operatingSystem": "macOS, Windows 11 x64, Linux (best effort)", "description": "Tandem Browser is the open-source, local-first browser for AI agents — where the AI lives inside your real browser session, bring any model via MCP.", "url": "https://tandembrowser.org", - "downloadUrl": "https://github.com/hydro13/tandem-browser/releases/tag/v1.10.0", - "softwareVersion": "1.10.0", + "downloadUrl": "https://github.com/hydro13/tandem-browser/releases/tag/v1.11.0", + "softwareVersion": "1.11.0", "license": "https://opensource.org/licenses/MIT", "offers": { "@type": "Offer", @@ -325,13 +325,13 @@
-
Open source · MIT license · developer preview · v1.10.0
+
Open source · MIT license · developer preview · v1.11.0

Tandem Browser is the open-source, local-first browser for AI agents — where the AI lives inside your real browser session, sharing your tabs, cookies, and DOM, ready to ask for your help the moment it gets stuck.

The browser becomes a programmable workspace where human intent and AI capability meet.

Tandem Browser is built around a simple shift: the AI is not a sidebar talking about your browser, it is inside it. Same tabs, same cookies, same logged-in sessions, same page you are looking at right now. Reading the accessibility tree, watching the network, writing live styles, handing back to you when something needs a human. It turns the web that already exists into something agents can actually operate on — no per-site API, no RPA rig, no screenshot loop. The real browser, as a shared human-AI symbiotic runtime.

513GitHub stars
-
253MCP tools
+
257MCP tools
300+HTTP endpoints
8Security layers
@@ -587,7 +587,7 @@

Up and running in 3 steps

1

Download and install

-

Download Tandem Browser for Windows v1.10.0 →

+

Download Tandem Browser for Windows v1.11.0 →

Windows 11 x64 installer and portable builds are official Tandem Browser downloads. They are unsigned for now, so Windows may show an unknown publisher or SmartScreen warning.

Download signed macOS Apple Silicon build v1.0.0 →

Linux remains best-effort and can be run from source.

@@ -607,7 +607,7 @@

Open Tandem and go to Settings

Copy the instructions into your AI

Tandem generates a ready-to-paste block. Copy it and give it to your AI agent. That's it — the agent reads Tandem's bootstrap surface and connects automatically.

Bring any AI. Claude, GPT, OpenClaw, local Ollama, LM Studio, custom scripts — anything that speaks MCP or HTTP works. Swap models, combine models, run fully offline. The browser doesn't care which AI you bring.

-

Same machine: MCP via stdio (253 tools) or HTTP API (300+ endpoints).
+

Same machine: MCP via stdio (257 tools) or HTTP API (300+ endpoints).
Another machine: MCP via Streamable HTTP or HTTP API over a private Tailscale network.

@@ -659,7 +659,7 @@

Sponsor Tandem Browser

Tandem Browser vs the alternatives

-

Honest, balanced side-by-side comparisons with the other browsers and tools in this space. Tandem Browser is in development preview (v1.10.0) — these pages are upfront about that and where the alternatives have real advantages.

+

Honest, balanced side-by-side comparisons with the other browsers and tools in this space. Tandem Browser is in development preview (v1.11.0) — these pages are upfront about that and where the alternatives have real advantages.

vs Comet

Open-source, local-first vs Perplexity's polished managed product.

vs Dia

Model-agnostic developer-facing vs design-led consumer browser.

diff --git a/docs/llms.txt b/docs/llms.txt index f03de5c6..a75f376b 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -14,8 +14,9 @@ Tandem Browser was started by Robin Waslander (the maintainer of OpenClaw, https - Author: Robin Waslander (independent, no company, no VC) - Repository: https://github.com/hydro13/tandem-browser - Website: https://tandembrowser.org -- Current version: 1.10.0 (developer preview) -- Architecture: local-first, no cloud backend, optional remote agent connection over Tailscale +- Current version: 1.11.0 (developer preview) +- Architecture: local-first, no cloud backend, optional remote agent connection over Tailscale/private overlay networks +- Agent API: configurable port, default 8765; local clients use 127.0.0.1 plus the configured port, remote agents use the Tailscale/private-network address plus the configured port - Agent surface: 250+ MCP tools, 300+ HTTP endpoints - Security: 8-layer pipeline (NetworkShield, OutboundGuard, ContentAnalyzer, ScriptGuard, BehaviorMonitor, Gatekeeper AI, EvolutionEngine, PromptInjection) diff --git a/docs/platform-support.md b/docs/platform-support.md index b6b63d43..d213f311 100644 --- a/docs/platform-support.md +++ b/docs/platform-support.md @@ -36,6 +36,7 @@ | Capability | macOS | Windows | Linux | Notes | |------------|-------|---------|-------|-------| | App startup (`npm start` from source) | supported | supported | partial | Windows startup is covered by required verify and smoke checks. | +| Configurable Agent API port | supported | supported | supported | The default remains `8765`; local clients discover the configured port through Tandem bootstrap files, and remote agents use the Tailscale/private-network address plus the configured port. | | Signed installer | supported | unsupported | unsupported | Windows installer and portable builds are official but unsigned; code signing is planned. | | Auto-update | supported | partial | unsupported | Windows has a manual `electron-updater` check path and generated `latest.yml` metadata, but automatic update installation remains blocked until end-to-end update validation is complete. | | Custom titlebar / window chrome | supported | supported | supported | Windows source and packaged runs use frameless shell-owned controls. | diff --git a/docs/public-launch.md b/docs/public-launch.md index 66992e04..24d19855 100644 --- a/docs/public-launch.md +++ b/docs/public-launch.md @@ -20,7 +20,7 @@ Tandem Browser is now public: the local-first browser for shared human-AI browse Tandem Browser is now public. Tandem Browser is a local-first browser built for human-AI collaboration on the local -machine. The human browses normally. Any AI agent that speaks MCP (253 tools) or +machine. The human browses normally. Any AI agent that speaks MCP (257 tools) or HTTP (300+ endpoints) can operate inside the same real browser context for navigation, extraction, automation, screenshots, session work, and observability, while websites continue to see a normal Chromium browser instead of an "AI diff --git a/docs/vs/arc-search.html b/docs/vs/arc-search.html index c13d94e3..9cc72e58 100644 --- a/docs/vs/arc-search.html +++ b/docs/vs/arc-search.html @@ -57,7 +57,7 @@

Tandem Browser vs Arc Search

-
Note: Tandem Browser is in active development preview (v1.10.0). Arc Search is a polished, widely-praised consumer product. They are also genuinely different categories of tool — please factor both in.
+
Note: Tandem Browser is in active development preview (v1.11.0). Arc Search is a polished, widely-praised consumer product. They are also genuinely different categories of tool — please factor both in.
diff --git a/docs/vs/browser-use.html b/docs/vs/browser-use.html index b563593e..4b43a4d8 100644 --- a/docs/vs/browser-use.html +++ b/docs/vs/browser-use.html @@ -57,7 +57,7 @@

Tandem Browser vs Browser Use

-
Note: Tandem Browser is in active development preview (v1.10.0). Browser Use is a mature, actively maintained library. Some differences below reflect category (browser vs library), not maturity — please weigh both.
+
Note: Tandem Browser is in active development preview (v1.11.0). Browser Use is a mature, actively maintained library. Some differences below reflect category (browser vs library), not maturity — please weigh both.
diff --git a/docs/vs/comet.html b/docs/vs/comet.html index 1f84caa1..73a0867f 100644 --- a/docs/vs/comet.html +++ b/docs/vs/comet.html @@ -57,7 +57,7 @@

Tandem Browser vs Comet

-
Note: Tandem Browser is in active development preview (v1.10.0). Comet is a production-grade, polished product. Some of the differences below reflect that maturity gap, not just architectural choice — please factor that in when deciding.
+
Note: Tandem Browser is in active development preview (v1.11.0). Comet is a production-grade, polished product. Some of the differences below reflect that maturity gap, not just architectural choice — please factor that in when deciding.
diff --git a/docs/vs/dia.html b/docs/vs/dia.html index efbfa4ba..36cc7a31 100644 --- a/docs/vs/dia.html +++ b/docs/vs/dia.html @@ -57,7 +57,7 @@

Tandem Browser vs Dia

-
Note: Tandem Browser is in active development preview (v1.10.0). Dia is a production-grade, polished product. Some of the differences below reflect that maturity gap, not just architectural choice — please factor that in when deciding.
+
Note: Tandem Browser is in active development preview (v1.11.0). Dia is a production-grade, polished product. Some of the differences below reflect that maturity gap, not just architectural choice — please factor that in when deciding.
diff --git a/package-lock.json b/package-lock.json index 6908c7d1..6ad7be19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tandem-browser", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tandem-browser", - "version": "1.10.0", + "version": "1.11.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a2c553a5..80942312 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tandem-browser", - "version": "1.10.0", - "description": "Local-first Electron browser with a 253-tool MCP server, MCP/HTTP control surfaces, and built-in security controls", + "version": "1.11.0", + "description": "Local-first Electron browser with a 257-tool MCP server, MCP/HTTP control surfaces, and built-in security controls", "main": "dist/main.js", "author": "Tandem Browser contributors", "license": "MIT", diff --git a/scripts/start.js b/scripts/start.js index ef9b2717..47b04d87 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -11,9 +11,50 @@ const os = require('os'); const path = require('path'); const root = path.join(__dirname, '..'); -const apiPort = '8765'; +const apiPort = String(readConfiguredApiPort()); const skipCompile = process.argv.includes('--skip-compile'); +function tandemDataDir() { + if (process.platform === 'win32') { + const appData = process.env.APPDATA && process.env.APPDATA.trim() + ? process.env.APPDATA + : path.join(os.homedir(), 'AppData', 'Roaming'); + return path.join(appData, 'Tandem Browser'); + } + return path.join(os.homedir(), '.tandem'); +} + +function parseApiPort(value) { + const raw = String(value ?? '').trim(); + if (!/^\d+$/.test(raw)) return null; + const port = Number(raw); + return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null; +} + +function readConfiguredApiPort() { + const envPort = parseApiPort(process.env.TANDEM_API_PORT); + if (envPort) return envPort; + + const portPath = path.join(tandemDataDir(), 'api-port'); + try { + if (fs.existsSync(portPath)) { + const port = parseApiPort(fs.readFileSync(portPath, 'utf-8')); + if (port) return port; + } + } catch {} + + const configPath = path.join(tandemDataDir(), 'config.json'); + try { + if (fs.existsSync(configPath)) { + const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + const port = parseApiPort(cfg && cfg.general && cfg.general.apiPort); + if (port) return port; + } + } catch {} + + return 8765; +} + function runXattrClear(electronApp) { return new Promise((resolve) => { execFile('xattr', ['-cr', electronApp], { cwd: root }, (error, stdout, stderr) => { @@ -38,6 +79,14 @@ function runKillPids(pids) { }); } +function runPsLookup(pid) { + return new Promise((resolve) => { + execFile('ps', ['-p', String(pid), '-o', 'comm=', '-o', 'args='], { cwd: root }, (error, stdout, stderr) => { + resolve({ error, stdout: stdout || '', stderr: stderr || '' }); + }); + }); +} + function runNetstat() { return new Promise((resolve) => { execFile('netstat.exe', ['-ano'], { cwd: root }, (error, stdout, stderr) => { @@ -54,6 +103,14 @@ function runTaskkill(pid) { }); } +function runWindowsProcessLookup(pid) { + return new Promise((resolve) => { + execFile('wmic.exe', ['process', 'where', `ProcessId=${pid}`, 'get', 'Name,CommandLine', '/FORMAT:LIST'], { cwd: root }, (error, stdout, stderr) => { + resolve({ error, stdout: stdout || '', stderr: stderr || '' }); + }); + }); +} + function runCommand(command, args, label) { return new Promise((resolve, reject) => { const child = spawn(command, args, { @@ -106,8 +163,26 @@ async function killUnixPortProcess() { if (pids.length === 0) return; - await runKillPids(pids); - console.log(`[start] Killed leftover process(es) on port ${apiPort}: ${pids.join(', ')}`); + const safePids = []; + const unsafePids = []; + for (const pid of pids) { + const info = await runPsLookup(pid); + const text = info.stdout.toLowerCase(); + if ((text.includes('electron') || text.includes('tandem')) && text.includes(root.toLowerCase())) { + safePids.push(pid); + } else { + unsafePids.push(pid); + } + } + + if (unsafePids.length > 0) { + throw new Error(`Agent API port ${apiPort} is already in use by another process (${unsafePids.join(', ')}). Tandem will not kill it automatically; stop that process or choose another Agent API port in Settings.`); + } + + if (safePids.length > 0) { + await runKillPids(safePids); + console.log(`[start] Killed leftover Tandem process(es) on port ${apiPort}: ${safePids.join(', ')}`); + } } function parseWindowsNetstatPids(output) { @@ -134,10 +209,28 @@ async function killWindowsPortProcess() { if (pids.length === 0) return; + const safePids = []; + const unsafePids = []; for (const pid of pids) { + const info = await runWindowsProcessLookup(pid); + const text = info.stdout.toLowerCase(); + if ((text.includes('electron.exe') || text.includes('tandem')) && text.includes(root.toLowerCase())) { + safePids.push(pid); + } else { + unsafePids.push(pid); + } + } + + if (unsafePids.length > 0) { + throw new Error(`Agent API port ${apiPort} is already in use by another process (${unsafePids.join(', ')}). Tandem will not kill it automatically; stop that process or choose another Agent API port in Settings.`); + } + + for (const pid of safePids) { await runTaskkill(pid); } - console.log(`[start] Killed leftover process(es) on port ${apiPort}: ${pids.join(', ')}`); + if (safePids.length > 0) { + console.log(`[start] Killed leftover Tandem process(es) on port ${apiPort}: ${safePids.join(', ')}`); + } } async function cleanupApiPort() { diff --git a/shell/about.html b/shell/about.html index 8d144753..1545d8c7 100644 --- a/shell/about.html +++ b/shell/about.html @@ -7,11 +7,12 @@ stamp data-theme before the inline