@usejarvis/brain 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 (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * CDP Client — Low-level Chrome DevTools Protocol WebSocket
3
+ *
4
+ * Handles command/response pairs and event subscriptions over
5
+ * a single CDP target (page) WebSocket connection.
6
+ */
7
+
8
+ type CDPEventHandler = (params: Record<string, unknown>) => void;
9
+
10
+ export class CDPClient {
11
+ private ws: WebSocket | null = null;
12
+ private nextId = 1;
13
+ private pending = new Map<number, {
14
+ resolve: (value: unknown) => void;
15
+ reject: (error: Error) => void;
16
+ }>();
17
+ private eventHandlers = new Map<string, Set<CDPEventHandler>>();
18
+
19
+ /**
20
+ * Connect to a CDP target by its WebSocket debugger URL.
21
+ */
22
+ async connect(wsUrl: string): Promise<void> {
23
+ return new Promise((resolve, reject) => {
24
+ this.ws = new WebSocket(wsUrl);
25
+
26
+ this.ws.onopen = () => resolve();
27
+
28
+ this.ws.onerror = () => reject(new Error('CDP WebSocket connection failed'));
29
+
30
+ this.ws.onmessage = (event) => {
31
+ try {
32
+ const msg = JSON.parse(event.data as string);
33
+
34
+ if (msg.id !== undefined) {
35
+ // Command response
36
+ const p = this.pending.get(msg.id);
37
+ if (p) {
38
+ this.pending.delete(msg.id);
39
+ if (msg.error) {
40
+ p.reject(new Error(`CDP error: ${msg.error.message}`));
41
+ } else {
42
+ p.resolve(msg.result);
43
+ }
44
+ }
45
+ } else if (msg.method) {
46
+ // Event notification
47
+ const handlers = this.eventHandlers.get(msg.method);
48
+ if (handlers) {
49
+ for (const h of handlers) h(msg.params ?? {});
50
+ }
51
+ }
52
+ } catch (err) {
53
+ console.error('[CDP] Failed to parse message:', err);
54
+ }
55
+ };
56
+
57
+ this.ws.onclose = () => {
58
+ for (const { reject: r } of this.pending.values()) {
59
+ r(new Error('CDP connection closed'));
60
+ }
61
+ this.pending.clear();
62
+ };
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Send a CDP command and wait for its response.
68
+ */
69
+ async send(method: string, params?: Record<string, unknown>): Promise<any> {
70
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
71
+ throw new Error('CDP not connected');
72
+ }
73
+
74
+ return new Promise((resolve, reject) => {
75
+ const id = this.nextId++;
76
+ this.pending.set(id, { resolve, reject });
77
+ this.ws!.send(JSON.stringify({ id, method, params }));
78
+
79
+ setTimeout(() => {
80
+ if (this.pending.has(id)) {
81
+ this.pending.delete(id);
82
+ reject(new Error(`CDP timeout: ${method}`));
83
+ }
84
+ }, 30000);
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Subscribe to a CDP event.
90
+ */
91
+ on(event: string, handler: CDPEventHandler): void {
92
+ if (!this.eventHandlers.has(event)) {
93
+ this.eventHandlers.set(event, new Set());
94
+ }
95
+ this.eventHandlers.get(event)!.add(handler);
96
+ }
97
+
98
+ /**
99
+ * Unsubscribe from a CDP event.
100
+ */
101
+ off(event: string, handler: CDPEventHandler): void {
102
+ this.eventHandlers.get(event)?.delete(handler);
103
+ }
104
+
105
+ /**
106
+ * Wait for a specific CDP event to fire.
107
+ */
108
+ async waitForEvent(event: string, timeout = 30000): Promise<Record<string, unknown>> {
109
+ return new Promise((resolve, reject) => {
110
+ const timer = setTimeout(() => {
111
+ this.off(event, handler);
112
+ reject(new Error(`Timeout waiting for ${event}`));
113
+ }, timeout);
114
+
115
+ const handler = (params: Record<string, unknown>) => {
116
+ clearTimeout(timer);
117
+ this.off(event, handler);
118
+ resolve(params);
119
+ };
120
+
121
+ this.on(event, handler);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Close the WebSocket connection.
127
+ */
128
+ async close(): Promise<void> {
129
+ this.ws?.close();
130
+ this.ws = null;
131
+ this.pending.clear();
132
+ this.eventHandlers.clear();
133
+ }
134
+
135
+ get isOpen(): boolean {
136
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
137
+ }
138
+ }
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Chrome Launcher — Auto-detect and launch Chrome/Chromium
3
+ *
4
+ * Finds the system's Chrome installation (Chrome, Brave, Edge, Chromium),
5
+ * launches it with CDP enabled and an isolated profile, and waits for
6
+ * the debug port to become reachable.
7
+ *
8
+ * Works on Linux, macOS, Windows, and WSL2 (calls Windows Chrome from Linux).
9
+ */
10
+
11
+ import { spawn, type Subprocess } from 'bun';
12
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
13
+ import { homedir } from 'node:os';
14
+ import { join } from 'node:path';
15
+
16
+ export type BrowserKind = 'chrome' | 'brave' | 'edge' | 'chromium';
17
+
18
+ export type BrowserExecutable = {
19
+ kind: BrowserKind;
20
+ path: string;
21
+ };
22
+
23
+ export type RunningBrowser = {
24
+ proc: Subprocess;
25
+ exe: BrowserExecutable;
26
+ cdpPort: number;
27
+ userDataDir: string;
28
+ startedAt: number;
29
+ };
30
+
31
+ /**
32
+ * Detect the default Chromium-based browser on this system.
33
+ * Checks common install paths for Chrome, Brave, Edge, Chromium.
34
+ */
35
+ export function findBrowserExecutable(): BrowserExecutable | null {
36
+ const platform = process.platform;
37
+
38
+ if (platform === 'linux') {
39
+ // Always try Linux-native browsers first — they share the network
40
+ // namespace so CDP on 127.0.0.1 works directly. On WSL2, Linux GUI
41
+ // apps display natively via WSLg.
42
+ const linuxCandidates = findLinuxCandidates();
43
+ for (const c of linuxCandidates) {
44
+ if (existsSync(c.path)) return c;
45
+ }
46
+
47
+ // On WSL2, fall back to Windows Chrome if no Linux browser found
48
+ const isWSL = existsSync('/proc/version') &&
49
+ readFileSync('/proc/version', 'utf-8').toLowerCase().includes('microsoft');
50
+
51
+ if (isWSL) {
52
+ const wslCandidates = findWSLCandidates();
53
+ for (const c of wslCandidates) {
54
+ if (existsSync(c.path)) return c;
55
+ }
56
+ }
57
+ }
58
+
59
+ if (platform === 'darwin') {
60
+ const macCandidates = findMacCandidates();
61
+ for (const c of macCandidates) {
62
+ if (existsSync(c.path)) return c;
63
+ }
64
+ }
65
+
66
+ if (platform === 'win32') {
67
+ const winCandidates = findWindowsCandidates();
68
+ for (const c of winCandidates) {
69
+ if (existsSync(c.path)) return c;
70
+ }
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ function findWSLCandidates(): BrowserExecutable[] {
77
+ // Windows Chrome accessible from WSL via /mnt/c/...
78
+ return [
79
+ { kind: 'chrome', path: '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe' },
80
+ { kind: 'chrome', path: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe' },
81
+ { kind: 'edge', path: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe' },
82
+ { kind: 'edge', path: '/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe' },
83
+ { kind: 'brave', path: '/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe' },
84
+ { kind: 'brave', path: '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe' },
85
+ ];
86
+ }
87
+
88
+ /** Exported for reuse by deps.ts dependency checker. */
89
+ export const LINUX_BROWSER_PATHS: BrowserExecutable[] = [
90
+ { kind: 'chrome', path: '/usr/bin/google-chrome' },
91
+ { kind: 'chrome', path: '/usr/bin/google-chrome-stable' },
92
+ { kind: 'chrome', path: '/usr/bin/chrome' },
93
+ { kind: 'brave', path: '/usr/bin/brave-browser' },
94
+ { kind: 'brave', path: '/usr/bin/brave-browser-stable' },
95
+ { kind: 'edge', path: '/usr/bin/microsoft-edge' },
96
+ { kind: 'edge', path: '/usr/bin/microsoft-edge-stable' },
97
+ { kind: 'chromium', path: '/usr/bin/chromium' },
98
+ { kind: 'chromium', path: '/usr/bin/chromium-browser' },
99
+ { kind: 'chromium', path: '/snap/bin/chromium' },
100
+ ];
101
+
102
+ /** Exported for reuse by deps.ts dependency checker. */
103
+ export const MACOS_BROWSER_PATHS: BrowserExecutable[] = [
104
+ { kind: 'chrome', path: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' },
105
+ { kind: 'chrome', path: join(homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome') },
106
+ { kind: 'brave', path: '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser' },
107
+ { kind: 'edge', path: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge' },
108
+ { kind: 'chromium', path: '/Applications/Chromium.app/Contents/MacOS/Chromium' },
109
+ ];
110
+
111
+ function findLinuxCandidates(): BrowserExecutable[] {
112
+ return LINUX_BROWSER_PATHS;
113
+ }
114
+
115
+ function findMacCandidates(): BrowserExecutable[] {
116
+ return MACOS_BROWSER_PATHS;
117
+ }
118
+
119
+ function findWindowsCandidates(): BrowserExecutable[] {
120
+ const localAppData = process.env.LOCALAPPDATA ?? '';
121
+ const programFiles = process.env.ProgramFiles ?? 'C:\\Program Files';
122
+ const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)';
123
+
124
+ const candidates: BrowserExecutable[] = [];
125
+
126
+ if (localAppData) {
127
+ candidates.push(
128
+ { kind: 'chrome', path: join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe') },
129
+ { kind: 'brave', path: join(localAppData, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe') },
130
+ { kind: 'edge', path: join(localAppData, 'Microsoft', 'Edge', 'Application', 'msedge.exe') },
131
+ );
132
+ }
133
+
134
+ candidates.push(
135
+ { kind: 'chrome', path: join(programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe') },
136
+ { kind: 'chrome', path: join(programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe') },
137
+ { kind: 'edge', path: join(programFiles, 'Microsoft', 'Edge', 'Application', 'msedge.exe') },
138
+ { kind: 'edge', path: join(programFilesX86, 'Microsoft', 'Edge', 'Application', 'msedge.exe') },
139
+ { kind: 'brave', path: join(programFiles, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe') },
140
+ );
141
+
142
+ return candidates;
143
+ }
144
+
145
+ /**
146
+ * Launch Chrome with CDP enabled and an isolated user profile.
147
+ * Returns when the CDP port is reachable.
148
+ */
149
+ export async function launchChrome(port: number = 9222, profileDir?: string): Promise<RunningBrowser> {
150
+ const exe = findBrowserExecutable();
151
+ if (!exe) {
152
+ throw new Error(
153
+ 'No Chrome/Brave/Edge/Chromium found on this system.\n' +
154
+ 'Install Chrome or set the path in config.'
155
+ );
156
+ }
157
+
158
+ // Isolated profile — doesn't touch the user's real browser data
159
+ const userDataDir = profileDir ?? join(homedir(), '.jarvis', 'browser', 'profile');
160
+ mkdirSync(userDataDir, { recursive: true });
161
+
162
+ const args = [
163
+ `--remote-debugging-port=${port}`,
164
+ `--user-data-dir=${userDataDir}`,
165
+ '--no-first-run',
166
+ '--no-default-browser-check',
167
+ '--disable-sync',
168
+ '--disable-background-networking',
169
+ '--disable-component-update',
170
+ '--disable-features=Translate,MediaRouter',
171
+ '--disable-session-crashed-bubble',
172
+ '--hide-crash-restore-bubble',
173
+ '--password-store=basic',
174
+ '--disable-blink-features=AutomationControlled', // stealth
175
+ ];
176
+
177
+ // Linux-specific (also applies to WSL2)
178
+ if (process.platform === 'linux') {
179
+ args.push('--disable-dev-shm-usage');
180
+ args.push('--no-sandbox'); // Required for Chromium in containers/WSL2
181
+ }
182
+
183
+ // Open a blank tab so a target exists
184
+ args.push('about:blank');
185
+
186
+ console.log(`[ChromeLauncher] Launching ${exe.kind} from ${exe.path}`);
187
+
188
+ const proc = spawn([exe.path, ...args], {
189
+ stdout: 'ignore',
190
+ stderr: 'ignore',
191
+ });
192
+
193
+ const startedAt = Date.now();
194
+
195
+ // Wait for CDP to become reachable (up to 15s)
196
+ const deadline = Date.now() + 15_000;
197
+ let reachable = false;
198
+
199
+ while (Date.now() < deadline) {
200
+ try {
201
+ const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
202
+ signal: AbortSignal.timeout(500),
203
+ });
204
+ if (res.ok) {
205
+ reachable = true;
206
+ break;
207
+ }
208
+ } catch {
209
+ // Not ready yet
210
+ }
211
+ await Bun.sleep(200);
212
+ }
213
+
214
+ if (!reachable) {
215
+ proc.kill();
216
+ throw new Error(
217
+ `Chrome started but CDP not reachable on port ${port} after 15s.\n` +
218
+ `Binary: ${exe.path}`
219
+ );
220
+ }
221
+
222
+ console.log(`[ChromeLauncher] ${exe.kind} ready on port ${port} (pid ${proc.pid})`);
223
+
224
+ return { proc, exe, cdpPort: port, userDataDir, startedAt };
225
+ }
226
+
227
+ /**
228
+ * Stop a running Chrome instance gracefully.
229
+ */
230
+ export async function stopChrome(running: RunningBrowser): Promise<void> {
231
+ try {
232
+ running.proc.kill();
233
+ } catch {
234
+ // Already dead
235
+ }
236
+
237
+ // Wait for process to exit (up to 3s)
238
+ const deadline = Date.now() + 3000;
239
+ while (Date.now() < deadline) {
240
+ if (running.proc.exitCode !== null) break;
241
+ await Bun.sleep(100);
242
+ }
243
+
244
+ // Force kill if still running
245
+ try {
246
+ running.proc.kill(9);
247
+ } catch {
248
+ // ignore
249
+ }
250
+
251
+ console.log(`[ChromeLauncher] Chrome stopped`);
252
+ }