openbrowser-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +109 -0
  2. package/dist/commands/doctor.d.ts +5 -0
  3. package/dist/commands/doctor.d.ts.map +1 -0
  4. package/dist/commands/doctor.js +16 -0
  5. package/dist/commands/doctor.js.map +1 -0
  6. package/dist/commands/login.d.ts +4 -0
  7. package/dist/commands/login.d.ts.map +1 -0
  8. package/dist/commands/login.js +6 -0
  9. package/dist/commands/login.js.map +1 -0
  10. package/dist/commands/setup.d.ts +4 -0
  11. package/dist/commands/setup.d.ts.map +1 -0
  12. package/dist/commands/setup.js +6 -0
  13. package/dist/commands/setup.js.map +1 -0
  14. package/dist/commands/status.d.ts +5 -0
  15. package/dist/commands/status.d.ts.map +1 -0
  16. package/dist/commands/status.js +16 -0
  17. package/dist/commands/status.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +49 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/lib/chrome-service.d.ts +30 -0
  23. package/dist/lib/chrome-service.d.ts.map +1 -0
  24. package/dist/lib/chrome-service.js +290 -0
  25. package/dist/lib/chrome-service.js.map +1 -0
  26. package/dist/lib/config.d.ts +5 -0
  27. package/dist/lib/config.d.ts.map +1 -0
  28. package/dist/lib/config.js +48 -0
  29. package/dist/lib/config.js.map +1 -0
  30. package/dist/lib/core.d.ts +25 -0
  31. package/dist/lib/core.d.ts.map +1 -0
  32. package/dist/lib/core.js +287 -0
  33. package/dist/lib/core.js.map +1 -0
  34. package/dist/lib/output.d.ts +5 -0
  35. package/dist/lib/output.d.ts.map +1 -0
  36. package/dist/lib/output.js +121 -0
  37. package/dist/lib/output.js.map +1 -0
  38. package/dist/lib/platform.d.ts +9 -0
  39. package/dist/lib/platform.d.ts.map +1 -0
  40. package/dist/lib/platform.js +77 -0
  41. package/dist/lib/platform.js.map +1 -0
  42. package/dist/lib/session.d.ts +9 -0
  43. package/dist/lib/session.d.ts.map +1 -0
  44. package/dist/lib/session.js +72 -0
  45. package/dist/lib/session.js.map +1 -0
  46. package/dist/lib/types.d.ts +53 -0
  47. package/dist/lib/types.d.ts.map +1 -0
  48. package/dist/lib/types.js +18 -0
  49. package/dist/lib/types.js.map +1 -0
  50. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # OpenBrowser
