From 9bc5e666c4480869441e661847853f84734ef571 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Fri, 20 Mar 2026 11:37:00 -0700 Subject: [PATCH] feat: auto-discover server port via ~/.browseros/server.json (#504) * feat: auto-discover server port via ~/.browseros/server.json Server writes its port to ~/.browseros/server.json on startup so the CLI can auto-discover the server URL without requiring `browseros-cli init`. Discovery chain: BROWSEROS_URL env > config.yaml > server.json > error Co-Authored-By: Claude Opus 4.6 (1M context) * fix: address review feedback for PR #504 - Use synchronous unlinkSync in stop() since process.exit() fires immediately after, abandoning any pending async operations - Wrap writeServerConfig in try/catch so a write failure doesn't crash a healthy server for a convenience feature Co-Authored-By: Claude Opus 4.6 (1M context) * feat: type server discovery config and add version metadata Add ServerDiscoveryConfig interface to @browseros/shared and enrich server.json with server_version, browseros_version, and chromium_version. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: normalize URL from server.json for consistency All other URL sources (env var, config.yaml) pass through normalizeServerURL; apply the same to the server.json path. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- packages/browseros-agent/apps/cli/cmd/root.go | 37 ++++++++++++++++++- .../apps/server/src/lib/browseros-dir.ts | 22 ++++++++++- .../browseros-agent/apps/server/src/main.ts | 22 ++++++++++- .../packages/shared/package.json | 4 ++ .../packages/shared/src/constants/paths.ts | 1 + .../shared/src/types/server-config.ts | 16 ++++++++ 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 packages/browseros-agent/packages/shared/src/types/server-config.ts diff --git a/packages/browseros-agent/apps/cli/cmd/root.go b/packages/browseros-agent/apps/cli/cmd/root.go index 6c7d85511..a8f1e18f5 100644 --- a/packages/browseros-agent/apps/cli/cmd/root.go +++ b/packages/browseros-agent/apps/cli/cmd/root.go @@ -1,8 +1,10 @@ package cmd import ( + "encoding/json" "fmt" "os" + "path/filepath" "strconv" "strings" "time" @@ -170,11 +172,44 @@ func defaultServerURL() string { } cfg, err := config.Load() + if err == nil { + if url := normalizeServerURL(cfg.ServerURL); url != "" { + return url + } + } + + if url := loadBrowserosServerURL(); url != "" { + return url + } + + return "" +} + +type serverDiscoveryConfig struct { + ServerPort int `json:"server_port"` + URL string `json:"url"` + ServerVersion string `json:"server_version"` + BrowserOSVersion string `json:"browseros_version,omitempty"` + ChromiumVersion string `json:"chromium_version,omitempty"` +} + +func loadBrowserosServerURL() string { + home, err := os.UserHomeDir() if err != nil { return "" } - return normalizeServerURL(cfg.ServerURL) + data, err := os.ReadFile(filepath.Join(home, ".browseros", "server.json")) + if err != nil { + return "" + } + + var sc serverDiscoveryConfig + if err := json.Unmarshal(data, &sc); err != nil { + return "" + } + + return normalizeServerURL(sc.URL) } func normalizeServerURL(raw string) string { diff --git a/packages/browseros-agent/apps/server/src/lib/browseros-dir.ts b/packages/browseros-agent/apps/server/src/lib/browseros-dir.ts index 84bd1521b..0c9cfca8f 100644 --- a/packages/browseros-agent/apps/server/src/lib/browseros-dir.ts +++ b/packages/browseros-agent/apps/server/src/lib/browseros-dir.ts @@ -1,7 +1,9 @@ -import { mkdir, readdir, rm, stat } from 'node:fs/promises' +import { unlinkSync } from 'node:fs' +import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises' import { homedir } from 'node:os' import { join } from 'node:path' import { PATHS } from '@browseros/shared/constants/paths' +import type { ServerDiscoveryConfig } from '@browseros/shared/types/server-config' import { logger } from './logger' export function getBrowserosDir(): string { @@ -32,6 +34,24 @@ export function getBuiltinSkillsDir(): string { return join(getSkillsDir(), PATHS.BUILTIN_DIR_NAME) } +export function getServerConfigPath(): string { + return join(getBrowserosDir(), PATHS.SERVER_CONFIG_FILE_NAME) +} + +export async function writeServerConfig( + config: ServerDiscoveryConfig, +): Promise { + await writeFile(getServerConfigPath(), `${JSON.stringify(config, null, 2)}\n`) +} + +export function removeServerConfigSync(): void { + try { + unlinkSync(getServerConfigPath()) + } catch { + // File may not exist or already be removed + } +} + export async function ensureBrowserosDir(): Promise { await mkdir(getMemoryDir(), { recursive: true }) await mkdir(getSkillsDir(), { recursive: true }) diff --git a/packages/browseros-agent/apps/server/src/main.ts b/packages/browseros-agent/apps/server/src/main.ts index 508d664f6..220c8b14d 100644 --- a/packages/browseros-agent/apps/server/src/main.ts +++ b/packages/browseros-agent/apps/server/src/main.ts @@ -18,7 +18,12 @@ import { ControllerBackend } from './browser/backends/controller' import { Browser } from './browser/browser' import type { ServerConfig } from './config' import { INLINED_ENV } from './env' -import { cleanOldSessions, ensureBrowserosDir } from './lib/browseros-dir' +import { + cleanOldSessions, + ensureBrowserosDir, + removeServerConfigSync, + writeServerConfig, +} from './lib/browseros-dir' import { initializeDb } from './lib/db' import { identity } from './lib/identity' import { logger } from './lib/logger' @@ -109,6 +114,20 @@ export class Application { this.handleStartupError('HTTP server', this.config.serverPort, error) } + try { + await writeServerConfig({ + server_port: this.config.serverPort, + url: `http://127.0.0.1:${this.config.serverPort}`, + server_version: VERSION, + browseros_version: this.config.instanceBrowserosVersion, + chromium_version: this.config.instanceChromiumVersion, + }) + } catch (error) { + logger.warn('Failed to write server config for auto-discovery', { + error: error instanceof Error ? error.message : String(error), + }) + } + logger.info( `HTTP server listening on http://127.0.0.1:${this.config.serverPort}`, ) @@ -125,6 +144,7 @@ export class Application { stop(reason?: string): void { logger.info('Shutting down server...', { reason }) stopSkillSync() + removeServerConfigSync() // Immediate exit without graceful shutdown. Chromium may kill us on update/restart, // and we need to free the port instantly so the HTTP port doesn't keep switching. diff --git a/packages/browseros-agent/packages/shared/package.json b/packages/browseros-agent/packages/shared/package.json index 81f022317..f963edb20 100644 --- a/packages/browseros-agent/packages/shared/package.json +++ b/packages/browseros-agent/packages/shared/package.json @@ -37,6 +37,10 @@ "types": "./src/types/logger.ts", "default": "./src/types/logger.ts" }, + "./types/server-config": { + "types": "./src/types/server-config.ts", + "default": "./src/types/server-config.ts" + }, "./schemas/llm": { "types": "./src/schemas/llm.ts", "default": "./src/schemas/llm.ts" diff --git a/packages/browseros-agent/packages/shared/src/constants/paths.ts b/packages/browseros-agent/packages/shared/src/constants/paths.ts index eedbd59fe..3577bfc87 100644 --- a/packages/browseros-agent/packages/shared/src/constants/paths.ts +++ b/packages/browseros-agent/packages/shared/src/constants/paths.ts @@ -16,6 +16,7 @@ export const PATHS = { CORE_MEMORY_FILE_NAME: 'CORE.md', SKILLS_DIR_NAME: 'skills', BUILTIN_DIR_NAME: 'builtin', + SERVER_CONFIG_FILE_NAME: 'server.json', SOUL_MAX_LINES: 150, MEMORY_RETENTION_DAYS: 30, SESSION_RETENTION_DAYS: 30, diff --git a/packages/browseros-agent/packages/shared/src/types/server-config.ts b/packages/browseros-agent/packages/shared/src/types/server-config.ts new file mode 100644 index 000000000..886bfaf1e --- /dev/null +++ b/packages/browseros-agent/packages/shared/src/types/server-config.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright 2025 BrowserOS + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * Shape of ~/.browseros/server.json written by the server on startup. + * The CLI reads this file for auto-discovery of the server URL. + */ + +export interface ServerDiscoveryConfig { + server_port: number + url: string + server_version: string + browseros_version?: string + chromium_version?: string +}