@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,278 @@
1
+ # Observer Layer
2
+
3
+ The Observer Layer monitors system events and emits standardized observations to the Vault. All observers implement a common interface and can be managed centrally through the `ObserverManager`.
4
+
5
+ ## Architecture
6
+
7
+ ### Common Interface
8
+
9
+ All observers implement the `Observer` interface:
10
+
11
+ ```typescript
12
+ interface Observer {
13
+ name: string;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ isRunning(): boolean;
17
+ onEvent(handler: ObserverEventHandler): void;
18
+ }
19
+ ```
20
+
21
+ Events are emitted in a standardized format:
22
+
23
+ ```typescript
24
+ type ObserverEvent = {
25
+ type: string;
26
+ data: Record<string, unknown>;
27
+ timestamp: number;
28
+ };
29
+ ```
30
+
31
+ ## Observers
32
+
33
+ ### FileWatcher (Fully Implemented)
34
+
35
+ Monitors file system changes in specified directories.
36
+
37
+ ```typescript
38
+ import { FileWatcher } from './file-watcher';
39
+
40
+ const watcher = new FileWatcher([
41
+ '/home/user/projects',
42
+ '/home/user/documents'
43
+ ]);
44
+
45
+ watcher.onEvent((event) => {
46
+ // event.type: 'file_change'
47
+ // event.data: { path, eventType: 'rename'|'change', filename, basePath }
48
+ });
49
+
50
+ await watcher.start();
51
+ ```
52
+
53
+ Features:
54
+ - Recursive directory watching using `node:fs` watch API
55
+ - Debouncing (100ms) to avoid duplicate rapid-fire events
56
+ - Automatic cleanup of old debounce entries to prevent memory leaks
57
+
58
+ ### ClipboardMonitor (Fully Implemented)
59
+
60
+ Polls the system clipboard and detects content changes.
61
+
62
+ ```typescript
63
+ import { ClipboardMonitor } from './clipboard';
64
+
65
+ const clipboard = new ClipboardMonitor(1000); // Poll every 1 second
66
+
67
+ clipboard.onEvent((event) => {
68
+ // event.type: 'clipboard'
69
+ // event.data: { content, length }
70
+ });
71
+
72
+ await clipboard.start();
73
+ ```
74
+
75
+ Features:
76
+ - Cross-platform clipboard reading (Linux/macOS/Windows/WSL)
77
+ - Automatic platform detection and command selection
78
+ - Silent failure on read errors to avoid spam
79
+
80
+ Platform commands used:
81
+ - Linux: `xclip` or `xsel`
82
+ - macOS: `pbpaste`
83
+ - Windows/WSL: `powershell.exe Get-Clipboard`
84
+
85
+ ### ProcessMonitor (Fully Implemented)
86
+
87
+ Monitors running processes and detects lifecycle changes.
88
+
89
+ ```typescript
90
+ import { ProcessMonitor } from './processes';
91
+
92
+ const processes = new ProcessMonitor(5000); // Poll every 5 seconds
93
+
94
+ processes.onEvent((event) => {
95
+ // event.type: 'process_started' or 'process_stopped'
96
+ // event.data: { pid, name, cpu?, memory? }
97
+ });
98
+
99
+ await processes.start();
100
+ ```
101
+
102
+ Features:
103
+ - Cross-platform process listing (Linux/macOS/Windows)
104
+ - Detects new and terminated processes
105
+ - Provides CPU and memory usage data
106
+ - Automatic cleanup of terminated process records
107
+
108
+ ### NotificationListener (Stub)
109
+
110
+ Placeholder for system notification monitoring.
111
+
112
+ **TODO:**
113
+ - Linux: Monitor D-Bus `org.freedesktop.Notifications`
114
+ - Windows: Use PowerShell to read notification center
115
+ - macOS: Use notification center API
116
+
117
+ ### CalendarSync (Stub)
118
+
119
+ Placeholder for calendar integration.
120
+
121
+ **TODO:**
122
+ - Google Calendar API integration (OAuth2)
123
+ - Microsoft Graph API (Outlook/Office 365)
124
+ - CalDAV support for generic providers
125
+
126
+ ### EmailSync (Stub)
127
+
128
+ Placeholder for email integration.
129
+
130
+ **TODO:**
131
+ - Gmail API integration (OAuth2)
132
+ - Microsoft Graph API (Outlook/Office 365)
133
+ - IMAP support for generic providers
134
+
135
+ ## ObserverManager
136
+
137
+ The `ObserverManager` provides centralized control over all observers.
138
+
139
+ ```typescript
140
+ import { ObserverManager, FileWatcher, ClipboardMonitor } from './observers';
141
+
142
+ const manager = new ObserverManager();
143
+
144
+ // Register observers
145
+ manager.register(new FileWatcher(['/path/to/watch']));
146
+ manager.register(new ClipboardMonitor(1000));
147
+
148
+ // Set event handler (typically the Vault's ingestion function)
149
+ manager.setEventHandler((event) => {
150
+ console.log(`[${event.type}]`, event.data);
151
+ // Forward to Vault for storage/analysis
152
+ });
153
+
154
+ // Start all observers
155
+ await manager.startAll();
156
+
157
+ // Get status
158
+ console.log(manager.getStatus());
159
+ // => { 'file-watcher': true, 'clipboard': true }
160
+
161
+ // Stop specific observer
162
+ await manager.stopObserver('clipboard');
163
+
164
+ // Stop all observers
165
+ await manager.stopAll();
166
+ ```
167
+
168
+ ### Manager API
169
+
170
+ - `register(observer: Observer)` - Register a new observer
171
+ - `setEventHandler(handler: ObserverEventHandler)` - Set global event handler
172
+ - `startAll()` - Start all registered observers
173
+ - `stopAll()` - Stop all registered observers
174
+ - `startObserver(name: string)` - Start specific observer
175
+ - `stopObserver(name: string)` - Stop specific observer
176
+ - `getStatus()` - Get running status of all observers
177
+ - `listObservers()` - Get list of registered observer names
178
+
179
+ ## Usage Example
180
+
181
+ See `example.ts` for a complete working example:
182
+
183
+ ```bash
184
+ bun run src/observers/example.ts
185
+ ```
186
+
187
+ This will run all observers for 30 seconds and log events to the console.
188
+
189
+ ## Integration with Vault
190
+
191
+ The Observer Layer is designed to integrate with the Vault for persistent storage:
192
+
193
+ ```typescript
194
+ import { ObserverManager } from './observers';
195
+ import { Vault } from '../vault';
196
+
197
+ const vault = new Vault();
198
+ const manager = new ObserverManager();
199
+
200
+ // Register all observers...
201
+ manager.register(new FileWatcher(['/home/user/projects']));
202
+ // ... etc
203
+
204
+ // Connect to Vault
205
+ manager.setEventHandler((event) => {
206
+ vault.storeObservation(event);
207
+ });
208
+
209
+ await manager.startAll();
210
+ ```
211
+
212
+ ## Event Types
213
+
214
+ Current event types emitted by observers:
215
+
216
+ - `file_change` - File system change detected
217
+ - `clipboard` - Clipboard content changed
218
+ - `process_started` - New process started
219
+ - `process_stopped` - Process terminated
220
+
221
+ Future event types (when stubs are implemented):
222
+
223
+ - `notification` - System notification received
224
+ - `calendar_event` - Calendar event upcoming/created/updated
225
+ - `new_email` - New email received
226
+ - `email_read` - Email marked as read
227
+ - `email_sent` - Email sent
228
+
229
+ ## Performance Considerations
230
+
231
+ - **FileWatcher**: Uses native `node:fs` watch API with minimal overhead
232
+ - **ClipboardMonitor**: Polling-based, configurable interval (default 1s)
233
+ - **ProcessMonitor**: Polling-based, configurable interval (default 5s)
234
+ - **Debouncing**: FileWatcher implements 100ms debouncing for rapid changes
235
+ - **Memory**: Old debounce entries are automatically cleaned up
236
+
237
+ ## Error Handling
238
+
239
+ All observers implement graceful error handling:
240
+
241
+ - Silent failures for transient errors (e.g., clipboard read failures)
242
+ - Console logging for persistent errors
243
+ - Proper cleanup in `stop()` methods
244
+ - Error isolation (one observer's failure doesn't affect others)
245
+
246
+ ## Platform Support
247
+
248
+ - **Linux**: Full support for all implemented observers
249
+ - **macOS**: Full support for all implemented observers
250
+ - **Windows**: Full support for all implemented observers
251
+ - **WSL**: Full support (uses Windows clipboard via PowerShell)
252
+
253
+ ## Testing
254
+
255
+ Run the example to test all observers:
256
+
257
+ ```bash
258
+ bun run src/observers/example.ts
259
+ ```
260
+
261
+ Or create custom tests:
262
+
263
+ ```typescript
264
+ import { test, expect } from 'bun:test';
265
+ import { FileWatcher } from './file-watcher';
266
+
267
+ test('FileWatcher starts and stops', async () => {
268
+ const watcher = new FileWatcher(['/tmp']);
269
+
270
+ expect(watcher.isRunning()).toBe(false);
271
+
272
+ await watcher.start();
273
+ expect(watcher.isRunning()).toBe(true);
274
+
275
+ await watcher.stop();
276
+ expect(watcher.isRunning()).toBe(false);
277
+ });
278
+ ```
@@ -0,0 +1,113 @@
1
+ /**
2
+ * CalendarSync — Google Calendar Observer
3
+ *
4
+ * Polls Calendar API every 2 minutes with a 30-minute look-ahead.
5
+ * Tracks announced event IDs to avoid duplicate alerts.
6
+ * Graceful: if no Google tokens, logs warning and stays no-op.
7
+ */
8
+
9
+ import type { Observer, ObserverEventHandler } from './index';
10
+ import type { GoogleAuth } from '../integrations/google-auth.ts';
11
+ import { listUpcomingEvents } from '../integrations/google-api.ts';
12
+
13
+ const POLL_INTERVAL_MS = 2 * 60_000; // 2 minutes
14
+ const LOOK_AHEAD_MS = 30 * 60_000; // 30 minutes
15
+
16
+ export class CalendarSync implements Observer {
17
+ name = 'calendar';
18
+ private running = false;
19
+ private handler: ObserverEventHandler | null = null;
20
+ private pollTimer: Timer | null = null;
21
+ private googleAuth: GoogleAuth | null;
22
+ private announcedEventIds: Set<string> = new Set();
23
+ private calendarId: string;
24
+
25
+ constructor(googleAuth?: GoogleAuth, calendarId?: string) {
26
+ this.googleAuth = googleAuth ?? null;
27
+ this.calendarId = calendarId ?? 'primary';
28
+ }
29
+
30
+ async start(): Promise<void> {
31
+ this.running = true;
32
+
33
+ if (!this.googleAuth || !this.googleAuth.isAuthenticated()) {
34
+ console.log('[calendar] No Google auth configured — calendar monitoring disabled');
35
+ console.log('[calendar] Run: bun run src/scripts/google-setup.ts to set up Calendar');
36
+ return;
37
+ }
38
+
39
+ console.log('[calendar] Observer started — polling Calendar every 2min (30min look-ahead)');
40
+
41
+ // Initial poll
42
+ this.poll();
43
+
44
+ // Set up recurring poll
45
+ this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
46
+ }
47
+
48
+ async stop(): Promise<void> {
49
+ this.running = false;
50
+
51
+ if (this.pollTimer) {
52
+ clearInterval(this.pollTimer);
53
+ this.pollTimer = null;
54
+ }
55
+
56
+ console.log('[calendar] Observer stopped');
57
+ }
58
+
59
+ isRunning(): boolean {
60
+ return this.running;
61
+ }
62
+
63
+ onEvent(handler: ObserverEventHandler): void {
64
+ this.handler = handler;
65
+ }
66
+
67
+ private async poll(): Promise<void> {
68
+ if (!this.googleAuth || !this.handler) return;
69
+
70
+ try {
71
+ const accessToken = await this.googleAuth.getAccessToken();
72
+ const now = new Date();
73
+ const later = new Date(now.getTime() + LOOK_AHEAD_MS);
74
+
75
+ const events = await listUpcomingEvents(
76
+ accessToken,
77
+ this.calendarId,
78
+ now.toISOString(),
79
+ later.toISOString(),
80
+ 10
81
+ );
82
+
83
+ for (const event of events) {
84
+ // Skip already-announced events
85
+ if (this.announcedEventIds.has(event.id)) continue;
86
+ this.announcedEventIds.add(event.id);
87
+
88
+ this.handler({
89
+ type: 'calendar',
90
+ data: {
91
+ id: event.id,
92
+ summary: event.summary,
93
+ description: event.description,
94
+ start: event.start,
95
+ end: event.end,
96
+ location: event.location,
97
+ attendees: event.attendees,
98
+ htmlLink: event.htmlLink,
99
+ },
100
+ timestamp: Date.now(),
101
+ });
102
+ }
103
+
104
+ // Cap announcedEventIds to prevent unbounded growth
105
+ if (this.announcedEventIds.size > 500) {
106
+ const arr = Array.from(this.announcedEventIds);
107
+ this.announcedEventIds = new Set(arr.slice(arr.length - 250));
108
+ }
109
+ } catch (err) {
110
+ console.error('[calendar] Poll error:', err);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * ClipboardMonitor - Monitors clipboard changes
3
+ *
4
+ * Polls the system clipboard at regular intervals and emits events when content changes.
5
+ * Uses platform-specific commands to read clipboard content.
6
+ */
7
+
8
+ import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
9
+
10
+ export class ClipboardMonitor implements Observer {
11
+ name = 'clipboard';
12
+ private interval: Timer | null = null;
13
+ private lastContent: string = '';
14
+ private handler: ObserverEventHandler | null = null;
15
+ private running = false;
16
+ private pollMs: number;
17
+
18
+ constructor(pollMs: number = 1000) {
19
+ this.pollMs = pollMs;
20
+ }
21
+
22
+ async start(): Promise<void> {
23
+ if (this.running) {
24
+ console.log('[clipboard] Already running');
25
+ return;
26
+ }
27
+
28
+ console.log(`[clipboard] Starting clipboard monitoring (polling every ${this.pollMs}ms)...`);
29
+
30
+ // Initialize with current clipboard content
31
+ try {
32
+ this.lastContent = await this.readClipboard();
33
+ } catch (error) {
34
+ console.error('[clipboard] Failed to read initial clipboard:', error);
35
+ this.lastContent = '';
36
+ }
37
+
38
+ // Start polling
39
+ this.interval = setInterval(async () => {
40
+ try {
41
+ const content = await this.readClipboard();
42
+
43
+ if (content !== this.lastContent) {
44
+ this.lastContent = content;
45
+
46
+ if (this.handler) {
47
+ const event: ObserverEvent = {
48
+ type: 'clipboard',
49
+ data: {
50
+ content,
51
+ length: content.length,
52
+ },
53
+ timestamp: Date.now(),
54
+ };
55
+
56
+ this.handler(event);
57
+ }
58
+ }
59
+ } catch (error) {
60
+ // Silent fail on read errors to avoid spam
61
+ // console.error('[clipboard] Failed to read clipboard:', error);
62
+ }
63
+ }, this.pollMs);
64
+
65
+ this.running = true;
66
+ }
67
+
68
+ async stop(): Promise<void> {
69
+ if (!this.running) {
70
+ return;
71
+ }
72
+
73
+ console.log('[clipboard] Stopping clipboard monitoring...');
74
+
75
+ if (this.interval) {
76
+ clearInterval(this.interval);
77
+ this.interval = null;
78
+ }
79
+
80
+ this.running = false;
81
+ }
82
+
83
+ isRunning(): boolean {
84
+ return this.running;
85
+ }
86
+
87
+ onEvent(handler: ObserverEventHandler): void {
88
+ this.handler = handler;
89
+ }
90
+
91
+ /**
92
+ * Read clipboard content using platform-specific commands
93
+ */
94
+ private async readClipboard(): Promise<string> {
95
+ const platform = process.platform;
96
+
97
+ try {
98
+ let result: { stdout: Buffer; stderr: Buffer };
99
+
100
+ if (platform === 'linux') {
101
+ // Try xclip first
102
+ try {
103
+ result = await Bun.$`xclip -selection clipboard -o`.quiet();
104
+ return result.stdout.toString().trim();
105
+ } catch {
106
+ // Fall back to xsel
107
+ try {
108
+ result = await Bun.$`xsel --clipboard --output`.quiet();
109
+ return result.stdout.toString().trim();
110
+ } catch {
111
+ // Check if we're in WSL and can use PowerShell
112
+ try {
113
+ result = await Bun.$`powershell.exe Get-Clipboard`.quiet();
114
+ return result.stdout.toString().trim();
115
+ } catch {
116
+ throw new Error('No clipboard tool available (tried xclip, xsel, powershell.exe)');
117
+ }
118
+ }
119
+ }
120
+ } else if (platform === 'darwin') {
121
+ // macOS
122
+ result = await Bun.$`pbpaste`.quiet();
123
+ return result.stdout.toString().trim();
124
+ } else if (platform === 'win32') {
125
+ // Windows
126
+ result = await Bun.$`powershell.exe Get-Clipboard`.quiet();
127
+ return result.stdout.toString().trim();
128
+ } else {
129
+ throw new Error(`Unsupported platform: ${platform}`);
130
+ }
131
+ } catch (error) {
132
+ // Return empty string on error
133
+ return '';
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * EmailSync — Gmail Observer
3
+ *
4
+ * Polls Gmail API every 60s for unread messages.
5
+ * Tracks seen message IDs to avoid re-emitting.
6
+ * Fetches detail (subject, from, snippet) for new messages.
7
+ * Graceful: if no Google tokens, logs warning and stays no-op.
8
+ */
9
+
10
+ import type { Observer, ObserverEventHandler } from './index';
11
+ import type { GoogleAuth } from '../integrations/google-auth.ts';
12
+ import { listUnreadEmails, getEmailDetail } from '../integrations/google-api.ts';
13
+
14
+ const POLL_INTERVAL_MS = 60_000; // 60 seconds
15
+
16
+ export class EmailSync implements Observer {
17
+ name = 'email';
18
+ private running = false;
19
+ private handler: ObserverEventHandler | null = null;
20
+ private pollTimer: Timer | null = null;
21
+ private googleAuth: GoogleAuth | null;
22
+ private seenMessageIds: Set<string> = new Set();
23
+
24
+ constructor(googleAuth?: GoogleAuth) {
25
+ this.googleAuth = googleAuth ?? null;
26
+ }
27
+
28
+ async start(): Promise<void> {
29
+ this.running = true;
30
+
31
+ if (!this.googleAuth || !this.googleAuth.isAuthenticated()) {
32
+ console.log('[email] No Google auth configured — email monitoring disabled');
33
+ console.log('[email] Run: bun run src/scripts/google-setup.ts to set up Gmail');
34
+ return;
35
+ }
36
+
37
+ console.log('[email] Observer started — polling Gmail every 60s');
38
+
39
+ // Initial poll
40
+ this.poll();
41
+
42
+ // Set up recurring poll
43
+ this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
44
+ }
45
+
46
+ async stop(): Promise<void> {
47
+ this.running = false;
48
+
49
+ if (this.pollTimer) {
50
+ clearInterval(this.pollTimer);
51
+ this.pollTimer = null;
52
+ }
53
+
54
+ console.log('[email] Observer stopped');
55
+ }
56
+
57
+ isRunning(): boolean {
58
+ return this.running;
59
+ }
60
+
61
+ onEvent(handler: ObserverEventHandler): void {
62
+ this.handler = handler;
63
+ }
64
+
65
+ private async poll(): Promise<void> {
66
+ if (!this.googleAuth || !this.handler) return;
67
+
68
+ try {
69
+ const accessToken = await this.googleAuth.getAccessToken();
70
+ const messages = await listUnreadEmails(accessToken, 10);
71
+
72
+ for (const msg of messages) {
73
+ // Skip already-seen messages
74
+ if (this.seenMessageIds.has(msg.id)) continue;
75
+ this.seenMessageIds.add(msg.id);
76
+
77
+ // Fetch detail
78
+ try {
79
+ const detail = await getEmailDetail(accessToken, msg.id);
80
+
81
+ this.handler({
82
+ type: 'email',
83
+ data: {
84
+ id: detail.id,
85
+ threadId: detail.threadId,
86
+ subject: detail.subject,
87
+ from: detail.from,
88
+ to: detail.to,
89
+ date: detail.date,
90
+ snippet: detail.snippet,
91
+ labels: detail.labels,
92
+ },
93
+ timestamp: Date.now(),
94
+ });
95
+ } catch (err) {
96
+ console.error(`[email] Failed to get detail for ${msg.id}:`, err);
97
+ }
98
+ }
99
+
100
+ // Cap seenMessageIds to prevent unbounded growth
101
+ if (this.seenMessageIds.size > 1000) {
102
+ const arr = Array.from(this.seenMessageIds);
103
+ this.seenMessageIds = new Set(arr.slice(arr.length - 500));
104
+ }
105
+ } catch (err) {
106
+ console.error('[email] Poll error:', err);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Example usage of the Observer Layer
3
+ *
4
+ * This demonstrates how to set up and use the ObserverManager with various observers.
5
+ */
6
+
7
+ import {
8
+ ObserverManager,
9
+ FileWatcher,
10
+ ClipboardMonitor,
11
+ ProcessMonitor,
12
+ NotificationListener,
13
+ CalendarSync,
14
+ EmailSync,
15
+ type ObserverEvent,
16
+ } from './index';
17
+
18
+ async function main() {
19
+ // Create the manager
20
+ const manager = new ObserverManager();
21
+
22
+ // Register observers
23
+ manager.register(new FileWatcher([import.meta.dir + '/../'])); // Watch src directory
24
+ manager.register(new ClipboardMonitor(2000)); // Poll clipboard every 2 seconds
25
+ manager.register(new ProcessMonitor(10000)); // Poll processes every 10 seconds
26
+ manager.register(new NotificationListener()); // Stub
27
+ manager.register(new CalendarSync()); // Stub
28
+ manager.register(new EmailSync()); // Stub
29
+
30
+ // Set up event handler (this would typically be the Vault's ingestion function)
31
+ manager.setEventHandler((event: ObserverEvent) => {
32
+ console.log(`[EVENT] ${event.type} at ${new Date(event.timestamp).toISOString()}`);
33
+ console.log(' Data:', JSON.stringify(event.data, null, 2));
34
+ });
35
+
36
+ // Start all observers
37
+ await manager.startAll();
38
+
39
+ // Get status
40
+ console.log('\n[STATUS] Observer running status:', manager.getStatus());
41
+
42
+ // Run for 30 seconds, then stop
43
+ console.log('\n[INFO] Running observers for 30 seconds...\n');
44
+
45
+ await new Promise(resolve => setTimeout(resolve, 30000));
46
+
47
+ // Stop all observers
48
+ await manager.stopAll();
49
+
50
+ console.log('\n[INFO] Example complete');
51
+ }
52
+
53
+ // Run example if executed directly
54
+ if (import.meta.main) {
55
+ main().catch(console.error);
56
+ }
57
+
58
+ export { main };