2
+
3
+ Managed authenticated browser for AI agents. Persistent Chrome sessions with CDP access, session health monitoring, and cross-platform service management.
4
+
5
+ Supports **macOS** and **Linux**. Windows is not supported.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Install and configure
11
+ npx openbrowser setup
12
+
13
+ # Log into websites (opens Chrome GUI on macOS, VNC on Linux)
14
+ npx openbrowser login
15
+
16
+ # Check status
17
+ npx openbrowser status
18
+
19
+ # Full diagnostics
20
+ npx openbrowser doctor
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ ### `openbrowser setup`
26
+
27
+ Installs a Chrome service (systemd on Linux, launchd on macOS), creates the profile directory, saves default config, and outputs MCP configuration.
28
+
29
+ ### `openbrowser login`
30
+
31
+ Opens Chrome with the managed profile for manual login.
32
+
33
+ - **macOS:** Opens a Chrome window directly. Log into your accounts, then close the window.
34
+ - **Linux (headless):** Starts Xvfb + Chrome + VNC. Connect via SSH tunnel and VNC client.
35
+
36
+ ### `openbrowser status`
37
+
38
+ Shows Chrome status and session health for Google, GitHub, and LinkedIn. Sessions are verified via CDP cookies (decrypted, live values).
39
+
40
+ ### `openbrowser doctor`
41
+
42
+ Runs diagnostics: Chrome binary, profile directory, CDP connection, session health, stale locks, config file. Each failure includes a fix suggestion.
43
+
44
+ ### Output Format
45
+
46
+ All commands support `--format json|text`. Default: text when interactive (TTY), JSON when piped.
47
+
48
+ JSON output uses a typed envelope:
49
+
50
+ ```json
51
+ {
52
+ "command": "status",
53
+ "version": "0.1.0",
54
+ "timestamp": "2026-03-03T00:00:00.000Z",
55
+ "success": true,
56
+ "data": { ... },
57
+ "summary": "Chrome running, 3 active sessions"
58
+ }
59
+ ```
60
+
61
+ ## MCP Integration
62
+
63
+ After `openbrowser setup`, add to your `claude_desktop_config.json`:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "browser": {
69
+ "command": "npx",
70
+ "args": ["@playwright/mcp@latest", "--cdp-endpoint=http://localhost:9222"]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Programmatic Usage
77
+
78
+ ```typescript
79
+ import { OpenBrowser } from 'openbrowser';
80
+
81
+ const ob = new OpenBrowser();
82
+ const status = await ob.getStatus();
83
+ console.log(status.sessions);
84
+
85
+ // Direct CDP connection
86
+ const browser = await ob.connect();
87
+ const page = await browser.contexts()[0].newPage();
88
+ // ... use authenticated browser
89
+ await browser.close(); // disconnects only, Chrome stays running
90
+ ```
91
+
92
+ ## Configuration
93
+
94
+ Config file: `~/.openbrowser/config.json`
95
+
96
+ ```json
97
+ {
98
+ "cdpPort": 9222,
99
+ "profileDir": "~/.openbrowser/chrome-profile",
100
+ "timezone": "Europe/Berlin",
101
+ "vncPassword": "temp1234",
102
+ "vncPort": 5900,
103
+ "xvfbDisplay": ":98"
104
+ }
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,5 @@
1
+ export declare function doctorCommand(options: {
2
+ format?: string;
3
+ profile?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAGA,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjG"}
@@ -0,0 +1,16 @@
1
+ import { OpenBrowser } from '../lib/core.js';
2
+ import { createOutput, resolveFormat, printOutput } from '../lib/output.js';
3
+ export async function doctorCommand(options) {
4
+ const ob = new OpenBrowser({ profileDir: options.profile });
5
+ const data = await ob.diagnose();
6
+ const fails = data.checks.filter((c) => c.status === 'fail').length;
7
+ const warns = data.checks.filter((c) => c.status === 'warn').length;
8
+ const summary = data.healthy
9
+ ? `All ${data.checks.length} checks passed`
10
+ : `${fails} failure${fails === 1 ? '' : 's'}, ${warns} warning${warns === 1 ? '' : 's'}`;
11
+ const output = createOutput('doctor', data, summary, data.healthy);
12
+ printOutput(output, resolveFormat(options.format));
13
+ if (!data.healthy)
14
+ process.exitCode = 1;
15
+ }
16
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA8C;IAChF,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC;IAEjC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACpE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO;QAC1B,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,gBAAgB;QAC3C,CAAC,CAAC,GAAG,KAAK,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAE3F,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACnE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnD,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function loginCommand(options?: {
2
+ profile?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAEA,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF"}
@@ -0,0 +1,6 @@
1
+ import { OpenBrowser } from '../lib/core.js';
2
+ export async function loginCommand(options) {
3
+ const ob = new OpenBrowser({ profileDir: options?.profile });
4
+ await ob.login();
5
+ }
6
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA8B;IAC/D,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function setupCommand(options?: {
2
+ profile?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAEA,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhF"}
@@ -0,0 +1,6 @@
1
+ import { OpenBrowser } from '../lib/core.js';
2
+ export async function setupCommand(options) {
3
+ const ob = new OpenBrowser({ profileDir: options?.profile });
4
+ await ob.setup();
5
+ }
6
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA8B;IAC/D,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function statusCommand(options: {
2
+ format?: string;
3
+ profile?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAajG"}
@@ -0,0 +1,16 @@
1
+ import { OpenBrowser } from '../lib/core.js';
2
+ import { createOutput, resolveFormat, printOutput } from '../lib/output.js';
3
+ export async function statusCommand(options) {
4
+ const ob = new OpenBrowser({ profileDir: options.profile });
5
+ const data = await ob.getStatus();
6
+ const activeSessions = data.sessions.filter((s) => s.active).length;
7
+ const warnings = data.sessions.filter((s) => s.warning).length;
8
+ let summary = data.chrome.running
9
+ ? `Chrome running, ${activeSessions} active session${activeSessions === 1 ? '' : 's'}`
10
+ : 'Chrome not running';
11
+ if (warnings > 0)
12
+ summary += `, ${warnings} warning${warnings === 1 ? '' : 's'}`;
13
+ const output = createOutput('status', data, summary);
14
+ printOutput(output, resolveFormat(options.format));
15
+ }
16
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA8C;IAChF,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,EAAE,CAAC;IAElC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IAC/D,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO;QAC/B,CAAC,CAAC,mBAAmB,cAAc,kBAAkB,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QACtF,CAAC,CAAC,oBAAoB,CAAC;IACzB,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,IAAI,KAAK,QAAQ,WAAW,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAEjF,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACrD,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { assertSupportedPlatform } from './lib/platform.js';
7
+ import { setupCommand } from './commands/setup.js';
8
+ import { loginCommand } from './commands/login.js';
9
+ import { statusCommand } from './commands/status.js';
10
+ import { doctorCommand } from './commands/doctor.js';
11
+ assertSupportedPlatform();
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
14
+ const program = new Command();
15
+ program
16
+ .name('openbrowser')
17
+ .description('Managed authenticated browser for AI agents')
18
+ .version(pkg.version)
19
+ .option('--profile <path>', 'Chrome profile directory to use');
20
+ program
21
+ .command('setup')
22
+ .description('Install Chrome service and configure OpenBrowser')
23
+ .option('--profile <path>', 'Use an existing Chrome profile directory')
24
+ .action(async (options) => {
25
+ const globalProfile = program.opts().profile;
26
+ await setupCommand({ profile: options.profile ?? globalProfile });
27
+ });
28
+ program
29
+ .command('login')
30
+ .description('Open Chrome GUI for manual login to websites')
31
+ .action(async () => {
32
+ await loginCommand({ profile: program.opts().profile });
33
+ });
34
+ program
35
+ .command('status')
36
+ .description('Show Chrome status and session health')
37
+ .option('--format <format>', 'Output format: json or text')
38
+ .action(async (options) => {
39
+ await statusCommand({ ...options, profile: program.opts().profile });
40
+ });
41
+ program
42
+ .command('doctor')
43
+ .description('Run diagnostics on the OpenBrowser setup')
44
+ .option('--format <format>', 'Output format: json or text')
45
+ .action(async (options) => {
46
+ await doctorCommand({ ...options, profile: program.opts().profile });
47
+ });
48
+ await program.parseAsync();
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,uBAAuB,EAAE,CAAC;AAE1B,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAC7D,CAAC;AAEF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,6CAA6C,CAAC;KAC1D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;KACpB,MAAM,CAAC,kBAAkB,EAAE,iCAAiC,CAAC,CAAC;AAEjE,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,kBAAkB,EAAE,0CAA0C,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,OAA6B,EAAE,EAAE;IAC9C,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC;IAC7C,MAAM,YAAY,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,aAAa,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,8CAA8C,CAAC;KAC3D,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,YAAY,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,uCAAuC,CAAC;KACpD,MAAM,CAAC,mBAAmB,EAAE,6BAA6B,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,OAA4B,EAAE,EAAE;IAC7C,MAAM,aAAa,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,mBAAmB,EAAE,6BAA6B,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,OAA4B,EAAE,EAAE;IAC7C,MAAM,aAAa,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEL,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import type { Config } from './types.js';
3
+ export declare class ChromeService {
4
+ private config;
5
+ private os;
6
+ constructor(config: Config);
7
+ getCdpInfo(): Promise<{
8
+ running: boolean;
9
+ version?: string;
10
+ }>;
11
+ isRunning(): Promise<boolean>;
12
+ getPid(): Promise<number | null>;
13
+ install(): {
14
+ path: string;
15
+ instructions: string;
16
+ };
17
+ private isRoot;
18
+ private getSystemdDir;
19
+ private installSystemd;
20
+ private installLaunchd;
21
+ private systemctl;
22
+ start(): void;
23
+ stop(): void;
24
+ restart(): void;
25
+ launchForLogin(): ChildProcess;
26
+ startXvfb(): void;
27
+ startVnc(): ChildProcess;
28
+ getMcpConfig(): object;
29
+ }
30
+ //# sourceMappingURL=chrome-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome-service.d.ts","sourceRoot":"","sources":["../../src/lib/chrome-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAwEzC,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAqB;gBAEnB,MAAM,EAAE,MAAM;IAKpB,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAa7D,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAK7B,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IActC,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAkBjD,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,SAAS;IAKjB,KAAK,IAAI,IAAI;IAcb,IAAI,IAAI,IAAI;IAsBZ,OAAO,IAAI,IAAI;IAKf,cAAc,IAAI,YAAY;IAqC9B,SAAS,IAAI,IAAI;IAqBjB,QAAQ,IAAI,YAAY;IAgBxB,YAAY,IAAI,MAAM;CAavB"}
@@ -0,0 +1,290 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { detectOS, findChromeBinary, cleanStaleLocks } from './platform.js';
5
+ const SYSTEMD_TEMPLATE = (config, chromeBinary, userMode) => `[Unit]
6
+ Description=OpenBrowser - Authenticated Chrome with CDP
7
+ After=network.target
8
+
9
+ [Service]
10
+ Type=simple
11
+ ExecStartPre=/bin/bash -c 'pgrep -f "Xvfb ${config.xvfbDisplay}" || (Xvfb ${config.xvfbDisplay} -screen 0 1280x800x24 & sleep 1)'
12
+ ExecStart=${chromeBinary} \\
13
+ --user-data-dir=${config.profileDir} \\
14
+ --no-sandbox \\
15
+ --disable-gpu \\
16
+ --remote-debugging-port=${config.cdpPort} \\
17
+ --remote-debugging-address=127.0.0.1 \\
18
+ --disable-background-timer-throttling \\
19
+ --disable-renderer-backgrounding \\
20
+ --disable-backgrounding-occluded-windows \\
21
+ --no-first-run \\
22
+ --disable-sync \\
23
+ --lang=en-US \\
24
+ --start-maximized \\
25
+ --window-size=1280,800 \\
26
+ --window-position=0,0
27
+ Restart=on-failure
28
+ RestartSec=5
29
+ Environment=DISPLAY=${config.xvfbDisplay}
30
+ Environment=TZ=${config.timezone}
31
+
32
+ [Install]
33
+ WantedBy=${userMode ? 'default.target' : 'multi-user.target'}
34
+ `;
35
+ const LAUNCHD_TEMPLATE = (config, chromeBinary) => `<?xml version="1.0" encoding="UTF-8"?>
36
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
37
+ <plist version="1.0">
38
+ <dict>
39
+ <key>Label</key>
40
+ <string>com.openbrowser.chrome</string>
41
+ <key>ProgramArguments</key>
42
+ <array>
43
+ <string>${chromeBinary}</string>
44
+ <string>--user-data-dir=${config.profileDir}</string>
45
+ <string>--headless=new</string>
46
+ <string>--remote-debugging-port=${config.cdpPort}</string>
47
+ <string>--remote-debugging-address=127.0.0.1</string>
48
+ <string>--disable-background-timer-throttling</string>
49
+ <string>--disable-renderer-backgrounding</string>
50
+ <string>--no-first-run</string>
51
+ <string>--disable-sync</string>
52
+ <string>--disable-gpu</string>
53
+ <string>--lang=en-US</string>
54
+ <string>--window-size=1280,800</string>
55
+ </array>
56
+ <key>RunAtLoad</key>
57
+ <true/>
58
+ <key>KeepAlive</key>
59
+ <true/>
60
+ <key>EnvironmentVariables</key>
61
+ <dict>
62
+ <key>TZ</key>
63
+ <string>${config.timezone}</string>
64
+ </dict>
65
+ <key>StandardErrorPath</key>
66
+ <string>/tmp/openbrowser-chrome.err</string>
67
+ <key>StandardOutPath</key>
68
+ <string>/tmp/openbrowser-chrome.out</string>
69
+ </dict>
70
+ </plist>
71
+ `;
72
+ export class ChromeService {
73
+ config;
74
+ os;
75
+ constructor(config) {
76
+ this.config = config;
77
+ this.os = detectOS();
78
+ }
79
+ async getCdpInfo() {
80
+ try {
81
+ const res = await fetch(`http://localhost:${this.config.cdpPort}/json/version`);
82
+ if (!res.ok)
83
+ return { running: false };
84
+ const data = (await res.json());
85
+ return { running: true, version: data.Browser ?? undefined };
86
+ }
87
+ catch {
88
+ return { running: false };
89
+ }
90
+ }
91
+ async isRunning() {
92
+ const info = await this.getCdpInfo();
93
+ return info.running;
94
+ }
95
+ async getPid() {
96
+ try {
97
+ const output = execSync(`lsof -ti :${this.config.cdpPort} 2>/dev/null || true`, { encoding: 'utf-8' }).trim();
98
+ if (!output)
99
+ return null;
100
+ const pid = parseInt(output.split('\n')[0], 10);
101
+ return isNaN(pid) ? null : pid;
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ install() {
108
+ const chromeBinary = findChromeBinary();
109
+ if (!chromeBinary) {
110
+ throw new Error('Chrome not found. Install Google Chrome or Chromium first.');
111
+ }
112
+ if (!existsSync(this.config.profileDir)) {
113
+ mkdirSync(this.config.profileDir, { recursive: true });
114
+ }
115
+ if (this.os === 'linux') {
116
+ return this.installSystemd(chromeBinary);
117
+ }
118
+ return this.installLaunchd(chromeBinary);
119
+ }
120
+ isRoot() {
121
+ return process.getuid?.() === 0;
122
+ }
123
+ getSystemdDir() {
124
+ if (this.isRoot()) {
125
+ return '/etc/systemd/system';
126
+ }
127
+ const home = process.env['HOME'] ?? '~';
128
+ const dir = join(home, '.config', 'systemd', 'user');
129
+ if (!existsSync(dir)) {
130
+ mkdirSync(dir, { recursive: true });
131
+ }
132
+ return dir;
133
+ }
134
+ installSystemd(chromeBinary) {
135
+ const root = this.isRoot();
136
+ const serviceDir = this.getSystemdDir();
137
+ const servicePath = join(serviceDir, 'openbrowser.service');
138
+ const content = SYSTEMD_TEMPLATE(this.config, chromeBinary, !root);
139
+ writeFileSync(servicePath, content, 'utf-8');
140
+ if (root) {
141
+ return {
142
+ path: servicePath,
143
+ instructions: [
144
+ 'Service installed. To start:',
145
+ ' systemctl daemon-reload',
146
+ ' systemctl enable openbrowser',
147
+ ' systemctl start openbrowser',
148
+ ].join('\n'),
149
+ };
150
+ }
151
+ return {
152
+ path: servicePath,
153
+ instructions: [
154
+ 'Service installed (user mode). To start:',
155
+ ' systemctl --user daemon-reload',
156
+ ' systemctl --user enable openbrowser',
157
+ ' systemctl --user start openbrowser',
158
+ ].join('\n'),
159
+ };
160
+ }
161
+ installLaunchd(chromeBinary) {
162
+ const plistDir = join(process.env['HOME'] ?? '~', 'Library', 'LaunchAgents');
163
+ if (!existsSync(plistDir)) {
164
+ mkdirSync(plistDir, { recursive: true });
165
+ }
166
+ const plistPath = join(plistDir, 'com.openbrowser.chrome.plist');
167
+ const content = LAUNCHD_TEMPLATE(this.config, chromeBinary);
168
+ writeFileSync(plistPath, content, 'utf-8');
169
+ return {
170
+ path: plistPath,
171
+ instructions: [
172
+ 'Service installed. To start:',
173
+ ` launchctl load ${plistPath}`,
174
+ '',
175
+ 'To stop:',
176
+ ` launchctl unload ${plistPath}`,
177
+ ].join('\n'),
178
+ };
179
+ }
180
+ systemctl(action) {
181
+ const flag = this.isRoot() ? '' : ' --user';
182
+ execSync(`systemctl${flag} ${action} openbrowser`, { stdio: 'inherit' });
183
+ }
184
+ start() {
185
+ if (this.os === 'linux') {
186
+ this.systemctl('start');
187
+ }
188
+ else {
189
+ const plistPath = join(process.env['HOME'] ?? '~', 'Library', 'LaunchAgents', 'com.openbrowser.chrome.plist');
190
+ execSync(`launchctl load ${plistPath}`, { stdio: 'inherit' });
191
+ }
192
+ }
193
+ stop() {
194
+ if (this.os === 'linux') {
195
+ try {
196
+ this.systemctl('stop');
197
+ }
198
+ catch {
199
+ // service might not be running
200
+ }
201
+ }
202
+ else {
203
+ const plistPath = join(process.env['HOME'] ?? '~', 'Library', 'LaunchAgents', 'com.openbrowser.chrome.plist');
204
+ try {
205
+ execSync(`launchctl unload ${plistPath}`, { stdio: 'inherit' });
206
+ }
207
+ catch {
208
+ // service might not be loaded
209
+ }
210
+ }
211
+ }
212
+ restart() {
213
+ this.stop();
214
+ this.start();
215
+ }
216
+ launchForLogin() {
217
+ const chromeBinary = findChromeBinary();
218
+ if (!chromeBinary) {
219
+ const os = detectOS();
220
+ const installHint = os === 'darwin'
221
+ ? 'Install from https://google.com/chrome or: brew install --cask google-chrome'
222
+ : 'Install with: sudo apt install google-chrome-stable (or: sudo dnf install google-chrome-stable)';
223
+ throw new Error(`Chrome not found. ${installHint}`);
224
+ }
225
+ cleanStaleLocks(this.config.profileDir);
226
+ if (!existsSync(this.config.profileDir)) {
227
+ mkdirSync(this.config.profileDir, { recursive: true });
228
+ }
229
+ const args = [
230
+ `--user-data-dir=${this.config.profileDir}`,
231
+ '--no-first-run',
232
+ '--disable-sync',
233
+ '--lang=en-US',
234
+ ];
235
+ if (this.os === 'linux') {
236
+ // On Linux headless, use Xvfb display
237
+ const env = {
238
+ ...process.env,
239
+ DISPLAY: this.config.xvfbDisplay,
240
+ TZ: this.config.timezone,
241
+ };
242
+ return spawn(chromeBinary, args, { env, stdio: 'ignore', detached: true });
243
+ }
244
+ // macOS: launch directly, user sees the window
245
+ return spawn(chromeBinary, args, { stdio: 'ignore', detached: false });
246
+ }
247
+ startXvfb() {
248
+ const display = this.config.xvfbDisplay;
249
+ const lockFile = `/tmp/.X${display.replace(':', '')}-lock`;
250
+ // Check if already running
251
+ if (existsSync(lockFile)) {
252
+ return;
253
+ }
254
+ spawn('Xvfb', [display, '-screen', '0', '1280x800x24', '-ac'], {
255
+ stdio: 'ignore',
256
+ detached: true,
257
+ }).unref();
258
+ // Wait for Xvfb to create lock file (up to 5 seconds)
259
+ for (let i = 0; i < 50; i++) {
260
+ if (existsSync(lockFile))
261
+ return;
262
+ execSync('sleep 0.1');
263
+ }
264
+ }
265
+ startVnc() {
266
+ return spawn('x11vnc', [
267
+ '-display',
268
+ this.config.xvfbDisplay,
269
+ '-passwd',
270
+ this.config.vncPassword,
271
+ '-rfbport',
272
+ String(this.config.vncPort),
273
+ '-once',
274
+ ], { stdio: 'pipe' });
275
+ }
276
+ getMcpConfig() {
277
+ return {
278
+ mcpServers: {
279
+ browser: {
280
+ command: 'npx',
281
+ args: [
282
+ '@playwright/mcp@latest',
283
+ `--cdp-endpoint=http://localhost:${this.config.cdpPort}`,
284
+ ],
285
+ },
286
+ },
287
+ };
288
+ }
289
+ }
290
+ //# sourceMappingURL=chrome-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome-service.js","sourceRoot":"","sources":["../../src/lib/chrome-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Config } from './types.js';
2
+ export declare function getConfigPath(): string;
3
+ export declare function loadConfig(configPath?: string, overrides?: Partial<Config>): Config;
4
+ export declare function saveConfig(config: Config, configPath?: string): void;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAgBzC,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAqBnF;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CASpE"}