@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,387 @@
1
+ # J.A.R.V.I.S. Configuration System
2
+
3
+ Type-safe YAML configuration management for Project J.A.R.V.I.S.
4
+
5
+ ## Overview
6
+
7
+ The configuration system provides:
8
+ - **Type-Safe Config**: Full TypeScript types for all configuration options
9
+ - **YAML Format**: Human-readable YAML files
10
+ - **Deep Merging**: Loaded config merges with defaults for missing values
11
+ - **Path Expansion**: Automatic `~` (tilde) expansion for home directory
12
+ - **Default Values**: Sensible defaults for all settings
13
+
14
+ ## Configuration File
15
+
16
+ Default location: `~/.jarvis/config.yaml`
17
+
18
+ See `config.example.yaml` in the project root for a full example.
19
+
20
+ ## Usage
21
+
22
+ ### Loading Configuration
23
+
24
+ ```typescript
25
+ import { loadConfig } from './config/index.ts';
26
+
27
+ // Load from default location (~/.jarvis/config.yaml)
28
+ const config = await loadConfig();
29
+
30
+ // Load from custom path
31
+ const config = await loadConfig('/path/to/config.yaml');
32
+
33
+ // Config is type-safe
34
+ console.log(config.daemon.port); // number
35
+ console.log(config.llm.primary); // string
36
+ console.log(config.personality.core_traits); // string[]
37
+ ```
38
+
39
+ ### Saving Configuration
40
+
41
+ ```typescript
42
+ import { saveConfig } from './config/index.ts';
43
+
44
+ // Modify config
45
+ config.daemon.port = 8888;
46
+ config.llm.primary = 'openai';
47
+
48
+ // Save to default location
49
+ await saveConfig(config);
50
+
51
+ // Save to custom path
52
+ await saveConfig(config, '/path/to/config.yaml');
53
+ ```
54
+
55
+ ### Using Default Config
56
+
57
+ ```typescript
58
+ import { DEFAULT_CONFIG } from './config/index.ts';
59
+
60
+ // Get a fresh copy of defaults
61
+ const config = { ...DEFAULT_CONFIG };
62
+ ```
63
+
64
+ ## Configuration Schema
65
+
66
+ ### `daemon`
67
+
68
+ Daemon server configuration.
69
+
70
+ ```typescript
71
+ daemon: {
72
+ port: number; // WebSocket server port (default: 7777)
73
+ data_dir: string; // Data directory path (default: ~/.jarvis)
74
+ db_path: string; // SQLite database path (default: ~/.jarvis/jarvis.db)
75
+ }
76
+ ```
77
+
78
+ ### `llm`
79
+
80
+ LLM provider configuration.
81
+
82
+ ```typescript
83
+ llm: {
84
+ primary: string; // Primary provider name ('anthropic' | 'openai' | 'ollama')
85
+ fallback: string[]; // Fallback providers in order
86
+
87
+ // Anthropic (Claude) configuration
88
+ anthropic?: {
89
+ api_key: string;
90
+ model?: string; // Default: claude-sonnet-4-5-20250929
91
+ };
92
+
93
+ // OpenAI (GPT) configuration
94
+ openai?: {
95
+ api_key: string;
96
+ model?: string; // Default: gpt-4o
97
+ };
98
+
99
+ // Ollama (local models) configuration
100
+ ollama?: {
101
+ base_url?: string; // Default: http://localhost:11434
102
+ model?: string; // Default: llama3
103
+ };
104
+ }
105
+ ```
106
+
107
+ ### `personality`
108
+
109
+ Core personality traits that guide J.A.R.V.I.S. behavior.
110
+
111
+ ```typescript
112
+ personality: {
113
+ core_traits: string[]; // Array of personality traits
114
+ }
115
+ ```
116
+
117
+ Default traits:
118
+ - `loyal`: Committed to serving the user
119
+ - `efficient`: Optimizes for speed and resource usage
120
+ - `proactive`: Anticipates needs and suggests improvements
121
+ - `respectful`: Maintains professional boundaries
122
+ - `adaptive`: Learns from interactions and adjusts behavior
123
+
124
+ ### `authority`
125
+
126
+ Authority and permission levels.
127
+
128
+ ```typescript
129
+ authority: {
130
+ default_level: number; // Default authority level (0-5)
131
+ }
132
+ ```
133
+
134
+ Authority levels:
135
+ - **0**: No permission - ask for everything
136
+ - **1**: Read-only operations
137
+ - **2**: Safe modifications (non-destructive)
138
+ - **3**: Standard operations (default)
139
+ - **4**: System changes (config, settings)
140
+ - **5**: Full control (destructive operations)
141
+
142
+ ### `active_role`
143
+
144
+ Active role configuration file name.
145
+
146
+ ```typescript
147
+ active_role: string; // Role file name (e.g., 'default', 'developer', 'assistant')
148
+ ```
149
+
150
+ Roles are loaded from `./roles/` directory.
151
+
152
+ ## Default Configuration
153
+
154
+ ```yaml
155
+ daemon:
156
+ port: 7777
157
+ data_dir: "~/.jarvis"
158
+ db_path: "~/.jarvis/jarvis.db"
159
+
160
+ llm:
161
+ primary: "anthropic"
162
+ fallback:
163
+ - "openai"
164
+ - "ollama"
165
+ anthropic:
166
+ api_key: ""
167
+ model: "claude-sonnet-4-5-20250929"
168
+ openai:
169
+ api_key: ""
170
+ model: "gpt-4o"
171
+ ollama:
172
+ base_url: "http://localhost:11434"
173
+ model: "llama3"
174
+
175
+ personality:
176
+ core_traits:
177
+ - "loyal"
178
+ - "efficient"
179
+ - "proactive"
180
+ - "respectful"
181
+ - "adaptive"
182
+
183
+ authority:
184
+ default_level: 3
185
+
186
+ active_role: "default"
187
+ ```
188
+
189
+ ## Example Usage
190
+
191
+ ### Initializing LLM Providers from Config
192
+
193
+ ```typescript
194
+ import { loadConfig } from './config/index.ts';
195
+ import { LLMManager, AnthropicProvider, OpenAIProvider, OllamaProvider } from './llm/index.ts';
196
+
197
+ const config = await loadConfig();
198
+ const manager = new LLMManager();
199
+
200
+ // Register providers based on config
201
+ if (config.llm.anthropic?.api_key) {
202
+ const anthropic = new AnthropicProvider(
203
+ config.llm.anthropic.api_key,
204
+ config.llm.anthropic.model
205
+ );
206
+ manager.registerProvider(anthropic);
207
+ }
208
+
209
+ if (config.llm.openai?.api_key) {
210
+ const openai = new OpenAIProvider(
211
+ config.llm.openai.api_key,
212
+ config.llm.openai.model
213
+ );
214
+ manager.registerProvider(openai);
215
+ }
216
+
217
+ if (config.llm.ollama) {
218
+ const ollama = new OllamaProvider(
219
+ config.llm.ollama.base_url,
220
+ config.llm.ollama.model
221
+ );
222
+ manager.registerProvider(ollama);
223
+ }
224
+
225
+ manager.setPrimary(config.llm.primary);
226
+ manager.setFallbackChain(config.llm.fallback);
227
+ ```
228
+
229
+ ### Setting Up Data Directory
230
+
231
+ ```typescript
232
+ import { mkdir } from 'node:fs/promises';
233
+ import { loadConfig } from './config/index.ts';
234
+
235
+ const config = await loadConfig();
236
+
237
+ // Ensure data directory exists
238
+ await mkdir(config.daemon.data_dir, { recursive: true });
239
+
240
+ console.log(`Data directory: ${config.daemon.data_dir}`);
241
+ console.log(`Database path: ${config.daemon.db_path}`);
242
+ ```
243
+
244
+ ### Dynamic Configuration Updates
245
+
246
+ ```typescript
247
+ import { loadConfig, saveConfig } from './config/index.ts';
248
+
249
+ // Load current config
250
+ const config = await loadConfig();
251
+
252
+ // Update settings
253
+ config.daemon.port = 8888;
254
+ config.llm.primary = 'openai';
255
+ config.personality.core_traits.push('humorous');
256
+
257
+ // Save changes
258
+ await saveConfig(config);
259
+
260
+ console.log('Configuration updated!');
261
+ ```
262
+
263
+ ## Setup Instructions
264
+
265
+ 1. **Copy Example Config**:
266
+ ```bash
267
+ mkdir -p ~/.jarvis
268
+ cp config.example.yaml ~/.jarvis/config.yaml
269
+ ```
270
+
271
+ 2. **Edit Configuration**:
272
+ ```bash
273
+ nano ~/.jarvis/config.yaml
274
+ ```
275
+
276
+ 3. **Add API Keys**:
277
+ - Get Anthropic API key from: https://console.anthropic.com/
278
+ - Get OpenAI API key from: https://platform.openai.com/
279
+ - Install Ollama from: https://ollama.ai/
280
+
281
+ 4. **Test Configuration**:
282
+ ```typescript
283
+ import { loadConfig } from './config/index.ts';
284
+ const config = await loadConfig();
285
+ console.log('Config loaded:', config);
286
+ ```
287
+
288
+ ## Environment Variables
289
+
290
+ You can also use environment variables for sensitive values:
291
+
292
+ ```yaml
293
+ llm:
294
+ anthropic:
295
+ api_key: "${ANTHROPIC_API_KEY}"
296
+ openai:
297
+ api_key: "${OPENAI_API_KEY}"
298
+ ```
299
+
300
+ Then set in your shell:
301
+ ```bash
302
+ export ANTHROPIC_API_KEY="sk-ant-..."
303
+ export OPENAI_API_KEY="sk-..."
304
+ ```
305
+
306
+ Note: The loader doesn't currently support env var substitution, but you can implement it with:
307
+
308
+ ```typescript
309
+ function substituteEnvVars(value: string): string {
310
+ return value.replace(/\$\{(\w+)\}/g, (_, key) => process.env[key] || '');
311
+ }
312
+ ```
313
+
314
+ ## Migration Guide
315
+
316
+ If you have an existing config and the schema changes:
317
+
318
+ 1. The deep merge ensures new fields get default values
319
+ 2. Old fields remain unchanged
320
+ 3. You can manually add new fields from `config.example.yaml`
321
+
322
+ ## Best Practices
323
+
324
+ 1. **Never commit API keys**: Add `~/.jarvis/config.yaml` to `.gitignore`
325
+ 2. **Use environment variables**: For CI/CD and production deployments
326
+ 3. **Keep backups**: Copy config before major changes
327
+ 4. **Validate on load**: Check that required API keys are present
328
+ 5. **Document custom settings**: Add comments to your config YAML
329
+
330
+ ## Type Safety
331
+
332
+ The configuration is fully typed:
333
+
334
+ ```typescript
335
+ import type { JarvisConfig } from './config/index.ts';
336
+
337
+ function validateConfig(config: JarvisConfig): boolean {
338
+ // TypeScript ensures all required fields exist
339
+ if (config.daemon.port < 1024 || config.daemon.port > 65535) {
340
+ return false;
341
+ }
342
+
343
+ if (config.authority.default_level < 0 || config.authority.default_level > 5) {
344
+ return false;
345
+ }
346
+
347
+ return true;
348
+ }
349
+ ```
350
+
351
+ ## Extending Configuration
352
+
353
+ To add new configuration sections:
354
+
355
+ 1. **Update Types** (`src/config/types.ts`):
356
+ ```typescript
357
+ export type JarvisConfig = {
358
+ // ... existing fields
359
+ new_section: {
360
+ setting1: string;
361
+ setting2: number;
362
+ };
363
+ };
364
+
365
+ export const DEFAULT_CONFIG: JarvisConfig = {
366
+ // ... existing defaults
367
+ new_section: {
368
+ setting1: 'default_value',
369
+ setting2: 42,
370
+ },
371
+ };
372
+ ```
373
+
374
+ 2. **Update Example** (`config.example.yaml`):
375
+ ```yaml
376
+ new_section:
377
+ setting1: "default_value"
378
+ setting2: 42
379
+ ```
380
+
381
+ 3. **Use in Code**:
382
+ ```typescript
383
+ const config = await loadConfig();
384
+ console.log(config.new_section.setting1);
385
+ ```
386
+
387
+ The deep merge ensures existing configs get new defaults automatically.
@@ -0,0 +1,6 @@
1
+ // Config types
2
+ export type { JarvisConfig } from './types.ts';
3
+ export { DEFAULT_CONFIG } from './types.ts';
4
+
5
+ // Config loader
6
+ export { loadConfig, saveConfig } from './loader.ts';
@@ -0,0 +1,137 @@
1
+ import { test, expect, describe, beforeEach, afterEach } from 'bun:test';
2
+ import { loadConfig, saveConfig } from './loader.ts';
3
+ import { DEFAULT_CONFIG } from './types.ts';
4
+ import { existsSync } from 'node:fs';
5
+ import { unlink } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+
8
+ const TEST_CONFIG_PATH = '/tmp/jarvis-test-config.yaml';
9
+
10
+ describe('Config Loader', () => {
11
+ afterEach(async () => {
12
+ // Clean up test config file
13
+ if (existsSync(TEST_CONFIG_PATH)) {
14
+ await unlink(TEST_CONFIG_PATH);
15
+ }
16
+ });
17
+
18
+ test('returns default config when file does not exist', async () => {
19
+ const config = await loadConfig('/tmp/nonexistent-config.yaml');
20
+ // Paths should be tilde-expanded, but all other fields match defaults
21
+ expect(config.daemon.port).toBe(DEFAULT_CONFIG.daemon.port);
22
+ expect(config.daemon.data_dir).not.toContain('~');
23
+ expect(config.daemon.db_path).not.toContain('~');
24
+ expect(config.llm).toEqual(DEFAULT_CONFIG.llm);
25
+ expect(config.personality).toEqual(DEFAULT_CONFIG.personality);
26
+ expect(config.authority).toEqual(DEFAULT_CONFIG.authority);
27
+ expect(config.active_role).toBe(DEFAULT_CONFIG.active_role);
28
+ });
29
+
30
+ test('can save and load config', async () => {
31
+ const testConfig = structuredClone(DEFAULT_CONFIG);
32
+ testConfig.daemon.port = 9999;
33
+ testConfig.llm.primary = 'openai';
34
+
35
+ await saveConfig(testConfig, TEST_CONFIG_PATH);
36
+ expect(existsSync(TEST_CONFIG_PATH)).toBe(true);
37
+
38
+ const loaded = await loadConfig(TEST_CONFIG_PATH);
39
+ expect(loaded.daemon.port).toBe(9999);
40
+ expect(loaded.llm.primary).toBe('openai');
41
+ });
42
+
43
+ test('deep merges partial config with defaults', async () => {
44
+ // Save a partial config (only some fields)
45
+ const partialYaml = `
46
+ daemon:
47
+ port: 8888
48
+
49
+ llm:
50
+ primary: "openai"
51
+ `;
52
+
53
+ await Bun.write(TEST_CONFIG_PATH, partialYaml);
54
+
55
+ const loaded = await loadConfig(TEST_CONFIG_PATH);
56
+
57
+ // Should have our custom values
58
+ expect(loaded.daemon.port).toBe(8888);
59
+ expect(loaded.llm.primary).toBe('openai');
60
+
61
+ // Should have defaults for missing values (paths are tilde-expanded)
62
+ expect(loaded.daemon.data_dir).not.toContain('~');
63
+ expect(loaded.personality.core_traits).toEqual(DEFAULT_CONFIG.personality.core_traits);
64
+ expect(loaded.authority.default_level).toBe(DEFAULT_CONFIG.authority.default_level);
65
+ });
66
+
67
+ test('preserves all config sections', async () => {
68
+ await saveConfig(DEFAULT_CONFIG, TEST_CONFIG_PATH);
69
+ const loaded = await loadConfig(TEST_CONFIG_PATH);
70
+
71
+ expect(loaded.daemon).toBeDefined();
72
+ expect(loaded.llm).toBeDefined();
73
+ expect(loaded.personality).toBeDefined();
74
+ expect(loaded.authority).toBeDefined();
75
+ expect(loaded.active_role).toBeDefined();
76
+ });
77
+ });
78
+
79
+ describe('Default Config', () => {
80
+ test('has all required fields', () => {
81
+ expect(DEFAULT_CONFIG.daemon).toBeDefined();
82
+ expect(DEFAULT_CONFIG.daemon.port).toBe(3142);
83
+ expect(DEFAULT_CONFIG.daemon.data_dir).toBe('~/.jarvis');
84
+ expect(DEFAULT_CONFIG.daemon.db_path).toBe('~/.jarvis/jarvis.db');
85
+
86
+ expect(DEFAULT_CONFIG.llm).toBeDefined();
87
+ expect(DEFAULT_CONFIG.llm.primary).toBe('anthropic');
88
+ expect(DEFAULT_CONFIG.llm.fallback).toEqual(['openai', 'ollama']);
89
+
90
+ expect(DEFAULT_CONFIG.personality).toBeDefined();
91
+ expect(DEFAULT_CONFIG.personality.core_traits).toBeInstanceOf(Array);
92
+
93
+ expect(DEFAULT_CONFIG.authority).toBeDefined();
94
+ expect(DEFAULT_CONFIG.authority.default_level).toBe(3);
95
+
96
+ expect(DEFAULT_CONFIG.active_role).toBe('personal-assistant');
97
+ });
98
+
99
+ test('has correct personality traits', () => {
100
+ const traits = DEFAULT_CONFIG.personality.core_traits;
101
+ expect(traits).toContain('loyal');
102
+ expect(traits).toContain('efficient');
103
+ expect(traits).toContain('proactive');
104
+ expect(traits).toContain('respectful');
105
+ expect(traits).toContain('adaptive');
106
+ });
107
+
108
+ test('has correct LLM defaults', () => {
109
+ expect(DEFAULT_CONFIG.llm.anthropic?.model).toBe('claude-sonnet-4-6');
110
+ expect(DEFAULT_CONFIG.llm.openai?.model).toBe('gpt-5.4');
111
+ expect(DEFAULT_CONFIG.llm.gemini?.model).toBe('gemini-3-flash-preview');
112
+ expect(DEFAULT_CONFIG.llm.ollama?.model).toBe('llama3');
113
+ expect(DEFAULT_CONFIG.llm.ollama?.base_url).toBe('http://localhost:11434');
114
+ });
115
+ });
116
+
117
+ describe('Path Expansion', () => {
118
+ test('expands tilde in paths', async () => {
119
+ const config = await loadConfig();
120
+
121
+ // Should expand ~ to home directory
122
+ expect(config.daemon.data_dir).not.toContain('~');
123
+ expect(config.daemon.db_path).not.toContain('~');
124
+ });
125
+
126
+ test('preserves non-tilde paths', async () => {
127
+ const testConfig = { ...DEFAULT_CONFIG };
128
+ testConfig.daemon.data_dir = '/absolute/path';
129
+ testConfig.daemon.db_path = '/absolute/db.db';
130
+
131
+ await saveConfig(testConfig, TEST_CONFIG_PATH);
132
+ const loaded = await loadConfig(TEST_CONFIG_PATH);
133
+
134
+ expect(loaded.daemon.data_dir).toBe('/absolute/path');
135
+ expect(loaded.daemon.db_path).toBe('/absolute/db.db');
136
+ });
137
+ });
@@ -0,0 +1,142 @@
1
+ import YAML from 'yaml';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import type { JarvisConfig } from './types.ts';
5
+ import { DEFAULT_CONFIG } from './types.ts';
6
+
7
+ function expandTilde(filepath: string): string {
8
+ if (filepath.startsWith('~/')) {
9
+ return join(homedir(), filepath.slice(2));
10
+ }
11
+ return filepath;
12
+ }
13
+
14
+ function deepMerge(target: any, source: any): any {
15
+ if (!source || typeof source !== 'object') {
16
+ return source !== undefined ? source : target;
17
+ }
18
+
19
+ if (Array.isArray(source)) {
20
+ return source;
21
+ }
22
+
23
+ const result = { ...target };
24
+
25
+ for (const key in source) {
26
+ if (source.hasOwnProperty(key)) {
27
+ if (
28
+ source[key] &&
29
+ typeof source[key] === 'object' &&
30
+ !Array.isArray(source[key]) &&
31
+ target[key] &&
32
+ typeof target[key] === 'object' &&
33
+ !Array.isArray(target[key])
34
+ ) {
35
+ result[key] = deepMerge(target[key], source[key]);
36
+ } else {
37
+ result[key] = source[key];
38
+ }
39
+ }
40
+ }
41
+
42
+ return result;
43
+ }
44
+
45
+ /**
46
+ * Apply environment variable overrides to config.
47
+ * Env vars take highest precedence (over YAML and defaults).
48
+ */
49
+ function applyEnvOverrides(config: JarvisConfig): void {
50
+ const env = process.env;
51
+
52
+ if (env.JARVIS_PORT) {
53
+ const port = parseInt(env.JARVIS_PORT, 10);
54
+ if (!isNaN(port)) config.daemon.port = port;
55
+ }
56
+
57
+ if (env.JARVIS_HOME) {
58
+ const home = env.JARVIS_HOME;
59
+ config.daemon.data_dir = home;
60
+ config.daemon.db_path = join(home, 'jarvis.db');
61
+ }
62
+
63
+ if (env.JARVIS_API_KEY) {
64
+ if (!config.llm.anthropic) config.llm.anthropic = { api_key: '', model: 'claude-sonnet-4-5-20250929' };
65
+ config.llm.anthropic.api_key = env.JARVIS_API_KEY;
66
+ }
67
+
68
+ if (env.JARVIS_OPENAI_KEY) {
69
+ if (!config.llm.openai) config.llm.openai = { api_key: '', model: 'gpt-4o' };
70
+ config.llm.openai.api_key = env.JARVIS_OPENAI_KEY;
71
+ }
72
+
73
+ if (env.JARVIS_OLLAMA_URL) {
74
+ if (!config.llm.ollama) config.llm.ollama = { base_url: '', model: 'llama3' };
75
+ config.llm.ollama.base_url = env.JARVIS_OLLAMA_URL;
76
+ }
77
+
78
+ if (env.JARVIS_BRAIN_DOMAIN) {
79
+ config.daemon.brain_domain = env.JARVIS_BRAIN_DOMAIN;
80
+ }
81
+
82
+ if (env.JARVIS_AUTH_TOKEN) {
83
+ if (!config.auth) config.auth = {};
84
+ config.auth.token = env.JARVIS_AUTH_TOKEN;
85
+ }
86
+ }
87
+
88
+ export async function loadConfig(configPath?: string): Promise<JarvisConfig> {
89
+ const path = configPath || expandTilde('~/.jarvis/config.yaml');
90
+
91
+ try {
92
+ const file = Bun.file(path);
93
+ const exists = await file.exists();
94
+
95
+ if (!exists) {
96
+ console.warn(`Config file not found at ${path}, using defaults`);
97
+ const config = structuredClone(DEFAULT_CONFIG);
98
+ config.daemon.data_dir = expandTilde(config.daemon.data_dir);
99
+ config.daemon.db_path = expandTilde(config.daemon.db_path);
100
+ applyEnvOverrides(config);
101
+ return config;
102
+ }
103
+
104
+ const text = await file.text();
105
+ const parsed = YAML.parse(text);
106
+
107
+ // Deep merge with defaults to ensure all required fields exist
108
+ const config = deepMerge(DEFAULT_CONFIG, parsed) as JarvisConfig;
109
+
110
+ // Expand tilde in paths
111
+ config.daemon.data_dir = expandTilde(config.daemon.data_dir);
112
+ config.daemon.db_path = expandTilde(config.daemon.db_path);
113
+
114
+ // Apply environment variable overrides
115
+ applyEnvOverrides(config);
116
+
117
+ return config;
118
+ } catch (err) {
119
+ console.error(`Failed to load config from ${path}:`, err);
120
+ return DEFAULT_CONFIG;
121
+ }
122
+ }
123
+
124
+ export async function saveConfig(
125
+ config: JarvisConfig,
126
+ configPath?: string
127
+ ): Promise<void> {
128
+ const path = configPath || expandTilde('~/.jarvis/config.yaml');
129
+
130
+ try {
131
+ const yaml = YAML.stringify(config, {
132
+ indent: 2,
133
+ lineWidth: 100,
134
+ defaultStringType: 'QUOTE_DOUBLE',
135
+ });
136
+
137
+ await Bun.write(path, yaml);
138
+ console.log(`Config saved to ${path}`);
139
+ } catch (err) {
140
+ throw new Error(`Failed to save config to ${path}: ${err}`);
141
+ }
142
+ }