@shykaruu/jarvis-brain 0.4.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 (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,506 @@
1
+ /**
2
+ * Browser Controller — High-level browser automation
3
+ *
4
+ * Wraps CDPClient with user-friendly operations:
5
+ * navigate, snapshot (interactive elements with IDs), click, type, screenshot.
6
+ *
7
+ * The snapshot approach: each interactive element gets a numeric [id].
8
+ * The LLM sees these IDs and references them in click/type commands.
9
+ */
10
+
11
+ import { CDPClient } from './cdp.ts';
12
+ import { STEALTH_SCRIPT } from './stealth.ts';
13
+ import { launchChrome, stopChrome, type RunningBrowser } from './chrome-launcher.ts';
14
+
15
+ export type PageElement = {
16
+ id: number;
17
+ tag: string;
18
+ text: string;
19
+ attrs: Record<string, string>;
20
+ };
21
+
22
+ export type PageSnapshot = {
23
+ title: string;
24
+ url: string;
25
+ text: string;
26
+ elements: PageElement[];
27
+ };
28
+
29
+ // JS function injected into the page to extract interactive elements
30
+ const SNAPSHOT_SCRIPT = `(() => {
31
+ const els = [];
32
+ const seen = new WeakSet();
33
+ const sel = [
34
+ 'a', 'button', 'input', 'select', 'textarea', 'summary',
35
+ '[role="button"]', '[role="link"]', '[role="tab"]', '[role="textbox"]',
36
+ '[role="combobox"]', '[role="menuitem"]', '[role="option"]',
37
+ '[role="row"]', '[role="gridcell"]',
38
+ '[onclick]', '[contenteditable="true"]', '[tabindex="0"]',
39
+ '[data-testid]'
40
+ ].join(', ');
41
+ document.querySelectorAll(sel).forEach((el) => {
42
+ // Skip duplicates (child of already-captured parent)
43
+ if (seen.has(el)) return;
44
+ seen.add(el);
45
+
46
+ const rect = el.getBoundingClientRect();
47
+ if (rect.width === 0 || rect.height === 0) return;
48
+ if (rect.width < 5 || rect.height < 5) return;
49
+ const style = window.getComputedStyle(el);
50
+ if (style.visibility === 'hidden') return;
51
+ if (style.display === 'none') return;
52
+ if (style.opacity === '0') return;
53
+
54
+ const tag = el.tagName.toLowerCase();
55
+ const text = (el.innerText || el.textContent || '').trim().replace(/\\s+/g, ' ').slice(0, 100);
56
+ const attrs = {};
57
+ for (const a of ['href', 'name', 'placeholder', 'type', 'aria-label', 'title', 'id', 'role', 'data-testid', 'contenteditable']) {
58
+ const v = el.getAttribute(a);
59
+ if (v) attrs[a] = v.slice(0, 200);
60
+ }
61
+ // Capture live value (JS property) for inputs — getAttribute('value') returns the HTML default
62
+ if ('value' in el && el.value) attrs.value = String(el.value).slice(0, 200);
63
+ els.push({
64
+ _el: el,
65
+ tag,
66
+ text,
67
+ attrs,
68
+ x: Math.round(rect.x + rect.width / 2),
69
+ y: Math.round(rect.y + rect.height / 2)
70
+ });
71
+ });
72
+
73
+ // Assign sequential IDs and store DOM refs for later direct focus
74
+ window.__jarvis_elements = els.map(e => e._el);
75
+ els.forEach((el, i) => { el.id = i + 1; delete el._el; });
76
+
77
+ // Get visible text, clean up whitespace
78
+ let bodyText = document.body.innerText || '';
79
+ bodyText = bodyText.replace(/\\n{3,}/g, '\\n\\n').trim().slice(0, 8000);
80
+
81
+ return {
82
+ title: document.title,
83
+ url: location.href,
84
+ text: bodyText,
85
+ elements: els
86
+ };
87
+ })()`;
88
+
89
+ export class BrowserController {
90
+ private cdp: CDPClient;
91
+ private port: number;
92
+ private profileDir: string | undefined;
93
+ private _connected = false;
94
+ private runningBrowser: RunningBrowser | null = null;
95
+ // Coordinates stored from last snapshot — not sent to LLM
96
+ private elementCoords = new Map<number, { x: number; y: number }>();
97
+
98
+ constructor(port: number = 9222, profileDir?: string) {
99
+ this.cdp = new CDPClient();
100
+ this.port = port;
101
+ this.profileDir = profileDir;
102
+ }
103
+
104
+ /**
105
+ * Check if Chrome CDP is already reachable on the debug port.
106
+ */
107
+ async isAvailable(): Promise<boolean> {
108
+ try {
109
+ const res = await fetch(`http://127.0.0.1:${this.port}/json/version`, {
110
+ signal: AbortSignal.timeout(2000),
111
+ });
112
+ return res.ok;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Connect to Chrome. If Chrome isn't running, auto-launches it
120
+ * with CDP enabled and an isolated profile. No user setup required.
121
+ */
122
+ async connect(): Promise<void> {
123
+ if (this._connected) return;
124
+
125
+ // If Chrome isn't running, launch it automatically
126
+ if (!(await this.isAvailable())) {
127
+ console.log('[BrowserController] Chrome not detected, launching automatically...');
128
+ this.runningBrowser = await launchChrome(this.port, this.profileDir);
129
+ }
130
+
131
+ // Discover page targets
132
+ const listRes = await fetch(`http://127.0.0.1:${this.port}/json/list`);
133
+ if (!listRes.ok) {
134
+ throw new Error('Chrome CDP not reachable after launch');
135
+ }
136
+
137
+ const targets = await listRes.json() as Array<{
138
+ type: string;
139
+ webSocketDebuggerUrl: string;
140
+ }>;
141
+
142
+ let pageTarget = targets.find(t => t.type === 'page');
143
+
144
+ if (!pageTarget) {
145
+ // Create a new tab
146
+ const newRes = await fetch(`http://127.0.0.1:${this.port}/json/new?about:blank`);
147
+ pageTarget = await newRes.json() as any;
148
+ }
149
+
150
+ if (!pageTarget?.webSocketDebuggerUrl) {
151
+ throw new Error('No page target found and could not create one');
152
+ }
153
+
154
+ // Connect CDP to the page
155
+ await this.cdp.connect(pageTarget.webSocketDebuggerUrl);
156
+
157
+ // Enable required CDP domains
158
+ await this.cdp.send('Page.enable');
159
+ await this.cdp.send('Runtime.enable');
160
+ await this.cdp.send('DOM.enable');
161
+
162
+ // Inject stealth scripts for all future navigations
163
+ await this.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
164
+ source: STEALTH_SCRIPT,
165
+ });
166
+
167
+ this._connected = true;
168
+ console.log('[BrowserController] Connected to Chrome');
169
+ }
170
+
171
+ /**
172
+ * Navigate to a URL and wait for the page to load.
173
+ */
174
+ async navigate(url: string): Promise<PageSnapshot> {
175
+ await this.ensureConnected();
176
+
177
+ const loadPromise = this.cdp.waitForEvent('Page.loadEventFired', 30000);
178
+
179
+ try {
180
+ await this.cdp.send('Page.navigate', { url });
181
+ } catch (err) {
182
+ // If navigate fails, suppress the dangling loadPromise timeout
183
+ loadPromise.catch(() => {});
184
+ throw err;
185
+ }
186
+
187
+ try {
188
+ await loadPromise;
189
+ } catch {
190
+ // Page.loadEventFired timeout — page may still be usable (SPAs, slow loads)
191
+ console.warn(`[BrowserController] Page load timeout for ${url}, continuing anyway`);
192
+ }
193
+
194
+ // Wait for JS to settle
195
+ await Bun.sleep(800);
196
+
197
+ return this.snapshot();
198
+ }
199
+
200
+ /**
201
+ * Get a snapshot of the current page: text content + numbered interactive elements.
202
+ */
203
+ async snapshot(): Promise<PageSnapshot> {
204
+ await this.ensureConnected();
205
+
206
+ const result = await this.cdp.send('Runtime.evaluate', {
207
+ expression: SNAPSHOT_SCRIPT,
208
+ returnByValue: true,
209
+ awaitPromise: true,
210
+ });
211
+
212
+ if (result.exceptionDetails) {
213
+ throw new Error(`Snapshot failed: ${JSON.stringify(result.exceptionDetails)}`);
214
+ }
215
+
216
+ const data = result.result.value as PageSnapshot & {
217
+ elements: Array<PageElement & { x: number; y: number }>;
218
+ };
219
+
220
+ // Store coordinates locally, strip from LLM-facing data
221
+ this.elementCoords.clear();
222
+ const cleanElements: PageElement[] = [];
223
+
224
+ for (const el of data.elements) {
225
+ this.elementCoords.set(el.id, { x: el.x, y: el.y });
226
+ cleanElements.push({
227
+ id: el.id,
228
+ tag: el.tag,
229
+ text: el.text,
230
+ attrs: el.attrs,
231
+ });
232
+ }
233
+
234
+ return {
235
+ title: data.title,
236
+ url: data.url,
237
+ text: data.text,
238
+ elements: cleanElements,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Click an element by its snapshot ID.
244
+ */
245
+ async click(elementId: number): Promise<string> {
246
+ await this.ensureConnected();
247
+
248
+ const coords = this.elementCoords.get(elementId);
249
+ if (!coords) {
250
+ return `Error: Element [${elementId}] not found. Run browser_snapshot first.`;
251
+ }
252
+
253
+ await this.cdp.send('Input.dispatchMouseEvent', {
254
+ type: 'mousePressed',
255
+ x: coords.x,
256
+ y: coords.y,
257
+ button: 'left',
258
+ clickCount: 1,
259
+ });
260
+ await this.cdp.send('Input.dispatchMouseEvent', {
261
+ type: 'mouseReleased',
262
+ x: coords.x,
263
+ y: coords.y,
264
+ button: 'left',
265
+ clickCount: 1,
266
+ });
267
+
268
+ // Wait for navigation/changes
269
+ await Bun.sleep(1000);
270
+
271
+ return `Clicked element [${elementId}]`;
272
+ }
273
+
274
+ /**
275
+ * Type text into an input element by its snapshot ID.
276
+ * Optionally press Enter after typing.
277
+ *
278
+ * Uses DOM focus + targeted value clearing instead of coordinate-click + Ctrl+A.
279
+ * This prevents misclicks from wiping the wrong field (e.g., typing subject
280
+ * text into the To field in Gmail's compact compose window).
281
+ */
282
+ async type(elementId: number, text: string, submit: boolean = false): Promise<string> {
283
+ await this.ensureConnected();
284
+
285
+ const coords = this.elementCoords.get(elementId);
286
+ if (!coords) {
287
+ return `Error: Element [${elementId}] not found. Run browser_snapshot first.`;
288
+ }
289
+
290
+ // Focus the element via DOM (more reliable than coordinate click for typing)
291
+ const focusResult = await this.cdp.send('Runtime.evaluate', {
292
+ expression: `(() => {
293
+ const el = window.__jarvis_elements && window.__jarvis_elements[${elementId - 1}];
294
+ if (!el) return 'not_found';
295
+ el.focus();
296
+ // Clear existing content based on element type
297
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {
298
+ el.value = '';
299
+ el.dispatchEvent(new Event('input', { bubbles: true }));
300
+ } else if (el.getAttribute('contenteditable') === 'true' || el.getAttribute('role') === 'textbox') {
301
+ // For contenteditable divs, select all within this element only
302
+ const range = document.createRange();
303
+ range.selectNodeContents(el);
304
+ const sel = window.getSelection();
305
+ sel.removeAllRanges();
306
+ sel.addRange(range);
307
+ // Delete the selection
308
+ document.execCommand('delete', false, null);
309
+ }
310
+ return 'ok';
311
+ })()`,
312
+ returnByValue: true,
313
+ });
314
+
315
+ const focusStatus = focusResult?.result?.value;
316
+ if (focusStatus === 'not_found') {
317
+ // Fallback: coordinate-based click (element refs may have been lost on navigation)
318
+ const clickResult = await this.click(elementId);
319
+ if (clickResult.startsWith('Error:')) return clickResult;
320
+ await Bun.sleep(200);
321
+ // Use Ctrl+A as fallback clearing (old behavior)
322
+ await this.cdp.send('Input.dispatchKeyEvent', {
323
+ type: 'keyDown', key: 'a', code: 'KeyA',
324
+ windowsVirtualKeyCode: 65, nativeVirtualKeyCode: 65, modifiers: 2,
325
+ });
326
+ await this.cdp.send('Input.dispatchKeyEvent', {
327
+ type: 'keyUp', key: 'a', code: 'KeyA',
328
+ windowsVirtualKeyCode: 65, nativeVirtualKeyCode: 65, modifiers: 2,
329
+ });
330
+ } else {
331
+ await Bun.sleep(200);
332
+ }
333
+
334
+ // Insert text (like paste — much more reliable than char-by-char)
335
+ await this.cdp.send('Input.insertText', { text });
336
+
337
+ let result = `Typed "${text}" into element [${elementId}]`;
338
+
339
+ if (submit) {
340
+ await Bun.sleep(100);
341
+ await this.pressEnter();
342
+ // Wait for page load after submit
343
+ await Bun.sleep(2000);
344
+ result += ' and pressed Enter';
345
+ }
346
+
347
+ return result;
348
+ }
349
+
350
+ /**
351
+ * Press Enter key.
352
+ */
353
+ async pressEnter(): Promise<void> {
354
+ await this.cdp.send('Input.dispatchKeyEvent', {
355
+ type: 'rawKeyDown',
356
+ key: 'Enter',
357
+ code: 'Enter',
358
+ windowsVirtualKeyCode: 13,
359
+ nativeVirtualKeyCode: 13,
360
+ });
361
+ await this.cdp.send('Input.dispatchKeyEvent', {
362
+ type: 'char',
363
+ key: 'Enter',
364
+ code: 'Enter',
365
+ windowsVirtualKeyCode: 13,
366
+ nativeVirtualKeyCode: 13,
367
+ });
368
+ await this.cdp.send('Input.dispatchKeyEvent', {
369
+ type: 'keyUp',
370
+ key: 'Enter',
371
+ code: 'Enter',
372
+ windowsVirtualKeyCode: 13,
373
+ nativeVirtualKeyCode: 13,
374
+ });
375
+ }
376
+
377
+ /**
378
+ * Scroll the page up or down.
379
+ * direction: 'down' or 'up'
380
+ * amount: pixels to scroll (default: one viewport height)
381
+ */
382
+ async scroll(direction: 'up' | 'down' = 'down', amount?: number): Promise<string> {
383
+ await this.ensureConnected();
384
+
385
+ const viewportHeight = (await this.evaluate('window.innerHeight') as number) || 600;
386
+ const scrollAmount = amount ?? viewportHeight;
387
+
388
+ const pixels = direction === 'down' ? scrollAmount : -scrollAmount;
389
+
390
+ await this.evaluate(`window.scrollBy(0, ${pixels})`);
391
+ await Bun.sleep(500); // Wait for lazy-loaded content
392
+
393
+ return `Scrolled ${direction} by ${scrollAmount}px`;
394
+ }
395
+
396
+ /**
397
+ * Upload a file to a <input type="file"> element on the page.
398
+ * Uses CDP DOM.setFileInputFiles to bypass the native file picker.
399
+ * If no selector is provided, finds the first visible file input.
400
+ */
401
+ async uploadFile(filePath: string, selector?: string): Promise<string> {
402
+ await this.ensureConnected();
403
+
404
+ // Resolve the file input element
405
+ const query = selector || 'input[type="file"]';
406
+ const doc = await this.cdp.send('DOM.getDocument');
407
+ const node = await this.cdp.send('DOM.querySelector', {
408
+ nodeId: doc.root.nodeId,
409
+ selector: query,
410
+ });
411
+
412
+ if (!node.nodeId) {
413
+ return `Error: No file input found matching "${query}". Click the upload/attach button first to trigger the file input.`;
414
+ }
415
+
416
+ // Set the file on the input element via CDP
417
+ try {
418
+ await this.cdp.send('DOM.setFileInputFiles', {
419
+ files: [filePath],
420
+ nodeId: node.nodeId,
421
+ });
422
+ } catch (err) {
423
+ return `Error setting file: ${err instanceof Error ? err.message : String(err)}`;
424
+ }
425
+
426
+ await Bun.sleep(1000); // Wait for the app to process the file
427
+
428
+ return `Uploaded file "${filePath}" to file input`;
429
+ }
430
+
431
+ /**
432
+ * Take a screenshot and save to a file.
433
+ */
434
+ async screenshot(filePath: string = '/tmp/jarvis-screenshot.png'): Promise<string> {
435
+ await this.ensureConnected();
436
+
437
+ const result = await this.cdp.send('Page.captureScreenshot', { format: 'png' });
438
+ const buffer = Buffer.from(result.data, 'base64');
439
+
440
+ await Bun.write(filePath, buffer);
441
+ return filePath;
442
+ }
443
+
444
+ /**
445
+ * Take a screenshot and return raw base64 data (for vision/LLM).
446
+ */
447
+ async screenshotBuffer(): Promise<{ base64: string; mimeType: string }> {
448
+ await this.ensureConnected();
449
+ const result = await this.cdp.send('Page.captureScreenshot', { format: 'png' });
450
+ return { base64: result.data, mimeType: 'image/png' };
451
+ }
452
+
453
+ /**
454
+ * Evaluate arbitrary JavaScript in the page context.
455
+ */
456
+ async evaluate(expression: string): Promise<unknown> {
457
+ await this.ensureConnected();
458
+
459
+ const result = await this.cdp.send('Runtime.evaluate', {
460
+ expression,
461
+ returnByValue: true,
462
+ awaitPromise: true,
463
+ });
464
+
465
+ if (result.exceptionDetails) {
466
+ throw new Error(`JS error: ${JSON.stringify(result.exceptionDetails)}`);
467
+ }
468
+
469
+ return result.result.value;
470
+ }
471
+
472
+ /**
473
+ * Disconnect from Chrome. If we auto-launched Chrome, stop it too.
474
+ */
475
+ async disconnect(): Promise<void> {
476
+ if (this._connected) {
477
+ await this.cdp.close();
478
+ this._connected = false;
479
+ this.elementCoords.clear();
480
+ console.log('[BrowserController] Disconnected');
481
+ }
482
+
483
+ // Stop the Chrome process we launched (if any)
484
+ if (this.runningBrowser) {
485
+ await stopChrome(this.runningBrowser);
486
+ this.runningBrowser = null;
487
+ }
488
+ }
489
+
490
+ get connected(): boolean {
491
+ return this._connected;
492
+ }
493
+
494
+ private async ensureConnected(): Promise<void> {
495
+ if (this._connected && !this.cdp.isOpen) {
496
+ // Connection went stale — reset and reconnect
497
+ console.warn('[BrowserController] CDP connection stale, reconnecting...');
498
+ this._connected = false;
499
+ this.elementCoords.clear();
500
+ }
501
+
502
+ if (!this._connected) {
503
+ await this.connect();
504
+ }
505
+ }
506
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Browser Stealth — Anti-detection scripts
3
+ *
4
+ * Injected into every new document via Page.addScriptToEvaluateOnNewDocument
5
+ * to hide automation fingerprints.
6
+ */
7
+
8
+ export const STEALTH_SCRIPT = `
9
+ // Hide webdriver flag
10
+ Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
11
+
12
+ // Fake plugins array (real browsers have plugins)
13
+ Object.defineProperty(navigator, 'plugins', {
14
+ get: () => {
15
+ const plugins = [
16
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
17
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
18
+ { name: 'Native Client', filename: 'internal-nacl-plugin' },
19
+ ];
20
+ plugins.length = 3;
21
+ return plugins;
22
+ }
23
+ });
24
+
25
+ // Fake languages
26
+ Object.defineProperty(navigator, 'languages', {
27
+ get: () => ['en-US', 'en']
28
+ });
29
+
30
+ // Remove automation-related properties from window
31
+ delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
32
+ delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
33
+ delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
34
+
35
+ // Fix chrome.runtime to look like a real browser
36
+ if (!window.chrome) window.chrome = {};
37
+ if (!window.chrome.runtime) window.chrome.runtime = {};
38
+
39
+ // Fix permissions query
40
+ const originalQuery = window.navigator.permissions?.query;
41
+ if (originalQuery) {
42
+ window.navigator.permissions.query = (parameters) => {
43
+ if (parameters.name === 'notifications') {
44
+ return Promise.resolve({ state: Notification.permission });
45
+ }
46
+ return originalQuery(parameters);
47
+ };
48
+ }
49
+ `;
@@ -0,0 +1,20 @@
1
+ // App Control exports
2
+ export { getAppController } from './app-control/interface.ts';
3
+ export type { AppController, WindowInfo, UIElement } from './app-control/interface.ts';
4
+ export { LinuxAppController } from './app-control/linux.ts';
5
+ export { WindowsAppController } from './app-control/windows.ts';
6
+ export { MacAppController } from './app-control/macos.ts';
7
+
8
+ // Browser exports
9
+ export { CDPClient as CDPBrowser } from './browser/cdp.ts';
10
+ export type { PageElement as BrowserTab } from './browser/session.ts';
11
+ export { BrowserController as BrowserSession } from './browser/session.ts';
12
+
13
+ // Terminal exports
14
+ export { TerminalExecutor } from './terminal/executor.ts';
15
+ export type { CommandResult, ExecuteOptions } from './terminal/executor.ts';
16
+ export { WSLBridge } from './terminal/wsl-bridge.ts';
17
+
18
+ // Tools exports
19
+ export { ToolRegistry } from './tools/registry.ts';
20
+ export type { ToolDefinition, ToolParameter } from './tools/registry.ts';