Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"commander": "^14.0.1",
"core-js": "3.45.1",
"debug": "4.4.3",
"dotenv": "^17.2.3",
"eslint": "^9.35.0",
"eslint-config-prettier": "^9.1.2",
"eslint-import-resolver-typescript": "^4.4.4",
Expand Down Expand Up @@ -902,6 +903,8 @@

"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],

"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],

"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],

"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "browseros-server",
"version": "0.0.7",
"version": "0.0.8",
"description": "Unified BrowserOS server with MCP and Agent support",
"private": true,
"type": "module",
"workspaces": [
"packages/*"
],
"scripts": {
"start": "bun run build:codex-sdk-ts && CODEX_BINARY_PATH=third_party/bin/codex bun --env-file=.env packages/server/src/index.ts",
"start:debug": "bun run build:codex-sdk-ts && CODEX_BINARY_PATH=third_party/bin/codex bun --inspect-brk --env-file=.env packages/server/src/index.ts",
"start": "bun run build:codex-sdk-ts && CODEX_BINARY_PATH=third_party/bin/codex bun --env-file=.env.dev packages/server/src/index.ts",
"start:debug": "bun run build:codex-sdk-ts && CODEX_BINARY_PATH=third_party/bin/codex bun --inspect-brk --env-file=.env.dev packages/server/src/index.ts",
"build:codex-sdk-ts": "bun run --filter @browseros/codex-sdk-ts prepare",
"test": "bun test; bun run test:cleanup",
"test:all": "bun test --workspace",
Expand All @@ -26,12 +26,12 @@
"dev:server:windows": "mkdir -p dist/server && bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server.exe --minify --target bun-windows-x64 --env inline && bun scripts/patch-windows-exe.ts dist/server/browseros-server.exe",
"dev:ext": "rimraf dist/ext && bun run --filter browseros-controller build:dev && mkdir -p dist/ext && cp -r packages/controller-ext/dist/* dist/ext/",
"dist:ext": "rimraf dist/ext && mkdir -p dist/ext && bun run --filter browseros-controller build && cp -r packages/controller-ext/dist/* dist/ext/",
"dist:server": "bun run build:codex-sdk-ts && rimraf dist/server && mkdir -p dist/server && bun run dist:server:linux-x64 && bun run dist:server:linux-arm64 && bun run dist:server:windows-x64 && bun run dist:server:darwin-arm64 && bun run dist:server:darwin-x64",
"dist:server:linux-x64": "bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server-linux-x64 --minify --sourcemap --target=bun-linux-x64-modern --env inline",
"dist:server:linux-arm64": "bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server-linux-arm64 --minify --sourcemap --target=bun-linux-arm64 --env inline",
"dist:server:windows-x64": "bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server-windows-x64.exe --minify --sourcemap --target=bun-windows-x64-modern --env inline && bun scripts/patch-windows-exe.ts dist/server/browseros-server-windows-x64.exe",
"dist:server:darwin-arm64": "bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server-darwin-arm64 --minify --sourcemap --target=bun-darwin-arm64 --env inline",
"dist:server:darwin-x64": "bun build --compile packages/server/src/index.ts --outfile dist/server/browseros-server-darwin-x64 --minify --sourcemap --target=bun-darwin-x64 --env inline",
"dist:server": "bun run build:codex-sdk-ts && rimraf dist/server && bun scripts/build_server.ts --mode=prod --target=all",
"dist:server:linux-x64": "bun run build:codex-sdk-ts && bun scripts/build_server.ts --mode=prod --target=linux-x64",
"dist:server:linux-arm64": "bun run build:codex-sdk-ts && bun scripts/build_server.ts --mode=prod --target=linux-arm64",
"dist:server:windows-x64": "bun run build:codex-sdk-ts && bun scripts/build_server.ts --mode=prod --target=windows-x64",
"dist:server:darwin-arm64": "bun run build:codex-sdk-ts && bun scripts/build_server.ts --mode=prod --target=darwin-arm64",
"dist:server:darwin-x64": "bun run build:codex-sdk-ts && bun scripts/build_server.ts --mode=prod --target=darwin-x64",
"format": "prettier --write --cache . || true ; eslint --cache --fix . || true",
"check-format": "prettier --check --cache . || true ; eslint --cache || true",
"docs": "npm run docs:generate && npm run format",
Expand Down Expand Up @@ -72,6 +72,7 @@
"commander": "^14.0.1",
"core-js": "3.45.1",
"debug": "4.4.3",
"dotenv": "^17.2.3",
"eslint": "^9.35.0",
"eslint-config-prettier": "^9.1.2",
"eslint-import-resolver-typescript": "^4.4.4",
Expand Down
30 changes: 21 additions & 9 deletions packages/agent/src/agent/CodexSDKAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,31 @@ export class CodexSDKAgent extends BaseAgent {
});
}

