@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,200 @@
1
+ /**
2
+ * Utility functions for working with roles
3
+ */
4
+
5
+ import type { RoleDefinition } from './types.ts';
6
+ import type { ActionCategory } from './authority.ts';
7
+ import { canPerform, listAllowedActions } from './authority.ts';
8
+
9
+ /**
10
+ * Find roles that can perform a specific action
11
+ */
12
+ export function findRolesWithPermission(
13
+ roles: Map<string, RoleDefinition>,
14
+ action: ActionCategory
15
+ ): RoleDefinition[] {
16
+ const result: RoleDefinition[] = [];
17
+
18
+ for (const role of roles.values()) {
19
+ if (canPerform(role, action)) {
20
+ result.push(role);
21
+ }
22
+ }
23
+
24
+ return result.sort((a, b) => a.authority_level - b.authority_level);
25
+ }
26
+
27
+ /**
28
+ * Find the least privileged role that can perform an action
29
+ */
30
+ export function findMinimalRoleForAction(
31
+ roles: Map<string, RoleDefinition>,
32
+ action: ActionCategory
33
+ ): RoleDefinition | null {
34
+ const capable = findRolesWithPermission(roles, action);
35
+ return capable.length > 0 ? capable[0]! : null;
36
+ }
37
+
38
+ /**
39
+ * Compare two roles and show permission differences
40
+ */
41
+ export function compareRoles(
42
+ role1: RoleDefinition,
43
+ role2: RoleDefinition
44
+ ): {
45
+ onlyInRole1: ActionCategory[];
46
+ onlyInRole2: ActionCategory[];
47
+ inBoth: ActionCategory[];
48
+ } {
49
+ const actions1 = new Set(listAllowedActions(role1));
50
+ const actions2 = new Set(listAllowedActions(role2));
51
+
52
+ const onlyInRole1: ActionCategory[] = [];
53
+ const onlyInRole2: ActionCategory[] = [];
54
+ const inBoth: ActionCategory[] = [];
55
+
56
+ for (const action of actions1) {
57
+ if (actions2.has(action)) {
58
+ inBoth.push(action);
59
+ } else {
60
+ onlyInRole1.push(action);
61
+ }
62
+ }
63
+
64
+ for (const action of actions2) {
65
+ if (!actions1.has(action)) {
66
+ onlyInRole2.push(action);
67
+ }
68
+ }
69
+
70
+ return { onlyInRole1, onlyInRole2, inBoth };
71
+ }
72
+
73
+ /**
74
+ * Get a summary of role hierarchy based on authority levels
75
+ */
76
+ export function getRoleHierarchy(roles: Map<string, RoleDefinition>): string {
77
+ const sorted = Array.from(roles.values()).sort(
78
+ (a, b) => b.authority_level - a.authority_level
79
+ );
80
+
81
+ const lines: string[] = [];
82
+ let currentLevel = -1;
83
+
84
+ for (const role of sorted) {
85
+ if (role.authority_level !== currentLevel) {
86
+ currentLevel = role.authority_level;
87
+ lines.push(`\nLevel ${currentLevel}:`);
88
+ }
89
+ lines.push(` - ${role.name} (${role.id})`);
90
+ }
91
+
92
+ return lines.join('\n').trim();
93
+ }
94
+
95
+ /**
96
+ * Check if a role can spawn a specific sub-role
97
+ */
98
+ export function canSpawnRole(
99
+ role: RoleDefinition,
100
+ subRoleId: string
101
+ ): boolean {
102
+ return role.sub_roles.some(sr => sr.role_id === subRoleId);
103
+ }
104
+
105
+ /**
106
+ * Get all roles that can spawn a specific role
107
+ */
108
+ export function findSpawnersOfRole(
109
+ roles: Map<string, RoleDefinition>,
110
+ targetRoleId: string
111
+ ): RoleDefinition[] {
112
+ const spawners: RoleDefinition[] = [];
113
+
114
+ for (const role of roles.values()) {
115
+ if (canSpawnRole(role, targetRoleId)) {
116
+ spawners.push(role);
117
+ }
118
+ }
119
+
120
+ return spawners;
121
+ }
122
+
123
+ /**
124
+ * Validate role hierarchy (check for circular dependencies)
125
+ */
126
+ export function validateRoleHierarchy(
127
+ roles: Map<string, RoleDefinition>
128
+ ): { valid: boolean; errors: string[] } {
129
+ const errors: string[] = [];
130
+
131
+ for (const [id, role] of roles) {
132
+ // Check if sub-roles exist
133
+ for (const subRole of role.sub_roles) {
134
+ if (!roles.has(subRole.role_id)) {
135
+ errors.push(
136
+ `Role '${id}' references non-existent sub-role '${subRole.role_id}'`
137
+ );
138
+ }
139
+
140
+ // Check for self-spawning
141
+ if (subRole.role_id === id) {
142
+ errors.push(`Role '${id}' attempts to spawn itself`);
143
+ }
144
+
145
+ // Check authority levels (parent should have higher authority)
146
+ const subRoleDef = roles.get(subRole.role_id);
147
+ if (subRoleDef && subRoleDef.authority_level > role.authority_level) {
148
+ errors.push(
149
+ `Role '${id}' (level ${role.authority_level}) attempts to spawn ` +
150
+ `'${subRole.role_id}' (level ${subRoleDef.authority_level}) with higher authority`
151
+ );
152
+ }
153
+ }
154
+ }
155
+
156
+ return {
157
+ valid: errors.length === 0,
158
+ errors,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Get statistics about a role collection
164
+ */
165
+ export function getRoleStats(roles: Map<string, RoleDefinition>): {
166
+ totalRoles: number;
167
+ averageAuthorityLevel: number;
168
+ totalTools: number;
169
+ totalKPIs: number;
170
+ rolesWithSubRoles: number;
171
+ authorityDistribution: Record<number, number>;
172
+ } {
173
+ let totalAuthority = 0;
174
+ let totalTools = 0;
175
+ let totalKPIs = 0;
176
+ let rolesWithSubRoles = 0;
177
+ const authorityDistribution: Record<number, number> = {};
178
+
179
+ for (const role of roles.values()) {
180
+ totalAuthority += role.authority_level;
181
+ totalTools += role.tools.length;
182
+ totalKPIs += role.kpis.length;
183
+
184
+ if (role.sub_roles.length > 0) {
185
+ rolesWithSubRoles++;
186
+ }
187
+
188
+ authorityDistribution[role.authority_level] =
189
+ (authorityDistribution[role.authority_level] || 0) + 1;
190
+ }
191
+
192
+ return {
193
+ totalRoles: roles.size,
194
+ averageAuthorityLevel: roles.size > 0 ? totalAuthority / roles.size : 0,
195
+ totalTools,
196
+ totalKPIs,
197
+ rolesWithSubRoles,
198
+ authorityDistribution,
199
+ };
200
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Google OAuth2 Setup Script
3
+ *
4
+ * Interactive CLI to authenticate JARVIS with Google APIs.
5
+ * Opens browser to Google consent screen, handles callback,
6
+ * and saves tokens to ~/.jarvis/google-tokens.json.
7
+ *
8
+ * Usage: bun run src/scripts/google-setup.ts
9
+ */
10
+
11
+ import { GoogleAuth } from '../integrations/google-auth.ts';
12
+ import { loadConfig } from '../config/loader.ts';
13
+
14
+ const SCOPES = [
15
+ 'https://www.googleapis.com/auth/gmail.readonly',
16
+ 'https://www.googleapis.com/auth/calendar.readonly',
17
+ ];
18
+
19
+ async function main() {
20
+ console.log('');
21
+ console.log('=== Google OAuth2 Setup for JARVIS ===');
22
+ console.log('');
23
+
24
+ // Load config to get client_id / client_secret
25
+ const config = await loadConfig();
26
+
27
+ let clientId = config.google?.client_id ?? '';
28
+ let clientSecret = config.google?.client_secret ?? '';
29
+
30
+ if (!clientId || !clientSecret) {
31
+ console.log('No Google OAuth credentials found in config.yaml.');
32
+ console.log('');
33
+ console.log('Add the following to your ~/.jarvis/config.yaml:');
34
+ console.log('');
35
+ console.log('google:');
36
+ console.log(' client_id: "your-client-id.apps.googleusercontent.com"');
37
+ console.log(' client_secret: "your-client-secret"');
38
+ console.log('');
39
+ console.log('To get credentials:');
40
+ console.log(' 1. Go to https://console.cloud.google.com/apis/credentials');
41
+ console.log(' 2. Create an OAuth2 client ID (Web application)');
42
+ console.log(' 3. Add http://localhost:3142/api/auth/google/callback as a redirect URI');
43
+ console.log(' 4. Copy client_id and client_secret into config.yaml');
44
+ console.log('');
45
+
46
+ // Try reading from stdin
47
+ const rl = require('readline').createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout,
50
+ });
51
+
52
+ const ask = (q: string): Promise<string> =>
53
+ new Promise((resolve) => rl.question(q, resolve));
54
+
55
+ clientId = (await ask('Client ID (or press Enter to abort): ')).trim();
56
+ if (!clientId) {
57
+ console.log('Aborted.');
58
+ rl.close();
59
+ process.exit(1);
60
+ }
61
+
62
+ clientSecret = (await ask('Client Secret: ')).trim();
63
+ if (!clientSecret) {
64
+ console.log('Aborted.');
65
+ rl.close();
66
+ process.exit(1);
67
+ }
68
+
69
+ rl.close();
70
+ }
71
+
72
+ const auth = new GoogleAuth(clientId, clientSecret);
73
+
74
+ // Check if already authenticated
75
+ if (auth.isAuthenticated()) {
76
+ console.log('Already authenticated! Tokens exist at ~/.jarvis/google-tokens.json');
77
+ console.log('To re-authenticate, delete that file and run this again.');
78
+ process.exit(0);
79
+ }
80
+
81
+ const authUrl = auth.getAuthUrl(SCOPES);
82
+
83
+ console.log('');
84
+ console.log('Opening browser for Google authorization...');
85
+ console.log('');
86
+ console.log('If the browser does not open, visit this URL:');
87
+ console.log(authUrl);
88
+ console.log('');
89
+
90
+ // Try to open browser
91
+ try {
92
+ const opener = process.platform === 'darwin'
93
+ ? 'open'
94
+ : process.platform === 'win32'
95
+ ? 'start'
96
+ : 'xdg-open';
97
+ Bun.spawn([opener, authUrl], { stdout: 'ignore', stderr: 'ignore' });
98
+ } catch {
99
+ // Ignore — user can open manually
100
+ }
101
+
102
+ // Start temporary HTTP server to receive the callback
103
+ console.log('Waiting for authorization callback on port 3142...');
104
+ console.log('');
105
+
106
+ const server = Bun.serve({
107
+ port: 3142,
108
+ async fetch(req) {
109
+ const url = new URL(req.url);
110
+
111
+ if (url.pathname === '/api/auth/google/callback') {
112
+ const code = url.searchParams.get('code');
113
+ const error = url.searchParams.get('error');
114
+
115
+ if (error) {
116
+ console.error('Authorization denied:', error);
117
+ setTimeout(() => {
118
+ server.stop();
119
+ process.exit(1);
120
+ }, 500);
121
+ return new Response(
122
+ '<html><body><h1>Authorization Denied</h1><p>You can close this tab.</p></body></html>',
123
+ { headers: { 'Content-Type': 'text/html' } }
124
+ );
125
+ }
126
+
127
+ if (!code) {
128
+ return new Response('Missing code', { status: 400 });
129
+ }
130
+
131
+ try {
132
+ const tokens = await auth.exchangeCode(code);
133
+ console.log('Authorization successful!');
134
+ console.log(`Access token: ${tokens.access_token.slice(0, 20)}...`);
135
+ console.log(`Refresh token: ${tokens.refresh_token.slice(0, 20)}...`);
136
+ console.log(`Tokens saved to ~/.jarvis/google-tokens.json`);
137
+
138
+ setTimeout(() => {
139
+ server.stop();
140
+ process.exit(0);
141
+ }, 500);
142
+
143
+ return new Response(
144
+ '<html><body><h1>JARVIS Google Authorization Complete!</h1><p>Tokens saved. You can close this tab.</p></body></html>',
145
+ { headers: { 'Content-Type': 'text/html' } }
146
+ );
147
+ } catch (err) {
148
+ console.error('Token exchange failed:', err);
149
+ setTimeout(() => {
150
+ server.stop();
151
+ process.exit(1);
152
+ }, 500);
153
+ return new Response(
154
+ `<html><body><h1>Token Exchange Failed</h1><pre>${err}</pre></body></html>`,
155
+ { headers: { 'Content-Type': 'text/html' }, status: 500 }
156
+ );
157
+ }
158
+ }
159
+
160
+ return new Response('Not found', { status: 404 });
161
+ },
162
+ });
163
+ }
164
+
165
+ main().catch((err) => {
166
+ console.error('Setup failed:', err);
167
+ process.exit(1);
168
+ });
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Sidecar Connection — Per-Sidecar WebSocket Wrapper
3
+ *
4
+ * Manages a single sidecar's WebSocket connection, including message
5
+ * parsing, validation, binary frame correlation, and heartbeat.
6
+ */
7
+
8
+ import type { ServerWebSocket } from 'bun';
9
+ import type { RPCRequest, SidecarEvent } from './protocol.ts';
10
+ import type { EventScheduler } from './scheduler.ts';
11
+ import { validateEvent, validateBinaryFrame, MAX_JSON_SIZE } from './validator.ts';
12
+
13
+ const HEARTBEAT_INTERVAL_MS = 30_000;
14
+ const MAX_MISSED_PONGS = 3;
15
+ const BINARY_WAIT_TIMEOUT_MS = 5_000;
16
+
17
+ interface PendingBinary {
18
+ resolve: (payload: Buffer) => void;
19
+ reject: (error: Error) => void;
20
+ timer: Timer;
21
+ }
22
+
23
+ export class SidecarConnection {
24
+ readonly sidecarId: string;
25
+ private ws: ServerWebSocket<unknown>;
26
+ private scheduler: EventScheduler;
27
+ private pendingBinary = new Map<string, PendingBinary>();
28
+ private heartbeatTimer: Timer | null = null;
29
+ private missedPongs = 0;
30
+ private alive = true;
31
+ private onDisconnect: () => void;
32
+
33
+ constructor(
34
+ sidecarId: string,
35
+ ws: ServerWebSocket<unknown>,
36
+ scheduler: EventScheduler,
37
+ onDisconnect: () => void,
38
+ ) {
39
+ this.sidecarId = sidecarId;
40
+ this.ws = ws;
41
+ this.scheduler = scheduler;
42
+ this.onDisconnect = onDisconnect;
43
+ }
44
+
45
+ /** Send an RPC request to the sidecar */
46
+ sendRPC(request: RPCRequest): void {
47
+ try {
48
+ this.ws.send(JSON.stringify(request));
49
+ } catch (err) {
50
+ console.error(`[SidecarConnection:${this.sidecarId}] Failed to send RPC:`, err);
51
+ }
52
+ }
53
+
54
+ /** Handle an inbound text (JSON) message */
55
+ async handleMessage(raw: string): Promise<void> {
56
+ if (raw.length > MAX_JSON_SIZE) {
57
+ console.warn(`[SidecarConnection:${this.sidecarId}] Message too large: ${raw.length} bytes`);
58
+ return;
59
+ }
60
+
61
+ let parsed: unknown;
62
+ try {
63
+ parsed = JSON.parse(raw);
64
+ } catch {
65
+ console.warn(`[SidecarConnection:${this.sidecarId}] Invalid JSON`);
66
+ return;
67
+ }
68
+
69
+ const result = validateEvent(parsed);
70
+ if (!result.valid || !result.event) {
71
+ console.warn(`[SidecarConnection:${this.sidecarId}] Validation failed: ${result.error}`);
72
+ return;
73
+ }
74
+
75
+ const event = result.event;
76
+
77
+ // If event references binary data via ref, wait for the binary frame
78
+ if (event.binary?.type === 'ref') {
79
+ const refId = event.binary.ref_id;
80
+ try {
81
+ const binaryPayload = await this.waitForBinary(refId);
82
+ // Attach resolved binary data to the event payload
83
+ (event.payload as Record<string, unknown>)._binary = binaryPayload;
84
+ } catch (err) {
85
+ console.warn(`[SidecarConnection:${this.sidecarId}] Binary wait failed for ${refId}:`, err);
86
+ return;
87
+ }
88
+ }
89
+
90
+ this.scheduler.enqueue(this.sidecarId, event, event.priority);
91
+ }
92
+
93
+ /** Handle an inbound binary frame */
94
+ handleBinary(data: Buffer): void {
95
+ const result = validateBinaryFrame(data);
96
+ if (!result.valid || !result.refId) {
97
+ console.warn(`[SidecarConnection:${this.sidecarId}] Invalid binary frame: ${result.error}`);
98
+ return;
99
+ }
100
+
101
+ const pending = this.pendingBinary.get(result.refId);
102
+ if (pending) {
103
+ clearTimeout(pending.timer);
104
+ this.pendingBinary.delete(result.refId);
105
+ pending.resolve(result.payload!);
106
+ } else {
107
+ console.warn(`[SidecarConnection:${this.sidecarId}] Unexpected binary ref: ${result.refId}`);
108
+ }
109
+ }
110
+
111
+ /** Start heartbeat ping/pong */
112
+ startHeartbeat(): void {
113
+ this.missedPongs = 0;
114
+ this.alive = true;
115
+
116
+ this.heartbeatTimer = setInterval(() => {
117
+ if (!this.alive) {
118
+ this.missedPongs++;
119
+ if (this.missedPongs >= MAX_MISSED_PONGS) {
120
+ console.warn(`[SidecarConnection:${this.sidecarId}] ${MAX_MISSED_PONGS} missed pongs, disconnecting`);
121
+ this.close();
122
+ this.onDisconnect();
123
+ return;
124
+ }
125
+ }
126
+
127
+ this.alive = false;
128
+ try {
129
+ this.ws.ping();
130
+ } catch {
131
+ this.close();
132
+ this.onDisconnect();
133
+ }
134
+ }, HEARTBEAT_INTERVAL_MS);
135
+ }
136
+
137
+ /** Called when a pong is received */
138
+ handlePong(): void {
139
+ this.alive = true;
140
+ this.missedPongs = 0;
141
+ }
142
+
143
+ /** Stop heartbeat */
144
+ stopHeartbeat(): void {
145
+ if (this.heartbeatTimer) {
146
+ clearInterval(this.heartbeatTimer);
147
+ this.heartbeatTimer = null;
148
+ }
149
+ }
150
+
151
+ /** Close connection and clean up */
152
+ close(): void {
153
+ this.stopHeartbeat();
154
+
155
+ // Reject all pending binary waits
156
+ for (const [refId, pending] of this.pendingBinary) {
157
+ clearTimeout(pending.timer);
158
+ pending.reject(new Error('Connection closed'));
159
+ }
160
+ this.pendingBinary.clear();
161
+
162
+ try {
163
+ this.ws.close();
164
+ } catch {
165
+ // Already closed
166
+ }
167
+ }
168
+
169
+ private waitForBinary(refId: string): Promise<Buffer> {
170
+ return new Promise((resolve, reject) => {
171
+ const timer = setTimeout(() => {
172
+ this.pendingBinary.delete(refId);
173
+ reject(new Error(`Binary frame timeout for ref ${refId}`));
174
+ }, BINARY_WAIT_TIMEOUT_MS);
175
+
176
+ this.pendingBinary.set(refId, { resolve, reject, timer });
177
+ });
178
+ }
179
+ }
@@ -0,0 +1,6 @@
1
+ export { SidecarManager } from './manager.ts';
2
+ export { EventScheduler } from './scheduler.ts';
3
+ export { RPCTracker } from './rpc.ts';
4
+ export { SidecarConnection } from './connection.ts';
5
+ export type * from './types.ts';
6
+ export type * from './protocol.ts';