@shykaruu/jarvis-brain 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +153 -0
- package/README.md +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -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 +261 -0
- package/src/actions/browser/session.ts +506 -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 +363 -0
- package/src/actions/tools/builtin.ts +950 -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/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -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 +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -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 +301 -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 +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -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/dashboard-auth.ts +75 -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 +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -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/dashboard-auth.test.ts +170 -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/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -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 +407 -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 +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -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 +120 -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 +218 -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 +195 -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/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -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 +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -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 +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -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-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -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
package/src/cli/deps.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Dependency Checker & Installer
|
|
3
|
+
*
|
|
4
|
+
* Detects and offers to install system dependencies during onboard:
|
|
5
|
+
* - Chromium/Chrome browser (all platforms)
|
|
6
|
+
* - Linux X11 tools: xdotool, xprop, imagemagick (Linux/WSL)
|
|
7
|
+
* - Google OAuth tokens (optional, all platforms)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { spawnSync } from 'bun';
|
|
14
|
+
import { c, printOk, printWarn, printErr, printInfo, askYesNo, ask, askSecret, startSpinner, detectPlatform } from './helpers.ts';
|
|
15
|
+
import { LINUX_BROWSER_PATHS, MACOS_BROWSER_PATHS, type BrowserExecutable } from '../actions/browser/chrome-launcher.ts';
|
|
16
|
+
|
|
17
|
+
export type DepStatus = {
|
|
18
|
+
name: string;
|
|
19
|
+
found: boolean;
|
|
20
|
+
path?: string;
|
|
21
|
+
message: string;
|
|
22
|
+
installable: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ── Detection ─────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a Chromium-based browser is installed.
|
|
29
|
+
*/
|
|
30
|
+
export function checkBrowser(): DepStatus {
|
|
31
|
+
const platform = detectPlatform();
|
|
32
|
+
|
|
33
|
+
const candidates: BrowserExecutable[] =
|
|
34
|
+
platform === 'macos' ? MACOS_BROWSER_PATHS : LINUX_BROWSER_PATHS;
|
|
35
|
+
|
|
36
|
+
for (const c of candidates) {
|
|
37
|
+
if (existsSync(c.path)) {
|
|
38
|
+
return {
|
|
39
|
+
name: 'Browser (Chrome/Chromium)',
|
|
40
|
+
found: true,
|
|
41
|
+
path: c.path,
|
|
42
|
+
message: `${c.kind} at ${c.path}`,
|
|
43
|
+
installable: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// On WSL, also check Windows-side browsers
|
|
49
|
+
if (platform === 'wsl') {
|
|
50
|
+
const wslPaths = [
|
|
51
|
+
'/mnt/c/Program Files/Google/Chrome/Application/chrome.exe',
|
|
52
|
+
'/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
|
|
53
|
+
'/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
|
54
|
+
'/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe',
|
|
55
|
+
];
|
|
56
|
+
for (const p of wslPaths) {
|
|
57
|
+
if (existsSync(p)) {
|
|
58
|
+
return {
|
|
59
|
+
name: 'Browser (Chrome/Chromium)',
|
|
60
|
+
found: true,
|
|
61
|
+
path: p,
|
|
62
|
+
message: `Windows browser at ${p}`,
|
|
63
|
+
installable: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: 'Browser (Chrome/Chromium)',
|
|
71
|
+
found: false,
|
|
72
|
+
message: 'Not found',
|
|
73
|
+
installable: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check for Linux X11 tools needed for app control.
|
|
79
|
+
*/
|
|
80
|
+
export function checkLinuxTools(): DepStatus[] {
|
|
81
|
+
const platform = detectPlatform();
|
|
82
|
+
if (platform === 'macos') return []; // Not needed on macOS
|
|
83
|
+
|
|
84
|
+
const tools = [
|
|
85
|
+
{ name: 'xdotool', pkg: 'xdotool', desc: 'keyboard/mouse automation' },
|
|
86
|
+
{ name: 'xprop', pkg: 'x11-utils', desc: 'window property inspection' },
|
|
87
|
+
{ name: 'import (ImageMagick)', cmd: 'import', pkg: 'imagemagick', desc: 'screenshot capture' },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
return tools.map(tool => {
|
|
91
|
+
const cmd = tool.cmd ?? tool.name;
|
|
92
|
+
const result = spawnSync(['which', cmd], { stdout: 'pipe', stderr: 'pipe' });
|
|
93
|
+
const found = result.exitCode === 0;
|
|
94
|
+
const path = found ? result.stdout.toString().trim() : undefined;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
name: tool.name,
|
|
98
|
+
found,
|
|
99
|
+
path,
|
|
100
|
+
message: found ? path! : `Not installed (${tool.desc})`,
|
|
101
|
+
installable: true,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if Google OAuth tokens exist.
|
|
108
|
+
*/
|
|
109
|
+
export function checkGoogleAuth(): DepStatus {
|
|
110
|
+
const tokensPath = join(homedir(), '.jarvis', 'google-tokens.json');
|
|
111
|
+
const found = existsSync(tokensPath);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name: 'Google OAuth',
|
|
115
|
+
found,
|
|
116
|
+
path: found ? tokensPath : undefined,
|
|
117
|
+
message: found ? 'Tokens exist' : 'Not configured (optional, for Gmail/Calendar)',
|
|
118
|
+
installable: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Installation ──────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Detect the system package manager.
|
|
126
|
+
*/
|
|
127
|
+
function detectPackageManager(): 'apt' | 'dnf' | 'pacman' | 'brew' | null {
|
|
128
|
+
if (spawnSync(['which', 'apt'], { stdout: 'pipe' }).exitCode === 0) return 'apt';
|
|
129
|
+
if (spawnSync(['which', 'dnf'], { stdout: 'pipe' }).exitCode === 0) return 'dnf';
|
|
130
|
+
if (spawnSync(['which', 'pacman'], { stdout: 'pipe' }).exitCode === 0) return 'pacman';
|
|
131
|
+
if (spawnSync(['which', 'brew'], { stdout: 'pipe' }).exitCode === 0) return 'brew';
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Install a Chromium-based browser.
|
|
137
|
+
*/
|
|
138
|
+
export async function installBrowser(): Promise<boolean> {
|
|
139
|
+
const platform = detectPlatform();
|
|
140
|
+
const pm = detectPackageManager();
|
|
141
|
+
|
|
142
|
+
if (platform === 'macos') {
|
|
143
|
+
if (pm === 'brew') {
|
|
144
|
+
console.log(c.dim(' Running: brew install --cask google-chrome'));
|
|
145
|
+
const result = spawnSync(['brew', 'install', '--cask', 'google-chrome'], {
|
|
146
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
147
|
+
});
|
|
148
|
+
return result.exitCode === 0;
|
|
149
|
+
}
|
|
150
|
+
printInfo('Install Chrome from: https://www.google.com/chrome/');
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Linux / WSL — install a Linux browser (preferred for CDP)
|
|
155
|
+
if (pm === 'apt') {
|
|
156
|
+
// Try chromium-browser first (most common on Ubuntu/Debian), then chromium
|
|
157
|
+
console.log(c.dim(' Running: sudo apt install -y chromium-browser'));
|
|
158
|
+
let result = spawnSync(['sudo', 'apt', 'install', '-y', 'chromium-browser'], {
|
|
159
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
160
|
+
});
|
|
161
|
+
if (result.exitCode === 0) return true;
|
|
162
|
+
|
|
163
|
+
console.log(c.dim(' Trying: sudo apt install -y chromium'));
|
|
164
|
+
result = spawnSync(['sudo', 'apt', 'install', '-y', 'chromium'], {
|
|
165
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
166
|
+
});
|
|
167
|
+
return result.exitCode === 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (pm === 'dnf') {
|
|
171
|
+
console.log(c.dim(' Running: sudo dnf install -y chromium'));
|
|
172
|
+
const result = spawnSync(['sudo', 'dnf', 'install', '-y', 'chromium'], {
|
|
173
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
174
|
+
});
|
|
175
|
+
return result.exitCode === 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (pm === 'pacman') {
|
|
179
|
+
console.log(c.dim(' Running: sudo pacman -S --noconfirm chromium'));
|
|
180
|
+
const result = spawnSync(['sudo', 'pacman', '-S', '--noconfirm', 'chromium'], {
|
|
181
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
182
|
+
});
|
|
183
|
+
return result.exitCode === 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
printInfo('Install Chromium manually for your distribution.');
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Install missing Linux X11 tools.
|
|
192
|
+
*/
|
|
193
|
+
export async function installLinuxTools(missing: string[]): Promise<boolean> {
|
|
194
|
+
const pm = detectPackageManager();
|
|
195
|
+
|
|
196
|
+
// Map tool names to package names
|
|
197
|
+
const pkgMap: Record<string, Record<string, string>> = {
|
|
198
|
+
apt: { xdotool: 'xdotool', xprop: 'x11-utils', 'import (ImageMagick)': 'imagemagick' },
|
|
199
|
+
dnf: { xdotool: 'xdotool', xprop: 'xprop', 'import (ImageMagick)': 'ImageMagick' },
|
|
200
|
+
pacman: { xdotool: 'xdotool', xprop: 'xorg-xprop', 'import (ImageMagick)': 'imagemagick' },
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (!pm || pm === 'brew') {
|
|
204
|
+
printInfo('Install manually: ' + missing.join(', '));
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const packages = missing
|
|
209
|
+
.map(name => pkgMap[pm]?.[name])
|
|
210
|
+
.filter(Boolean) as string[];
|
|
211
|
+
|
|
212
|
+
if (packages.length === 0) return true;
|
|
213
|
+
|
|
214
|
+
const unique = [...new Set(packages)];
|
|
215
|
+
|
|
216
|
+
if (pm === 'apt') {
|
|
217
|
+
console.log(c.dim(` Running: sudo apt install -y ${unique.join(' ')}`));
|
|
218
|
+
const result = spawnSync(['sudo', 'apt', 'install', '-y', ...unique], {
|
|
219
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
220
|
+
});
|
|
221
|
+
return result.exitCode === 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (pm === 'dnf') {
|
|
225
|
+
console.log(c.dim(` Running: sudo dnf install -y ${unique.join(' ')}`));
|
|
226
|
+
const result = spawnSync(['sudo', 'dnf', 'install', '-y', ...unique], {
|
|
227
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
228
|
+
});
|
|
229
|
+
return result.exitCode === 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (pm === 'pacman') {
|
|
233
|
+
console.log(c.dim(` Running: sudo pacman -S --noconfirm ${unique.join(' ')}`));
|
|
234
|
+
const result = spawnSync(['sudo', 'pacman', '-S', '--noconfirm', ...unique], {
|
|
235
|
+
stdout: 'inherit', stderr: 'inherit',
|
|
236
|
+
});
|
|
237
|
+
return result.exitCode === 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Run inline Google OAuth setup flow.
|
|
245
|
+
*/
|
|
246
|
+
export async function setupGoogleOAuth(config: any): Promise<boolean> {
|
|
247
|
+
let clientId = config.google?.client_id ?? '';
|
|
248
|
+
let clientSecret = config.google?.client_secret ?? '';
|
|
249
|
+
|
|
250
|
+
if (!clientId || !clientSecret) {
|
|
251
|
+
printInfo('Google OAuth requires OAuth2 credentials from Google Cloud Console.');
|
|
252
|
+
printInfo('1. Go to https://console.cloud.google.com/apis/credentials');
|
|
253
|
+
printInfo('2. Create an OAuth2 client ID (Web application)');
|
|
254
|
+
printInfo('3. Add redirect URI: http://localhost:3142/api/auth/google/callback');
|
|
255
|
+
console.log('');
|
|
256
|
+
|
|
257
|
+
clientId = await ask('Google OAuth Client ID (or press Enter to skip)');
|
|
258
|
+
if (!clientId) return false;
|
|
259
|
+
|
|
260
|
+
clientSecret = await askSecret('Google OAuth Client Secret');
|
|
261
|
+
if (!clientSecret) return false;
|
|
262
|
+
|
|
263
|
+
// Save credentials to config
|
|
264
|
+
config.google = { ...config.google, client_id: clientId, client_secret: clientSecret };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Import GoogleAuth and start inline flow
|
|
268
|
+
const { GoogleAuth } = await import('../integrations/google-auth.ts');
|
|
269
|
+
const auth = new GoogleAuth(clientId, clientSecret);
|
|
270
|
+
|
|
271
|
+
if (auth.isAuthenticated()) {
|
|
272
|
+
printOk('Already authenticated with Google!');
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const SCOPES = [
|
|
277
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
278
|
+
'https://www.googleapis.com/auth/calendar.readonly',
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
const authUrl = auth.getAuthUrl(SCOPES);
|
|
282
|
+
|
|
283
|
+
console.log('');
|
|
284
|
+
printInfo('Opening browser for Google authorization...');
|
|
285
|
+
console.log(c.dim(` ${authUrl}`));
|
|
286
|
+
console.log('');
|
|
287
|
+
|
|
288
|
+
// Try to open browser
|
|
289
|
+
try {
|
|
290
|
+
const platform = detectPlatform();
|
|
291
|
+
if (platform === 'macos') {
|
|
292
|
+
spawnSync(['open', authUrl], { stdout: 'ignore', stderr: 'ignore' });
|
|
293
|
+
} else if (platform === 'wsl') {
|
|
294
|
+
spawnSync(['wslview', authUrl], { stdout: 'ignore', stderr: 'ignore' });
|
|
295
|
+
} else {
|
|
296
|
+
spawnSync(['xdg-open', authUrl], { stdout: 'ignore', stderr: 'ignore' });
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
// User can open manually
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Start temporary callback server for OAuth redirect
|
|
303
|
+
printInfo('Waiting for authorization callback on port 3142...');
|
|
304
|
+
|
|
305
|
+
return new Promise<boolean>((resolve) => {
|
|
306
|
+
let server: ReturnType<typeof Bun.serve>;
|
|
307
|
+
try {
|
|
308
|
+
server = Bun.serve({
|
|
309
|
+
port: 3142,
|
|
310
|
+
async fetch(req) {
|
|
311
|
+
const url = new URL(req.url);
|
|
312
|
+
|
|
313
|
+
if (url.pathname === '/api/auth/google/callback') {
|
|
314
|
+
const code = url.searchParams.get('code');
|
|
315
|
+
const error = url.searchParams.get('error');
|
|
316
|
+
|
|
317
|
+
if (error) {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
printErr(`Authorization denied: ${error}`);
|
|
320
|
+
setTimeout(() => { server.stop(); resolve(false); }, 300);
|
|
321
|
+
return new Response(
|
|
322
|
+
'<html><body><h1>Authorization Denied</h1><p>You can close this tab.</p></body></html>',
|
|
323
|
+
{ headers: { 'Content-Type': 'text/html' } }
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!code) {
|
|
328
|
+
return new Response('Missing code', { status: 400 });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
await auth.exchangeCode(code);
|
|
333
|
+
clearTimeout(timeout);
|
|
334
|
+
printOk('Google OAuth configured! Tokens saved.');
|
|
335
|
+
setTimeout(() => { server.stop(); resolve(true); }, 300);
|
|
336
|
+
return new Response(
|
|
337
|
+
'<html><body><h1>JARVIS Google Authorization Complete!</h1><p>You can close this tab.</p></body></html>',
|
|
338
|
+
{ headers: { 'Content-Type': 'text/html' } }
|
|
339
|
+
);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
clearTimeout(timeout);
|
|
342
|
+
printErr(`Token exchange failed: ${err}`);
|
|
343
|
+
setTimeout(() => { server.stop(); resolve(false); }, 300);
|
|
344
|
+
return new Response(
|
|
345
|
+
`<html><body><h1>Token Exchange Failed</h1><pre>${err}</pre></body></html>`,
|
|
346
|
+
{ headers: { 'Content-Type': 'text/html' }, status: 500 }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return new Response('Not found', { status: 404 });
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
} catch (err) {
|
|
355
|
+
printErr(`Could not start OAuth callback server on port 3142 (port in use?)`);
|
|
356
|
+
printInfo('Stop the JARVIS daemon first, or run later with: bun run setup:google');
|
|
357
|
+
resolve(false);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const timeout = setTimeout(() => {
|
|
362
|
+
server.stop();
|
|
363
|
+
printWarn('Timeout waiting for Google authorization (60s).');
|
|
364
|
+
printInfo('Run later with: bun run setup:google');
|
|
365
|
+
resolve(false);
|
|
366
|
+
}, 60_000);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ── Main Step Runner ──────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Run the full dependency check + install flow.
|
|
374
|
+
* Called as Step 3 of the onboard wizard.
|
|
375
|
+
*/
|
|
376
|
+
export async function runDependencyCheck(config: any): Promise<void> {
|
|
377
|
+
// Collect all dependency statuses
|
|
378
|
+
const deps: DepStatus[] = [];
|
|
379
|
+
|
|
380
|
+
deps.push(checkBrowser());
|
|
381
|
+
|
|
382
|
+
const linuxTools = checkLinuxTools();
|
|
383
|
+
deps.push(...linuxTools);
|
|
384
|
+
|
|
385
|
+
deps.push(checkGoogleAuth());
|
|
386
|
+
|
|
387
|
+
// Display status table
|
|
388
|
+
console.log('');
|
|
389
|
+
for (const dep of deps) {
|
|
390
|
+
const icon = dep.found ? c.green('\u2713') : c.red('\u2717');
|
|
391
|
+
const name = dep.name.padEnd(26);
|
|
392
|
+
const detail = dep.found
|
|
393
|
+
? c.dim(dep.path ?? dep.message)
|
|
394
|
+
: c.yellow(dep.message);
|
|
395
|
+
console.log(` ${icon} ${name} ${detail}`);
|
|
396
|
+
}
|
|
397
|
+
console.log('');
|
|
398
|
+
|
|
399
|
+
const missing = deps.filter(d => !d.found && d.installable);
|
|
400
|
+
|
|
401
|
+
if (missing.length === 0) {
|
|
402
|
+
printOk('All system dependencies are satisfied!');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
printInfo(`${missing.length} optional ${missing.length === 1 ? 'dependency' : 'dependencies'} not found.`);
|
|
407
|
+
console.log('');
|
|
408
|
+
|
|
409
|
+
// Offer to install each missing dependency
|
|
410
|
+
// Group: browser
|
|
411
|
+
const missingBrowser = missing.find(d => d.name.includes('Browser'));
|
|
412
|
+
if (missingBrowser) {
|
|
413
|
+
const install = await askYesNo('Install a Chromium-based browser?', true);
|
|
414
|
+
if (install) {
|
|
415
|
+
const ok = await installBrowser();
|
|
416
|
+
if (ok) printOk('Browser installed!');
|
|
417
|
+
else printWarn('Browser install incomplete. Install manually later.');
|
|
418
|
+
} else {
|
|
419
|
+
printInfo('Skip. Install later: sudo apt install chromium-browser');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Group: Linux tools (batch install)
|
|
424
|
+
const missingLinux = missing.filter(d =>
|
|
425
|
+
d.name === 'xdotool' || d.name === 'xprop' || d.name === 'import (ImageMagick)'
|
|
426
|
+
);
|
|
427
|
+
if (missingLinux.length > 0) {
|
|
428
|
+
const names = missingLinux.map(d => d.name).join(', ');
|
|
429
|
+
const install = await askYesNo(`Install Linux tools (${names})?`, true);
|
|
430
|
+
if (install) {
|
|
431
|
+
const ok = await installLinuxTools(missingLinux.map(d => d.name));
|
|
432
|
+
if (ok) printOk('Linux tools installed!');
|
|
433
|
+
else printWarn('Some tools may not have installed. Check manually.');
|
|
434
|
+
} else {
|
|
435
|
+
printInfo('Skip. Install later: sudo apt install xdotool x11-utils imagemagick');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Group: Google OAuth (special — only if user has google config or wants to set up)
|
|
440
|
+
const missingGoogle = missing.find(d => d.name === 'Google OAuth');
|
|
441
|
+
if (missingGoogle) {
|
|
442
|
+
const install = await askYesNo('Set up Google OAuth for Gmail/Calendar? (optional)', false);
|
|
443
|
+
if (install) {
|
|
444
|
+
await setupGoogleOAuth(config);
|
|
445
|
+
} else {
|
|
446
|
+
printInfo('Skip. Set up later with: bun run setup:google');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* J.A.R.V.I.S. Doctor — Environment Diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Checks system requirements, configuration, and connectivity.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import {
|
|
11
|
+
c, printBanner, printOk, printWarn, printErr, printInfo, startSpinner, closeRL,
|
|
12
|
+
} from './helpers.ts';
|
|
13
|
+
|
|
14
|
+
const JARVIS_DIR = join(homedir(), '.jarvis');
|
|
15
|
+
const CONFIG_PATH = join(JARVIS_DIR, 'config.yaml');
|
|
16
|
+
|
|
17
|
+
interface CheckResult {
|
|
18
|
+
name: string;
|
|
19
|
+
status: 'ok' | 'warn' | 'fail' | 'skip';
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function runDoctor(): Promise<void> {
|
|
24
|
+
printBanner();
|
|
25
|
+
console.log(c.bold('Running system diagnostics...\n'));
|
|
26
|
+
|
|
27
|
+
const results: CheckResult[] = [];
|
|
28
|
+
|
|
29
|
+
// ── Check 1: Bun Version ──────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const bunVersion = Bun.version;
|
|
32
|
+
const [major] = bunVersion.split('.').map(Number);
|
|
33
|
+
if (major! >= 1) {
|
|
34
|
+
results.push({ name: 'Bun Runtime', status: 'ok', message: `v${bunVersion}` });
|
|
35
|
+
} else {
|
|
36
|
+
results.push({ name: 'Bun Runtime', status: 'warn', message: `v${bunVersion} (>= 1.0.0 recommended)` });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Check 2: Data Directory ───────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
if (existsSync(JARVIS_DIR)) {
|
|
42
|
+
results.push({ name: 'Data Directory', status: 'ok', message: JARVIS_DIR });
|
|
43
|
+
} else {
|
|
44
|
+
results.push({ name: 'Data Directory', status: 'warn', message: `${JARVIS_DIR} not found. Run: jarvis onboard` });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Check 3: Config File ──────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
let config: any = null;
|
|
50
|
+
if (existsSync(CONFIG_PATH)) {
|
|
51
|
+
try {
|
|
52
|
+
const YAML = (await import('yaml')).default;
|
|
53
|
+
const text = readFileSync(CONFIG_PATH, 'utf-8');
|
|
54
|
+
config = YAML.parse(text);
|
|
55
|
+
results.push({ name: 'Config File', status: 'ok', message: CONFIG_PATH });
|
|
56
|
+
} catch (err) {
|
|
57
|
+
results.push({ name: 'Config File', status: 'fail', message: `Invalid YAML: ${err}` });
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
results.push({ name: 'Config File', status: 'fail', message: 'Not found. Run: jarvis onboard' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Check 4: LLM API Key ─────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
if (config) {
|
|
66
|
+
const primary = config.llm?.primary ?? 'anthropic';
|
|
67
|
+
const providerConfig = config.llm?.[primary];
|
|
68
|
+
|
|
69
|
+
if (primary === 'ollama') {
|
|
70
|
+
results.push({ name: 'LLM Provider', status: 'ok', message: `ollama (${providerConfig?.model ?? 'llama3'})` });
|
|
71
|
+
} else if (providerConfig?.api_key && providerConfig.api_key !== '') {
|
|
72
|
+
results.push({
|
|
73
|
+
name: 'LLM Provider',
|
|
74
|
+
status: 'ok',
|
|
75
|
+
message: `${primary} (key: ${providerConfig.api_key.slice(0, 10)}...)`,
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
results.push({ name: 'LLM Provider', status: 'fail', message: `${primary} API key not set` });
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
results.push({ name: 'LLM Provider', status: 'skip', message: 'No config file' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Check 5: LLM Connectivity ────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
if (config) {
|
|
87
|
+
const spin = startSpinner('Testing LLM connectivity...');
|
|
88
|
+
try {
|
|
89
|
+
const { LLMManager, AnthropicProvider, OpenAIProvider, GroqProvider, GeminiProvider, OllamaProvider, OpenRouterProvider } = await import('../llm/index.ts');
|
|
90
|
+
const manager = new LLMManager();
|
|
91
|
+
const primary = config.llm?.primary ?? 'anthropic';
|
|
92
|
+
|
|
93
|
+
if (primary === 'anthropic' && config.llm?.anthropic?.api_key) {
|
|
94
|
+
manager.registerProvider(new AnthropicProvider(config.llm.anthropic.api_key, config.llm.anthropic.model));
|
|
95
|
+
} else if (primary === 'openai' && config.llm?.openai?.api_key) {
|
|
96
|
+
manager.registerProvider(new OpenAIProvider(
|
|
97
|
+
config.llm.openai.api_key,
|
|
98
|
+
config.llm.openai.model,
|
|
99
|
+
config.llm.openai.base_url,
|
|
100
|
+
));
|
|
101
|
+
} else if (primary === 'groq' && config.llm?.groq?.api_key) {
|
|
102
|
+
manager.registerProvider(new GroqProvider(config.llm.groq.api_key, config.llm.groq.model));
|
|
103
|
+
} else if (primary === 'gemini' && config.llm?.gemini?.api_key) {
|
|
104
|
+
manager.registerProvider(new GeminiProvider(config.llm.gemini.api_key, config.llm.gemini.model));
|
|
105
|
+
} else if (primary === 'openrouter' && config.llm?.openrouter?.api_key) {
|
|
106
|
+
manager.registerProvider(new OpenRouterProvider(config.llm.openrouter.api_key, config.llm.openrouter.model));
|
|
107
|
+
} else if (primary === 'ollama') {
|
|
108
|
+
manager.registerProvider(new OllamaProvider(config.llm.ollama?.base_url, config.llm.ollama?.model));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
manager.setPrimary(primary);
|
|
112
|
+
const resp = await manager.chat(
|
|
113
|
+
[{ role: 'user', content: 'Say OK' }],
|
|
114
|
+
{ max_tokens: 10 },
|
|
115
|
+
);
|
|
116
|
+
spin.stop();
|
|
117
|
+
results.push({ name: 'LLM Connectivity', status: 'ok', message: `Model: ${resp.model}` });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
spin.stop();
|
|
120
|
+
results.push({ name: 'LLM Connectivity', status: 'fail', message: String(err).slice(0, 100) });
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
results.push({ name: 'LLM Connectivity', status: 'skip', message: 'No config file' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Check 6: Browser (Chromium/Chrome) ────────────────────────────
|
|
127
|
+
|
|
128
|
+
const browserPaths = process.platform === 'darwin'
|
|
129
|
+
? ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium']
|
|
130
|
+
: ['/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable'];
|
|
131
|
+
|
|
132
|
+
const foundBrowser = browserPaths.find(p => existsSync(p));
|
|
133
|
+
if (foundBrowser) {
|
|
134
|
+
results.push({ name: 'Browser', status: 'ok', message: foundBrowser });
|
|
135
|
+
} else {
|
|
136
|
+
// Try which
|
|
137
|
+
const which = Bun.spawnSync(['which', 'chromium-browser']);
|
|
138
|
+
if (which.exitCode === 0) {
|
|
139
|
+
results.push({ name: 'Browser', status: 'ok', message: which.stdout.toString().trim() });
|
|
140
|
+
} else {
|
|
141
|
+
results.push({ name: 'Browser', status: 'warn', message: 'Chromium/Chrome not found. Browser tools will be limited.' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Check 7: Port Availability ────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
const port = config?.daemon?.port ?? 3142;
|
|
148
|
+
try {
|
|
149
|
+
const server = Bun.serve({ port, fetch: () => new Response('') });
|
|
150
|
+
server.stop(true);
|
|
151
|
+
results.push({ name: 'Port', status: 'ok', message: `${port} is available` });
|
|
152
|
+
} catch {
|
|
153
|
+
results.push({ name: 'Port', status: 'warn', message: `${port} is in use (daemon may already be running)` });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Check 8: SQLite ───────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const { Database } = await import('bun:sqlite');
|
|
160
|
+
const db = new Database(':memory:');
|
|
161
|
+
db.run('CREATE TABLE test (id INTEGER PRIMARY KEY)');
|
|
162
|
+
db.close();
|
|
163
|
+
results.push({ name: 'SQLite', status: 'ok', message: 'bun:sqlite working' });
|
|
164
|
+
} catch (err) {
|
|
165
|
+
results.push({ name: 'SQLite', status: 'fail', message: String(err) });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Check 9: TTS/STT Providers ────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
if (config?.tts?.enabled) {
|
|
171
|
+
results.push({ name: 'TTS', status: 'ok', message: `${config.tts.provider ?? 'edge'} (${config.tts.voice ?? 'default'})` });
|
|
172
|
+
} else {
|
|
173
|
+
results.push({ name: 'TTS', status: 'skip', message: 'Disabled' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (config?.stt?.provider) {
|
|
177
|
+
const sttProv = config.stt.provider;
|
|
178
|
+
const hasKey = sttProv === 'ollama' || sttProv === 'local'
|
|
179
|
+
|| (sttProv === 'openai' && config.stt.openai?.api_key)
|
|
180
|
+
|| (sttProv === 'groq' && config.stt.groq?.api_key);
|
|
181
|
+
results.push({
|
|
182
|
+
name: 'STT',
|
|
183
|
+
status: hasKey ? 'ok' : 'warn',
|
|
184
|
+
message: hasKey ? `${sttProv} configured` : `${sttProv} (API key missing)`,
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
results.push({ name: 'STT', status: 'skip', message: 'Not configured' });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Check 11: Channels ────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
if (config?.channels?.telegram?.enabled && config.channels.telegram.bot_token) {
|
|
193
|
+
results.push({ name: 'Telegram', status: 'ok', message: 'Bot token set' });
|
|
194
|
+
} else {
|
|
195
|
+
results.push({ name: 'Telegram', status: 'skip', message: 'Not configured' });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (config?.channels?.discord?.enabled && config.channels.discord.bot_token) {
|
|
199
|
+
results.push({ name: 'Discord', status: 'ok', message: 'Bot token set' });
|
|
200
|
+
} else {
|
|
201
|
+
results.push({ name: 'Discord', status: 'skip', message: 'Not configured' });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Results ───────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
console.log(c.bold('\nDiagnostics Results:\n'));
|
|
207
|
+
|
|
208
|
+
let okCount = 0;
|
|
209
|
+
let warnCount = 0;
|
|
210
|
+
let failCount = 0;
|
|
211
|
+
|
|
212
|
+
for (const r of results) {
|
|
213
|
+
const icon = r.status === 'ok' ? c.green('✓')
|
|
214
|
+
: r.status === 'warn' ? c.yellow('!')
|
|
215
|
+
: r.status === 'fail' ? c.red('✗')
|
|
216
|
+
: c.dim('○');
|
|
217
|
+
|
|
218
|
+
const nameStr = r.name.padEnd(20);
|
|
219
|
+
console.log(` ${icon} ${nameStr} ${c.dim(r.message)}`);
|
|
220
|
+
|
|
221
|
+
if (r.status === 'ok') okCount++;
|
|
222
|
+
else if (r.status === 'warn') warnCount++;
|
|
223
|
+
else if (r.status === 'fail') failCount++;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log(` ${c.green(`${okCount} passed`)} ${c.yellow(`${warnCount} warnings`)} ${c.red(`${failCount} failed`)}`);
|
|
228
|
+
|
|
229
|
+
if (failCount > 0) {
|
|
230
|
+
console.log(c.red('\nSome checks failed. Run "jarvis onboard" to fix configuration.\n'));
|
|
231
|
+
} else if (warnCount > 0) {
|
|
232
|
+
console.log(c.yellow('\nAll critical checks passed, but some optional features need setup.\n'));
|
|
233
|
+
} else {
|
|
234
|
+
console.log(c.green('\nAll checks passed! JARVIS is ready.\n'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
closeRL();
|
|
238
|
+
}
|