@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.
- package/LICENSE +153 -0
- package/README.md +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +252 -0
- package/src/actions/browser/session.ts +437 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +321 -0
- package/src/actions/tools/builtin.ts +846 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +297 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +348 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +119 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +194 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +190 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- 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,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
|
+
}
|