private isExecutableFile(path: string): boolean {
try {
accessSync(path, fsConstants.X_OK);
return true;
} catch {
return false;
}
}

private resolveCodexExecutablePath(): string {
const codexBinaryName =
process.platform === 'win32' ? 'codex.exe' : 'codex';

// Check CODEX_BINARY_PATH env var first
if (process.env.CODEX_BINARY_PATH) {
return process.env.CODEX_BINARY_PATH;
const envPath = process.env.CODEX_BINARY_PATH;
if (this.isExecutableFile(envPath)) {
return envPath;
}
logger.warn(
'CODEX_BINARY_PATH set but file not found or not executable',
{
path: envPath,
},
);
}

// Check resourcesDir if provided
Expand All @@ -170,22 +188,16 @@ export class CodexSDKAgent extends BaseAgent {
'bin',
codexBinaryName,
);
try {
accessSync(resourcesCodexPath, fsConstants.X_OK);
if (this.isExecutableFile(resourcesCodexPath)) {
return resourcesCodexPath;
} catch {
// Ignore failures; fall back to next option
}
}

// Check bundled codex in current binary directory
const currentBinaryDirectory = dirname(process.execPath);
const bundledCodexPath = join(currentBinaryDirectory, codexBinaryName);
try {
accessSync(bundledCodexPath, fsConstants.X_OK);
if (this.isExecutableFile(bundledCodexPath)) {
return bundledCodexPath;
} catch {
// Ignore failures; fall through to error
}

throw new Error(
Expand Down
219 changes: 219 additions & 0 deletions scripts/build_server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/env bun
/**
* Build script for BrowserOS server binaries
*
* Usage:
* bun scripts/build_server.ts --mode=prod [--target=darwin-arm64]
* bun scripts/build_server.ts --mode=dev [--target=all]
*
* Modes:
* prod - Clean environment build using only .env.prod
* dev - Normal build using shell environment + .env.dev
*
* Targets:
* linux-x64, linux-arm64, windows-x64, darwin-arm64, darwin-x64, all
*/

import { spawn } from "child_process";
import { readFileSync, mkdirSync } from "fs";
import { resolve, join } from "path";
import { parse } from "dotenv";

interface BuildTarget {
name: string;
bunTarget: string;
outfile: string;
}

const TARGETS: Record<string, BuildTarget> = {
"linux-x64": {
name: "Linux x64",
bunTarget: "bun-linux-x64-modern",
outfile: "dist/server/browseros-server-linux-x64",
},
"linux-arm64": {
name: "Linux ARM64",
bunTarget: "bun-linux-arm64",
outfile: "dist/server/browseros-server-linux-arm64",
},
"windows-x64": {
name: "Windows x64",
bunTarget: "bun-windows-x64-modern",
outfile: "dist/server/browseros-server-windows-x64.exe",
},
"darwin-arm64": {
name: "macOS ARM64",
bunTarget: "bun-darwin-arm64",
outfile: "dist/server/browseros-server-darwin-arm64",
},
"darwin-x64": {
name: "macOS x64",
bunTarget: "bun-darwin-x64",
outfile: "dist/server/browseros-server-darwin-x64",
},
};

const MINIMAL_SYSTEM_VARS = ["PATH"];

function parseArgs(): { mode: "prod" | "dev"; targets: string[] } {
const args = process.argv.slice(2);
let mode: "prod" | "dev" = "prod";
let targetArg = "all";

for (const arg of args) {
if (arg.startsWith("--mode=")) {
const modeValue = arg.split("=")[1];
if (modeValue !== "prod" && modeValue !== "dev") {
console.error(`Invalid mode: ${modeValue}. Must be 'prod' or 'dev'`);
process.exit(1);
}
mode = modeValue;
} else if (arg.startsWith("--target=")) {
targetArg = arg.split("=")[1];
}
}

const targets =
targetArg === "all"
? Object.keys(TARGETS)
: targetArg.split(",").map((t) => t.trim());

for (const target of targets) {
if (!TARGETS[target]) {
console.error(`Invalid target: ${target}`);
console.error(`Available targets: ${Object.keys(TARGETS).join(", ")}, all`);
process.exit(1);
}
}

return { mode, targets };
}

function loadEnvFile(path: string): Record<string, string> {
try {
const content = readFileSync(path, "utf-8");
const parsed = parse(content);
return parsed;
} catch (error) {
console.error(`Failed to load ${path}:`, error);
process.exit(1);
}
}

function createCleanEnv(envVars: Record<string, string>): Record<string, string> {
const cleanEnv: Record<string, string> = {};

for (const varName of MINIMAL_SYSTEM_VARS) {
const value = process.env[varName];
if (value) {
cleanEnv[varName] = value;
}
}

Object.assign(cleanEnv, envVars);

return cleanEnv;
}

function runCommand(
command: string,
args: string[],
env: NodeJS.ProcessEnv
): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
env,
stdio: "inherit",
});

child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command exited with code ${code}`));
}
});

child.on("error", (error) => {
reject(error);
});
});
}

async function buildTarget(
target: BuildTarget,
mode: "prod" | "dev",
envVars: Record<string, string>
): Promise<void> {
console.log(`\n📦 Building ${target.name}...`);

const args = [
"build",
"--compile",
"packages/server/src/index.ts",
"--outfile",
target.outfile,
"--minify",
"--sourcemap",
`--target=${target.bunTarget}`,
"--env",
"inline",
];

const buildEnv = mode === "prod" ? createCleanEnv(envVars) : { ...process.env, ...envVars };

try {
await runCommand("bun", args, buildEnv);
console.log(`✅ ${target.name} built successfully`);

if (target.outfile.endsWith(".exe")) {
console.log(`🔧 Patching Windows executable...`);
await runCommand("bun", ["scripts/patch-windows-exe.ts", target.outfile], process.env);
}
} catch (error) {
console.error(`❌ Failed to build ${target.name}:`, error);
throw error;
}
}

async function main() {
const { mode, targets } = parseArgs();
const rootDir = resolve(import.meta.dir, "..");
process.chdir(rootDir);

console.log(`🚀 Building BrowserOS server binaries`);
console.log(` Mode: ${mode}`);
console.log(` Targets: ${targets.join(", ")}`);

const envFile = mode === "prod" ? ".env.prod" : ".env.dev";
const envPath = join(rootDir, envFile);

console.log(`\n📄 Loading environment from ${envFile}...`);
const envVars = loadEnvFile(envPath);
console.log(` Loaded ${Object.keys(envVars).length} variables`);

if (mode === "prod") {
console.log(`\n🔒 Production mode: Using CLEAN environment (only ${envFile} + minimal system vars)`);
console.log(` System vars: ${MINIMAL_SYSTEM_VARS.join(", ")}`);
} else {
console.log(`\n🔓 Development mode: Using shell environment + ${envFile}`);
}

mkdirSync("dist/server", { recursive: true });

for (const targetKey of targets) {
const target = TARGETS[targetKey];
await buildTarget(target, mode, envVars);
}

console.log(`\n✨ All builds completed successfully!`);
console.log(`\n📦 Output files:`);
for (const targetKey of targets) {
console.log(` ${TARGETS[targetKey].outfile}`);
}
}

main().catch((error) => {
console.error("\n💥 Build failed:", error);
process.exit(1);
});