@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,169 @@
1
+ /**
2
+ * Site Builder — Project Templates & Makefile Generation
3
+ */
4
+
5
+ import type { ProjectTemplate } from './types.ts';
6
+
7
+ export const TEMPLATES: ProjectTemplate[] = [
8
+ {
9
+ id: 'vite-react',
10
+ name: 'React (Vite)',
11
+ description: 'React 19 + TypeScript + Vite',
12
+ command: 'bunx',
13
+ args: ['create-vite', '--template', 'react-ts'],
14
+ framework: 'vite-react',
15
+ },
16
+ {
17
+ id: 'vite-vue',
18
+ name: 'Vue (Vite)',
19
+ description: 'Vue 3 + TypeScript + Vite',
20
+ command: 'bunx',
21
+ args: ['create-vite', '--template', 'vue-ts'],
22
+ framework: 'vite-vue',
23
+ },
24
+ {
25
+ id: 'vite-svelte',
26
+ name: 'Svelte (Vite)',
27
+ description: 'Svelte + TypeScript + Vite',
28
+ command: 'bunx',
29
+ args: ['create-vite', '--template', 'svelte-ts'],
30
+ framework: 'vite-svelte',
31
+ },
32
+ {
33
+ id: 'vite-vanilla',
34
+ name: 'Vanilla (Vite)',
35
+ description: 'Vanilla TypeScript + Vite',
36
+ command: 'bunx',
37
+ args: ['create-vite', '--template', 'vanilla-ts'],
38
+ framework: 'vite-vanilla',
39
+ },
40
+ {
41
+ id: 'next',
42
+ name: 'Next.js',
43
+ description: 'Next.js with App Router + TypeScript',
44
+ command: 'bunx',
45
+ args: ['create-next-app', '--ts', '--app', '--no-eslint', '--no-tailwind', '--no-src-dir', '--import-alias', '@/*'],
46
+ framework: 'next',
47
+ },
48
+ {
49
+ id: 'bun-react',
50
+ name: 'Bun + React',
51
+ description: 'Bun.serve() with React 19 + HTML imports',
52
+ command: 'scaffold',
53
+ args: [],
54
+ framework: 'bun-react',
55
+ },
56
+ ];
57
+
58
+ /**
59
+ * Generate a Makefile for the given framework.
60
+ * All Makefiles must support `make dev` and respect the PORT env var.
61
+ */
62
+ export function generateMakefile(framework: string): string {
63
+ const header = `.PHONY: dev build clean install\n\nPORT ?= 3000\n\n`;
64
+
65
+ switch (framework) {
66
+ case 'vite-react':
67
+ case 'vite-vue':
68
+ case 'vite-svelte':
69
+ case 'vite-vanilla':
70
+ return header + `install:\n\tbun install\n\ndev:\n\tbunx vite --port $(PORT) --host 127.0.0.1\n\nbuild:\n\tbunx vite build\n\nclean:\n\trm -rf dist node_modules\n`;
71
+
72
+ case 'next':
73
+ return header + `install:\n\tbun install\n\ndev:\n\tbunx next dev -p $(PORT) -H 127.0.0.1\n\nbuild:\n\tbunx next build\n\nclean:\n\trm -rf .next node_modules\n`;
74
+
75
+ case 'bun-react':
76
+ return header + `install:\n\tbun install\n\ndev:\n\tBUN_PORT=$(PORT) bun --hot index.ts\n\nbuild:\n\tbun build index.html --outdir=dist\n\nclean:\n\trm -rf dist node_modules\n`;
77
+
78
+ default:
79
+ return header + `install:\n\tbun install\n\ndev:\n\techo "Configure your dev server to use port $(PORT)"\n\nbuild:\n\techo "Configure your build command"\n\nclean:\n\techo "Configure your clean command"\n`;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Scaffold the internal "bun-react" template (no CLI tool needed).
85
+ */
86
+ export function scaffoldBunReact(projectPath: string): void {
87
+ const indexHtml = `<!DOCTYPE html>
88
+ <html lang="en">
89
+ <head>
90
+ <meta charset="UTF-8" />
91
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
92
+ <title>My App</title>
93
+ </head>
94
+ <body>
95
+ <div id="root"></div>
96
+ <script type="module" src="./src/App.tsx"></script>
97
+ </body>
98
+ </html>`;
99
+
100
+ const indexTs = `import index from "./index.html";
101
+
102
+ Bun.serve({
103
+ port: parseInt(process.env.BUN_PORT || "3000"),
104
+ routes: {
105
+ "/": index,
106
+ },
107
+ development: {
108
+ hmr: true,
109
+ console: true,
110
+ },
111
+ });
112
+
113
+ console.log(\`Server running on http://localhost:\${process.env.BUN_PORT || 3000}\`);
114
+ `;
115
+
116
+ const appTsx = `import React from "react";
117
+ import { createRoot } from "react-dom/client";
118
+
119
+ function App() {
120
+ return (
121
+ <div style={{ fontFamily: "system-ui", padding: "2rem", maxWidth: "600px", margin: "0 auto" }}>
122
+ <h1>Hello, World!</h1>
123
+ <p>Start editing <code>src/App.tsx</code> to get started.</p>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ createRoot(document.getElementById("root")!).render(<App />);
129
+ `;
130
+
131
+ const packageJson = JSON.stringify({
132
+ name: "my-app",
133
+ version: "0.1.0",
134
+ dependencies: {
135
+ react: "^19.0.0",
136
+ "react-dom": "^19.0.0",
137
+ },
138
+ devDependencies: {
139
+ "@types/react": "^19.0.0",
140
+ "@types/react-dom": "^19.0.0",
141
+ "bun-types": "latest",
142
+ },
143
+ }, null, 2);
144
+
145
+ const tsconfig = JSON.stringify({
146
+ compilerOptions: {
147
+ target: "ESNext",
148
+ module: "ESNext",
149
+ moduleResolution: "bundler",
150
+ jsx: "react-jsx",
151
+ strict: true,
152
+ esModuleInterop: true,
153
+ skipLibCheck: true,
154
+ },
155
+ include: ["src", "index.ts"],
156
+ }, null, 2);
157
+
158
+ const gitignore = `node_modules/
159
+ dist/
160
+ .DS_Store
161
+ `;
162
+
163
+ Bun.write(`${projectPath}/index.html`, indexHtml);
164
+ Bun.write(`${projectPath}/index.ts`, indexTs);
165
+ Bun.write(`${projectPath}/src/App.tsx`, appTsx);
166
+ Bun.write(`${projectPath}/package.json`, packageJson);
167
+ Bun.write(`${projectPath}/tsconfig.json`, tsconfig);
168
+ Bun.write(`${projectPath}/.gitignore`, gitignore);
169
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Site Builder — Type Definitions
3
+ */
4
+
5
+ export type ProjectStatus = 'stopped' | 'starting' | 'running' | 'error';
6
+
7
+ export type Project = {
8
+ id: string; // directory name (sanitized)
9
+ name: string; // display name
10
+ path: string; // absolute path on disk
11
+ framework: string; // 'vite-react' | 'next' | 'bun' | 'custom'
12
+ devPort: number | null; // dynamically assigned port when running
13
+ devServerPid: number | null;
14
+ status: ProjectStatus;
15
+ gitBranch: string | null;
16
+ gitDirty: boolean;
17
+ createdAt: number;
18
+ lastOpenedAt: number;
19
+ githubUrl: string | null; // e.g., "https://github.com/owner/repo"
20
+ };
21
+
22
+ export type ProjectMeta = {
23
+ name: string;
24
+ framework: string;
25
+ createdAt: number;
26
+ lastOpenedAt: number;
27
+ github?: {
28
+ owner: string;
29
+ repo: string;
30
+ remoteUrl: string;
31
+ lastPushedAt: number | null;
32
+ };
33
+ };
34
+
35
+ export type ProjectTemplate = {
36
+ id: string;
37
+ name: string;
38
+ description: string;
39
+ command: string; // e.g. 'bunx' or 'scaffold' for internal
40
+ args: string[];
41
+ framework: string;
42
+ };
43
+
44
+ export type FileEntry = {
45
+ name: string;
46
+ path: string; // relative to project root
47
+ type: 'file' | 'directory';
48
+ children?: FileEntry[];
49
+ size?: number;
50
+ modified?: number;
51
+ };
52
+
53
+ export type GitCommit = {
54
+ hash: string;
55
+ shortHash: string;
56
+ message: string;
57
+ author: string;
58
+ date: number;
59
+ };
60
+
61
+ export type GitBranch = {
62
+ name: string;
63
+ current: boolean;
64
+ };
65
+
66
+ export type GitRemoteStatus = {
67
+ hasRemote: boolean;
68
+ remoteUrl: string | null;
69
+ owner: string | null;
70
+ repo: string | null;
71
+ ahead: number;
72
+ behind: number;
73
+ lastPushedAt: number | null;
74
+ };
75
+
76
+ export type GitHubRepoOptions = {
77
+ name: string;
78
+ description?: string;
79
+ private: boolean;
80
+ };
81
+
82
+ export type SiteBuilderConfig = {
83
+ enabled: boolean;
84
+ projects_dir: string;
85
+ port_range_start: number;
86
+ port_range_end: number;
87
+ auto_commit: boolean;
88
+ max_concurrent_servers: number;
89
+ };
@@ -0,0 +1,84 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+ import { closeDb, initDatabase } from '../vault/schema.ts';
3
+ import { getUserProfile, saveUserProfile } from '../vault/user-profile.ts';
4
+ import {
5
+ clearUserProfileFollowupState,
6
+ maybeCreateUserProfileFollowupPrompt,
7
+ recordUserProfileTurn,
8
+ } from './profile-followup.ts';
9
+
10
+ describe('User Profile Followup', () => {
11
+ afterEach(() => {
12
+ closeDb();
13
+ });
14
+
15
+ test('asks an occasional followup for unanswered profile fields', () => {
16
+ initDatabase(':memory:');
17
+ saveUserProfile({
18
+ preferred_name: 'Alex',
19
+ interests: 'AI',
20
+ });
21
+
22
+ for (let i = 0; i < 5; i++) {
23
+ recordUserProfileTurn(`message ${i}`);
24
+ }
25
+ expect(maybeCreateUserProfileFollowupPrompt()).toBeNull();
26
+
27
+ recordUserProfileTurn('message 5');
28
+ const prompt = maybeCreateUserProfileFollowupPrompt();
29
+ expect(prompt).toContain('One quick question so I can personalize better:');
30
+ });
31
+
32
+ test('captures a pending followup answer into the profile', () => {
33
+ initDatabase(':memory:');
34
+ saveUserProfile({
35
+ preferred_name: 'Alex',
36
+ interests: 'AI',
37
+ });
38
+
39
+ for (let i = 0; i < 6; i++) {
40
+ recordUserProfileTurn(`message ${i}`);
41
+ }
42
+ const prompt = maybeCreateUserProfileFollowupPrompt();
43
+ expect(prompt).toBeString();
44
+
45
+ const result = recordUserProfileTurn('Blunt, concise, and actionable.');
46
+ expect(result.answeredQuestion).toBeDefined();
47
+
48
+ const profile = getUserProfile();
49
+ expect(profile?.answers.communication_preferences).toBe('Blunt, concise, and actionable.');
50
+ });
51
+
52
+ test('skip clears the pending followup without writing an answer', () => {
53
+ initDatabase(':memory:');
54
+ saveUserProfile({
55
+ preferred_name: 'Alex',
56
+ interests: 'AI',
57
+ });
58
+
59
+ for (let i = 0; i < 6; i++) {
60
+ recordUserProfileTurn(`message ${i}`);
61
+ }
62
+ maybeCreateUserProfileFollowupPrompt();
63
+
64
+ const result = recordUserProfileTurn('skip');
65
+ expect(result.skippedQuestion).toBeDefined();
66
+ expect(getUserProfile()?.answers.communication_preferences).toBeUndefined();
67
+ });
68
+
69
+ test('clear resets followup state', () => {
70
+ initDatabase(':memory:');
71
+ saveUserProfile({
72
+ preferred_name: 'Alex',
73
+ interests: 'AI',
74
+ });
75
+
76
+ for (let i = 0; i < 6; i++) {
77
+ recordUserProfileTurn(`message ${i}`);
78
+ }
79
+ expect(maybeCreateUserProfileFollowupPrompt()).toBeString();
80
+
81
+ clearUserProfileFollowupState();
82
+ expect(maybeCreateUserProfileFollowupPrompt()).toBeNull();
83
+ });
84
+ });
@@ -0,0 +1,185 @@
1
+ import { deleteSetting, getSetting, setSetting } from '../vault/settings.ts';
2
+ import {
3
+ USER_PROFILE_QUESTIONS,
4
+ type UserProfileQuestion,
5
+ type UserProfileQuestionId,
6
+ } from './profile.ts';
7
+ import { getUserProfile, saveUserProfile } from '../vault/user-profile.ts';
8
+
9
+ const USER_PROFILE_FOLLOWUP_STATE_KEY = 'user.profile.followup.v1';
10
+ const MIN_MESSAGES_BEFORE_FIRST_ASK = 6;
11
+ const MIN_MESSAGES_BETWEEN_ASKS = 8;
12
+ const MIN_MS_BETWEEN_ASKS = 45 * 60 * 1000;
13
+
14
+ type UserProfileFollowupState = {
15
+ version: 1;
16
+ pending_question_id: UserProfileQuestionId | null;
17
+ total_user_messages: number;
18
+ asked_at_user_message_count: number | null;
19
+ last_asked_at: number | null;
20
+ last_answered_at: number | null;
21
+ };
22
+
23
+ type FollowupTurnResult = {
24
+ answeredQuestion?: UserProfileQuestion;
25
+ skippedQuestion?: UserProfileQuestion;
26
+ };
27
+
28
+ const DEFAULT_STATE: UserProfileFollowupState = {
29
+ version: 1,
30
+ pending_question_id: null,
31
+ total_user_messages: 0,
32
+ asked_at_user_message_count: null,
33
+ last_asked_at: null,
34
+ last_answered_at: null,
35
+ };
36
+
37
+ const QUESTION_PRIORITY: UserProfileQuestionId[] = [
38
+ 'communication_preferences',
39
+ 'current_projects',
40
+ 'goals_next_90_days',
41
+ 'tools_stack',
42
+ 'routines_constraints',
43
+ 'pet_peeves',
44
+ 'important_people',
45
+ 'work_role',
46
+ 'location_timezone',
47
+ 'interests',
48
+ 'pronouns',
49
+ 'anything_else',
50
+ 'preferred_name',
51
+ ];
52
+
53
+ export function recordUserProfileTurn(text: string): FollowupTurnResult {
54
+ const state = getFollowupState();
55
+ state.total_user_messages += 1;
56
+
57
+ const pendingQuestion = state.pending_question_id
58
+ ? USER_PROFILE_QUESTIONS.find((question) => question.id === state.pending_question_id) ?? null
59
+ : null;
60
+
61
+ if (!pendingQuestion) {
62
+ saveFollowupState(state);
63
+ return {};
64
+ }
65
+
66
+ if (isSkipResponse(text)) {
67
+ state.pending_question_id = null;
68
+ saveFollowupState(state);
69
+ return { skippedQuestion: pendingQuestion };
70
+ }
71
+
72
+ if (!looksLikeProfileAnswer(text)) {
73
+ saveFollowupState(state);
74
+ return {};
75
+ }
76
+
77
+ const existing = getUserProfile();
78
+ const answers = {
79
+ ...(existing?.answers ?? {}),
80
+ [pendingQuestion.id]: text.trim(),
81
+ };
82
+ saveUserProfile(answers);
83
+
84
+ state.pending_question_id = null;
85
+ state.last_answered_at = Date.now();
86
+ saveFollowupState(state);
87
+ return { answeredQuestion: pendingQuestion };
88
+ }
89
+
90
+ export function maybeCreateUserProfileFollowupPrompt(): string | null {
91
+ const profile = getUserProfile();
92
+ if (!profile) return null;
93
+
94
+ const state = getFollowupState();
95
+ if (state.pending_question_id) return null;
96
+
97
+ const unanswered = getUnansweredQuestions(profile.answers);
98
+ if (unanswered.length === 0) return null;
99
+
100
+ const now = Date.now();
101
+ if (state.last_asked_at && now - state.last_asked_at < MIN_MS_BETWEEN_ASKS) {
102
+ return null;
103
+ }
104
+
105
+ if (state.asked_at_user_message_count === null) {
106
+ if (state.total_user_messages < MIN_MESSAGES_BEFORE_FIRST_ASK) {
107
+ return null;
108
+ }
109
+ } else if (state.total_user_messages - state.asked_at_user_message_count < MIN_MESSAGES_BETWEEN_ASKS) {
110
+ return null;
111
+ }
112
+
113
+ const question = unanswered[0]!;
114
+ state.pending_question_id = question.id;
115
+ state.asked_at_user_message_count = state.total_user_messages;
116
+ state.last_asked_at = now;
117
+ saveFollowupState(state);
118
+
119
+ return [
120
+ 'One quick question so I can personalize better:',
121
+ question.prompt,
122
+ '',
123
+ `${question.description} You can answer briefly, or say "skip" if you do not want to answer right now.`,
124
+ ].join('\n');
125
+ }
126
+
127
+ export function clearUserProfileFollowupState(): void {
128
+ deleteSetting(USER_PROFILE_FOLLOWUP_STATE_KEY);
129
+ }
130
+
131
+ function getFollowupState(): UserProfileFollowupState {
132
+ const raw = getSetting(USER_PROFILE_FOLLOWUP_STATE_KEY);
133
+ if (!raw) return { ...DEFAULT_STATE };
134
+
135
+ try {
136
+ const parsed = JSON.parse(raw) as Partial<UserProfileFollowupState>;
137
+ return {
138
+ version: 1,
139
+ pending_question_id: isQuestionId(parsed.pending_question_id) ? parsed.pending_question_id : null,
140
+ total_user_messages: typeof parsed.total_user_messages === 'number' ? parsed.total_user_messages : 0,
141
+ asked_at_user_message_count: typeof parsed.asked_at_user_message_count === 'number' ? parsed.asked_at_user_message_count : null,
142
+ last_asked_at: typeof parsed.last_asked_at === 'number' ? parsed.last_asked_at : null,
143
+ last_answered_at: typeof parsed.last_answered_at === 'number' ? parsed.last_answered_at : null,
144
+ };
145
+ } catch {
146
+ return { ...DEFAULT_STATE };
147
+ }
148
+ }
149
+
150
+ function saveFollowupState(state: UserProfileFollowupState): void {
151
+ setSetting(USER_PROFILE_FOLLOWUP_STATE_KEY, JSON.stringify(state));
152
+ }
153
+
154
+ function isQuestionId(value: unknown): value is UserProfileQuestionId {
155
+ return typeof value === 'string' && USER_PROFILE_QUESTIONS.some((question) => question.id === value);
156
+ }
157
+
158
+ function getUnansweredQuestions(
159
+ answers: Partial<Record<UserProfileQuestionId, string>>,
160
+ ): UserProfileQuestion[] {
161
+ const unansweredIds = new Set(
162
+ USER_PROFILE_QUESTIONS
163
+ .filter((question) => !answers[question.id]?.trim())
164
+ .map((question) => question.id),
165
+ );
166
+
167
+ return QUESTION_PRIORITY
168
+ .filter((questionId) => unansweredIds.has(questionId))
169
+ .map((questionId) => USER_PROFILE_QUESTIONS.find((question) => question.id === questionId))
170
+ .filter((question): question is UserProfileQuestion => Boolean(question));
171
+ }
172
+
173
+ function looksLikeProfileAnswer(text: string): boolean {
174
+ const trimmed = text.trim();
175
+ if (!trimmed) return false;
176
+ if (trimmed.length > 600) return false;
177
+ if (trimmed.includes('?')) return false;
178
+ if (/^(can you|could you|would you|please|what|why|how|when|where|who)\b/i.test(trimmed)) return false;
179
+ if (/\b(run|open|read|write|edit|check|search|find|look up|browse|visit|show me)\b/i.test(trimmed)) return false;
180
+ return true;
181
+ }
182
+
183
+ function isSkipResponse(text: string): boolean {
184
+ return /^(skip|not now|maybe later|later|no thanks|no thank you|pass)$/i.test(text.trim());
185
+ }