@shykaruu/jarvis-brain 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Autostart Setup for J.A.R.V.I.S.
3
+ *
4
+ * Installs/uninstalls keepalive daemon autostart:
5
+ * - Linux: systemd user service
6
+ * - macOS: launchd user agent
7
+ */
8
+
9
+ import { join } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ import { Buffer } from 'node:buffer';
12
+ import { spawn } from 'node:child_process';
13
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
14
+ import { c, printOk, printErr, printWarn } from './helpers.ts';
15
+
16
+ function canSpawnBinary(binary: string): boolean {
17
+ try {
18
+ return Boolean(Bun.which(binary));
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ function spawnDetachedShell(command: string, requiredBinaries: string[]): boolean {
25
+ if (!requiredBinaries.every(canSpawnBinary)) {
26
+ return false;
27
+ }
28
+
29
+ try {
30
+ const child = spawn('bash', ['-lc', command], {
31
+ detached: true,
32
+ stdio: 'ignore',
33
+ env: { ...process.env },
34
+ });
35
+ if (child.pid == null) {
36
+ return false;
37
+ }
38
+ child.once('error', () => {});
39
+ child.unref();
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function getBunPath(): string {
47
+ try {
48
+ return Bun.which('bun') ?? 'bun';
49
+ } catch {
50
+ return 'bun';
51
+ }
52
+ }
53
+
54
+ function getJarvisPath(): string {
55
+ // When installed globally, import.meta.dir points to the package
56
+ return join(import.meta.dir, '../../bin/jarvis.ts');
57
+ }
58
+
59
+ function canUseSystemdUserService(): boolean {
60
+ try {
61
+ const version = Bun.spawnSync(['systemctl', '--user', '--version']);
62
+ if (version.exitCode !== 0) return false;
63
+
64
+ const state = Bun.spawnSync(['systemctl', '--user', 'is-system-running'], {
65
+ stdout: 'ignore',
66
+ stderr: 'ignore',
67
+ });
68
+
69
+ // "running" exits 0, degraded/offline can still manage units and usually exits non-zero.
70
+ // We only need the user manager to be reachable, not fully healthy.
71
+ if (state.exitCode === 0) return true;
72
+
73
+ const env = Bun.spawnSync(['systemctl', '--user', 'show-environment'], {
74
+ stdout: 'ignore',
75
+ stderr: 'ignore',
76
+ });
77
+ return env.exitCode === 0;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ // ── systemd (Linux) ──────────────────────────────────────────────────
84
+
85
+ const SYSTEMD_DIR = join(homedir(), '.config', 'systemd', 'user');
86
+ const SYSTEMD_SERVICE = join(SYSTEMD_DIR, 'jarvis.service');
87
+
88
+ function generateSystemdUnit(): string {
89
+ const bunPath = getBunPath();
90
+ const jarvisPath = getJarvisPath();
91
+
92
+ return `[Unit]
93
+ Description=J.A.R.V.I.S. Daemon
94
+ After=network.target
95
+
96
+ [Service]
97
+ Type=simple
98
+ ExecStart=${bunPath} ${jarvisPath} start --foreground
99
+ Restart=on-failure
100
+ RestartSec=5
101
+ Environment=HOME=${homedir()}
102
+
103
+ [Install]
104
+ WantedBy=default.target
105
+ `;
106
+ }
107
+
108
+ async function installSystemd(): Promise<boolean> {
109
+ try {
110
+ if (!existsSync(SYSTEMD_DIR)) {
111
+ mkdirSync(SYSTEMD_DIR, { recursive: true });
112
+ }
113
+
114
+ writeFileSync(SYSTEMD_SERVICE, generateSystemdUnit(), 'utf-8');
115
+
116
+ // Reload systemd and enable
117
+ const reload = Bun.spawnSync(['systemctl', '--user', 'daemon-reload']);
118
+ if (reload.exitCode !== 0) {
119
+ printErr('Failed to reload systemd. You may need to run: systemctl --user daemon-reload');
120
+ return false;
121
+ }
122
+
123
+ const enable = Bun.spawnSync(['systemctl', '--user', 'enable', 'jarvis.service']);
124
+ if (enable.exitCode !== 0) {
125
+ printErr('Failed to enable service. You may need to run: systemctl --user enable jarvis.service');
126
+ return false;
127
+ }
128
+
129
+ // Enable lingering so the service runs even when not logged in
130
+ const lingering = Bun.spawnSync(['loginctl', 'enable-linger', process.env.USER ?? '']);
131
+ if (lingering.exitCode !== 0) {
132
+ printWarn('Could not enable lingering. Service may stop when you log out.');
133
+ }
134
+
135
+ printOk(`Installed systemd service: ${SYSTEMD_SERVICE}`);
136
+ printOk('Service will restart automatically and start on boot.');
137
+ return true;
138
+ } catch (err) {
139
+ printErr(`Failed to install systemd service: ${err}`);
140
+ return false;
141
+ }
142
+ }
143
+
144
+ async function startSystemdService(): Promise<boolean> {
145
+ try {
146
+ const start = Bun.spawnSync(['systemctl', '--user', 'start', 'jarvis.service']);
147
+ if (start.exitCode !== 0) {
148
+ printErr('Failed to start systemd service. You may need to run: systemctl --user start jarvis.service');
149
+ return false;
150
+ }
151
+
152
+ printOk('JARVIS keepalive service is running.');
153
+ return true;
154
+ } catch (err) {
155
+ printErr(`Failed to start systemd service: ${err}`);
156
+ return false;
157
+ }
158
+ }
159
+
160
+ function scheduleSystemdRestart(): boolean {
161
+ return spawnDetachedShell(
162
+ 'sleep 1; systemctl --user restart jarvis.service >/dev/null 2>&1',
163
+ ['bash', 'systemctl'],
164
+ );
165
+ }
166
+
167
+ async function uninstallSystemd(): Promise<boolean> {
168
+ try {
169
+ Bun.spawnSync(['systemctl', '--user', 'stop', 'jarvis.service']);
170
+ Bun.spawnSync(['systemctl', '--user', 'disable', 'jarvis.service']);
171
+
172
+ if (existsSync(SYSTEMD_SERVICE)) {
173
+ unlinkSync(SYSTEMD_SERVICE);
174
+ }
175
+
176
+ Bun.spawnSync(['systemctl', '--user', 'daemon-reload']);
177
+ printOk('Uninstalled systemd service.');
178
+ return true;
179
+ } catch (err) {
180
+ printErr(`Failed to uninstall systemd service: ${err}`);
181
+ return false;
182
+ }
183
+ }
184
+
185
+ function isSystemdInstalled(): boolean {
186
+ return existsSync(SYSTEMD_SERVICE);
187
+ }
188
+
189
+ // ── launchd (macOS) ──────────────────────────────────────────────────
190
+
191
+ const LAUNCHD_DIR = join(homedir(), 'Library', 'LaunchAgents');
192
+ const LAUNCHD_PLIST = join(LAUNCHD_DIR, 'ai.jarvis.daemon.plist');
193
+
194
+ function generateLaunchdPlist(): string {
195
+ const bunPath = getBunPath();
196
+ const jarvisPath = getJarvisPath();
197
+ const logDir = join(homedir(), '.jarvis', 'logs');
198
+
199
+ return `<?xml version="1.0" encoding="UTF-8"?>
200
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
201
+ <plist version="1.0">
202
+ <dict>
203
+ <key>Label</key>
204
+ <string>ai.jarvis.daemon</string>
205
+ <key>ProgramArguments</key>
206
+ <array>
207
+ <string>${bunPath}</string>
208
+ <string>${jarvisPath}</string>
209
+ <string>start</string>
210
+ <string>--foreground</string>
211
+ </array>
212
+ <key>RunAtLoad</key>
213
+ <true/>
214
+ <key>KeepAlive</key>
215
+ <true/>
216
+ <key>StandardOutPath</key>
217
+ <string>${logDir}/jarvis.log</string>
218
+ <key>StandardErrorPath</key>
219
+ <string>${logDir}/jarvis-error.log</string>
220
+ <key>EnvironmentVariables</key>
221
+ <dict>
222
+ <key>HOME</key>
223
+ <string>${homedir()}</string>
224
+ <key>PATH</key>
225
+ <string>/usr/local/bin:/usr/bin:/bin:${join(homedir(), '.bun', 'bin')}</string>
226
+ </dict>
227
+ </dict>
228
+ </plist>
229
+ `;
230
+ }
231
+
232
+ async function installLaunchd(): Promise<boolean> {
233
+ try {
234
+ if (!existsSync(LAUNCHD_DIR)) {
235
+ mkdirSync(LAUNCHD_DIR, { recursive: true });
236
+ }
237
+
238
+ // Ensure log directory exists
239
+ const logDir = join(homedir(), '.jarvis', 'logs');
240
+ if (!existsSync(logDir)) {
241
+ mkdirSync(logDir, { recursive: true });
242
+ }
243
+
244
+ writeFileSync(LAUNCHD_PLIST, generateLaunchdPlist(), 'utf-8');
245
+
246
+ printOk(`Installed launchd plist: ${LAUNCHD_PLIST}`);
247
+ printOk('Service will restart automatically and stay running after the terminal closes.');
248
+ return true;
249
+ } catch (err) {
250
+ printErr(`Failed to install launchd plist: ${err}`);
251
+ return false;
252
+ }
253
+ }
254
+
255
+ function decodeLaunchctlOutput(output: Uint8Array | ArrayBuffer | null | undefined): string {
256
+ if (!output) {
257
+ return '';
258
+ }
259
+
260
+ try {
261
+ if (output instanceof Uint8Array) {
262
+ return Buffer.from(output).toString('utf8');
263
+ }
264
+ return Buffer.from(new Uint8Array(output)).toString('utf8');
265
+ } catch {
266
+ return '';
267
+ }
268
+ }
269
+
270
+ function isLaunchdAlreadyLoaded(
271
+ result: { exitCode: number; stdout?: Uint8Array | ArrayBuffer | null; stderr?: Uint8Array | ArrayBuffer | null },
272
+ ): boolean {
273
+ if (result.exitCode === 0) {
274
+ return false;
275
+ }
276
+
277
+ const combinedOutput = `${decodeLaunchctlOutput(result.stdout)}\n${decodeLaunchctlOutput(result.stderr)}`.toLowerCase();
278
+ return (
279
+ combinedOutput.includes('already loaded') ||
280
+ combinedOutput.includes('service already loaded') ||
281
+ combinedOutput.includes('already bootstrapped') ||
282
+ combinedOutput.includes('service already exists')
283
+ );
284
+ }
285
+
286
+ async function startLaunchdService(): Promise<boolean> {
287
+ try {
288
+ const getuid = process.getuid;
289
+ const uid = typeof getuid === 'function' ? getuid.call(process) : undefined;
290
+
291
+ if (typeof uid === 'number') {
292
+ const bootstrap = Bun.spawnSync(['launchctl', 'bootstrap', `gui/${uid}`, LAUNCHD_PLIST]);
293
+ if (bootstrap.exitCode === 0 || isLaunchdAlreadyLoaded(bootstrap)) {
294
+ printOk('JARVIS launch agent is running.');
295
+ return true;
296
+ }
297
+ } else {
298
+ printWarn('Could not determine the current user UID; skipping launchctl bootstrap and falling back to launchctl load.');
299
+ }
300
+
301
+ const load = Bun.spawnSync(['launchctl', 'load', LAUNCHD_PLIST]);
302
+ if (load.exitCode !== 0 && !isLaunchdAlreadyLoaded(load)) {
303
+ printWarn('Installed launchd plist, but could not start it immediately. It should start on next login.');
304
+ return false;
305
+ }
306
+
307
+ printOk('JARVIS launch agent is running.');
308
+ return true;
309
+ } catch (err) {
310
+ printWarn(`Installed launchd plist, but could not start it immediately: ${err}`);
311
+ return false;
312
+ }
313
+ }
314
+
315
+ function scheduleLaunchdRestart(): boolean {
316
+ const uid = process.getuid?.();
317
+ const command = uid != null
318
+ ? `sleep 1; launchctl kickstart -k gui/${uid}/ai.jarvis.daemon >/dev/null 2>&1`
319
+ : `sleep 1; launchctl kickstart -k gui/$(id -u)/ai.jarvis.daemon >/dev/null 2>&1`;
320
+ return spawnDetachedShell(command, ['bash', 'launchctl']);
321
+ }
322
+
323
+ async function uninstallLaunchd(): Promise<boolean> {
324
+ try {
325
+ if (existsSync(LAUNCHD_PLIST)) {
326
+ Bun.spawnSync(['launchctl', 'unload', LAUNCHD_PLIST]);
327
+ unlinkSync(LAUNCHD_PLIST);
328
+ }
329
+
330
+ printOk('Uninstalled launchd plist.');
331
+ return true;
332
+ } catch (err) {
333
+ printErr(`Failed to uninstall launchd plist: ${err}`);
334
+ return false;
335
+ }
336
+ }
337
+
338
+ function isLaunchdInstalled(): boolean {
339
+ return existsSync(LAUNCHD_PLIST);
340
+ }
341
+
342
+ // ── Public API ───────────────────────────────────────────────────────
343
+
344
+ /**
345
+ * Install autostart for the current platform.
346
+ */
347
+ export async function installAutostart(): Promise<boolean> {
348
+ if (process.platform === 'darwin') {
349
+ return installLaunchd();
350
+ }
351
+ return installSystemd();
352
+ }
353
+
354
+ /**
355
+ * Start the installed autostart service for the current platform.
356
+ */
357
+ export async function startAutostartService(): Promise<boolean> {
358
+ if (process.platform === 'darwin') {
359
+ return startLaunchdService();
360
+ }
361
+ return startSystemdService();
362
+ }
363
+
364
+ /**
365
+ * Schedule a restart of the installed autostart service without blocking
366
+ * the current process. Useful when the API call is served by that service.
367
+ */
368
+ export function scheduleAutostartRestart(): boolean {
369
+ if (process.platform === 'darwin') {
370
+ return scheduleLaunchdRestart();
371
+ }
372
+ if (process.platform === 'linux') {
373
+ return scheduleSystemdRestart();
374
+ }
375
+ return false;
376
+ }
377
+
378
+ /**
379
+ * Uninstall autostart for the current platform.
380
+ */
381
+ export async function uninstallAutostart(): Promise<boolean> {
382
+ if (process.platform === 'darwin') {
383
+ return uninstallLaunchd();
384
+ }
385
+ return uninstallSystemd();
386
+ }
387
+
388
+ /**
389
+ * Check if autostart is installed for the current platform.
390
+ */
391
+ export function isAutostartInstalled(): boolean {
392
+ if (process.platform === 'darwin') {
393
+ return isLaunchdInstalled();
394
+ }
395
+ return isSystemdInstalled();
396
+ }
397
+
398
+ /**
399
+ * Check whether the current platform can use the keepalive manager.
400
+ * Linux and WSL2 require a reachable user systemd instance.
401
+ */
402
+ export function isAutostartSupported(): boolean {
403
+ if (process.platform === 'darwin') {
404
+ return true;
405
+ }
406
+ return canUseSystemdUserService();
407
+ }
408
+
409
+ /**
410
+ * Get the name of the autostart mechanism for the current platform.
411
+ */
412
+ export function getAutostartName(): string {
413
+ if (process.platform === 'darwin') {
414
+ return 'launchd (User Agent)';
415
+ }
416
+ return 'systemd (User Service)';
417
+ }